diff --git a/yazi-core/src/completion/completion.rs b/yazi-core/src/completion/completion.rs index 3429b2e8..3fd1f4ad 100644 --- a/yazi-core/src/completion/completion.rs +++ b/yazi-core/src/completion/completion.rs @@ -2,8 +2,9 @@ use crate::{completion::CompletionOpt, Position}; #[derive(Default)] pub struct Completion { - items: Vec, - cursor: usize, + items: Vec, + cursor: usize, + pub identifier: String, pub position: Position, pub column_cnt: u8, @@ -15,6 +16,13 @@ impl Completion { pub fn show(&mut self, opt: CompletionOpt) { self.close(); self.visible = true; + self.identifier = format!( + "{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() + ); self.items = opt.items; self.position = opt.position; @@ -24,6 +32,7 @@ impl Completion { pub fn close(&mut self) -> bool { self.cursor = 0; + self.identifier = String::new(); self.visible = false; true } diff --git a/yazi-core/src/input/completion.rs b/yazi-core/src/input/completion.rs index b61c5dd3..aaf0d28e 100644 --- a/yazi-core/src/input/completion.rs +++ b/yazi-core/src/input/completion.rs @@ -1,29 +1,43 @@ use crossterm::event::KeyCode; -use yazi_config::keymap::Key; +use yazi_config::keymap::{Exec, Key, KeymapLayer}; -use crate::{completion::CompletionOpt, input::Input}; +use crate::{completion::CompletionOpt, emit, input::Input}; impl Input { pub fn complete(&mut self) -> bool { if !self.completion.visible { - let current = self.snaps.current().value.as_str(); - let result = self.init_completion.as_ref().map(|f| f(current)).unwrap_or_default(); - match result.len() { - 0 => false, - 1 => { - if let Some(f) = self.finish_completion.as_ref() { - self.replace_str(f(current, result.get(0).unwrap()).as_str()) - } else { - false - } - } - _ => { - self.completion.show(CompletionOpt::top().with_items(result)); - true + 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 } } - } else { - false + _ => { + self.completion.show(CompletionOpt::top().with_items(exec.args.clone())); + true + } } } diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index 39f9aafd..e70c87f8 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -6,7 +6,7 @@ use unicode_width::UnicodeWidthStr; use yazi_config::keymap::Key; use yazi_shared::{CharKind, InputError}; -use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; +use super::{mode::InputMode, op::InputOp, FinishCompletionType, InitCompletionType, InputOpt, InputSnap, InputSnaps}; use crate::{completion::Completion, external, Position}; #[derive(Default)] @@ -25,8 +25,8 @@ pub struct Input { pub(super) highlight: bool, pub completion: Completion, - pub(super) init_completion: Option Vec + Send>>, - pub(super) finish_completion: Option String + Send>>, + pub(super) init_completion: Option, + pub(super) finish_completion: Option, } impl Input { diff --git a/yazi-core/src/input/option.rs b/yazi-core/src/input/option.rs index e9c1aece..332835a5 100644 --- a/yazi-core/src/input/option.rs +++ b/yazi-core/src/input/option.rs @@ -1,15 +1,21 @@ +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 Vec + Send>>, - pub finish_completion: Option String + Send>>, + pub init_completion: Option, + pub finish_completion: Option, } impl InputOpt { @@ -63,7 +69,7 @@ impl InputOpt { #[inline] pub fn with_completion< - F1: Fn(&str) -> Vec + Send + 'static, + F1: Fn(String) -> Pin> + Send>> + Send + 'static, F2: Fn(&str, &str) -> String + Send + 'static, >( mut self, diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 4c98f92d..67109939 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -62,30 +62,25 @@ impl Tab { let mut result = emit!(Input( InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion( |prefix| { - let mut cmp_prefix = prefix; - if prefix.contains('/') { - let (old_prefix, old_file_prefix) = prefix.rsplit_once('/').unwrap(); - cmp_prefix = old_file_prefix; - std::fs::read_dir(old_prefix.to_string() + "/") - } else { - std::fs::read_dir(".") - } - .ok() - .map(|list| { - list - .filter_map(|file| { - file.ok().and_then(|f| { - let name = f.file_name().to_string_lossy().to_string(); - if f.metadata().is_ok_and(|m| m.is_dir()) && name.starts_with(cmp_prefix) { - Some(name) - } else { - None - } - }) - }) - .collect() + 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 }) - .unwrap_or_default() }, |current, new| { if let Some((prefix, _)) = current.rsplit_once('/') { diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 2139fecc..67c23062 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -233,6 +233,9 @@ impl Executor { let in_operating = exec.named.contains_key("in-operating"); return if in_operating { cx.input.move_in_operating(step) } else { cx.input.move_(step) }; } + + // asynchronized completion + "complete" => return cx.input.fill_completion(exec), _ => {} }