From 9af792ef69b52e841a3e8574f892cc233049790f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Wed, 18 Feb 2026 17:16:05 +0800 Subject: [PATCH] feat: new `--in` for `search` action to set search directory (#3696) --- CHANGELOG.md | 2 ++ Cargo.lock | 25 ++++++++++++------------- Cargo.toml | 4 ++-- yazi-actor/src/mgr/search.rs | 15 ++++++++++----- yazi-actor/src/mgr/tab_create.rs | 11 +++++++---- yazi-core/src/tab/backstack.rs | 2 ++ yazi-parser/src/mgr/search.rs | 29 +++++++++++++++++------------ yazi-plugin/src/external/fd.rs | 5 ++++- yazi-plugin/src/external/rg.rs | 7 +++++-- yazi-plugin/src/external/rga.rs | 7 +++++-- yazi-proxy/src/mgr.rs | 9 +-------- yazi-shared/src/url/buf.rs | 8 -------- yazi-shared/src/url/url.rs | 9 ++++++++- yazi-vfs/Cargo.toml | 2 +- 14 files changed, 76 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b6aad7..108d89b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): ### Added - Custom tab name ([#3666]) +- New `--in` for `search` action to set search directory ([#3696]) - Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638]) - New `sort_fallback` option to control fallback sorting behavior ([#3077]) - New `fs.access()` API to access the filesystem ([#3668]) @@ -1664,3 +1665,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#3684]: https://github.com/sxyazi/yazi/pull/3684 [#3687]: https://github.com/sxyazi/yazi/pull/3687 [#3689]: https://github.com/sxyazi/yazi/pull/3689 +[#3696]: https://github.com/sxyazi/yazi/pull/3696 diff --git a/Cargo.lock b/Cargo.lock index 095f7493..1877823e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -600,9 +600,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", "clap_derive", @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -1105,21 +1105,20 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deadpool" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +checksum = "883466cb8db62725aee5f4a6011e8a5d42912b42632df32aad57fc91127c6e04" dependencies = [ "deadpool-runtime", - "lazy_static", "num_cpus", "tokio", ] [[package]] name = "deadpool-runtime" -version = "0.1.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae" dependencies = [ "tokio", ] @@ -4676,9 +4675,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.1+spec-1.1.0" +version = "1.0.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe30f93627849fa362d4a602212d41bb237dc2bd0f8ba0b2ce785012e124220" +checksum = "d1dfefef6a142e93f346b64c160934eb13b5594b84ab378133ac6815cb2bd57f" dependencies = [ "indexmap 2.13.0", "serde_core", @@ -4700,9 +4699,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 03f07117..e75801b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ ansi-to-tui = "8.0.1" anyhow = "1.0.101" base64 = "0.22.1" bitflags = { version = "2.11.0", features = [ "serde" ] } -clap = { version = "4.5.58", features = [ "derive" ] } +clap = { version = "4.5.59", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.29.0", features = [ "event-stream" ] } dirs = "6.0.0" @@ -62,7 +62,7 @@ thiserror = "2.0.18" tokio = { version = "1.49.0", features = [ "full" ] } tokio-stream = "0.1.18" tokio-util = "0.7.18" -toml = { version = "1.0.1" } +toml = { version = "1.0.2" } tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] } twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] } typed-path = "0.12.3" diff --git a/yazi-actor/src/mgr/search.rs b/yazi-actor/src/mgr/search.rs index c9362254..acaab7a7 100644 --- a/yazi-actor/src/mgr/search.rs +++ b/yazi-actor/src/mgr/search.rs @@ -9,7 +9,7 @@ use yazi_macro::{act, succ}; use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}}; use yazi_plugin::external; use yazi_proxy::{InputProxy, MgrProxy, NotifyProxy}; -use yazi_shared::{data::Data, url::UrlLike}; +use yazi_shared::{data::Data, url::{AsUrl, UrlLike}}; use crate::{Actor, Ctx}; @@ -53,7 +53,8 @@ impl Actor for SearchDo { } let hidden = tab.pref.show_hidden; - let Ok(cwd) = tab.cwd().to_search(&opt.subject) else { + let r#in = opt.r#in.as_ref().map_or_else(|| tab.cwd().as_url(), |u| u.as_url()); + let Ok(cwd) = r#in.to_search(&opt.subject) else { succ!(NotifyProxy::push_warn("Search", "Only local filesystem searches are supported")); }; @@ -109,10 +110,14 @@ impl Actor for SearchStop { handle.abort(); } - if tab.cwd().is_search() { - act!(mgr:cd, cx, (tab.cwd().to_regular()?, CdSource::Escape))?; + if !tab.cwd().is_search() { + succ!(); } - succ!(); + if let Some(u) = tab.backstack.current().cloned() { + act!(mgr:cd, cx, (u, CdSource::Escape)) + } else { + act!(mgr:cd, cx, (tab.cwd().to_regular()?, CdSource::Escape)) + } } } diff --git a/yazi-actor/src/mgr/tab_create.rs b/yazi-actor/src/mgr/tab_create.rs index ed31a0b6..848b176e 100644 --- a/yazi-actor/src/mgr/tab_create.rs +++ b/yazi-actor/src/mgr/tab_create.rs @@ -30,12 +30,15 @@ impl Actor for TabCreate { } else if let Some(h) = cx.hovered() { tab.pref = cx.tab().pref.clone(); (false, h.url.clone()) - } else if cx.cwd().is_search() { - tab.pref = cx.tab().pref.clone(); - (true, cx.cwd().to_regular()?) - } else { + } else if !cx.cwd().is_search() { tab.pref = cx.tab().pref.clone(); (true, cx.cwd().clone()) + } else if let Some(u) = tab.backstack.current().cloned() { + tab.pref = cx.tab().pref.clone(); + (true, u) + } else { + tab.pref = cx.tab().pref.clone(); + (true, tab.cwd().to_regular()?) }; let tabs = &mut cx.mgr.tabs; diff --git a/yazi-core/src/tab/backstack.rs b/yazi-core/src/tab/backstack.rs index b3f3f79e..84b73998 100644 --- a/yazi-core/src/tab/backstack.rs +++ b/yazi-core/src/tab/backstack.rs @@ -33,6 +33,8 @@ impl Backstack { } } + pub fn current(&self) -> Option<&UrlBuf> { self.stack.get(self.cursor) } + pub fn shift_backward(&mut self) -> Option<&UrlBuf> { if self.cursor > 0 { self.cursor -= 1; diff --git a/yazi-parser/src/mgr/search.rs b/yazi-parser/src/mgr/search.rs index 87fddfe5..f4110804 100644 --- a/yazi-parser/src/mgr/search.rs +++ b/yazi-parser/src/mgr/search.rs @@ -3,37 +3,42 @@ use std::str::FromStr; use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use serde::Deserialize; -use yazi_shared::{SStr, event::ActionCow}; +use yazi_shared::{SStr, event::ActionCow, url::{UrlCow, UrlLike}}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct SearchOpt { pub via: SearchOptVia, pub subject: SStr, pub args: Vec, pub args_raw: SStr, + pub r#in: Option>, } impl TryFrom for SearchOpt { type Error = anyhow::Error; fn try_from(mut a: ActionCow) -> Result { - // TODO: remove this - let (via, subject) = if let Ok(s) = a.get("via") { - (str::parse(s)?, a.take_first().unwrap_or_default()) - } else { - (a.str(0).parse()?, "".into()) - }; + if let Some(opt) = a.take_any2("opt") { + return opt; + } + + let r#in = a.take::("in").ok(); + if let Some(u) = &r#in + && (!u.is_absolute() || u.is_search()) + { + bail!("invalid 'in' in SearchOpt"); + } let Ok(args) = yazi_shared::shell::unix::split(a.str("args"), false) else { - bail!("Invalid 'args' in SearchOpt"); + bail!("invalid 'args' in SearchOpt"); }; Ok(Self { - via, - subject, - // TODO: use second positional argument instead of `args` parameter + via: a.str("via").parse()?, + subject: a.take_first().unwrap_or_default(), args: args.0, args_raw: a.take("args").unwrap_or_default(), + r#in, }) } } diff --git a/yazi-plugin/src/external/fd.rs b/yazi-plugin/src/external/fd.rs index 5b0545a5..a8311aeb 100644 --- a/yazi-plugin/src/external/fd.rs +++ b/yazi-plugin/src/external/fd.rs @@ -1,4 +1,4 @@ -use std::process::Stdio; +use std::{path::Path, process::Stdio}; use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}}; @@ -21,6 +21,9 @@ pub fn fd(opt: FdOpt) -> Result> { tokio::spawn(async move { while let Ok(Some(line)) = it.next_line().await { + if Path::new(&line).is_absolute() { + continue; + } let Ok(url) = opt.cwd.try_join(line) else { continue; }; diff --git a/yazi-plugin/src/external/rg.rs b/yazi-plugin/src/external/rg.rs index 65c72431..5ab3a8ed 100644 --- a/yazi-plugin/src/external/rg.rs +++ b/yazi-plugin/src/external/rg.rs @@ -1,4 +1,4 @@ -use std::process::Stdio; +use std::{path::Path, process::Stdio}; use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}}; @@ -19,7 +19,7 @@ pub fn rg(opt: RgOpt) -> Result> { .arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) .args(opt.args) .arg(opt.subject) - .arg(&*opt.cwd.as_url().unified_path()) + .current_dir(&*opt.cwd.as_url().unified_path()) .kill_on_drop(true) .stdout(Stdio::piped()) .stderr(Stdio::null()) @@ -30,6 +30,9 @@ pub fn rg(opt: RgOpt) -> Result> { tokio::spawn(async move { while let Ok(Some(line)) = it.next_line().await { + if Path::new(&line).is_absolute() { + continue; + } let Ok(url) = opt.cwd.try_join(line) else { continue; }; diff --git a/yazi-plugin/src/external/rga.rs b/yazi-plugin/src/external/rga.rs index a41a5acb..5da3f7f6 100644 --- a/yazi-plugin/src/external/rga.rs +++ b/yazi-plugin/src/external/rga.rs @@ -1,4 +1,4 @@ -use std::process::Stdio; +use std::{path::Path, process::Stdio}; use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}}; @@ -19,7 +19,7 @@ pub fn rga(opt: RgaOpt) -> Result> { .arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) .args(opt.args) .arg(opt.subject) - .arg(&*opt.cwd.as_url().unified_path()) + .current_dir(&*opt.cwd.as_url().unified_path()) .kill_on_drop(true) .stdout(Stdio::piped()) .stderr(Stdio::null()) @@ -30,6 +30,9 @@ pub fn rga(opt: RgaOpt) -> Result> { tokio::spawn(async move { while let Ok(Some(line)) = it.next_line().await { + if Path::new(&line).is_absolute() { + continue; + } let Ok(url) = opt.cwd.try_join(line) else { continue; }; diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs index 9b4fdb58..25777a0e 100644 --- a/yazi-proxy/src/mgr.rs +++ b/yazi-proxy/src/mgr.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use yazi_macro::{emit, relay}; use yazi_parser::mgr::{DisplaceDoOpt, FilterOpt, FindDoOpt, OpenDoOpt, OpenOpt, SearchOpt, UpdatePeekedOpt, UpdateSpottedOpt}; use yazi_shared::{Id, SStr, url::UrlBuf}; @@ -50,12 +48,7 @@ impl MgrProxy { } pub fn search_do(opt: SearchOpt) { - emit!(Call( - // TODO: use second positional argument instead of `args` parameter - relay!(mgr:search_do, [opt.subject]) - .with("via", Cow::Borrowed(opt.via.into_str())) - .with("args", opt.args_raw.into_owned()) - )); + emit!(Call(relay!(mgr:search_do).with_any("opt", opt))); } pub fn tab_rename(tab: Id, name: impl Into) { diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index 3acfca56..0c690057 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -179,14 +179,6 @@ impl UrlBuf { Ok(Self::Regular(self.into_loc().into_os()?.into())) } - #[inline] - pub fn to_search(&self, domain: impl AsRef) -> Result { - Ok(Self::Search { - loc: LocBuf::::zeroed(self.loc().to_os_owned()?), - domain: Pool::::intern(domain), - }) - } - #[inline] pub fn into_search(self, domain: impl AsRef) -> Result { Ok(Self::Search { diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index 54fdd1e1..44afc8da 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -5,7 +5,7 @@ use hashbrown::Equivalent; use serde::Serialize; use super::Encode as EncodeUrl; -use crate::{loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, pool::InternStr, scheme::{Encode as EncodeScheme, SchemeCow, SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}}; +use crate::{loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, pool::{InternStr, Pool}, scheme::{Encode as EncodeScheme, SchemeCow, SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}}; #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub enum Url<'a> { @@ -222,6 +222,13 @@ impl<'a> Url<'a> { #[inline] pub fn to_owned(self) -> UrlBuf { self.into() } + pub fn to_search(self, domain: impl AsRef) -> Result { + Ok(UrlBuf::Search { + loc: LocBuf::::zeroed(self.loc().to_os_owned()?), + domain: Pool::::intern(domain), + }) + } + pub fn trail(self) -> Self { let uri = self.uri(); match self { diff --git a/yazi-vfs/Cargo.toml b/yazi-vfs/Cargo.toml index f2fd10e0..e98d1009 100644 --- a/yazi-vfs/Cargo.toml +++ b/yazi-vfs/Cargo.toml @@ -20,7 +20,7 @@ yazi-shared = { path = "../yazi-shared", version = "26.2.2" } # External dependencies anyhow = { workspace = true } -deadpool = { version = "0.12.3", default-features = false, features = [ "managed", "rt_tokio_1" ] } +deadpool = { version = "0.13.0", default-features = false, features = [ "managed", "rt_tokio_1" ] } either = { workspace = true } futures = { workspace = true } hashbrown = { workspace = true }