feat: new --in for search action to set search directory (#3696)

This commit is contained in:
三咲雅 misaki masa 2026-02-18 17:16:05 +08:00 committed by GitHub
parent 578016e00f
commit 9af792ef69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 76 additions and 59 deletions

View file

@ -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

25
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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))
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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<String>,
pub args_raw: SStr,
pub r#in: Option<UrlCow<'static>>,
}
impl TryFrom<ActionCow> for SearchOpt {
type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
// 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::<UrlCow>("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,
})
}
}

View file

@ -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<UnboundedReceiver<File>> {
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;
};

View file

@ -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<UnboundedReceiver<File>> {
.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<UnboundedReceiver<File>> {
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;
};

View file

@ -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<UnboundedReceiver<File>> {
.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<UnboundedReceiver<File>> {
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;
};

View file

@ -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<SStr>) {

View file

@ -179,14 +179,6 @@ impl UrlBuf {
Ok(Self::Regular(self.into_loc().into_os()?.into()))
}
#[inline]
pub fn to_search(&self, domain: impl AsRef<str>) -> Result<Self, PathDynError> {
Ok(Self::Search {
loc: LocBuf::<PathBuf>::zeroed(self.loc().to_os_owned()?),
domain: Pool::<str>::intern(domain),
})
}
#[inline]
pub fn into_search(self, domain: impl AsRef<str>) -> Result<Self, PathDynError> {
Ok(Self::Search {

View file

@ -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<str>) -> Result<UrlBuf, PathDynError> {
Ok(UrlBuf::Search {
loc: LocBuf::<PathBuf>::zeroed(self.loc().to_os_owned()?),
domain: Pool::<str>::intern(domain),
})
}
pub fn trail(self) -> Self {
let uri = self.uri();
match self {

View file

@ -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 }