mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: cd path auto-completion supports ~ expansion (#1081)
This commit is contained in:
parent
58e5d2280a
commit
5f21998665
6 changed files with 50 additions and 36 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2769,6 +2769,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"libc",
|
"libc",
|
||||||
"notify",
|
"notify",
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
bitflags = "2.5.0"
|
bitflags = "2.5.0"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
dirs = "5.0.1"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
|
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
|
||||||
parking_lot = "0.12.2"
|
parking_lot = "0.12.2"
|
||||||
|
|
@ -29,11 +30,11 @@ ratatui = "0.26.3"
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
serde = "1.0.202"
|
serde = "1.0.202"
|
||||||
|
shell-words = "1.1.0"
|
||||||
tokio = { version = "1.37.0", features = [ "full" ] }
|
tokio = { version = "1.37.0", features = [ "full" ] }
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = "0.1.15"
|
||||||
tokio-util = "0.7.11"
|
tokio-util = "0.7.11"
|
||||||
unicode-width = "0.1.12"
|
unicode-width = "0.1.12"
|
||||||
shell-words = "1.1.0"
|
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ pub struct Opt {
|
||||||
impl From<Cmd> for Opt {
|
impl From<Cmd> for Opt {
|
||||||
fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } }
|
fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } }
|
||||||
}
|
}
|
||||||
|
impl From<bool> for Opt {
|
||||||
|
fn from(submit: bool) -> Self { Self { submit } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
pub fn close(&mut self, opt: impl Into<Opt>) {
|
pub fn close(&mut self, opt: impl Into<Opt>) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{mem, path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}};
|
use std::{borrow::Cow, mem, path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR}};
|
||||||
|
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
|
use yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
|
||||||
|
|
@ -33,7 +33,9 @@ impl Completion {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ticket = opt.ticket;
|
self.ticket = opt.ticket;
|
||||||
let (parent, child) = Self::split_path(&opt.word);
|
let Some((parent, child)) = Self::split_path(&opt.word) else {
|
||||||
|
return self.close(false);
|
||||||
|
};
|
||||||
|
|
||||||
if self.caches.contains_key(&parent) {
|
if self.caches.contains_key(&parent) {
|
||||||
return self.show(
|
return self.show(
|
||||||
|
|
@ -72,12 +74,24 @@ impl Completion {
|
||||||
render!(mem::replace(&mut self.visible, false));
|
render!(mem::replace(&mut self.visible, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
fn split_path(s: &str) -> Option<(String, String)> {
|
||||||
fn split_path(s: &str) -> (String, String) {
|
if s == "~" {
|
||||||
match s.rsplit_once(SEPARATOR) {
|
return None; // We don't autocomplete a `~`, but `~/`
|
||||||
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
|
|
||||||
None => (".".to_owned(), s.to_owned()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let s = if let Some(rest) = s.strip_prefix("~") {
|
||||||
|
Cow::Owned(format!(
|
||||||
|
"{}{rest}",
|
||||||
|
dirs::home_dir().unwrap_or_default().to_string_lossy().trim_end_matches(SEPARATOR),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(s)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(match s.rsplit_once(SEPARATOR) {
|
||||||
|
Some((p, c)) => (format!("{p}{}", MAIN_SEPARATOR), c.to_owned()),
|
||||||
|
None => (".".to_owned(), s.into_owned()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,28 +99,32 @@ impl Completion {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn compare(s: &str, parent: &str, child: &str) -> bool {
|
||||||
|
matches!(Completion::split_path(s), Some((p, c)) if p == parent && c == child)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split() {
|
fn test_split() {
|
||||||
assert_eq!(Completion::split_path(""), (".".to_owned(), "".to_owned()));
|
assert!(compare("", ".", ""));
|
||||||
assert_eq!(Completion::split_path(" "), (".".to_owned(), " ".to_owned()));
|
assert!(compare(" ", ".", " "));
|
||||||
assert_eq!(Completion::split_path("/"), ("/".to_owned(), "".to_owned()));
|
assert!(compare("/", "/", ""));
|
||||||
assert_eq!(Completion::split_path("//"), ("//".to_owned(), "".to_owned()));
|
assert!(compare("//", "//", ""));
|
||||||
assert_eq!(Completion::split_path("/foo"), ("/".to_owned(), "foo".to_owned()));
|
assert!(compare("/foo", "/", "foo"));
|
||||||
assert_eq!(Completion::split_path("/foo/"), ("/foo/".to_owned(), "".to_owned()));
|
assert!(compare("/foo/", "/foo/", ""));
|
||||||
assert_eq!(Completion::split_path("/foo/bar"), ("/foo/".to_owned(), "bar".to_owned()));
|
assert!(compare("/foo/bar", "/foo/", "bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_split() {
|
fn test_split() {
|
||||||
assert_eq!(Completion::split_path("foo"), (".".to_owned(), "foo".to_owned()));
|
assert!(compare("foo", ".", "foo"));
|
||||||
assert_eq!(Completion::split_path("foo\\"), ("foo\\".to_owned(), "".to_owned()));
|
assert!(compare("foo\\", "foo\\", ""));
|
||||||
assert_eq!(Completion::split_path("foo\\bar"), ("foo\\".to_owned(), "bar".to_owned()));
|
assert!(compare("foo\\bar", "foo\\", "bar"));
|
||||||
assert_eq!(Completion::split_path("foo\\bar\\"), ("foo\\bar\\".to_owned(), "".to_owned()));
|
assert!(compare("foo\\bar\\", "foo\\bar\\", ""));
|
||||||
assert_eq!(Completion::split_path("C:\\"), ("C:\\".to_owned(), "".to_owned()));
|
assert!(compare("C:\\", "C:\\", ""));
|
||||||
assert_eq!(Completion::split_path("C:\\foo"), ("C:\\".to_owned(), "foo".to_owned()));
|
assert!(compare("C:\\foo", "C:\\", "foo"));
|
||||||
assert_eq!(Completion::split_path("C:\\foo\\"), ("C:\\foo\\".to_owned(), "".to_owned()));
|
assert!(compare("C:\\foo\\", "C:\\foo\\", ""));
|
||||||
assert_eq!(Completion::split_path("C:\\foo\\bar"), ("C:\\foo\\".to_owned(), "bar".to_owned()));
|
assert!(compare("C:\\foo\\bar", "C:\\foo\\", "bar"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::env;
|
use std::{env, path::MAIN_SEPARATOR};
|
||||||
|
|
||||||
use crossterm::{execute, terminal::SetTitle};
|
use crossterm::{execute, terminal::SetTitle};
|
||||||
use yazi_shared::event::Cmd;
|
use yazi_shared::event::Cmd;
|
||||||
|
|
@ -7,13 +7,9 @@ use crate::{manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
#[cfg(unix)]
|
let home = dirs::home_dir().unwrap_or_default();
|
||||||
let home = env::var_os("HOME").unwrap_or_default();
|
|
||||||
#[cfg(windows)]
|
|
||||||
let home = env::var_os("USERPROFILE").unwrap_or_default();
|
|
||||||
|
|
||||||
if let Some(p) = self.cwd().strip_prefix(home) {
|
if let Some(p) = self.cwd().strip_prefix(home) {
|
||||||
format!("Yazi: ~/{}", p.display())
|
format!("Yazi: ~{}{}", MAIN_SEPARATOR, p.display())
|
||||||
} else {
|
} else {
|
||||||
format!("Yazi: {}", self.cwd().display())
|
format!("Yazi: {}", self.cwd().display())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,7 @@ fn _expand_path(p: &Path) -> PathBuf {
|
||||||
|
|
||||||
let p = Path::new(s.as_ref());
|
let p = Path::new(s.as_ref());
|
||||||
if let Ok(rest) = p.strip_prefix("~") {
|
if let Ok(rest) = p.strip_prefix("~") {
|
||||||
#[cfg(unix)]
|
return dirs::home_dir().unwrap_or_default().join(rest);
|
||||||
let home = env::var_os("HOME");
|
|
||||||
#[cfg(windows)]
|
|
||||||
let home = env::var_os("USERPROFILE");
|
|
||||||
|
|
||||||
return if let Some(p) = home { PathBuf::from(p).join(rest) } else { rest.to_path_buf() };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.is_absolute() {
|
if p.is_absolute() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue