mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
127 lines
3.1 KiB
Rust
127 lines
3.1 KiB
Rust
use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}};
|
|
|
|
use tokio::fs;
|
|
use yazi_fs::{CWD, expand_path};
|
|
use yazi_macro::{emit, render};
|
|
use yazi_parser::cmp::TriggerOpt;
|
|
use yazi_proxy::options::CmpItem;
|
|
use yazi_shared::{event::Cmd, natsort};
|
|
|
|
use crate::cmp::Cmp;
|
|
|
|
impl Cmp {
|
|
#[yazi_codegen::command]
|
|
pub fn trigger(&mut self, opt: TriggerOpt) {
|
|
if let Some(t) = opt.ticket {
|
|
if t < self.ticket {
|
|
return;
|
|
}
|
|
self.ticket = t;
|
|
}
|
|
|
|
let Some((parent, word)) = Self::split_path(&opt.word) else {
|
|
return self.close(false);
|
|
};
|
|
|
|
if self.caches.contains_key(&parent) {
|
|
return self.show(
|
|
Cmd::default()
|
|
.with_any("cache-name", parent)
|
|
.with("word", word)
|
|
.with("ticket", self.ticket),
|
|
);
|
|
}
|
|
|
|
let ticket = self.ticket;
|
|
tokio::spawn(async move {
|
|
let mut dir = fs::read_dir(&parent).await?;
|
|
let mut cache = vec![];
|
|
|
|
// "/" is both a directory separator and the root directory per se
|
|
// As there's no parent directory for the FS root, it is a special case
|
|
if parent == Path::new("/") {
|
|
cache.push(CmpItem { name: OsString::new(), is_dir: true });
|
|
}
|
|
|
|
while let Ok(Some(ent)) = dir.next_entry().await {
|
|
if let Ok(ft) = ent.file_type().await {
|
|
cache.push(CmpItem { name: ent.file_name(), is_dir: ft.is_dir() });
|
|
}
|
|
}
|
|
|
|
if !cache.is_empty() {
|
|
cache.sort_unstable_by(|a, b| {
|
|
natsort(a.name.as_encoded_bytes(), b.name.as_encoded_bytes(), false)
|
|
});
|
|
emit!(Call(
|
|
Cmd::new("cmp:show")
|
|
.with_any("cache", cache)
|
|
.with_any("cache-name", parent)
|
|
.with("word", word)
|
|
.with("ticket", ticket)
|
|
));
|
|
}
|
|
|
|
Ok::<_, anyhow::Error>(())
|
|
});
|
|
|
|
render!(mem::replace(&mut self.visible, false));
|
|
}
|
|
|
|
fn split_path(s: &str) -> Option<(PathBuf, String)> {
|
|
if s == "~" {
|
|
return None; // We don't autocomplete a `~`, but `~/`
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
const SEP: [char; 2] = ['/', '\\'];
|
|
#[cfg(not(windows))]
|
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
|
|
|
Some(match s.rsplit_once(SEP) {
|
|
Some(("", c)) => (PathBuf::from(MAIN_SEPARATOR_STR), c.to_owned()),
|
|
Some((p, c)) => (expand_path(p), c.to_owned()),
|
|
None => (CWD.load().to_path_buf(), s.to_owned()),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::path::Path;
|
|
|
|
use super::*;
|
|
|
|
fn compare(s: &str, parent: &str, child: &str) {
|
|
let (p, c) = Cmp::split_path(s).unwrap();
|
|
let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(&p);
|
|
assert_eq!((p, c.as_str()), (Path::new(parent), child));
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn test_split() {
|
|
yazi_fs::init();
|
|
compare("", "", "");
|
|
compare(" ", "", " ");
|
|
compare("/", "/", "");
|
|
compare("//", "//", "");
|
|
compare("/foo", "/", "foo");
|
|
compare("/foo/", "/foo/", "");
|
|
compare("/foo/bar", "/foo/", "bar");
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn test_split() {
|
|
yazi_fs::init();
|
|
compare("foo", "", "foo");
|
|
compare("foo\\", "foo\\", "");
|
|
compare("foo\\bar", "foo\\", "bar");
|
|
compare("foo\\bar\\", "foo\\bar\\", "");
|
|
compare("C:\\", "C:\\", "");
|
|
compare("C:\\foo", "C:\\", "foo");
|
|
compare("C:\\foo\\", "C:\\foo\\", "");
|
|
compare("C:\\foo\\bar", "C:\\foo\\", "bar");
|
|
}
|
|
}
|