From 810059822469f36c40275f6ed58e4baeb155e3fd Mon Sep 17 00:00:00 2001 From: sxyazi Date: Wed, 8 Nov 2023 10:07:52 +0800 Subject: [PATCH] .. --- Cargo.lock | 1 + yazi-core/src/event.rs | 4 -- yazi-core/src/external/fzf.rs | 8 +-- yazi-core/src/tab/commands/cd.rs | 10 ++-- yazi-core/src/tab/commands/jump.rs | 3 +- yazi-core/src/tab/commands/search.rs | 6 +- yazi-fm/src/app.rs | 5 +- yazi-fm/src/executor.rs | 4 +- yazi-shared/Cargo.toml | 19 ++++--- yazi-shared/src/fns.rs | 9 ++- yazi-shared/src/url.rs | 83 +++++++++++++++++++++++++++- 11 files changed, 117 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f4b2f4a..4d5af202 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2565,6 +2565,7 @@ dependencies = [ "futures", "libc", "parking_lot", + "percent-encoding", "ratatui", "regex", "tokio", diff --git a/yazi-core/src/event.rs b/yazi-core/src/event.rs index d09f30f0..82baeb99 100644 --- a/yazi-core/src/event.rs +++ b/yazi-core/src/event.rs @@ -21,7 +21,6 @@ pub enum Event { Call(Vec, KeymapLayer), // Manager - Cd(Url), Refresh, Files(FilesOp), Pages(usize), @@ -74,9 +73,6 @@ macro_rules! emit { $crate::Event::Call($exec, $layer).emit(); }; - (Cd($url:expr)) => { - $crate::Event::Cd($url).emit(); - }; (Files($op:expr)) => { $crate::Event::Files($op).emit(); }; diff --git a/yazi-core/src/external/fzf.rs b/yazi-core/src/external/fzf.rs index 625c0156..dfe67329 100644 --- a/yazi-core/src/external/fzf.rs +++ b/yazi-core/src/external/fzf.rs @@ -1,4 +1,4 @@ -use std::process::Stdio; +use std::{path::Path, process::Stdio}; use anyhow::{bail, Result}; use tokio::process::Command; @@ -15,8 +15,8 @@ pub async fn fzf(opt: FzfOpt) -> Result { let output = child.wait_with_output().await?; let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if !selected.is_empty() { - return Ok(Url::from(selected)); + if selected.is_empty() { + bail!("No match") } - bail!("No match") + return Ok(Url::from(Path::new(&opt.cwd).join(selected))); } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 45eb6a9f..1e72694d 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -3,15 +3,14 @@ use std::{mem, time::Duration}; use tokio::pin; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use yazi_config::keymap::{Exec, KeymapLayer}; -use yazi_shared::{Debounce, InputError, Url}; +use yazi_shared::{expand_path, Debounce, InputError, Url}; use crate::{emit, files::{File, FilesOp}, input::InputOpt, tab::Tab}; impl Tab { - // TODO: change to sync, and remove `Event::Cd` pub fn cd(&mut self, mut target: Url) -> bool { let mut hovered = None; - if let (false, Some(parent)) = (target.was_dir(), target.parent_url()) { + if let (false, Some(parent)) = (target.pop_dir(), target.parent_url()) { emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map()))); hovered = Some(target); target = parent; @@ -68,7 +67,10 @@ impl Tab { while let Some(result) = rx.next().await { match result { Ok(s) => { - emit!(Cd(Url::from(s.trim()))); + emit!(Call( + Exec::call("cd", vec![expand_path(s).to_string_lossy().to_string()]).vec(), + KeymapLayer::Manager + )); } Err(InputError::Completed(before, ticket)) => { emit!(Call( diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index 77042771..11725caf 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -1,3 +1,4 @@ +use yazi_config::keymap::{Exec, KeymapLayer}; use yazi_shared::Defer; use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER}; @@ -17,7 +18,7 @@ impl Tab { external::zoxide(ZoxideOpt { cwd }).await }?; - emit!(Cd(url)); + emit!(Call(Exec::call("cd", vec![url.to_string()]).vec(), KeymapLayer::Manager)); Ok::<(), anyhow::Error>(()) }); false diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index fa2fa927..4ca695ab 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -3,6 +3,7 @@ use std::{mem, time::Duration}; use anyhow::bail; use tokio::pin; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; +use yazi_config::keymap::{Exec, KeymapLayer}; use crate::{emit, external, files::FilesOp, input::InputOpt, tab::Tab}; @@ -34,7 +35,10 @@ impl Tab { let mut first = true; while let Some(chunk) = rx.next().await { if first { - emit!(Cd(cwd.clone())); + emit!(Call( + Exec::call("cd", vec![cwd.clone().into_dir().to_string()]).vec(), + KeymapLayer::Manager + )); first = false; } emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk))); diff --git a/yazi-fm/src/app.rs b/yazi-fm/src/app.rs index 44ce1aa6..ded9cc75 100644 --- a/yazi-fm/src/app.rs +++ b/yazi-fm/src/app.rs @@ -5,7 +5,7 @@ use crossterm::event::KeyEvent; use tokio::sync::oneshot; use yazi_config::{keymap::{Exec, Key, KeymapLayer}, BOOT}; use yazi_core::{emit, files::FilesOp, input::InputMode, Ctx, Event}; -use yazi_shared::{expand_url, Term}; +use yazi_shared::Term; use crate::{Executor, Logs, Root, Signals}; @@ -121,9 +121,6 @@ impl App { let manager = &mut self.cx.manager; let tasks = &mut self.cx.tasks; match event { - Event::Cd(url) => { - manager.active_mut().cd(expand_url(url)); - } Event::Refresh => { manager.refresh(); } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 5b8062d5..39424d38 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -1,6 +1,6 @@ use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}; use yazi_core::{input::InputMode, tab::FinderCase, Ctx}; -use yazi_shared::{optional_bool, Url}; +use yazi_shared::{expand_url, optional_bool, Url}; pub(super) struct Executor<'a> { cx: &'a mut Ctx, @@ -96,7 +96,7 @@ impl<'a> Executor<'a> { if exec.named.contains_key("interactive") { self.cx.manager.active_mut().cd_interactive(url) } else { - self.cx.manager.active_mut().cd(url) + self.cx.manager.active_mut().cd(expand_url(url)) } } diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index bd5447c9..2dd4fbe0 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -9,12 +9,13 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -anyhow = "^1" -bitflags = "^2" -crossterm = "^0" -futures = "^0" -libc = "^0" -parking_lot = "^0" -ratatui = "^0" -regex = "^1" -tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] } +anyhow = "^1" +bitflags = "^2" +crossterm = "^0" +futures = "^0" +libc = "^0" +parking_lot = "^0" +percent-encoding = "^2" +ratatui = "^0" +regex = "^1" +tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] } diff --git a/yazi-shared/src/fns.rs b/yazi-shared/src/fns.rs index 7e4cb931..7d8b5a32 100644 --- a/yazi-shared/src/fns.rs +++ b/yazi-shared/src/fns.rs @@ -4,7 +4,7 @@ use tokio::fs; use crate::Url; -pub fn expand_path(p: impl AsRef) -> PathBuf { +fn _expand_path(p: &Path) -> PathBuf { // ${HOME} or $HOME #[cfg(unix)] let re = regex::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap(); @@ -13,7 +13,7 @@ pub fn expand_path(p: impl AsRef) -> PathBuf { #[cfg(windows)] let re = regex::Regex::new(r"%([^%]+)%").unwrap(); - let s = p.as_ref().to_string_lossy(); + let s = p.to_string_lossy(); let s = re.replace_all(&s, |caps: ®ex::Captures| { let name = caps.get(2).or_else(|| caps.get(1)).unwrap(); env::var(name.as_str()).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_owned()) @@ -37,9 +37,12 @@ pub fn expand_path(p: impl AsRef) -> PathBuf { env::current_dir().map_or_else(|_| p.to_path_buf(), |c| c.join(p)) } +#[inline] +pub fn expand_path(p: impl AsRef) -> PathBuf { _expand_path(p.as_ref()) } + #[inline] pub fn expand_url(mut u: Url) -> Url { - u.set_path(expand_path(&u)); + u.set_path(_expand_path(&u)); u } diff --git a/yazi-shared/src/url.rs b/yazi-shared/src/url.rs index 270d802f..f9d19296 100644 --- a/yazi-shared/src/url.rs +++ b/yazi-shared/src/url.rs @@ -1,5 +1,9 @@ use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR}}; +use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS}; + +const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#'); + #[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Url { scheme: UrlScheme, @@ -42,15 +46,41 @@ impl From<&Path> for Url { } impl From for Url { - fn from(path: String) -> Self { Self::from(PathBuf::from(path)) } + fn from(path: String) -> Self { Self::from(path.as_str()) } } impl From<&String> for Url { - fn from(path: &String) -> Self { Self::from(PathBuf::from(path)) } + fn from(path: &String) -> Self { Self::from(path.as_str()) } } impl From<&str> for Url { - fn from(path: &str) -> Self { Self::from(PathBuf::from(path)) } + fn from(mut path: &str) -> Self { + let mut url = Url::default(); + match path.split_once("://").map(|(a, b)| (UrlScheme::from(a), b)) { + None => { + url.path = PathBuf::from(path); + return url; + } + Some((UrlScheme::Regular, b)) => { + url.path = PathBuf::from(b); + return url; + } + Some((a, b)) => { + url.scheme = a; + path = b; + } + } + match path.split_once('#') { + None => { + url.path = percent_decode_str(path).decode_utf8_lossy().into_owned().into(); + } + Some((a, b)) => { + url.path = percent_decode_str(a).decode_utf8_lossy().into_owned().into(); + url.frag = Some(b.to_string()).filter(|s| !s.is_empty()); + } + } + url + } } impl AsRef for Url { @@ -65,6 +95,32 @@ impl AsRef for Url { fn as_ref(&self) -> &OsStr { self.path.as_os_str() } } +impl ToString for Url { + fn to_string(&self) -> String { + if self.scheme == UrlScheme::Regular { + return self.path.to_string_lossy().to_string(); + } + + let scheme = match self.scheme { + UrlScheme::Regular => unreachable!(), + UrlScheme::Search => "search://", + UrlScheme::Archive => "archive://", + }; + + #[cfg(unix)] + let path = { + use std::os::unix::ffi::OsStrExt; + percent_encode(self.path.as_os_str().as_bytes(), ENCODE_SET) + }; + #[cfg(windows)] + let path = percent_encode(self.path.to_string_lossy().as_bytes(), ENCODE_SET).to_string(); + + let frag = self.frag.as_ref().map(|s| format!("#{s}")).unwrap_or_default(); + + format!("{scheme}{path}{frag}") + } +} + impl Url { #[inline] pub fn join(&self, path: impl AsRef) -> Self { @@ -122,6 +178,17 @@ impl Url { } } + #[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() { @@ -180,3 +247,13 @@ impl Url { #[inline] pub fn frag(&self) -> Option<&str> { self.frag.as_deref() } } + +impl From<&str> for UrlScheme { + fn from(value: &str) -> Self { + match value { + "search" => UrlScheme::Search, + "archive" => UrlScheme::Archive, + _ => UrlScheme::Regular, + } + } +}