yazi_core/completion/commands/
show.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::{borrow::Cow, mem, ops::ControlFlow, path::PathBuf};

use yazi_macro::render;
use yazi_shared::event::{Cmd, CmdCow, Data};

use crate::completion::Completion;

const LIMIT: usize = 30;

struct Opt {
	cache:      Vec<String>,
	cache_name: PathBuf,
	word:       Cow<'static, str>,
	ticket:     usize,
}

impl From<CmdCow> for Opt {
	fn from(mut c: CmdCow) -> Self {
		Self {
			cache:      c.take_any("cache").unwrap_or_default(),
			cache_name: c.take_any("cache-name").unwrap_or_default(),
			word:       c.take_str("word").unwrap_or_default(),
			ticket:     c.get("ticket").and_then(Data::as_usize).unwrap_or(0),
		}
	}
}

impl From<Cmd> for Opt {
	fn from(c: Cmd) -> Self { Self::from(CmdCow::from(c)) }
}

impl Completion {
	#[yazi_codegen::command]
	pub fn show(&mut self, opt: Opt) {
		if self.ticket != opt.ticket {
			return;
		}

		if !opt.cache.is_empty() {
			self.caches.insert(opt.cache_name.clone(), opt.cache);
		}
		let Some(cache) = self.caches.get(&opt.cache_name) else {
			return;
		};

		self.ticket = opt.ticket;
		self.cands = Self::match_candidates(&opt.word, cache);
		if self.cands.is_empty() {
			return render!(mem::replace(&mut self.visible, false));
		}

		self.offset = 0;
		self.cursor = 0;
		self.visible = true;
		render!();
	}

	fn match_candidates(word: &str, cache: &[String]) -> Vec<String> {
		let smart = !word.bytes().any(|c| c.is_ascii_uppercase());

		let flow = cache.iter().try_fold(
			(Vec::with_capacity(LIMIT), Vec::with_capacity(LIMIT)),
			|(mut prefixed, mut fuzzy), s| {
				if (smart && s.to_lowercase().starts_with(word)) || (!smart && s.starts_with(word)) {
					if s != word {
						prefixed.push(s);
						if prefixed.len() >= LIMIT {
							return ControlFlow::Break((prefixed, fuzzy));
						}
					}
				} else if fuzzy.len() < LIMIT - prefixed.len() && s.contains(word) {
					// here we don't break the control flow, since we want more exact matching.
					fuzzy.push(s)
				}
				ControlFlow::Continue((prefixed, fuzzy))
			},
		);

		let (mut prefixed, fuzzy) = match flow {
			ControlFlow::Continue(v) => v,
			ControlFlow::Break(v) => v,
		};
		if prefixed.len() < LIMIT {
			prefixed.extend(fuzzy.into_iter().take(LIMIT - prefixed.len()))
		}
		prefixed.into_iter().map(ToOwned::to_owned).collect()
	}
}