feat: expose stash action and give it a new search source (#3872)

This commit is contained in:
三咲雅 misaki masa 2026-04-11 07:59:38 +08:00 committed by GitHub
parent f6408656b4
commit 74d903a49a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 471 additions and 274 deletions

88
Cargo.lock generated
View file

@ -588,9 +588,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.59" version = "1.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -1365,9 +1365,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]] [[package]]
name = "elliptic-curve" name = "elliptic-curve"
version = "0.14.0-rc.29" version = "0.14.0-rc.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e84043d573efd4ac9d2d125817979a379204bf7e328b25a4a30487e8d100e618" checksum = "7d7a0bfd012613a7bcfe02cbfccf2b846e9ef9e1bccb641c48d461253cfb034d"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"crypto-bigint", "crypto-bigint",
@ -1793,9 +1793,9 @@ dependencies = [
[[package]] [[package]]
name = "gif" name = "gif"
version = "0.14.1" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [ dependencies = [
"color_quant", "color_quant",
"weezl", "weezl",
@ -1851,6 +1851,17 @@ name = "hashbrown"
version = "0.16.1" version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@ -2010,12 +2021,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.1" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"serde", "serde",
"serde_core", "serde_core",
] ]
@ -2279,9 +2290,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -3176,7 +3187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
dependencies = [ dependencies = [
"base64", "base64",
"indexmap 2.13.1", "indexmap 2.14.0",
"quick-xml", "quick-xml",
"serde", "serde",
"time", "time",
@ -4103,7 +4114,7 @@ dependencies = [
"chrono", "chrono",
"hex", "hex",
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.13.1", "indexmap 2.14.0",
"schemars 0.9.0", "schemars 0.9.0",
"schemars 1.2.1", "schemars 1.2.1",
"serde_core", "serde_core",
@ -4667,9 +4678,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.51.0" version = "1.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -4723,7 +4734,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [ dependencies = [
"indexmap 2.13.1", "indexmap 2.14.0",
"serde_core", "serde_core",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@ -5153,7 +5164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"indexmap 2.13.1", "indexmap 2.14.0",
"wasm-encoder", "wasm-encoder",
"wasmparser", "wasmparser",
] ]
@ -5166,7 +5177,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.13.1", "indexmap 2.14.0",
"semver", "semver",
] ]
@ -5662,7 +5673,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"heck", "heck",
"indexmap 2.13.1", "indexmap 2.14.0",
"prettyplease", "prettyplease",
"syn 2.0.117", "syn 2.0.117",
"wasm-metadata", "wasm-metadata",
@ -5693,7 +5704,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.0",
"indexmap 2.13.1", "indexmap 2.14.0",
"log", "log",
"serde", "serde",
"serde_derive", "serde_derive",
@ -5712,7 +5723,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"id-arena", "id-arena",
"indexmap 2.13.1", "indexmap 2.14.0",
"log", "log",
"semver", "semver",
"serde", "serde",
@ -5745,7 +5756,7 @@ dependencies = [
"crossterm 0.29.0", "crossterm 0.29.0",
"either", "either",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"libc", "libc",
"mlua", "mlua",
"paste", "paste",
@ -5809,7 +5820,7 @@ dependencies = [
"anyhow", "anyhow",
"crossterm 0.29.0", "crossterm 0.29.0",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"libc", "libc",
"mlua", "mlua",
"paste", "paste",
@ -5840,7 +5851,7 @@ dependencies = [
"clap_complete_fig", "clap_complete_fig",
"clap_complete_nushell", "clap_complete_nushell",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"regex", "regex",
"vergen-gitcl", "vergen-gitcl",
"yazi-adapter", "yazi-adapter",
@ -5869,7 +5880,7 @@ dependencies = [
"clap_complete_fig", "clap_complete_fig",
"clap_complete_nushell", "clap_complete_nushell",
"crossterm 0.29.0", "crossterm 0.29.0",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@ -5900,8 +5911,8 @@ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"crossterm 0.29.0", "crossterm 0.29.0",
"globset", "globset",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"indexmap 2.13.1", "indexmap 2.14.0",
"ratatui", "ratatui",
"regex", "regex",
"serde", "serde",
@ -5923,8 +5934,8 @@ dependencies = [
"anyhow", "anyhow",
"crossterm 0.29.0", "crossterm 0.29.0",
"dyn-clone", "dyn-clone",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"indexmap 2.13.1", "indexmap 2.14.0",
"mlua", "mlua",
"parking_lot", "parking_lot",
"ratatui", "ratatui",
@ -5959,8 +5970,8 @@ name = "yazi-dds"
version = "26.2.2" version = "26.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"indexmap 2.13.1", "indexmap 2.14.0",
"mlua", "mlua",
"ordered-float 5.3.0", "ordered-float 5.3.0",
"parking_lot", "parking_lot",
@ -6060,7 +6071,7 @@ dependencies = [
"dirs", "dirs",
"either", "either",
"foldhash 0.2.0", "foldhash 0.2.0",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"libc", "libc",
"objc2", "objc2",
"parking_lot", "parking_lot",
@ -6097,7 +6108,7 @@ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.0",
"crossterm 0.29.0", "crossterm 0.29.0",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"mlua", "mlua",
"paste", "paste",
"ratatui", "ratatui",
@ -6172,6 +6183,7 @@ dependencies = [
"yazi-macro", "yazi-macro",
"yazi-scheduler", "yazi-scheduler",
"yazi-shared", "yazi-shared",
"yazi-shim",
"yazi-widgets", "yazi-widgets",
] ]
@ -6180,7 +6192,7 @@ name = "yazi-runner"
version = "26.2.2" version = "26.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"mlua", "mlua",
"parking_lot", "parking_lot",
"thiserror 2.0.18", "thiserror 2.0.18",
@ -6203,7 +6215,7 @@ dependencies = [
"anyhow", "anyhow",
"async-priority-channel", "async-priority-channel",
"foldhash 0.2.0", "foldhash 0.2.0",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"libc", "libc",
"lru", "lru",
"mlua", "mlua",
@ -6245,7 +6257,7 @@ dependencies = [
"dyn-clone", "dyn-clone",
"foldhash 0.2.0", "foldhash 0.2.0",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"libc", "libc",
"memchr", "memchr",
"ordered-float 5.3.0", "ordered-float 5.3.0",
@ -6311,7 +6323,7 @@ dependencies = [
"deadpool", "deadpool",
"either", "either",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"parking_lot", "parking_lot",
"russh", "russh",
"tokio", "tokio",
@ -6329,7 +6341,7 @@ name = "yazi-watcher"
version = "26.2.2" version = "26.2.2"
dependencies = [ dependencies = [
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"notify", "notify",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",

View file

@ -50,8 +50,8 @@ either = { version = "1.15.0" }
foldhash = "0.2.0" foldhash = "0.2.0"
futures = "0.3.32" futures = "0.3.32"
globset = "0.4.18" globset = "0.4.18"
hashbrown = { version = "0.16.1", features = [ "serde" ] } hashbrown = { version = "0.17.0", features = [ "serde" ] }
indexmap = { version = "2.13.1", features = [ "serde" ] } indexmap = { version = "2.14.0", features = [ "serde" ] }
libc = "0.2.184" libc = "0.2.184"
lru = "0.16.3" lru = "0.16.3"
mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] } mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] }
@ -71,7 +71,7 @@ serde_with = "3.18.0"
strum = { version = "0.28.0", features = [ "derive" ] } strum = { version = "0.28.0", features = [ "derive" ] }
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.18" thiserror = "2.0.18"
tokio = { version = "1.51.0", features = [ "full" ] } tokio = { version = "1.51.1", features = [ "full" ] }
tokio-stream = "0.1.18" tokio-stream = "0.1.18"
tokio-util = "0.7.18" tokio-util = "0.7.18"
toml = { version = "1.1.2" } toml = { version = "1.1.2" }

View file

@ -1,8 +1,9 @@
use anyhow::Result; use anyhow::Result;
use yazi_actor::Ctx; use yazi_actor::Ctx;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::CdSource}; use yazi_parser::VoidForm;
use yazi_shared::{data::Data, strand::StrandLike, url::UrlLike}; use yazi_shared::{data::Data, strand::StrandLike, url::UrlLike};
use crate::Actor; use crate::Actor;

View file

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::CdSource}; use yazi_parser::VoidForm;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -4,6 +4,7 @@ use anyhow::Result;
use tokio::pin; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_core::mgr::CdSource;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}}; use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
use yazi_macro::{act, err, render, succ}; use yazi_macro::{act, err, render, succ};
@ -26,14 +27,15 @@ impl Actor for Cd {
act!(mgr:escape_visual, cx)?; act!(mgr:escape_visual, cx)?;
if form.interactive { if form.interactive {
return Self::cd_interactive(cx); return Self::cd_interactive(cx);
} } else if form.target == *cx.cwd() {
let tab = cx.tab_mut();
if form.target == *tab.cwd() {
succ!(); succ!();
} }
// Stash first so it's possible to access the original cwd in hooks
act!(mgr:stash, cx, &form).ok();
// Take parent to history // Take parent to history
let tab = cx.tab_mut();
if let Some(t) = tab.parent.take() { if let Some(t) = tab.parent.take() {
tab.history.insert(t.url.clone(), t); tab.history.insert(t.url.clone(), t);
} }
@ -54,7 +56,6 @@ impl Actor for Cd {
act!(mgr:sort, cx).ok(); act!(mgr:sort, cx).ok();
act!(mgr:hover, cx)?; act!(mgr:hover, cx)?;
act!(mgr:refresh, cx)?; act!(mgr:refresh, cx)?;
act!(mgr:stash, cx, form).ok();
act!(app:title, cx).ok(); act!(app:title, cx).ok();
succ!(render!()); succ!(render!());
} }
@ -77,7 +78,7 @@ impl Cd {
let Ok(file) = File::new(&url).await else { return }; let Ok(file) = File::new(&url).await else { return };
if file.is_dir() { if file.is_dir() {
return MgrProxy::cd(&url); return MgrProxy::cd(&url, CdSource::Cd);
} }
if let Some(p) = url.parent() { if let Some(p) = url.parent() {

View file

@ -1,7 +1,8 @@
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_core::mgr::CdSource;
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::mgr::{CdSource, DisplaceDoForm}; use yazi_parser::mgr::DisplaceDoForm;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::CdSource}; use yazi_parser::VoidForm;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,7 +1,8 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::mgr::CdSource;
use yazi_fs::path::clean_url; use yazi_fs::path::clean_url;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::CdSource}; use yazi_parser::VoidForm;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::CdSource}; use yazi_parser::VoidForm;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::CdSource}; use yazi_parser::VoidForm;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -4,10 +4,10 @@ use anyhow::Result;
use tokio::pin; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_core::mgr::SearchVia; use yazi_core::mgr::{CdSource, SearchVia};
use yazi_fs::{FilesOp, cha::Cha}; use yazi_fs::{FilesOp, cha::Cha};
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidForm, mgr::{CdSource, SearchForm}}; use yazi_parser::{VoidForm, mgr::SearchForm};
use yazi_plugin::external; use yazi_plugin::external;
use yazi_proxy::{InputProxy, MgrProxy}; use yazi_proxy::{InputProxy, MgrProxy};
use yazi_scheduler::NotifyProxy; use yazi_scheduler::NotifyProxy;
@ -85,7 +85,7 @@ impl Actor for SearchDo {
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(5000, Duration::from_millis(500)); let rx = UnboundedReceiverStream::new(rx).chunks_timeout(5000, Duration::from_millis(500));
pin!(rx); pin!(rx);
let ((), ticket) = (MgrProxy::cd(&cwd), FilesOp::prepare(&cwd)); let ((), ticket) = (MgrProxy::cd(&cwd, CdSource::Search), FilesOp::prepare(&cwd));
while let Some(chunk) = rx.next().await { while let Some(chunk) = rx.next().await {
FilesOp::Part(cwd.clone(), chunk, ticket).emit(); FilesOp::Part(cwd.clone(), chunk, ticket).emit();
} }

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::tab::Tab; use yazi_core::{mgr::CdSource, tab::Tab};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::{CdSource, TabCreateForm}; use yazi_parser::mgr::TabCreateForm;
use yazi_scheduler::NotifyProxy; use yazi_scheduler::NotifyProxy;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
@ -25,8 +25,8 @@ impl Actor for TabCreate {
} }
let mut tab = Tab::default(); let mut tab = Tab::default();
let (cd, url) = if let Some(wd) = form.url { let (cd, url) = if let Some(target) = form.target {
(true, wd.into_owned()) (true, target)
} else if let Some(h) = cx.hovered() { } else if let Some(h) = cx.hovered() {
tab.pref = cx.tab().pref.clone(); tab.pref = cx.tab().pref.clone();
(false, h.url.clone()) (false, h.url.clone())

View file

@ -3,7 +3,7 @@ use yazi_core::tab::Folder;
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::UpdateFilesForm; use yazi_parser::mgr::UpdateFilesForm;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::{UrlLike, UrlMapExt}};
use yazi_watcher::local::LINKED; use yazi_watcher::local::LINKED;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -87,7 +87,7 @@ impl UpdateFiles {
fn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result<Data> { fn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
let (id, url) = (cx.tab().id, op.cwd()); let (id, url) = (cx.tab().id, op.cwd());
let folder = cx.tab_mut().history.entry_ref(url).or_insert_with(|| Folder::from(url)); let folder = cx.tab_mut().history.get_or_insert_with(url, |u| Folder::from(u));
if folder.update_pub(id, op) { if folder.update_pub(id, op) {
act!(mgr:peek, cx, true)?; act!(mgr:peek, cx, true)?;
@ -101,15 +101,11 @@ impl UpdateFiles {
|(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(&n)), |(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(&n)),
); );
tab tab.history.get_or_insert_with(op.cwd(), |u| Folder::from(u)).update_pub(tab.id, op);
.history
.entry_ref(op.cwd())
.or_insert_with(|| Folder::from(op.cwd()))
.update_pub(tab.id, op);
if leave { if leave {
act!(mgr:leave, cx)?; act!(mgr:leave, cx)?;
} }
succ!(); succ!();
} }
} }

View file

@ -1,7 +1,7 @@
use std::{collections::VecDeque, mem}; use std::{collections::VecDeque, mem};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use hashbrown::{HashMap, hash_map::EntryRef}; use hashbrown::HashMap;
use mlua::Function; use mlua::Function;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -49,15 +49,9 @@ impl Runtime {
pub fn put_block(&mut self, f: &Function) -> Option<usize> { pub fn put_block(&mut self, f: &Function) -> Option<usize> {
let cur = self.frames.back().filter(|f| f.id != "init")?; let cur = self.frames.back().filter(|f| f.id != "init")?;
Some(match self.blocks.entry_ref(&cur.id) { let blocks = self.blocks.entry_ref(&cur.id).or_default();
EntryRef::Occupied(mut oe) => {
oe.get_mut().push(f.clone()); blocks.push(f.clone());
oe.get().len() - 1 Some(blocks.len() - 1)
}
EntryRef::Vacant(ve) => {
ve.insert(vec![f.clone()]);
0
}
})
} }
} }

View file

@ -6,6 +6,7 @@ use serde::Deserialize;
use yazi_shared::{Layer, Source, event::Action}; use yazi_shared::{Layer, Source, event::Action};
use super::Key; use super::Key;
use crate::Platform;
static RE: OnceLock<Regex> = OnceLock::new(); static RE: OnceLock<Regex> = OnceLock::new();
@ -16,7 +17,8 @@ pub struct Chord {
#[serde(deserialize_with = "super::deserialize_run")] #[serde(deserialize_with = "super::deserialize_run")]
pub run: Vec<Action>, pub run: Vec<Action>,
pub desc: Option<String>, pub desc: Option<String>,
pub r#for: Option<String>, #[serde(default)]
pub r#for: Platform,
} }
impl PartialEq for Chord { impl PartialEq for Chord {

View file

@ -3,6 +3,7 @@ use std::ops::Deref;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
use super::Chord; use super::Chord;
use crate::Platform;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ChordCow { pub enum ChordCow {
@ -31,7 +32,7 @@ impl Deref for ChordCow {
impl Default for ChordCow { impl Default for ChordCow {
fn default() -> Self { fn default() -> Self {
const C: &Chord = &Chord { on: vec![], run: vec![], desc: None, r#for: None }; const C: &Chord = &Chord { on: vec![], run: vec![], desc: None, r#for: Platform::All };
Self::Borrowed(C) Self::Borrowed(C)
} }
} }

View file

@ -7,7 +7,7 @@ use yazi_codegen::DeserializeOver2;
use yazi_shared::Layer; use yazi_shared::Layer;
use super::Chord; use super::Chord;
use crate::{Preset, check_for, keymap::Key}; use crate::{Preset, keymap::Key};
#[derive(Default, Deserialize, DeserializeOver2)] #[derive(Default, Deserialize, DeserializeOver2)]
pub struct KeymapRules { pub struct KeymapRules {
@ -39,9 +39,8 @@ impl KeymapRules {
self.keymap.into_iter().filter(|v| !a_seen.contains(&on(v))), self.keymap.into_iter().filter(|v| !a_seen.contains(&on(v))),
self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))), self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))),
) )
.map(|mut chord| (chord.r#for.take(), chord)) .filter(|chord| !chord.noop() && chord.r#for.matches())
.filter(|(r#for, chord)| !chord.noop() && check_for(r#for.as_deref())) .map(|chord| chord.reshape(layer))
.map(|(_, chord)| chord.reshape(layer))
.collect::<Result<_>>()?; .collect::<Result<_>>()?;
Ok(Self { keymap, ..Default::default() }) Ok(Self { keymap, ..Default::default() })

View file

@ -8,7 +8,6 @@ use toml::{Spanned, de::DeTable};
use yazi_codegen::DeserializeOver; use yazi_codegen::DeserializeOver;
use super::OpenerRule; use super::OpenerRule;
use crate::check_for;
#[derive(Debug, Deserialize, DeserializeOver)] #[derive(Debug, Deserialize, DeserializeOver)]
pub struct Opener(HashMap<String, Vec<OpenerRule>>); pub struct Opener(HashMap<String, Vec<OpenerRule>>);
@ -47,9 +46,8 @@ impl Opener {
for rules in self.0.values_mut() { for rules in self.0.values_mut() {
*rules = mem::take(rules) *rules = mem::take(rules)
.into_iter() .into_iter()
.map(|mut r| (r.r#for.take(), r)) .filter(|r| r.r#for.matches())
.filter(|(r#for, _)| check_for(r#for.as_deref())) .map(|r| r.reshape())
.map(|(_, r)| r.reshape())
.collect::<Result<IndexSet<_>>>()? .collect::<Result<IndexSet<_>>>()?
.into_iter() .into_iter()
.collect(); .collect();

View file

@ -2,6 +2,8 @@ use anyhow::{Result, bail};
use serde::Deserialize; use serde::Deserialize;
use yazi_fs::Splatter; use yazi_fs::Splatter;
use crate::Platform;
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OpenerRule { pub struct OpenerRule {
pub run: String, pub run: String,
@ -11,7 +13,8 @@ pub struct OpenerRule {
pub orphan: bool, pub orphan: bool,
#[serde(default)] #[serde(default)]
pub desc: String, pub desc: String,
pub r#for: Option<String>, #[serde(default)]
pub r#for: Platform,
#[serde(skip)] #[serde(skip)]
pub spread: bool, pub spread: bool,
} }

View file

@ -1,9 +1,26 @@
#[inline] use serde::Deserialize;
pub(crate) fn check_for(r#for: Option<&str>) -> bool {
match r#for.as_ref().map(|s| s.as_ref()) { #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
Some("unix") if cfg!(unix) => true, #[serde(rename_all = "kebab-case")]
Some(os) if os == std::env::consts::OS => true, pub enum Platform {
Some(_) => false, #[default]
None => true, All,
Linux,
Macos,
Windows,
Android,
Unix,
}
impl Platform {
pub(crate) fn matches(self) -> bool {
match self {
Self::All => true,
Self::Linux => cfg!(target_os = "linux"),
Self::Macos => cfg!(target_os = "macos"),
Self::Windows => cfg!(windows),
Self::Android => cfg!(target_os = "android"),
Self::Unix => cfg!(unix),
}
} }
} }

24
yazi-core/src/mgr/cd.rs Normal file
View file

@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
use strum::IntoStaticStr;
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, IntoStaticStr, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum CdSource {
#[default]
Cd,
Reveal,
Enter,
Leave,
Follow,
Search,
Escape,
Forward,
Back,
Tab,
Displace,
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(batcher displace filter find mgr mimetype open search tabs yanked); yazi_macro::mod_flat!(batcher cd displace filter find mgr mimetype open search tabs yanked);

View file

@ -3,7 +3,7 @@ use std::ops::Deref;
use hashbrown::HashMap; use hashbrown::HashMap;
use indexmap::IndexMap; use indexmap::IndexMap;
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_shared::{timestamp_us, url::{Url, UrlBuf, UrlBufCov, UrlCov}}; use yazi_shared::{timestamp_us, url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlCovMapExt}};
#[derive(Default)] #[derive(Default)]
pub struct Selected { pub struct Selected {
@ -66,7 +66,7 @@ impl Selected {
self.inner.extend(urls.iter().enumerate().map(|(i, u)| (u.into(), now + i as u64))); self.inner.extend(urls.iter().enumerate().map(|(i, u)| (u.into(), now + i as u64)));
for u in parents { for u in parents {
*self.parents.entry_ref(&UrlCov::new(u)).or_default() += self.inner.len() - len; *self.parents.get_or_insert_default(UrlCov::new(u)) += self.inner.len() - len;
} }
urls.len() urls.len()
} }

View file

@ -1,6 +1,39 @@
use yazi_scheduler::plugin::PluginInEntry; use std::borrow::Cow;
use yazi_scheduler::{TaskIn, plugin::PluginInEntry};
use yazi_shared::{Id, SStr};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TaskOpt { pub enum TaskOpt {
Plugin(PluginInEntry), Plugin(PluginInEntry),
} }
impl TaskIn for TaskOpt {
type Prog = ();
fn id(&self) -> Id {
match self {
Self::Plugin(r#in) => r#in.id(),
}
}
fn set_id(&mut self, id: Id) -> &mut Self {
match self {
Self::Plugin(r#in) => _ = r#in.set_id(id),
}
self
}
fn title(&self) -> Cow<'_, str> {
match self {
Self::Plugin(r#in) => r#in.title(),
}
}
fn set_title(&mut self, title: impl Into<SStr>) -> &mut Self {
match self {
Self::Plugin(r#in) => _ = r#in.set_title(title),
}
self
}
}

View file

@ -101,6 +101,7 @@ impl<'a> Executor<'a> {
on!(forward); on!(forward);
on!(reveal); on!(reveal);
on!(follow); on!(follow);
on!(stash);
// Toggle // Toggle
on!(toggle); on!(toggle);

View file

@ -2,7 +2,7 @@ use std::path::Path;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use yazi_macro::relay; use yazi_macro::relay;
use yazi_shared::{Id, Ids, path::PathBufDyn, url::{UrlBuf, UrlLike}}; use yazi_shared::{Id, Ids, path::PathBufDyn, url::{UrlBuf, UrlLike, UrlMapExt}};
use super::File; use super::File;
use crate::{cha::Cha, error::Error}; use crate::{cha::Cha, error::Error};
@ -52,15 +52,15 @@ impl FilesOp {
} }
pub fn rename(map: HashMap<UrlBuf, File>) { pub fn rename(map: HashMap<UrlBuf, File>) {
let mut parents: HashMap<_, (HashSet<_>, HashMap<_, _>)> = Default::default(); let mut parents: HashMap<UrlBuf, (HashSet<_>, HashMap<_, _>)> = Default::default();
for (o, n) in map { for (o, n) in map {
let Some(o_p) = o.parent() else { continue }; let Some(o_p) = o.parent() else { continue };
let Some(n_p) = n.url.parent() else { continue }; let Some(n_p) = n.url.parent() else { continue };
if o_p == n_p { if o_p == n_p {
parents.entry_ref(&o_p).or_default().1.insert(o.urn().into(), n); parents.get_or_insert_default(o_p).1.insert(o.urn().into(), n);
} else { } else {
parents.entry_ref(&o_p).or_default().0.insert(o.urn().into()); parents.get_or_insert_default(o_p).0.insert(o.urn().into());
parents.entry_ref(&n_p).or_default().1.insert(n.urn().into(), n); parents.get_or_insert_default(n_p).1.insert(n.urn().into(), n);
} }
} }
for (p, (o, n)) in parents { for (p, (o, n)) in parents {

View file

@ -1,41 +1,46 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use yazi_core::mgr::CdSource;
use yazi_fs::path::{clean_url, expand_url}; use yazi_fs::path::{clean_url, expand_url};
use yazi_shared::{event::ActionCow, url::{Url, UrlBuf}}; use yazi_shared::{event::ActionCow, url::{Url, UrlBuf}};
use yazi_vfs::provider; use yazi_vfs::provider;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct CdForm { pub struct CdForm {
#[serde(alias = "0")]
pub target: UrlBuf, pub target: UrlBuf,
#[serde(default)]
pub interactive: bool, pub interactive: bool,
#[serde(default)]
pub raw: bool,
#[serde(default)]
pub source: CdSource, pub source: CdSource,
} }
impl From<ActionCow> for CdForm { impl TryFrom<ActionCow> for CdForm {
fn from(mut a: ActionCow) -> Self { type Error = anyhow::Error;
let mut target = a.take_first().unwrap_or_default();
if !a.bool("raw") { fn try_from(a: ActionCow) -> Result<Self, Self::Error> {
target = expand_url(target); let mut me: Self = a.deserialize()?;
if !me.raw {
me.target = expand_url(me.target).into_owned();
} }
if let Some(u) = provider::try_absolute(&target) if let Some(u) = provider::try_absolute(&me.target)
&& u.is_owned() && u.is_owned()
{ {
target = u.into_static(); me.target = u.into_owned();
} }
Self { me.target = clean_url(me.target);
target: clean_url(target), Ok(me)
interactive: a.bool("interactive"),
source: CdSource::Cd,
}
} }
} }
impl From<(UrlBuf, CdSource)> for CdForm { impl From<(UrlBuf, CdSource)> for CdForm {
fn from((target, source): (UrlBuf, CdSource)) -> Self { fn from((target, source): (UrlBuf, CdSource)) -> Self {
Self { target, interactive: false, source } Self { target, interactive: false, raw: false, source }
} }
} }
@ -50,19 +55,3 @@ impl FromLua for CdForm {
impl IntoLua for CdForm { impl IntoLua for CdForm {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) } fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
} }
// --- Source
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum CdSource {
Tab,
Cd,
Reveal,
Enter,
Leave,
Follow,
Forward,
Back,
Escape,
Displace,
}

View file

@ -5,21 +5,20 @@ use serde::Deserialize;
use strum::EnumString; use strum::EnumString;
use yazi_shared::{SStr, event::ActionCow, strand::AsStrand}; use yazi_shared::{SStr, event::ActionCow, strand::AsStrand};
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct CopyForm { pub struct CopyForm {
#[serde(alias = "0")]
pub r#type: SStr, pub r#type: SStr,
#[serde(default)]
pub separator: CopySeparator, pub separator: CopySeparator,
#[serde(default)]
pub hovered: bool, pub hovered: bool,
} }
impl From<ActionCow> for CopyForm { impl TryFrom<ActionCow> for CopyForm {
fn from(mut a: ActionCow) -> Self { type Error = anyhow::Error;
Self {
r#type: a.take_first().unwrap_or_default(), fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
separator: a.str("separator").parse().unwrap_or_default(),
hovered: a.bool("hovered"),
}
}
} }
impl FromLua for CopyForm { impl FromLua for CopyForm {

View file

@ -1,14 +1,19 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct CreateForm { pub struct CreateForm {
#[serde(default)]
pub dir: bool, pub dir: bool,
#[serde(default)]
pub force: bool, pub force: bool,
} }
impl From<ActionCow> for CreateForm { impl TryFrom<ActionCow> for CreateForm {
fn from(a: ActionCow) -> Self { Self { dir: a.bool("dir"), force: a.bool("force") } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for CreateForm { impl FromLua for CreateForm {

View file

@ -1,14 +1,19 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct HardlinkForm { pub struct HardlinkForm {
#[serde(default)]
pub force: bool, pub force: bool,
#[serde(default)]
pub follow: bool, pub follow: bool,
} }
impl From<ActionCow> for HardlinkForm { impl TryFrom<ActionCow> for HardlinkForm {
fn from(a: ActionCow) -> Self { Self { force: a.bool("force"), follow: a.bool("follow") } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for HardlinkForm { impl FromLua for HardlinkForm {

View file

@ -1,14 +1,19 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct LinkForm { pub struct LinkForm {
#[serde(default)]
pub relative: bool, pub relative: bool,
#[serde(default)]
pub force: bool, pub force: bool,
} }
impl From<ActionCow> for LinkForm { impl TryFrom<ActionCow> for LinkForm {
fn from(a: ActionCow) -> Self { Self { relative: a.bool("relative"), force: a.bool("force") } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for LinkForm { impl FromLua for LinkForm {

View file

@ -1,14 +1,19 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct PasteForm { pub struct PasteForm {
#[serde(default)]
pub force: bool, pub force: bool,
#[serde(default)]
pub follow: bool, pub follow: bool,
} }
impl From<ActionCow> for PasteForm { impl TryFrom<ActionCow> for PasteForm {
fn from(a: ActionCow) -> Self { Self { force: a.bool("force"), follow: a.bool("follow") } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for PasteForm { impl FromLua for PasteForm {

View file

@ -1,41 +1,53 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_core::mgr::CdSource;
use yazi_fs::path::{clean_url, expand_url}; use yazi_fs::path::{clean_url, expand_url};
use yazi_shared::{event::ActionCow, url::UrlBuf}; use yazi_shared::{event::ActionCow, url::UrlBuf};
use yazi_vfs::provider; use yazi_vfs::provider;
use crate::mgr::CdSource; #[derive(Debug, Deserialize)]
#[derive(Debug)]
pub struct RevealForm { pub struct RevealForm {
#[serde(alias = "0")]
pub target: UrlBuf, pub target: UrlBuf,
#[serde(default)]
pub raw: bool,
#[serde(default = "default_source")]
pub source: CdSource, pub source: CdSource,
#[serde(alias = "no-dummy", default)]
pub no_dummy: bool, pub no_dummy: bool,
} }
impl From<ActionCow> for RevealForm { impl TryFrom<ActionCow> for RevealForm {
fn from(mut a: ActionCow) -> Self { type Error = anyhow::Error;
let mut target = a.take_first().unwrap_or_default();
if !a.bool("raw") { fn try_from(a: ActionCow) -> Result<Self, Self::Error> {
target = expand_url(target); let mut me: Self = a.deserialize()?;
if !me.raw {
me.target = expand_url(me.target).into_owned();
} }
if let Some(u) = provider::try_absolute(&target) if let Some(u) = provider::try_absolute(&me.target)
&& u.is_owned() && u.is_owned()
{ {
target = u.into_static(); me.target = u.into_owned();
} }
Self { target: clean_url(target), source: CdSource::Reveal, no_dummy: a.bool("no-dummy") } me.target = clean_url(me.target);
Ok(me)
} }
} }
impl From<UrlBuf> for RevealForm { impl From<UrlBuf> for RevealForm {
fn from(target: UrlBuf) -> Self { Self { target, source: CdSource::Reveal, no_dummy: false } } fn from(target: UrlBuf) -> Self {
Self { target, raw: false, source: CdSource::Reveal, no_dummy: false }
}
} }
impl From<(UrlBuf, CdSource)> for RevealForm { impl From<(UrlBuf, CdSource)> for RevealForm {
fn from((target, source): (UrlBuf, CdSource)) -> Self { Self { target, source, no_dummy: false } } fn from((target, source): (UrlBuf, CdSource)) -> Self {
Self { target, raw: false, source, no_dummy: false }
}
} }
impl FromLua for RevealForm { impl FromLua for RevealForm {
@ -45,3 +57,5 @@ impl FromLua for RevealForm {
impl IntoLua for RevealForm { impl IntoLua for RevealForm {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) } fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
} }
fn default_source() -> CdSource { CdSource::Reveal }

View file

@ -1,13 +1,17 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct SeekForm { pub struct SeekForm {
#[serde(alias = "0")]
pub units: i16, pub units: i16,
} }
impl From<ActionCow> for SeekForm { impl TryFrom<ActionCow> for SeekForm {
fn from(a: ActionCow) -> Self { Self { units: a.first().unwrap_or(0) } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for SeekForm { impl FromLua for SeekForm {

View file

@ -1,13 +1,16 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug, Default)] #[derive(Debug, Default, Deserialize)]
pub struct SpotOpt { pub struct SpotOpt {
pub skip: Option<usize>, pub skip: Option<usize>,
} }
impl From<ActionCow> for SpotOpt { impl TryFrom<ActionCow> for SpotOpt {
fn from(a: ActionCow) -> Self { Self { skip: a.get("skip").ok() } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl From<usize> for SpotOpt { impl From<usize> for SpotOpt {

View file

@ -1,13 +1,14 @@
use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use yazi_binding::{SER_OPT, Url}; use yazi_binding::{SER_OPT, Url};
use yazi_core::mgr::CdSource;
use yazi_shared::{event::ActionCow, url::UrlBuf}; use yazi_shared::{event::ActionCow, url::UrlBuf};
use crate::mgr::{CdForm, CdSource}; use crate::mgr::CdForm;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct StashForm { pub struct StashForm {
#[serde(alias = "0")]
pub target: UrlBuf, pub target: UrlBuf,
pub source: CdSource, pub source: CdSource,
} }
@ -15,11 +16,11 @@ pub struct StashForm {
impl TryFrom<ActionCow> for StashForm { impl TryFrom<ActionCow> for StashForm {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(_: ActionCow) -> Result<Self, Self::Error> { bail!("unsupported") } fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl From<CdForm> for StashForm { impl From<&CdForm> for StashForm {
fn from(form: CdForm) -> Self { Self { target: form.target, source: form.source } } fn from(form: &CdForm) -> Self { Self { target: form.target.clone(), source: form.source } }
} }
impl FromLua for StashForm { impl FromLua for StashForm {

View file

@ -1,13 +1,17 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct TabCloseForm { pub struct TabCloseForm {
#[serde(alias = "0", default)]
pub idx: usize, pub idx: usize,
} }
impl From<ActionCow> for TabCloseForm { impl TryFrom<ActionCow> for TabCloseForm {
fn from(a: ActionCow) -> Self { Self { idx: a.first().unwrap_or(0) } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl From<usize> for TabCloseForm { impl From<usize> for TabCloseForm {

View file

@ -1,35 +1,45 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_fs::path::{clean_url, expand_url}; use yazi_fs::path::{clean_url, expand_url};
use yazi_shared::{event::ActionCow, url::UrlCow}; use yazi_shared::{event::ActionCow, url::UrlBuf};
use yazi_vfs::provider; use yazi_vfs::provider;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct TabCreateForm { pub struct TabCreateForm {
pub url: Option<UrlCow<'static>>, #[serde(alias = "0")]
pub target: Option<UrlBuf>,
#[serde(default)]
pub current: bool,
#[serde(default)]
pub raw: bool,
} }
impl From<ActionCow> for TabCreateForm { impl TryFrom<ActionCow> for TabCreateForm {
fn from(mut a: ActionCow) -> Self { type Error = anyhow::Error;
if a.bool("current") {
return Self { url: None }; fn try_from(a: ActionCow) -> Result<Self, Self::Error> {
let mut me: Self = a.deserialize()?;
if me.current {
me.target = None;
} else if me.target.is_none() {
me.target = Some(BOOT.cwds[0].clone());
} else if let Some(mut target) = me.target {
if !me.raw {
target = expand_url(target).into_owned();
}
if let Some(u) = provider::try_absolute(&target)
&& u.is_owned()
{
target = u.into_owned();
}
me.target = Some(clean_url(target));
} }
let Ok(mut url) = a.take_first() else { Ok(me)
return Self { url: Some(UrlCow::from(&BOOT.cwds[0])) };
};
if !a.bool("raw") {
url = expand_url(url);
}
if let Some(u) = provider::try_absolute(&url)
&& u.is_owned()
{
url = u.into_static();
}
Self { url: Some(clean_url(url).into()) }
} }
} }

View file

@ -1,16 +1,19 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct TabSwitchForm { pub struct TabSwitchForm {
#[serde(alias = "0")]
pub step: isize, pub step: isize,
#[serde(default)]
pub relative: bool, pub relative: bool,
} }
impl From<ActionCow> for TabSwitchForm { impl TryFrom<ActionCow> for TabSwitchForm {
fn from(a: ActionCow) -> Self { type Error = anyhow::Error;
Self { step: a.first().unwrap_or(0), relative: a.bool("relative") }
} fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for TabSwitchForm { impl FromLua for TabSwitchForm {

View file

@ -1,4 +1,4 @@
use anyhow::bail; use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_core::tab::PreviewLock; use yazi_core::tab::PreviewLock;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
@ -12,15 +12,9 @@ impl TryFrom<ActionCow> for UpdatePeekedForm {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> { fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
if let Some(opt) = a.take_any2("opt") { Ok(Self {
return opt; lock: a.take_any("lock").ok_or_else(|| anyhow!("Invalid 'lock' in UpdatePeekedForm"))?,
} })
let Some(lock) = a.take_any("lock") else {
bail!("Invalid 'lock' in UpdatePeekedForm");
};
Ok(Self { lock })
} }
} }

View file

@ -1,4 +1,4 @@
use anyhow::bail; use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_core::spot::SpotLock; use yazi_core::spot::SpotLock;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
@ -12,11 +12,9 @@ impl TryFrom<ActionCow> for UpdateSpottedForm {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> { fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
let Some(lock) = a.take_any("lock") else { Ok(Self {
bail!("Invalid 'lock' in UpdateSpottedForm"); lock: a.take_any("lock").ok_or_else(|| anyhow!("Invalid 'lock' in UpdateSpottedForm"))?,
}; })
Ok(Self { lock })
} }
} }

View file

@ -1,6 +1,6 @@
use std::ops::Deref; use std::ops::Deref;
use anyhow::bail; use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
@ -18,11 +18,7 @@ impl TryFrom<ActionCow> for UpdateYankedForm<'_> {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> { fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
let Some(state) = a.take_any("state") else { a.take_any(0).map(Self).ok_or_else(|| anyhow!("Invalid payload in UpdateYankedForm"))
bail!("Invalid 'state' in UpdateYankedForm");
};
Ok(Self(state))
} }
} }

View file

@ -1,13 +1,17 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct VisualModeForm { pub struct VisualModeForm {
#[serde(default)]
pub unset: bool, pub unset: bool,
} }
impl From<ActionCow> for VisualModeForm { impl TryFrom<ActionCow> for VisualModeForm {
fn from(a: ActionCow) -> Self { Self { unset: a.bool("unset") } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for VisualModeForm { impl FromLua for VisualModeForm {

View file

@ -1,13 +1,17 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
#[derive(Debug)] #[derive(Debug, Deserialize)]
pub struct YankForm { pub struct YankForm {
#[serde(default)]
pub cut: bool, pub cut: bool,
} }
impl From<ActionCow> for YankForm { impl TryFrom<ActionCow> for YankForm {
fn from(a: ActionCow) -> Self { Self { cut: a.bool("cut") } } type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
} }
impl FromLua for YankForm { impl FromLua for YankForm {

View file

@ -6,12 +6,12 @@ function M:setup()
ps.sub_remote("extract", function(args) ps.sub_remote("extract", function(args)
ya.async(function() ya.async(function()
for i, arg in ipairs(args) do for i, arg in ipairs(args) do
ya.task("plugin", { local in_ = {
self._id, self._id,
args = { arg, "", noisy = #args == 1 }, args = { arg, "", noisy = #args == 1 },
title = "Extract " .. arg,
track = i == 1, track = i == 1,
}):spawn() }
ya.task("plugin", in_):name("Extract " .. arg):spawn()
end end
end) end)
end) end)

View file

@ -1,6 +1,6 @@
local function setup(_, opts) local function setup(_, opts)
if opts.sync_yanked then if opts.sync_yanked then
ps.sub_remote("@yank", function(state) ya.emit("update_yanked", { state = state }) end) ps.sub_remote("@yank", function(state) ya.emit("update_yanked", { state }) end)
end end
end end

View file

@ -1,5 +1,6 @@
use mlua::{UserData, UserDataMethods}; use mlua::{AnyUserData, UserData, UserDataMethods};
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::TaskIn;
use crate::tasks::Task; use crate::tasks::Task;
@ -8,6 +9,10 @@ pub(crate) struct TaskOpt(pub(crate) yazi_core::tasks::TaskOpt);
impl UserData for TaskOpt { impl UserData for TaskOpt {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_function_mut("name", |_, (ud, name): (AnyUserData, mlua::String)| {
ud.borrow_mut::<Self>()?.0.set_title(name.to_string_lossy());
Ok(ud)
});
methods.add_async_method_once("spawn", |_, me, ()| async move { methods.add_async_method_once("spawn", |_, me, ()| async move {
Ok(Task { id: TasksProxy::spawn(me.0).await? }) Ok(Task { id: TasksProxy::spawn(me.0).await? })
}); });

View file

@ -3,7 +3,7 @@ use std::{str::FromStr, time::Duration};
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value}; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use yazi_binding::{InputRx, elements::{Line, Pos, Text}, runtime}; use yazi_binding::{InputRx, elements::{Line, Pos, Text}, runtime};
use yazi_config::{keymap::{Chord, ChordCow, Key}, popup::{ConfirmCfg, InputCfg}}; use yazi_config::{Platform, keymap::{Chord, ChordCow, Key}, popup::{ConfirmCfg, InputCfg}};
use yazi_core::notify::MessageOpt; use yazi_core::notify::MessageOpt;
use yazi_macro::relay; use yazi_macro::relay;
use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy}; use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy};
@ -28,7 +28,7 @@ impl Utils {
on: Self::parse_keys(cand.raw_get("on")?)?, on: Self::parse_keys(cand.raw_get("on")?)?,
run: vec![relay!(which:callback, [i + 1])], run: vec![relay!(which:callback, [i + 1])],
desc: cand.raw_get("desc").ok(), desc: cand.raw_get("desc").ok(),
r#for: None, r#for: Platform::All,
})) }))
}) })
.collect::<mlua::Result<_>>()?; .collect::<mlua::Result<_>>()?;

View file

@ -18,6 +18,7 @@ yazi-core = { path = "../yazi-core", version = "26.2.2" }
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-scheduler = { path = "../yazi-scheduler", version = "26.2.2" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
yazi-widgets = { path = "../yazi-widgets", version = "26.2.2" } yazi-widgets = { path = "../yazi-widgets", version = "26.2.2" }
# External dependencies # External dependencies

View file

@ -1,6 +1,7 @@
use yazi_core::{mgr::{DisplaceOpt, FilterOpt, FindDoOpt, OpenDoOpt, OpenOpt, SearchOpt}, spot::SpotLock}; use yazi_core::{mgr::{CdSource, DisplaceOpt, FilterOpt, FindDoOpt, OpenDoOpt, OpenOpt, SearchOpt}, spot::SpotLock};
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
use yazi_shared::{Id, SStr, url::UrlBuf}; use yazi_shared::{Id, SStr, url::UrlBuf};
use yazi_shim::strum::IntoStr;
pub struct MgrProxy; pub struct MgrProxy;
@ -9,8 +10,10 @@ impl MgrProxy {
emit!(Call(relay!(mgr:arrow, [step.into()]))); emit!(Call(relay!(mgr:arrow, [step.into()])));
} }
pub fn cd(target: impl Into<UrlBuf>) { pub fn cd(target: impl Into<UrlBuf>, source: CdSource) {
emit!(Call(relay!(mgr:cd, [target.into()]).with("raw", true))); emit!(Call(
relay!(mgr:cd, [target.into()]).with("raw", true).with("source", source.into_str())
));
} }
pub fn displace_do(tab: Id, opt: DisplaceOpt) { pub fn displace_do(tab: Id, opt: DisplaceOpt) {

View file

@ -1,8 +1,8 @@
use std::borrow::Cow; use std::borrow::Cow;
use yazi_shared::Id; use yazi_shared::{Id, SStr};
pub(crate) trait TaskIn { pub trait TaskIn {
type Prog; type Prog;
fn id(&self) -> Id; fn id(&self) -> Id;
@ -10,4 +10,6 @@ pub(crate) trait TaskIn {
fn set_id(&mut self, id: Id) -> &mut Self; fn set_id(&mut self, id: Id) -> &mut Self;
fn title(&self) -> Cow<'_, str>; fn title(&self) -> Cow<'_, str>;
fn set_title(&mut self, _title: impl Into<SStr>) -> &mut Self { self }
} }

View file

@ -65,6 +65,11 @@ impl TaskIn for PluginInEntry {
Cow::Borrowed(&self.title) Cow::Borrowed(&self.title)
} }
} }
fn set_title(&mut self, title: impl Into<SStr>) -> &mut Self {
self.title = title.into();
self
}
} }
impl PluginInEntry { impl PluginInEntry {
@ -80,11 +85,10 @@ impl FromLua for PluginInEntry {
}; };
Ok(Self { Ok(Self {
id: Id::ZERO,
plugin: t.raw_get::<String>(1)?.into(), plugin: t.raw_get::<String>(1)?.into(),
args: Sendable::table_to_args(lua, t.raw_get("args")?)?, args: Sendable::table_to_args(lua, t.raw_get("args")?)?,
title: t.raw_get::<Option<String>>("title")?.unwrap_or_default().into(), track: t.raw_get("track")?,
track: t.raw_get("track")?, ..Default::default()
}) })
} }
} }

View file

@ -82,6 +82,10 @@ impl From<Url<'_>> for UrlBufCov {
fn from(value: Url<'_>) -> Self { Self(value.to_owned()) } fn from(value: Url<'_>) -> Self { Self(value.to_owned()) }
} }
impl From<UrlCov<'_>> for UrlBufCov {
fn from(value: UrlCov<'_>) -> Self { Self(UrlBuf::from(&value.0)) }
}
impl From<&UrlCov<'_>> for UrlBufCov { impl From<&UrlCov<'_>> for UrlBufCov {
fn from(value: &UrlCov<'_>) -> Self { Self(UrlBuf::from(&value.0)) } fn from(value: &UrlCov<'_>) -> Self { Self(UrlBuf::from(&value.0)) }
} }

View file

@ -261,21 +261,6 @@ impl<'a> UrlCow<'a> {
pub fn into_path(self) -> PathCow<'a> { self.into_pair().1 } pub fn into_path(self) -> PathCow<'a> { self.into_pair().1 }
pub fn into_static(self) -> UrlCow<'static> {
match self {
UrlCow::Regular(loc) => UrlCow::Regular(loc.into_owned().into()),
UrlCow::Search { loc, domain } => {
UrlCow::Search { loc: loc.into_owned().into(), domain: domain.into_owned().into() }
}
UrlCow::Archive { loc, domain } => {
UrlCow::Archive { loc: loc.into_owned().into(), domain: domain.into_owned().into() }
}
UrlCow::Sftp { loc, domain } => {
UrlCow::Sftp { loc: loc.into_owned().into(), domain: domain.into_owned().into() }
}
}
}
pub fn to_owned(&self) -> UrlBuf { self.as_url().into() } pub fn to_owned(&self) -> UrlBuf { self.as_url().into() }
} }

View file

@ -1,6 +1,8 @@
use std::path::{Path, PathBuf}; use std::{hash::BuildHasher, path::{Path, PathBuf}};
use crate::{loc::Loc, url::{Url, UrlBuf, UrlCow}}; use hashbrown::{HashMap, hash_map::EntryRef};
use crate::{loc::Loc, url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlCow}};
// --- AsUrl // --- AsUrl
pub trait AsUrl { pub trait AsUrl {
@ -86,3 +88,63 @@ where
{ {
fn from(value: &'a mut T) -> Self { value.as_url() } fn from(value: &'a mut T) -> Self { value.as_url() }
} }
// --- UrlMapExt
pub trait UrlMapExt<V> {
fn get_or_insert_default<U>(&mut self, url: U) -> &mut V
where
U: AsUrl,
V: Default;
fn get_or_insert_with<U, F>(&mut self, url: U, default: F) -> &mut V
where
U: AsUrl,
F: FnOnce(Url<'_>) -> V;
}
impl<V, S> UrlMapExt<V> for HashMap<UrlBuf, V, S>
where
S: BuildHasher,
{
fn get_or_insert_default<U>(&mut self, url: U) -> &mut V
where
U: AsUrl,
V: Default,
{
self.get_or_insert_with(url, |_| Default::default())
}
fn get_or_insert_with<U, F>(&mut self, url: U, default: F) -> &mut V
where
U: AsUrl,
F: FnOnce(Url<'_>) -> V,
{
let url = url.as_url();
match self.entry_ref(&url) {
EntryRef::Occupied(oe) => oe.into_mut(),
EntryRef::Vacant(ve) => ve.insert_with_key(url.into(), default(url)),
}
}
}
// --- UrlCovMapExt
pub trait UrlCovMapExt<V> {
fn get_or_insert_default(&mut self, url: UrlCov<'_>) -> &mut V
where
V: Default;
}
impl<V, S> UrlCovMapExt<V> for HashMap<UrlBufCov, V, S>
where
S: BuildHasher,
{
fn get_or_insert_default(&mut self, url: UrlCov<'_>) -> &mut V
where
V: Default,
{
match self.entry_ref(&url) {
EntryRef::Occupied(oe) => oe.into_mut(),
EntryRef::Vacant(ve) => ve.insert_with_key(url.into(), Default::default()),
}
}
}