diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 57d62670..31cb5109 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -237,3 +237,18 @@ keymap = [ # Filtering { on = [ "/" ], exec = "filter", desc = "Apply a filter for the help items" }, ] + +[completion] + +keymap = [ + { on = [ "" ], exec = "close", desc = "Cancel completion" }, + { on = [ "" ], exec = "close --submit", desc = "Submit the completion" }, + + { on = [ "k" ], exec = "arrow -1", desc = "Move cursor up" }, + { on = [ "j" ], exec = "arrow 1", desc = "Move cursor down" }, + + { on = [ "" ], exec = "arrow -1", desc = "Move cursor up" }, + { on = [ "" ], exec = "arrow 1", desc = "Move cursor down" }, + + { on = [ "~" ], exec = "help", desc = "Open help" } +] diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index d4ef0795..77be1670 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -7,11 +7,12 @@ use crate::MERGED_KEYMAP; #[derive(Debug)] pub struct Keymap { - pub manager: Vec, - pub tasks: Vec, - pub select: Vec, - pub input: Vec, - pub help: Vec, + pub manager: Vec, + pub tasks: Vec, + pub select: Vec, + pub input: Vec, + pub help: Vec, + pub completion: Vec, } impl<'de> Deserialize<'de> for Keymap { @@ -21,11 +22,12 @@ impl<'de> Deserialize<'de> for Keymap { { #[derive(Deserialize)] struct Shadow { - manager: Inner, - tasks: Inner, - select: Inner, - input: Inner, - help: Inner, + manager: Inner, + tasks: Inner, + select: Inner, + input: Inner, + help: Inner, + completion: Inner, } #[derive(Deserialize)] struct Inner { @@ -34,11 +36,12 @@ impl<'de> Deserialize<'de> for Keymap { let shadow = Shadow::deserialize(deserializer)?; Ok(Self { - manager: shadow.manager.keymap, - tasks: shadow.tasks.keymap, - select: shadow.select.keymap, - input: shadow.input.keymap, - help: shadow.help.keymap, + manager: shadow.manager.keymap, + tasks: shadow.tasks.keymap, + select: shadow.select.keymap, + input: shadow.input.keymap, + help: shadow.help.keymap, + completion: shadow.completion.keymap, }) } } @@ -56,6 +59,7 @@ impl Keymap { KeymapLayer::Select => &self.select, KeymapLayer::Input => &self.input, KeymapLayer::Help => &self.help, + KeymapLayer::Completion => &self.completion, KeymapLayer::Which => unreachable!(), } } @@ -69,6 +73,7 @@ pub enum KeymapLayer { Select, Input, Help, + Completion, Which, } @@ -80,6 +85,7 @@ impl Display for KeymapLayer { KeymapLayer::Select => write!(f, "select"), KeymapLayer::Input => write!(f, "input"), KeymapLayer::Help => write!(f, "help"), + KeymapLayer::Completion => write!(f, "completion"), KeymapLayer::Which => write!(f, "which"), } } diff --git a/yazi-core/src/context.rs b/yazi-core/src/context.rs index 86c22800..12a056c8 100644 --- a/yazi-core/src/context.rs +++ b/yazi-core/src/context.rs @@ -3,26 +3,28 @@ use ratatui::prelude::Rect; use yazi_config::keymap::KeymapLayer; use yazi_shared::Term; -use crate::{help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position}; +use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position}; pub struct Ctx { - pub manager: Manager, - pub which: Which, - pub help: Help, - pub input: Input, - pub select: Select, - pub tasks: Tasks, + pub manager: Manager, + pub tasks: Tasks, + pub select: Select, + pub input: Input, + pub help: Help, + pub completion: Completion, + pub which: Which, } impl Ctx { pub fn make() -> Self { Self { - manager: Manager::make(), - which: Default::default(), - help: Default::default(), - input: Default::default(), - select: Default::default(), - tasks: Tasks::start(), + manager: Manager::make(), + tasks: Tasks::start(), + select: Default::default(), + input: Default::default(), + help: Default::default(), + completion: Default::default(), + which: Default::default(), } } @@ -72,6 +74,8 @@ impl Ctx { pub fn layer(&self) -> KeymapLayer { if self.which.visible { KeymapLayer::Which + } else if self.completion.visible { + KeymapLayer::Completion } else if self.help.visible { KeymapLayer::Help } else if self.input.visible { diff --git a/yazi-core/src/input/completion.rs b/yazi-core/src/input/completion.rs deleted file mode 100644 index 9806f3a6..00000000 --- a/yazi-core/src/input/completion.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crossterm::event::KeyCode; -use yazi_config::keymap::{Exec, Key, KeymapLayer}; - -use crate::{completion::CompletionOpt, emit, input::Input}; - -impl Input { - pub fn complete(&mut self) -> bool { - if !self.completion.visible { - if let Some(f) = self.init_completion.as_ref() { - let future = f(self.snaps.current().value.clone()); - let id = self.completion.identifier.clone(); - tokio::spawn(async move { - let result = future.await; - let mut exec = Exec::call("complete", result); - exec.named.insert("identifier".to_string(), id); - emit!(Call(exec.vec(), KeymapLayer::Input)); - }); - } - } - false - } - - pub fn fill_completion(&mut self, exec: &Exec) -> bool { - if !exec.named.get("identifier").is_some_and(|id| *id == self.completion.identifier) { - return false; - }; - match exec.args.len() { - 0 => false, - 1 => { - if let Some(f) = self.finish_completion.as_ref() { - self - .replace_str(f(self.snaps.current().value.as_str(), exec.args.get(0).unwrap()).as_str()) - } else { - false - } - } - _ => { - self.completion.show(CompletionOpt::top().with_items(exec.args.clone())); - true - } - } - } - - pub fn navigate_completion(&mut self, key: &Key) -> bool { - match key.code { - KeyCode::Up => self.completion.prev(self.completion.column_cnt as usize), - KeyCode::Down => self.completion.next(self.completion.column_cnt as usize), - KeyCode::Left => self.completion.prev(1), - KeyCode::Right | KeyCode::Tab => self.completion.next(1), - _ => false, - } - } - - pub fn finish_completion(&mut self) -> bool { - if let (Some(val), Some(f)) = (self.completion.selected(), &self.finish_completion) { - let final_val = f(self.snaps.current().value.as_str(), val.as_str()); - self.replace_str(final_val.as_str()); - } - self.completion.close() - } -} diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index e70c87f8..4ad4847c 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -6,8 +6,8 @@ use unicode_width::UnicodeWidthStr; use yazi_config::keymap::Key; use yazi_shared::{CharKind, InputError}; -use super::{mode::InputMode, op::InputOp, FinishCompletionType, InitCompletionType, InputOpt, InputSnap, InputSnaps}; -use crate::{completion::Completion, external, Position}; +use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; +use crate::{external, Position}; #[derive(Default)] pub struct Input { @@ -23,10 +23,6 @@ pub struct Input { // Shell pub(super) highlight: bool, - - pub completion: Completion, - pub(super) init_completion: Option, - pub(super) finish_completion: Option, } impl Input { @@ -44,9 +40,6 @@ impl Input { // Shell self.highlight = opt.highlight; - - self.init_completion = opt.init_completion; - self.finish_completion = opt.finish_completion; } pub fn close(&mut self, submit: bool) -> bool { @@ -56,7 +49,6 @@ impl Input { } self.visible = false; - self.completion.close(); true } @@ -197,25 +189,6 @@ impl Input { return false; } - if self.completion.visible { - match key { - Key { - code: KeyCode::Tab | KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right, - shift: false, - ctrl: false, - alt: false, - } => return self.navigate_completion(key), - Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false } => { - return self.finish_completion(); - } - _ => { - self.completion.close(); - } - } - } else if matches!(key, Key { code: KeyCode::Tab, shift: false, ctrl: false, alt: false }) { - return self.complete(); - } - if let Some(c) = key.plain() { return self.type_char(c); } @@ -244,13 +217,6 @@ impl Input { self.move_(s.chars().count() as isize) } - pub fn replace_str(&mut self, s: &str) -> bool { - let snap = self.snaps.current_mut(); - snap.value = s.to_string(); - self.flush_value(); - self.move_(s.chars().count() as isize) - } - pub fn backspace(&mut self) -> bool { let snap = self.snaps.current_mut(); if snap.cursor < 1 { diff --git a/yazi-core/src/input/mod.rs b/yazi-core/src/input/mod.rs index 457945c6..9fa10f1a 100644 --- a/yazi-core/src/input/mod.rs +++ b/yazi-core/src/input/mod.rs @@ -1,4 +1,3 @@ -mod completion; mod input; mod mode; mod op; diff --git a/yazi-core/src/input/option.rs b/yazi-core/src/input/option.rs index 332835a5..6c380c9a 100644 --- a/yazi-core/src/input/option.rs +++ b/yazi-core/src/input/option.rs @@ -1,51 +1,39 @@ -use std::{future::Future, pin::Pin}; - use ratatui::prelude::Rect; use crate::Position; -pub type InitCompletionType = - Box Pin> + Send>> + Send>; -pub type FinishCompletionType = Box String + Send>; - pub struct InputOpt { - pub title: String, - pub value: String, - pub position: Position, - pub realtime: bool, - pub highlight: bool, - pub init_completion: Option, - pub finish_completion: Option, + pub title: String, + pub value: String, + pub position: Position, + pub realtime: bool, + pub highlight: bool, } impl InputOpt { pub fn top(title: impl AsRef) -> Self { Self { - title: title.as_ref().to_owned(), - value: String::new(), - position: Position::Top( + title: title.as_ref().to_owned(), + value: String::new(), + position: Position::Top( // TODO: hardcode Rect { x: 0, y: 2, width: 50, height: 3 }, ), - realtime: false, - highlight: false, - init_completion: None, - finish_completion: None, + realtime: false, + highlight: false, } } pub fn hovered(title: impl AsRef) -> Self { Self { - title: title.as_ref().to_owned(), - value: String::new(), - position: Position::Hovered( + title: title.as_ref().to_owned(), + value: String::new(), + position: Position::Hovered( // TODO: hardcode Rect { x: 0, y: 1, width: 50, height: 3 }, ), - realtime: false, - highlight: false, - init_completion: None, - finish_completion: None, + realtime: false, + highlight: false, } } @@ -68,16 +56,5 @@ impl InputOpt { } #[inline] - pub fn with_completion< - F1: Fn(String) -> Pin> + Send>> + Send + 'static, - F2: Fn(&str, &str) -> String + Send + 'static, - >( - mut self, - init_completion: F1, - finish_completion: F2, - ) -> Self { - self.init_completion = Some(Box::new(init_completion)); - self.finish_completion = Some(Box::new(finish_completion)); - self - } + pub fn with_completion(mut self) -> Self { todo!() } } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 67109939..90ec2388 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -59,38 +59,8 @@ impl Tab { pub fn cd_interactive(&mut self, target: Url) -> bool { tokio::spawn(async move { - let mut result = emit!(Input( - InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion( - |prefix| { - Box::pin(async move { - let mut cmp_prefix = prefix.as_str(); - let mut result = vec![]; - if let Ok(mut list) = if prefix.contains('/') { - let (old_prefix, old_file_prefix) = prefix.rsplit_once('/').unwrap(); - cmp_prefix = old_file_prefix; - tokio::fs::read_dir(old_prefix.to_string() + "/").await - } else { - tokio::fs::read_dir(".").await - } { - while let Ok(Some(f)) = list.next_entry().await { - let name = f.file_name().to_string_lossy().to_string(); - if f.metadata().await.is_ok_and(|m| m.is_dir()) && name.starts_with(cmp_prefix) { - result.push(name.clone()) - } - } - } - result - }) - }, - |current, new| { - if let Some((prefix, _)) = current.rsplit_once('/') { - format!("{prefix}/{new}/") - } else { - format!("{new}/") - } - } - ) - )); + let mut result = + emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy()))); if let Some(Ok(s)) = result.recv().await { emit!(Cd(Url::from(s.trim()))); diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs index 22ec12f7..06958bec 100644 --- a/yazi-fm/src/completion/completion.rs +++ b/yazi-fm/src/completion/completion.rs @@ -1,7 +1,4 @@ -use std::mem; - -use ratatui::{buffer::Buffer, layout::{Constraint, Rect}, widgets::{Block, BorderType, Borders, Cell, Clear, Row, Table, Widget}}; -use yazi_config::THEME; +use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Cell, Clear, Row, Table, Widget}}; use yazi_core::Ctx; pub(crate) struct Completion<'a> { @@ -14,46 +11,47 @@ impl<'a> Completion<'a> { impl<'a> Widget for Completion<'a> { fn render(self, _: Rect, buf: &mut Buffer) { - let completion = &self.cx.input.completion; - let area = self.cx.area(&completion.position); + todo!() + // let completion = &self.cx.input.completion; + // let area = self.cx.area(&completion.position); - let constraint = (0..completion.column_cnt) - .map(|_| Constraint::Percentage(completion.max_width)) - .collect::>(); - let table = { - let max_width = completion.max_width as usize; - let mut table = vec![]; - let mut cur_row = vec![]; - for (idx, s) in completion.items.iter().enumerate() { - if idx != 0 && idx % completion.column_cnt as usize == 0 { - let t = mem::take(&mut cur_row); - table.push(Row::new(t)); - } - cur_row.push( - Cell::from(if s.len() < max_width { - s.to_owned() - } else { - s.split_at(max_width - 1).0.to_string() + "…" - }) - .style(if completion.cursor == idx { - THEME.completion.active.into() - } else { - THEME.completion.inactive.into() - }), - ); - } - table.push(Row::new(cur_row)); - Table::new(table) - .block( - Block::new() - .borders(Borders::ALL) - .border_type(BorderType::Double) - .border_style(THEME.completion.border.into()), - ) - .widths(&constraint) - }; + // let constraint = (0..completion.column_cnt) + // .map(|_| Constraint::Percentage(completion.max_width)) + // .collect::>(); + // let table = { + // let max_width = completion.max_width as usize; + // let mut table = vec![]; + // let mut cur_row = vec![]; + // for (idx, s) in completion.items.iter().enumerate() { + // if idx != 0 && idx % completion.column_cnt as usize == 0 { + // let t = mem::take(&mut cur_row); + // table.push(Row::new(t)); + // } + // cur_row.push( + // Cell::from(if s.len() < max_width { + // s.to_owned() + // } else { + // s.split_at(max_width - 1).0.to_string() + "…" + // }) + // .style(if completion.cursor == idx { + // THEME.completion.active.into() + // } else { + // THEME.completion.inactive.into() + // }), + // ); + // } + // table.push(Row::new(cur_row)); + // Table::new(table) + // .block( + // Block::new() + // .borders(Borders::ALL) + // .border_type(BorderType::Double) + // .border_style(THEME.completion.border.into()), + // ) + // .widths(&constraint) + // }; - Clear.render(area, buf); - table.render(area, buf); + // Clear.render(area, buf); + // table.render(area, buf); } } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 67c23062..a4722b03 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -43,6 +43,7 @@ impl Executor { KeymapLayer::Select => Self::select(cx, e), KeymapLayer::Input => Self::input(cx, e), KeymapLayer::Help => Self::help(cx, e), + KeymapLayer::Completion => Self::completion(cx, e), KeymapLayer::Which => unreachable!(), }; } @@ -234,8 +235,7 @@ impl Executor { return if in_operating { cx.input.move_in_operating(step) } else { cx.input.move_(step) }; } - // asynchronized completion - "complete" => return cx.input.fill_completion(exec), + "complete" => todo!(), _ => {} } @@ -278,4 +278,21 @@ impl Executor { _ => false, } } + + fn completion(cx: &mut Ctx, exec: &Exec) -> bool { + match exec.cmd.as_str() { + "close" => cx.completion.close(), + + "arrow" => { + let step: isize = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0); + if step > 0 { + cx.completion.next(step as usize) + } else { + cx.completion.prev(step.unsigned_abs()) + } + } + + _ => false, + } + } } diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index 5bf3021a..4dde5b7b 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -36,14 +36,14 @@ impl<'a> Widget for Root<'a> { input::Input::new(self.cx).render(area, buf); } - if self.cx.input.completion.visible { - completion::Completion::new(self.cx).render(area, buf); - } - if self.cx.help.visible { help::Layout::new(self.cx).render(area, buf); } + if self.cx.completion.visible { + completion::Completion::new(self.cx).render(area, buf); + } + if self.cx.which.visible { which::Which::new(self.cx).render(area, buf); }