diff --git a/yazi-core/src/external/zoxide.rs b/yazi-core/src/external/zoxide.rs index 36828b57..d764ec56 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).into_dir()); + return Ok(Url::from(selected).push_slash()); } bail!("No match") } diff --git a/yazi-core/src/tab/commands/backstack.rs b/yazi-core/src/tab/commands/backstack.rs index a53cb7c3..b6d9fb05 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.into_dir()); + self.cd(url.push_slash()); } false } pub fn forward(&mut self) -> bool { if let Some(url) = self.backstack.shift_forward().cloned() { - self.cd(url.into_dir()); + self.cd(url.push_slash()); } false } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 1e72694d..3e5c445d 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -10,7 +10,7 @@ use crate::{emit, files::{File, FilesOp}, 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_dir(), target.parent_url()) { + 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; diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index 4ca695ab..a71bf8ae 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -36,7 +36,7 @@ impl Tab { while let Some(chunk) = rx.next().await { if first { emit!(Call( - Exec::call("cd", vec![cwd.clone().into_dir().to_string()]).vec(), + Exec::call("cd", vec![cwd.clone().push_slash().to_string()]).vec(), KeymapLayer::Manager )); first = false; diff --git a/yazi-shared/src/fns.rs b/yazi-shared/src/fns.rs index 7d8b5a32..f2af1661 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}}; +use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR, MAIN_SEPARATOR_STR}}; use tokio::fs; @@ -20,15 +20,24 @@ fn _expand_path(p: &Path) -> PathBuf { }); let p = Path::new(s.as_ref()); - if let Ok(p) = p.strip_prefix("~") { + if let (slash, Ok(rest)) = (ends_with_slash(p), p.strip_prefix("~")) { #[cfg(unix)] - if let Some(home) = env::var_os("HOME") { - return Path::new(&home).join(p); - } + let Some(home) = env::var_os("HOME") else { + return rest.to_path_buf(); + }; #[cfg(windows)] - if let Some(home) = env::var_os("USERPROFILE") { - return Path::new(&home).join(p); + let Some(home) = env::var_os("USERPROFILE") else { + return rest.to_path_buf(); + }; + + 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; } if p.is_absolute() { @@ -46,6 +55,39 @@ pub fn expand_url(mut u: Url) -> Url { u } +#[inline] +pub fn ends_with_slash(p: &Path) -> bool { + // TODO: uncomment this when Rust 1.74 is released + // let b = p.as_os_str().as_encoded_bytes(); + // if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } + + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + let b = p.as_os_str().as_bytes(); + if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } + } + + #[cfg(windows)] + { + let s = p.to_string_lossy(); + let b = s.as_bytes(); + if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } + } +} + +#[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 f9d19296..ed3771bb 100644 --- a/yazi-shared/src/url.rs +++ b/yazi-shared/src/url.rs @@ -1,7 +1,9 @@ -use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR}}; +use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR_STR}}; 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)] @@ -158,43 +160,14 @@ impl Url { } #[inline] - pub fn was_dir(&self) -> bool { - // TODO: uncomment this when Rust 1.74 is released - // let b = self.path.as_os_str().as_encoded_bytes(); - // if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } - - #[cfg(unix)] - { - use std::os::unix::ffi::OsStrExt; - let b = self.path.as_os_str().as_bytes(); - if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } - } - - #[cfg(windows)] - { - let s = self.path.to_string_lossy(); - let b = s.as_bytes(); - if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } - } - } + pub fn pop_slash(&mut self) -> bool { pop_end_slash(self) } #[inline] - pub fn pop_dir(&mut self) -> bool { - if !self.was_dir() { - return false; - } - if let Some(n) = self.path.file_name() { - self.path.set_file_name(n.to_owned()); - } - true - } - - #[inline] - pub fn into_dir(mut self) -> Self { - if self.was_dir() { + pub fn push_slash(mut self) -> Self { + if !ends_with_slash(&self) { self } else { - self.path.as_mut_os_string().push("/"); + self.path.as_mut_os_string().push(MAIN_SEPARATOR_STR); self } }