mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: which-key and multi-key support (#4)
This commit is contained in:
parent
ca3c7c90b5
commit
1f8c170359
20 changed files with 378 additions and 105 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
|
@ -382,9 +382,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
|
|
@ -665,15 +665,6 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
|
|
@ -1334,9 +1325,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.31"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -2358,12 +2349,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "2.5.0"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "688597db5a750e9cad4511cb94729a078e274308099a0382b5b8203bbc767fee"
|
||||
dependencies = [
|
||||
"home",
|
||||
]
|
||||
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{collections::BTreeMap, fmt};
|
|||
|
||||
use serde::{de::{self, Visitor}, Deserializer};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Exec {
|
||||
pub cmd: String,
|
||||
pub args: Vec<String>,
|
||||
|
|
@ -28,6 +28,25 @@ impl From<&str> for Exec {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToString for Exec {
|
||||
fn to_string(&self) -> String {
|
||||
let mut s = self.cmd.clone();
|
||||
for arg in &self.args {
|
||||
s.push(' ');
|
||||
s.push_str(arg);
|
||||
}
|
||||
for (name, value) in &self.named {
|
||||
s.push_str(" --");
|
||||
s.push_str(name);
|
||||
if !value.is_empty() {
|
||||
s.push('=');
|
||||
s.push_str(value);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl Exec {
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Exec>, D::Error>
|
||||
where
|
||||
|
|
|
|||
|
|
@ -102,3 +102,62 @@ impl TryFrom<String> for Key {
|
|||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Key {
|
||||
fn to_string(&self) -> String {
|
||||
if let Some(c) = self.plain() {
|
||||
let c = if self.shift { c.to_ascii_uppercase() } else { c };
|
||||
return if c == ' ' { "<Space>".to_string() } else { c.to_string() };
|
||||
}
|
||||
|
||||
let mut s = "<".to_string();
|
||||
if self.ctrl {
|
||||
s += "C-";
|
||||
}
|
||||
if self.alt {
|
||||
s += "A-";
|
||||
}
|
||||
if self.shift && !matches!(self.code, KeyCode::Char(_)) {
|
||||
s += "S-";
|
||||
}
|
||||
|
||||
let code = match self.code {
|
||||
KeyCode::Char(' ') => "Space",
|
||||
KeyCode::Backspace => "Backspace",
|
||||
KeyCode::Enter => "Enter",
|
||||
KeyCode::Left => "Left",
|
||||
KeyCode::Right => "Right",
|
||||
KeyCode::Up => "Up",
|
||||
KeyCode::Down => "Down",
|
||||
KeyCode::Home => "Home",
|
||||
KeyCode::End => "End",
|
||||
KeyCode::PageUp => "PageUp",
|
||||
KeyCode::PageDown => "PageDown",
|
||||
KeyCode::Tab => "Tab",
|
||||
KeyCode::Delete => "Delete",
|
||||
KeyCode::Insert => "Insert",
|
||||
KeyCode::F(1) => "F1",
|
||||
KeyCode::F(2) => "F2",
|
||||
KeyCode::F(3) => "F3",
|
||||
KeyCode::F(4) => "F4",
|
||||
KeyCode::F(5) => "F5",
|
||||
KeyCode::F(6) => "F6",
|
||||
KeyCode::F(7) => "F7",
|
||||
KeyCode::F(8) => "F8",
|
||||
KeyCode::F(9) => "F9",
|
||||
KeyCode::F(10) => "F10",
|
||||
KeyCode::F(11) => "F11",
|
||||
KeyCode::F(12) => "F12",
|
||||
KeyCode::Esc => "Esc",
|
||||
|
||||
KeyCode::Char(c) if c == ' ' => "Space",
|
||||
KeyCode::Char(c) => {
|
||||
s.push(if self.shift { c.to_ascii_uppercase() } else { c });
|
||||
""
|
||||
}
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
s + code + ">"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use serde::{Deserialize, Deserializer};
|
|||
use super::{Exec, Key};
|
||||
use crate::config::MERGED_KEYMAP;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Single {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Control {
|
||||
pub on: Vec<Key>,
|
||||
#[serde(deserialize_with = "Exec::deserialize")]
|
||||
pub exec: Vec<Exec>,
|
||||
|
|
@ -12,10 +12,19 @@ pub struct Single {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Keymap {
|
||||
pub manager: Vec<Single>,
|
||||
pub tasks: Vec<Single>,
|
||||
pub select: Vec<Single>,
|
||||
pub input: Vec<Single>,
|
||||
pub manager: Vec<Control>,
|
||||
pub tasks: Vec<Control>,
|
||||
pub select: Vec<Control>,
|
||||
pub input: Vec<Control>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum KeymapLayer {
|
||||
Manager,
|
||||
Tasks,
|
||||
Select,
|
||||
Input,
|
||||
Which,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Keymap {
|
||||
|
|
@ -32,7 +41,7 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Inner {
|
||||
keymap: Vec<Single>,
|
||||
keymap: Vec<Control>,
|
||||
}
|
||||
|
||||
let shadow = Shadow::deserialize(deserializer)?;
|
||||
|
|
@ -47,4 +56,15 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
|
||||
impl Keymap {
|
||||
pub fn new() -> Self { toml::from_str(&MERGED_KEYMAP).unwrap() }
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, layer: KeymapLayer) -> &Vec<Control> {
|
||||
match layer {
|
||||
KeymapLayer::Manager => &self.manager,
|
||||
KeymapLayer::Tasks => &self.tasks,
|
||||
KeymapLayer::Select => &self.select,
|
||||
KeymapLayer::Input => &self.input,
|
||||
KeymapLayer::Which => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ pub struct Theme {
|
|||
impl Theme {
|
||||
pub fn new() -> Self {
|
||||
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
|
||||
theme.preview.syntect_theme = absolute_path(&theme.preview.syntect_theme);
|
||||
theme.preview.syntect_theme =
|
||||
futures::executor::block_on(absolute_path(&theme.preview.syntect_theme));
|
||||
theme
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ use crossterm::event::KeyEvent;
|
|||
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||
|
||||
use super::{files::FilesOp, input::InputOpt, manager::PreviewData, select::SelectOpt};
|
||||
use crate::config::open::Opener;
|
||||
use crate::config::{keymap::{Control, KeymapLayer}, open::Opener};
|
||||
|
||||
static mut TX: Option<UnboundedSender<Event>> = None;
|
||||
|
||||
pub enum Event {
|
||||
Quit,
|
||||
Stop(bool, Option<oneshot::Sender<()>>),
|
||||
Key(KeyEvent),
|
||||
Render(String),
|
||||
Resize(u16, u16),
|
||||
Stop(bool, Option<oneshot::Sender<()>>),
|
||||
Ctrl(Control, KeymapLayer),
|
||||
|
||||
// Manager
|
||||
Cd(PathBuf),
|
||||
|
|
@ -57,10 +58,6 @@ impl Event {
|
|||
|
||||
#[macro_export]
|
||||
macro_rules! emit {
|
||||
(Stop($state:expr)) => {{
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
$crate::core::Event::Stop($state, Some(tx)).wait(rx)
|
||||
}};
|
||||
(Key($key:expr)) => {
|
||||
$crate::core::Event::Key($key).emit();
|
||||
};
|
||||
|
|
@ -70,6 +67,13 @@ macro_rules! emit {
|
|||
(Resize($cols:expr, $rows:expr)) => {
|
||||
$crate::core::Event::Resize($cols, $rows).emit();
|
||||
};
|
||||
(Stop($state:expr)) => {{
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
$crate::core::Event::Stop($state, Some(tx)).wait(rx)
|
||||
}};
|
||||
(Ctrl($exec:expr, $layer:expr)) => {
|
||||
$crate::core::Event::Ctrl($exec, $layer).emit();
|
||||
};
|
||||
|
||||
(Cd($op:expr)) => {
|
||||
$crate::core::Event::Cd($op).emit();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, mem, path::PathBuf};
|
||||
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, mem, path::PathBuf};
|
||||
|
||||
use tokio::fs;
|
||||
|
||||
|
|
@ -25,6 +25,8 @@ impl Manager {
|
|||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
env::set_current_dir(&self.current().cwd).ok();
|
||||
|
||||
self.watcher.trigger(&self.current().cwd);
|
||||
if let Some(p) = self.parent() {
|
||||
self.watcher.trigger(&p.cwd);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub mod manager;
|
|||
pub mod position;
|
||||
pub mod select;
|
||||
pub mod tasks;
|
||||
pub mod which;
|
||||
|
||||
pub use blocker::*;
|
||||
pub use event::*;
|
||||
|
|
|
|||
4
src/core/which/mod.rs
Normal file
4
src/core/which/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
mod which;
|
||||
|
||||
pub use which::*;
|
||||
|
||||
52
src/core/which/which.rs
Normal file
52
src/core/which/which.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use std::mem;
|
||||
|
||||
use crate::{config::{keymap::{Control, Key, KeymapLayer}, KEYMAP}, emit};
|
||||
|
||||
pub struct Which {
|
||||
layer: KeymapLayer,
|
||||
pub times: usize,
|
||||
pub cands: Vec<Control>,
|
||||
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Default for Which {
|
||||
fn default() -> Self {
|
||||
Self { layer: KeymapLayer::Manager, times: 0, cands: Default::default(), visible: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Which {
|
||||
pub fn show(&mut self, key: &Key, layer: KeymapLayer) -> bool {
|
||||
self.layer = layer;
|
||||
self.times = 1;
|
||||
self.cands = KEYMAP
|
||||
.get(layer)
|
||||
.into_iter()
|
||||
.filter(|s| !s.on.is_empty() && s.on[0] == *key)
|
||||
.cloned()
|
||||
.collect();
|
||||
self.visible = true;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn press(&mut self, key: Key) -> bool {
|
||||
self.cands = mem::replace(&mut self.cands, Vec::new())
|
||||
.into_iter()
|
||||
.filter(|s| s.on.len() > self.times && s.on[self.times] == key)
|
||||
.collect();
|
||||
|
||||
if self.cands.is_empty() {
|
||||
self.visible = false;
|
||||
} else if self.cands.len() == 1 {
|
||||
self.visible = false;
|
||||
emit!(Ctrl(self.cands.remove(0), self.layer));
|
||||
} else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times + 1) {
|
||||
emit!(Ctrl(self.cands.remove(i), self.layer));
|
||||
self.visible = false;
|
||||
}
|
||||
|
||||
self.times += 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ use std::{env, path::{Path, PathBuf}};
|
|||
|
||||
use tokio::fs;
|
||||
|
||||
pub fn absolute_path(p: &Path) -> PathBuf {
|
||||
pub async fn absolute_path(p: impl AsRef<Path>) -> PathBuf {
|
||||
let p = p.as_ref();
|
||||
if p.starts_with("~") {
|
||||
if let Ok(home) = env::var("HOME") {
|
||||
let mut expanded = PathBuf::new();
|
||||
|
|
@ -11,7 +12,7 @@ pub fn absolute_path(p: &Path) -> PathBuf {
|
|||
return expanded;
|
||||
}
|
||||
}
|
||||
p.to_path_buf()
|
||||
fs::canonicalize(p).await.unwrap_or_else(|_| p.to_path_buf())
|
||||
}
|
||||
|
||||
pub fn readable_path(p: &Path, base: &Path) -> String {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crossterm::event::KeyEvent;
|
|||
use tokio::sync::oneshot::{self};
|
||||
|
||||
use super::{root::Root, Ctx, Executor, Logs, Signals, Term};
|
||||
use crate::{config::keymap::Key, core::{files::FilesOp, Event}, emit};
|
||||
use crate::{config::keymap::{Control, Key, KeymapLayer}, core::{files::FilesOp, Event}, emit, misc::absolute_path};
|
||||
|
||||
pub struct App {
|
||||
cx: Ctx,
|
||||
|
|
@ -22,30 +22,17 @@ impl App {
|
|||
while let Some(event) = app.signals.rx.recv().await {
|
||||
match event {
|
||||
Event::Quit => break,
|
||||
Event::Stop(state, tx) => app.dispatch_stop(state, tx),
|
||||
Event::Key(key) => app.dispatch_key(key),
|
||||
Event::Render(_) => app.dispatch_render(),
|
||||
Event::Resize(..) => app.dispatch_resize(),
|
||||
Event::Stop(state, tx) => app.dispatch_stop(state, tx),
|
||||
Event::Ctrl(ctrl, layer) => app.dispatch_ctrl(ctrl, layer),
|
||||
event => app.dispatch_module(event).await,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_stop(&mut self, state: bool, tx: Option<oneshot::Sender<()>>) {
|
||||
if state {
|
||||
self.signals.stop_term(true);
|
||||
self.term = None;
|
||||
} else {
|
||||
self.term = Some(Term::start().unwrap());
|
||||
self.signals.stop_term(false);
|
||||
emit!(Render);
|
||||
}
|
||||
if let Some(tx) = tx {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_key(&mut self, key: KeyEvent) {
|
||||
let key = Key::from(key);
|
||||
if Executor::handle(&mut self.cx, key) {
|
||||
|
|
@ -71,12 +58,33 @@ impl App {
|
|||
emit!(Render);
|
||||
}
|
||||
|
||||
fn dispatch_stop(&mut self, state: bool, tx: Option<oneshot::Sender<()>>) {
|
||||
if state {
|
||||
self.signals.stop_term(true);
|
||||
self.term = None;
|
||||
} else {
|
||||
self.term = Some(Term::start().unwrap());
|
||||
self.signals.stop_term(false);
|
||||
emit!(Render);
|
||||
}
|
||||
if let Some(tx) = tx {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_ctrl(&mut self, ctrl: Control, layer: KeymapLayer) {
|
||||
if Executor::dispatch(&mut self.cx, &ctrl.exec, layer) {
|
||||
emit!(Render);
|
||||
}
|
||||
}
|
||||
|
||||
async fn dispatch_module(&mut self, event: Event) {
|
||||
let manager = &mut self.cx.manager;
|
||||
let tasks = &mut self.cx.tasks;
|
||||
match event {
|
||||
Event::Cd(path) => {
|
||||
manager.active_mut().cd(path).await;
|
||||
manager.active_mut().cd(absolute_path(path).await).await;
|
||||
}
|
||||
Event::Refresh => {
|
||||
manager.refresh();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::{core::{input::Input, manager::Manager, select::Select, tasks::Tasks, Position}, misc::tty_size};
|
||||
use crate::{config::keymap::KeymapLayer, core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position}, misc::tty_size};
|
||||
|
||||
pub struct Ctx {
|
||||
pub cursor: Option<(u16, u16)>,
|
||||
|
||||
pub manager: Manager,
|
||||
pub which: Which,
|
||||
pub select: Select,
|
||||
pub input: Input,
|
||||
pub tasks: Tasks,
|
||||
|
|
@ -15,12 +16,28 @@ impl Ctx {
|
|||
cursor: None,
|
||||
|
||||
manager: Manager::new(),
|
||||
select: Select::default(),
|
||||
input: Input::default(),
|
||||
which: Default::default(),
|
||||
select: Default::default(),
|
||||
input: Default::default(),
|
||||
tasks: Tasks::start(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn layer(&self) -> KeymapLayer {
|
||||
if self.which.visible {
|
||||
KeymapLayer::Which
|
||||
} else if self.input.visible {
|
||||
KeymapLayer::Input
|
||||
} else if self.select.visible {
|
||||
KeymapLayer::Select
|
||||
} else if self.tasks.visible {
|
||||
KeymapLayer::Tasks
|
||||
} else {
|
||||
KeymapLayer::Manager
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self, pos: Position) -> Position {
|
||||
match pos {
|
||||
Position::Top => Position::Coords((tty_size().ws_col / 2).saturating_sub(25), 2),
|
||||
|
|
|
|||
|
|
@ -1,52 +1,49 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use super::Ctx;
|
||||
use crate::{config::{keymap::{Exec, Key, Single}, KEYMAP}, core::input::InputMode, misc::optional_bool};
|
||||
use crate::{config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}, core::input::InputMode, emit, misc::optional_bool};
|
||||
|
||||
pub struct Executor;
|
||||
|
||||
impl Executor {
|
||||
pub fn handle(cx: &mut Ctx, key: Key) -> bool {
|
||||
let layer = if cx.input.visible {
|
||||
3
|
||||
} else if cx.select.visible {
|
||||
2
|
||||
} else if cx.tasks.visible {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let layer = cx.layer();
|
||||
if layer == KeymapLayer::Which {
|
||||
return cx.which.press(key);
|
||||
}
|
||||
|
||||
let mut render = false;
|
||||
let mut matched = false;
|
||||
let keymap = [&KEYMAP.manager, &KEYMAP.tasks, &KEYMAP.select, &KEYMAP.input][layer];
|
||||
if layer == KeymapLayer::Input && cx.input.mode() == InputMode::Insert {
|
||||
if let Some(c) = key.plain() {
|
||||
return cx.input.type_(c);
|
||||
}
|
||||
}
|
||||
|
||||
for Single { on, exec } in keymap {
|
||||
if on.len() < 1 || on[0] != key {
|
||||
for Control { on, exec } in KEYMAP.get(layer) {
|
||||
if on.is_empty() || on[0] != key {
|
||||
continue;
|
||||
}
|
||||
|
||||
matched = true;
|
||||
for e in exec {
|
||||
if layer == 0 {
|
||||
render = Self::manager(cx, e) || render;
|
||||
} else if layer == 1 {
|
||||
render = Self::tasks(cx, e) || render;
|
||||
} else if layer == 2 {
|
||||
render = Self::select(cx, e) || render;
|
||||
} else if layer == 3 {
|
||||
if cx.input.mode() != InputMode::Insert || key.plain().is_none() {
|
||||
render = Self::input(cx, Some(e), &key) || render;
|
||||
} else {
|
||||
render = Self::input(cx, None, &key) || render;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return if on.len() > 1 {
|
||||
cx.which.show(&key, layer)
|
||||
} else {
|
||||
Self::dispatch(cx, exec, layer)
|
||||
};
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
if layer == 3 && !matched {
|
||||
render = Self::input(cx, None, &key);
|
||||
#[inline]
|
||||
pub fn dispatch(cx: &mut Ctx, exec: &Vec<Exec>, layer: KeymapLayer) -> bool {
|
||||
let mut render = false;
|
||||
for e in exec {
|
||||
render |= match layer {
|
||||
KeymapLayer::Manager => Self::manager(cx, e),
|
||||
KeymapLayer::Tasks => Self::tasks(cx, e),
|
||||
KeymapLayer::Select => Self::select(cx, e),
|
||||
KeymapLayer::Input => Self::input(cx, e),
|
||||
KeymapLayer::Which => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
render
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +62,11 @@ impl Executor {
|
|||
"enter" => cx.manager.active_mut().enter(),
|
||||
"back" => cx.manager.active_mut().back(),
|
||||
"forward" => cx.manager.active_mut().forward(),
|
||||
"cd" => {
|
||||
let path = exec.args.get(0).map(|s| PathBuf::from(s)).unwrap_or_default();
|
||||
emit!(Cd(path));
|
||||
false
|
||||
}
|
||||
|
||||
// Selection
|
||||
"select" => {
|
||||
|
|
@ -170,18 +172,7 @@ impl Executor {
|
|||
}
|
||||
}
|
||||
|
||||
fn input(cx: &mut Ctx, exec: Option<&Exec>, key: &Key) -> bool {
|
||||
let exec = if let Some(e) = exec {
|
||||
e
|
||||
} else {
|
||||
if cx.input.mode() == InputMode::Insert {
|
||||
if let Some(c) = key.plain() {
|
||||
return cx.input.type_(c);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
fn input(cx: &mut Ctx, exec: &Exec) -> bool {
|
||||
match exec.cmd.as_str() {
|
||||
"close" => return cx.input.close(exec.named.contains_key("submit")),
|
||||
"escape" => return cx.input.escape(),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ impl Logs {
|
|||
.context("failed to get XDG base directories")?
|
||||
.get_state_home();
|
||||
|
||||
let appender = tracing_appender::rolling::hourly(root, "yazi.log");
|
||||
let appender = tracing_appender::rolling::never(root, "yazi.log");
|
||||
let (handle, guard) = tracing_appender::non_blocking(appender);
|
||||
|
||||
// let filter = EnvFilter::from_default_env();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ mod signals;
|
|||
mod status;
|
||||
mod tasks;
|
||||
mod term;
|
||||
mod which;
|
||||
|
||||
pub use app::*;
|
||||
pub use context::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget};
|
||||
|
||||
use super::{header, manager, status, tasks, Ctx, Input, Select};
|
||||
use super::{header, manager, status, tasks, which::Which, Ctx, Input, Select};
|
||||
|
||||
pub struct Root<'a> {
|
||||
cx: &'a mut Ctx,
|
||||
|
|
@ -35,5 +35,9 @@ impl<'a> Widget for Root<'a> {
|
|||
} else {
|
||||
self.cx.cursor = None;
|
||||
}
|
||||
|
||||
if self.cx.which.visible {
|
||||
Which::new(self.cx).render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
src/ui/which/layout.rs
Normal file
47
src/ui/which/layout.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, widgets::{Clear, Widget}};
|
||||
|
||||
use super::Side;
|
||||
use crate::ui::Ctx;
|
||||
|
||||
pub struct Which<'a> {
|
||||
cx: &'a mut Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Which<'a> {
|
||||
pub fn new(cx: &'a mut Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl Widget for Which<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let which = &self.cx.which;
|
||||
let mut cands: (Vec<_>, Vec<_>, Vec<_>) = Default::default();
|
||||
for (i, c) in which.cands.iter().enumerate() {
|
||||
match i % 3 {
|
||||
0 => cands.0.push(c),
|
||||
1 => cands.1.push(c),
|
||||
2 => cands.2.push(c),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let height = cands.0.len() as u16 + 2;
|
||||
let area = Rect {
|
||||
x: 1,
|
||||
y: area.height.saturating_sub(height + 2),
|
||||
width: area.width.saturating_sub(2),
|
||||
height,
|
||||
};
|
||||
|
||||
let chunks = layout::Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[Constraint::Ratio(1, 3), Constraint::Ratio(1, 3), Constraint::Ratio(1, 3)].as_ref(),
|
||||
)
|
||||
.split(area);
|
||||
|
||||
Clear.render(area, buf);
|
||||
Side::new(which.times, cands.0).render(chunks[0], buf);
|
||||
Side::new(which.times, cands.1).render(chunks[1], buf);
|
||||
Side::new(which.times, cands.2).render(chunks[2], buf);
|
||||
}
|
||||
}
|
||||
5
src/ui/which/mod.rs
Normal file
5
src/ui/which/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod layout;
|
||||
mod side;
|
||||
|
||||
pub use layout::*;
|
||||
pub use side::*;
|
||||
49
src/ui/which/side.rs
Normal file
49
src/ui/which/side.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use ratatui::{prelude::{Buffer, Rect}, style::{Color, Style, Stylize}, text::{Line, Span}, widgets::{Block, List, ListItem, Padding, Widget}};
|
||||
|
||||
use crate::config::keymap::Control;
|
||||
|
||||
pub struct Side<'a> {
|
||||
times: usize,
|
||||
cands: Vec<&'a Control>,
|
||||
}
|
||||
|
||||
impl<'a> Side<'a> {
|
||||
pub fn new(times: usize, cands: Vec<&'a Control>) -> Self { Self { times, cands } }
|
||||
}
|
||||
|
||||
impl Widget for Side<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let items = self
|
||||
.cands
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
let mut spans = vec![];
|
||||
|
||||
// Keys
|
||||
let keys = c.on[self.times..].iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
spans.push(Span::raw(" ".repeat(10usize.saturating_sub(keys.join("").len()))));
|
||||
spans.push(Span::styled(keys[0].clone(), Style::default().fg(Color::LightCyan)));
|
||||
spans.extend(
|
||||
keys
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|k| Span::styled(k.to_string(), Style::default().fg(Color::DarkGray))),
|
||||
);
|
||||
|
||||
// Separator
|
||||
spans.push(Span::styled(" ".to_string(), Style::default().fg(Color::DarkGray)));
|
||||
|
||||
// Exec
|
||||
let exec = c.exec.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ");
|
||||
spans.push(Span::styled(exec, Style::default().fg(Color::Magenta)));
|
||||
|
||||
ListItem::new(Line::from(spans))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
List::new(items)
|
||||
.block(Block::new().padding(Padding::new(0, 1, 1, 1)))
|
||||
.bg(Color::Rgb(47, 51, 73))
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue