diff --git a/yazi-core/src/external/zoxide.rs b/yazi-core/src/external/zoxide.rs index d764ec56..11900af8 100644 --- a/yazi-core/src/external/zoxide.rs +++ b/yazi-core/src/external/zoxide.rs @@ -20,7 +20,7 @@ pub async fn zoxide(opt: ZoxideOpt) -> Result { let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !selected.is_empty() { - return Ok(Url::from(selected).push_slash()); + return Ok(Url::from(selected)); } bail!("No match") } diff --git a/yazi-core/src/files/file.rs b/yazi-core/src/files/file.rs index 468a1859..c5c9f95b 100644 --- a/yazi-core/src/files/file.rs +++ b/yazi-core/src/files/file.rs @@ -39,7 +39,7 @@ impl File { cm |= ChaMeta::BAD_LINK; } - if url.was_hidden() { + if url.is_hidden() { cm |= ChaMeta::HIDDEN; } @@ -48,7 +48,7 @@ impl File { #[inline] pub fn from_dummy(url: Url) -> Self { - let cm = if url.was_hidden() { ChaMeta::HIDDEN } else { ChaMeta::empty() }; + let cm = if url.is_hidden() { ChaMeta::HIDDEN } else { ChaMeta::empty() }; Self { url, cha: Cha::default().with_meta(cm), link_to: None } } diff --git a/yazi-core/src/tab/commands/backstack.rs b/yazi-core/src/tab/commands/backstack.rs index b6d9fb05..5d11b7ab 100644 --- a/yazi-core/src/tab/commands/backstack.rs +++ b/yazi-core/src/tab/commands/backstack.rs @@ -3,14 +3,14 @@ use crate::tab::Tab; impl Tab { pub fn back(&mut self) -> bool { if let Some(url) = self.backstack.shift_backward().cloned() { - self.cd(url.push_slash()); + self.cd(url); } false } pub fn forward(&mut self) -> bool { if let Some(url) = self.backstack.shift_forward().cloned() { - self.cd(url.push_slash()); + self.cd(url); } false } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 3e5c445d..4ff3240f 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -1,26 +1,15 @@ use std::{mem, time::Duration}; -use tokio::pin; +use tokio::{fs, pin}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use yazi_config::keymap::{Exec, KeymapLayer}; use yazi_shared::{expand_path, Debounce, InputError, Url}; -use crate::{emit, files::{File, FilesOp}, input::InputOpt, tab::Tab}; +use crate::{emit, input::InputOpt, tab::Tab}; impl Tab { - pub fn cd(&mut self, mut target: Url) -> bool { - let mut hovered = None; - if let (false, Some(parent)) = (target.pop_slash(), target.parent_url()) { - emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map()))); - hovered = Some(target); - target = parent; - } - - // Already in target + pub fn cd(&mut self, target: Url) -> bool { if self.current.cwd == target { - if let Some(h) = hovered { - emit!(Hover(h)); - } return false; } @@ -41,11 +30,6 @@ impl Tab { self.parent = Some(self.history_new(&parent)); } - // Hover the file - if let Some(h) = hovered { - emit!(Hover(h)); - } - // Backstack if target.is_regular() { self.backstack.push(target.clone()); @@ -67,8 +51,16 @@ impl Tab { while let Some(result) = rx.next().await { match result { Ok(s) => { + let p = expand_path(s); + let Ok(meta) = fs::metadata(&p).await else { + return; + }; + emit!(Call( - Exec::call("cd", vec![expand_path(s).to_string_lossy().to_string()]).vec(), + Exec::call(if meta.is_dir() { "cd" } else { "reveal" }, vec![ + p.to_string_lossy().to_string() + ]) + .vec(), KeymapLayer::Manager )); } diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index 11725caf..6a0aea4c 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -1,5 +1,5 @@ use yazi_config::keymap::{Exec, KeymapLayer}; -use yazi_shared::Defer; +use yazi_shared::{ends_with_slash, Defer}; use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER}; @@ -18,7 +18,8 @@ impl Tab { external::zoxide(ZoxideOpt { cwd }).await }?; - emit!(Call(Exec::call("cd", vec![url.to_string()]).vec(), KeymapLayer::Manager)); + let op = if global && !ends_with_slash(&url) { "reveal" } else { "cd" }; + emit!(Call(Exec::call(op, vec![url.to_string()]).vec(), KeymapLayer::Manager)); Ok::<(), anyhow::Error>(()) }); false diff --git a/yazi-core/src/tab/commands/mod.rs b/yazi-core/src/tab/commands/mod.rs index 47fa94f7..fa8d1c39 100644 --- a/yazi-core/src/tab/commands/mod.rs +++ b/yazi-core/src/tab/commands/mod.rs @@ -9,6 +9,7 @@ mod hidden; mod jump; mod leave; mod linemode; +mod reveal; mod search; mod select; mod shell; diff --git a/yazi-core/src/tab/commands/reveal.rs b/yazi-core/src/tab/commands/reveal.rs new file mode 100644 index 00000000..bb2d713d --- /dev/null +++ b/yazi-core/src/tab/commands/reveal.rs @@ -0,0 +1,28 @@ +use yazi_config::keymap::Exec; +use yazi_shared::Url; + +use crate::{emit, files::{File, FilesOp}, tab::Tab}; + +pub struct Opt<'a> { + target: &'a str, +} + +impl<'a> From<&'a Exec> for Opt<'a> { + fn from(e: &'a Exec) -> Self { Self { target: e.args.first().map(|s| s.as_str()).unwrap_or("") } } +} + +impl Tab { + pub fn reveal<'a>(&mut self, opt: impl Into>) -> bool { + let opt = opt.into() as Opt; + + let target = Url::from(opt.target); + let Some(parent) = target.parent_url() else { + return false; + }; + + let b = self.cd(parent.clone()); + emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map()))); + emit!(Hover(target)); + b + } +} diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index a71bf8ae..80191ce2 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -35,10 +35,7 @@ impl Tab { let mut first = true; while let Some(chunk) = rx.next().await { if first { - emit!(Call( - Exec::call("cd", vec![cwd.clone().push_slash().to_string()]).vec(), - KeymapLayer::Manager - )); + emit!(Call(Exec::call("cd", vec![cwd.clone().to_string()]).vec(), KeymapLayer::Manager)); first = false; } emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk))); diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 39424d38..9a27f06e 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -99,6 +99,7 @@ impl<'a> Executor<'a> { self.cx.manager.active_mut().cd(expand_url(url)) } } + "reveal" => self.cx.manager.active_mut().reveal(exec), // Selection "select" => { diff --git a/yazi-shared/src/fns.rs b/yazi-shared/src/fns.rs index f2af1661..59e0e24a 100644 --- a/yazi-shared/src/fns.rs +++ b/yazi-shared/src/fns.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR, MAIN_SEPARATOR_STR}}; +use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}}; use tokio::fs; @@ -20,24 +20,13 @@ fn _expand_path(p: &Path) -> PathBuf { }); let p = Path::new(s.as_ref()); - if let (slash, Ok(rest)) = (ends_with_slash(p), p.strip_prefix("~")) { + if let Ok(rest) = p.strip_prefix("~") { #[cfg(unix)] - let Some(home) = env::var_os("HOME") else { - return rest.to_path_buf(); - }; + let home = env::var_os("HOME"); #[cfg(windows)] - let Some(home) = env::var_os("USERPROFILE") else { - return rest.to_path_buf(); - }; + let home = env::var_os("USERPROFILE"); - let mut home = PathBuf::from(home); - pop_end_slash(&mut home); - - let mut p = if rest == Path::new("") { home } else { home.join(rest) }; - if slash { - p.as_mut_os_string().push(MAIN_SEPARATOR_STR); - } - return p; + return if let Some(p) = home { PathBuf::from(p).join(rest) } else { rest.to_path_buf() }; } if p.is_absolute() { @@ -76,18 +65,6 @@ pub fn ends_with_slash(p: &Path) -> bool { } } -#[inline] -#[allow(clippy::unnecessary_to_owned)] -pub fn pop_end_slash(p: &mut PathBuf) -> bool { - if !ends_with_slash(p) { - return false; - } - if let Some(n) = p.file_name() { - p.set_file_name(n.to_owned()); - } - true -} - pub async fn unique_path(mut p: Url) -> Url { let Some(stem) = p.file_stem().map(|s| s.to_owned()) else { return p; diff --git a/yazi-shared/src/url.rs b/yazi-shared/src/url.rs index ed3771bb..4724c4c7 100644 --- a/yazi-shared/src/url.rs +++ b/yazi-shared/src/url.rs @@ -1,9 +1,7 @@ -use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR_STR}}; +use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf}}; use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS}; -use crate::{ends_with_slash, pop_end_slash}; - const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#'); #[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -155,22 +153,9 @@ impl Url { pub fn into_os_string(self) -> OsString { self.path.into_os_string() } #[inline] - pub fn was_hidden(&self) -> bool { + pub fn is_hidden(&self) -> bool { self.file_name().map_or(false, |s| s.to_string_lossy().starts_with('.')) } - - #[inline] - pub fn pop_slash(&mut self) -> bool { pop_end_slash(self) } - - #[inline] - pub fn push_slash(mut self) -> Self { - if !ends_with_slash(&self) { - self - } else { - self.path.as_mut_os_string().push(MAIN_SEPARATOR_STR); - self - } - } } impl Url {