diff --git a/yazi-core/src/completion/commands/arrow.rs b/yazi-core/src/completion/commands/arrow.rs index a7e7e453..735b5e94 100644 --- a/yazi-core/src/completion/commands/arrow.rs +++ b/yazi-core/src/completion/commands/arrow.rs @@ -10,7 +10,7 @@ impl From<&Exec> for Opt { impl Completion { fn next(&mut self, step: usize) -> bool { - let len = self.items.len(); + let len = self.cands.len(); if len == 0 { return false; } diff --git a/yazi-core/src/completion/commands/close.rs b/yazi-core/src/completion/commands/close.rs index b08b90f1..f5a4c91e 100644 --- a/yazi-core/src/completion/commands/close.rs +++ b/yazi-core/src/completion/commands/close.rs @@ -17,10 +17,7 @@ impl Completion { let submit = opt.into().0; if submit { emit!(Call( - Exec::call("complete", vec![self.items[self.cursor].to_owned()]) - .with_bool("apply", true) - .with("ticket", self.ticket) - .vec(), + Exec::call("complete", vec![self.selected().into()]).with("ticket", self.ticket).vec(), KeymapLayer::Input )); } diff --git a/yazi-core/src/completion/commands/show.rs b/yazi-core/src/completion/commands/show.rs index d31d5c05..f9c811bc 100644 --- a/yazi-core/src/completion/commands/show.rs +++ b/yazi-core/src/completion/commands/show.rs @@ -1,17 +1,23 @@ +use std::ops::ControlFlow; + use yazi_config::keymap::Exec; use crate::completion::Completion; pub struct Opt<'a> { - cands: &'a Vec, - ticket: usize, + cache: &'a Vec, + cache_name: &'a str, + word: &'a str, + ticket: usize, } impl<'a> From<&'a Exec> for Opt<'a> { fn from(e: &'a Exec) -> Self { Self { - cands: &e.args, - ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0), + cache: &e.args, + cache_name: e.named.get("cache-name").map(|n| n.as_str()).unwrap_or_default(), + word: e.named.get("word").map(|w| w.as_str()).unwrap_or_default(), + ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0), } } } @@ -23,10 +29,33 @@ impl Completion { return false; } + if !opt.cache.is_empty() { + self.caches.insert(opt.cache_name.to_owned(), opt.cache.clone()); + } + let Some(cache) = self.caches.get(opt.cache_name) else { + return false; + }; + + let flow = cache.iter().try_fold(Vec::with_capacity(30), |mut v, s| { + if s.contains(opt.word) && s != opt.word { + v.push(s.to_owned()); + if v.len() >= 30 { + return ControlFlow::Break(v); + } + } + ControlFlow::Continue(v) + }); + self.close(false); - self.items = opt.cands.clone(); + self.cands = match flow { + ControlFlow::Continue(v) => v, + ControlFlow::Break(v) => v, + }; self.ticket = opt.ticket; - self.visible = true; + + if !self.cands.is_empty() { + self.visible = true; + } true } } diff --git a/yazi-core/src/completion/commands/trigger.rs b/yazi-core/src/completion/commands/trigger.rs index 0c442811..617d94e2 100644 --- a/yazi-core/src/completion/commands/trigger.rs +++ b/yazi-core/src/completion/commands/trigger.rs @@ -4,15 +4,15 @@ use yazi_config::keymap::{Exec, KeymapLayer}; use crate::{completion::Completion, emit}; pub struct Opt<'a> { - word: &'a str, + before: &'a str, ticket: usize, } impl<'a> From<&'a Exec> for Opt<'a> { fn from(e: &'a Exec) -> Self { Self { - word: e.args.first().map(|w| w.as_str()).unwrap_or_default(), - ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0), + before: e.named.get("before").map(|s| s.as_str()).unwrap_or_default(), + ticket: e.named.get("ticket").and_then(|s| s.parse().ok()).unwrap_or(0), } } } @@ -27,28 +27,43 @@ impl Completion { self.close(false); self.ticket = opt.ticket; - let word = opt.word.to_owned(); + let (parent, child) = opt.before.rsplit_once('/').unwrap_or((".", opt.before)); + if self.caches.contains_key(parent) { + return self.show( + &Exec::call("show", vec![]) + .with("cache-name", parent) + .with("word", child) + .with("ticket", opt.ticket), + ); + } + let ticket = self.ticket; + let (parent, child) = (parent.to_owned(), child.to_owned()); tokio::spawn(async move { - let (parent, child) = word.rsplit_once('/').unwrap_or((".", word.as_str())); - - let mut dir = fs::read_dir(parent).await?; - let mut cands = Vec::new(); + let mut dir = fs::read_dir(&parent).await?; + let mut cache = Vec::new(); while let Ok(Some(f)) = dir.next_entry().await { - let name = f.file_name().to_string_lossy().into_owned(); - if !name.starts_with(child) { + let Ok(meta) = f.metadata().await else { continue; - } + }; - cands.push(name); - if cands.len() >= 20 { - break; - } + let sep = if !meta.is_dir() { + "" + } else if cfg!(windows) { + "\\" + } else { + "/" + }; + cache.push(format!("{}{sep}", f.file_name().to_string_lossy())); } - if !cands.is_empty() { + if !cache.is_empty() { emit!(Call( - Exec::call("show", cands).with("ticket", ticket).vec(), + Exec::call("show", cache) + .with("cache-name", parent) + .with("word", child) + .with("ticket", ticket) + .vec(), KeymapLayer::Completion )); } diff --git a/yazi-core/src/completion/completion.rs b/yazi-core/src/completion/completion.rs index 42c30f29..86bd63f9 100644 --- a/yazi-core/src/completion/completion.rs +++ b/yazi-core/src/completion/completion.rs @@ -1,20 +1,26 @@ +use std::collections::BTreeMap; + #[derive(Default)] pub struct Completion { - pub(super) items: Vec, + pub(super) caches: BTreeMap>, + pub(super) cands: Vec, pub(super) offset: usize, pub cursor: usize, - pub ticket: usize, - pub visible: bool, + pub(super) ticket: usize, + pub visible: bool, } impl Completion { #[inline] pub fn window(&self) -> &[String] { - let end = (self.offset + self.limit()).min(self.items.len()); - &self.items[self.offset..end] + let end = (self.offset + self.limit()).min(self.cands.len()); + &self.cands[self.offset..end] } #[inline] - pub fn limit(&self) -> usize { self.items.len().min(5) } + pub fn limit(&self) -> usize { self.cands.len().min(5) } + + #[inline] + pub fn selected(&self) -> &String { &self.cands[self.cursor] } } diff --git a/yazi-core/src/input/commands/complete.rs b/yazi-core/src/input/commands/complete.rs index 168fd227..e73612cc 100644 --- a/yazi-core/src/input/commands/complete.rs +++ b/yazi-core/src/input/commands/complete.rs @@ -39,7 +39,7 @@ impl Input { snap.value = new; self.move_(delta); - self.flush_value(true); + self.flush_value(); true } } diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index fb3707d5..98def8f0 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -213,7 +213,7 @@ impl Input { } self.move_(s.chars().count() as isize); - self.flush_value(false); + self.flush_value(); true } @@ -226,7 +226,7 @@ impl Input { } self.move_(-1); - self.flush_value(false); + self.flush_value(); true } @@ -320,13 +320,13 @@ impl Input { return false; } if !matches!(old.op, InputOp::None | InputOp::Select(_)) { - self.snaps.tag().then(|| self.flush_value(false)); + self.snaps.tag().then(|| self.flush_value()); } true } #[inline] - pub(super) fn flush_value(&mut self, no_complete: bool) { + pub(super) fn flush_value(&mut self) { self.ticket = self.ticket.wrapping_add(1); if self.realtime { @@ -334,7 +334,7 @@ impl Input { self.callback.as_ref().unwrap().send(Err(InputError::Typed(value))).ok(); } - if self.completion && !no_complete { + if self.completion { let before = self.partition()[0].to_owned(); self.callback.as_ref().unwrap().send(Err(InputError::Completed(before, self.ticket))).ok(); } diff --git a/yazi-core/src/input/mod.rs b/yazi-core/src/input/mod.rs index 397890e7..328d8295 100644 --- a/yazi-core/src/input/mod.rs +++ b/yazi-core/src/input/mod.rs @@ -11,6 +11,5 @@ pub use input::*; pub use mode::*; use op::*; pub use option::*; -pub use shell::*; use snap::*; use snaps::*; diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index f7ec9656..2722a6d0 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -76,7 +76,7 @@ impl Tab { } Err(InputError::Completed(before, ticket)) => { emit!(Call( - Exec::call("complete", vec![before]).with("ticket", ticket).vec(), + Exec::call("complete", vec![]).with("before", before).with("ticket", ticket).vec(), KeymapLayer::Input )); } diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs index c6129eaa..dea3dd07 100644 --- a/yazi-fm/src/completion/completion.rs +++ b/yazi-fm/src/completion/completion.rs @@ -19,8 +19,13 @@ impl<'a> Widget for Completion<'a> { .iter() .enumerate() .map(|(i, x)| { - let mut item = ListItem::new(format!(" {} {}", THEME.completion.icon_file, x)); + let icon = if x.ends_with('/') || x.ends_with('\\') { + &THEME.completion.icon_folder + } else { + &THEME.completion.icon_file + }; + let mut item = ListItem::new(format!(" {} {}", icon, x)); if i == self.cx.completion.cursor { item = item.style(THEME.completion.active.into()); } else { diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 0d20970f..9ad78584 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -268,10 +268,10 @@ impl<'a> Executor<'a> { } "complete" => { - return if exec.named.contains_key("apply") { - self.cx.input.complete(exec) - } else { + return if exec.args.is_empty() { self.cx.completion.trigger(exec) + } else { + self.cx.input.complete(exec) }; } _ => {} diff --git a/yazi-plugin/src/bindings/mod.rs b/yazi-plugin/src/bindings/mod.rs index 60bbb912..a6ee7ff7 100644 --- a/yazi-plugin/src/bindings/mod.rs +++ b/yazi-plugin/src/bindings/mod.rs @@ -9,6 +9,7 @@ mod tasks; pub use active::*; pub use bindings::*; +#[allow(unused_imports)] pub use files::*; pub use shared::*; pub use tabs::*; diff --git a/yazi-shared/src/term/mod.rs b/yazi-shared/src/term/mod.rs index 9b1c3465..1fc2dcdc 100644 --- a/yazi-shared/src/term/mod.rs +++ b/yazi-shared/src/term/mod.rs @@ -3,5 +3,4 @@ mod cursor; mod term; -pub use cursor::*; pub use term::*;