diff --git a/Cargo.lock b/Cargo.lock index f7d2a814..5f246022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,7 +433,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ - "hybrid-array 0.4.9", + "hybrid-array 0.4.10", ] [[package]] @@ -922,7 +922,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "hybrid-array 0.4.9", + "hybrid-array 0.4.10", ] [[package]] @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a79f2aff40c18ab8615ddc5caa9eb5b96314aef18fe5823090f204ad988e813" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "typenum", ] @@ -2083,9 +2083,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.93" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "cfg-if", "futures-util", @@ -2172,9 +2172,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libfuzzer-sys" @@ -3377,7 +3377,7 @@ dependencies = [ "kasuari", "lru", "serde", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", @@ -3442,7 +3442,7 @@ dependencies = [ "line-clipping", "ratatui-core", "serde", - "strum", + "strum 0.27.2", "time", "unicode-segmentation", "unicode-width", @@ -3921,9 +3921,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -4239,7 +4239,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -4254,6 +4263,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4553,9 +4574,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap 2.13.0", "serde_core", @@ -4568,27 +4589,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tracing" @@ -4907,9 +4928,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.116" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -4920,9 +4941,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.66" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19280959e2844181895ef62f065c63e0ca07ece4771b53d89bfdb967d97cbf05" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ "js-sys", "wasm-bindgen", @@ -4930,9 +4951,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.116" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4940,9 +4961,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.116" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -4953,9 +4974,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.116" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -6067,8 +6088,10 @@ dependencies = [ "memchr", "ordered-float 5.3.0", "parking_lot", + "paste", "percent-encoding", "serde", + "strum 0.28.0", "thiserror 2.0.18", "tokio", "typed-path", diff --git a/Cargo.toml b/Cargo.toml index 3a8c940a..78dc3849 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ futures = "0.3.32" globset = "0.4.18" hashbrown = { version = "0.16.1", features = [ "serde" ] } indexmap = { version = "2.13.0", features = [ "serde" ] } -libc = "0.2.183" +libc = "0.2.184" lru = "0.16.3" mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] } objc2 = "0.6.4" @@ -67,12 +67,13 @@ scopeguard = "1.2.0" serde = { version = "1.0.228", features = [ "derive" ] } serde_json = "1.0.149" serde_with = "3.18.0" +strum = { version = "0.28.0", features = [ "derive" ] } syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } thiserror = "2.0.18" tokio = { version = "1.50.0", features = [ "full" ] } tokio-stream = "0.1.18" tokio-util = "0.7.18" -toml = { version = "1.1.0" } +toml = { version = "1.1.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/update_mimes.rs b/yazi-actor/src/mgr/update_mimes.rs index 2ed894e9..abda4e1b 100644 --- a/yazi-actor/src/mgr/update_mimes.rs +++ b/yazi-actor/src/mgr/update_mimes.rs @@ -19,7 +19,6 @@ impl Actor for UpdateMimes { let updates = opt .updates .into_iter() - .flat_map(|(key, value)| key.into_url().zip(value.into_sstr())) .filter(|(url, mime)| cx.mgr.mimetype.get(url) != Some(mime)) .fold(HashMap::new(), |mut map, (u, m)| { for u in linked.from_file(u.as_url()) { diff --git a/yazi-binding/src/scheme.rs b/yazi-binding/src/scheme.rs index 53f686b7..b0199dad 100644 --- a/yazi-binding/src/scheme.rs +++ b/yazi-binding/src/scheme.rs @@ -27,7 +27,7 @@ impl Scheme { impl UserData for Scheme { fn add_fields>(fields: &mut F) { - cached_field!(fields, kind, |_, me| Ok(me.kind().as_str())); + cached_field!(fields, kind, |_, me| Ok(Into::<&'static str>::into(me.kind()))); cached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new))); fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual())); diff --git a/yazi-core/src/app/quit.rs b/yazi-core/src/app/quit.rs index f7544fcf..3d79209b 100644 --- a/yazi-core/src/app/quit.rs +++ b/yazi-core/src/app/quit.rs @@ -5,22 +5,18 @@ use yazi_shared::{event::ActionCow, strand::StrandBuf}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct QuitOpt { + #[serde(default)] pub code: i32, #[serde(skip)] pub selected: Option, + #[serde(default, alias = "no-cwd-file")] pub no_cwd_file: bool, } impl TryFrom for QuitOpt { type Error = anyhow::Error; - fn try_from(a: ActionCow) -> Result { - Ok(Self { - code: a.get("code").unwrap_or_default(), - selected: None, - no_cwd_file: a.bool("no-cwd-file"), - }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for QuitOpt { diff --git a/yazi-core/src/notify/option.rs b/yazi-core/src/notify/option.rs index 646384b2..0bc33b1b 100644 --- a/yazi-core/src/notify/option.rs +++ b/yazi-core/src/notify/option.rs @@ -4,15 +4,17 @@ use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use serde_with::{DurationSecondsWithFrac, serde_as}; use yazi_binding::SER_OPT; -use yazi_shared::{data::Data, event::ActionCow}; +use yazi_shared::event::ActionCow; use crate::notify::MessageLevel; #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize)] pub struct MessageOpt { - pub title: String, + #[serde(alias = "0")] pub content: String, + #[serde(alias = "1")] + pub title: String, #[serde(default)] pub level: MessageLevel, #[serde_as(as = "DurationSecondsWithFrac")] @@ -22,14 +24,7 @@ pub struct MessageOpt { impl TryFrom for MessageOpt { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - Ok(Self { - title: a.take_second()?, - content: a.take_first()?, - level: <_>::deserialize(a.get::<&Data>("level")?)?, - timeout: <_>::deserialize(a.get::<&Data>("timeout")?)?, - }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for MessageOpt { diff --git a/yazi-fs/src/url.rs b/yazi-fs/src/url.rs index ab23e9fc..9755c7ba 100644 --- a/yazi-fs/src/url.rs +++ b/yazi-fs/src/url.rs @@ -78,9 +78,8 @@ impl<'a> FsUrl<'a> for UrlCow<'a> { fn unified_path(self) -> Cow<'a, Path> { match self { - Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner().into(), - Self::RegularRef(loc) | Self::SearchRef { loc, .. } => loc.as_inner().into(), - Self::Archive { .. } | Self::ArchiveRef { .. } | Self::Sftp { .. } | Self::SftpRef { .. } => { + Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner(), + Self::Archive { .. } | Self::Sftp { .. } => { self.cache().expect("non-local URL should have a cache path").into() } } diff --git a/yazi-parser/src/app/deprecate.rs b/yazi-parser/src/app/deprecate.rs index 67bc3cd4..ffa30597 100644 --- a/yazi-parser/src/app/deprecate.rs +++ b/yazi-parser/src/app/deprecate.rs @@ -1,8 +1,8 @@ -use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{SStr, event::ActionCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct DeprecateForm { pub content: SStr, } @@ -10,13 +10,7 @@ pub struct DeprecateForm { impl TryFrom for DeprecateForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - let Ok(content) = a.take("content") else { - bail!("Invalid 'content' in DeprecateForm"); - }; - - Ok(Self { content }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for DeprecateForm { diff --git a/yazi-parser/src/app/lua.rs b/yazi-parser/src/app/lua.rs index c183d6be..735ebd4a 100644 --- a/yazi-parser/src/app/lua.rs +++ b/yazi-parser/src/app/lua.rs @@ -2,17 +2,19 @@ use std::fmt::Debug; use anyhow::Result; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{SStr, event::ActionCow}; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct LuaForm { + #[serde(alias = "0")] pub code: SStr, } impl TryFrom for LuaForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { Ok(Self { code: a.take_first()? }) } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for LuaForm { diff --git a/yazi-parser/src/help/toggle.rs b/yazi-parser/src/help/toggle.rs index aaee10b2..2d53704d 100644 --- a/yazi-parser/src/help/toggle.rs +++ b/yazi-parser/src/help/toggle.rs @@ -1,15 +1,17 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{Layer, event::ActionCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct ToggleForm { + #[serde(alias = "0")] pub layer: Layer, } impl TryFrom for ToggleForm { type Error = anyhow::Error; - fn try_from(a: ActionCow) -> Result { Ok(Self { layer: a.str(0).parse()? }) } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl From for ToggleForm { diff --git a/yazi-parser/src/mgr/bulk_exit.rs b/yazi-parser/src/mgr/bulk_exit.rs index 41c205b6..633537ec 100644 --- a/yazi-parser/src/mgr/bulk_exit.rs +++ b/yazi-parser/src/mgr/bulk_exit.rs @@ -1,23 +1,19 @@ -use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{event::ActionCow, url::UrlCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct BulkExitForm { + #[serde(alias = "0")] pub target: UrlCow<'static>, + #[serde(default)] pub accept: bool, } impl TryFrom for BulkExitForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - let Ok(target) = a.take_first::() else { - bail!("invalid target in BulkExitForm"); - }; - - Ok(Self { target, accept: a.bool("accept") }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for BulkExitForm { diff --git a/yazi-parser/src/mgr/linemode.rs b/yazi-parser/src/mgr/linemode.rs index fe9623d1..ad68af21 100644 --- a/yazi-parser/src/mgr/linemode.rs +++ b/yazi-parser/src/mgr/linemode.rs @@ -1,25 +1,25 @@ use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{SStr, event::ActionCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct LinemodeForm { + #[serde(alias = "0")] pub new: SStr, } impl TryFrom for LinemodeForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - let Ok(new) = a.take_first::() else { - bail!("a string argument is required for LinemodeForm"); - }; + fn try_from(a: ActionCow) -> Result { + let me: Self = a.deserialize()?; - if new.is_empty() || new.len() > 20 { + if me.new.is_empty() || me.new.len() > 20 { bail!("Linemode must be between 1 and 20 characters long"); } - Ok(Self { new }) + Ok(me) } } diff --git a/yazi-parser/src/mgr/shell.rs b/yazi-parser/src/mgr/shell.rs index 58253f89..e2492821 100644 --- a/yazi-parser/src/mgr/shell.rs +++ b/yazi-parser/src/mgr/shell.rs @@ -1,14 +1,19 @@ use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{SStr, event::ActionCow, url::UrlCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct ShellForm { + #[serde(default, alias = "0")] pub run: SStr, pub cwd: Option>, + #[serde(default)] pub block: bool, + #[serde(default)] pub orphan: bool, + #[serde(default)] pub interactive: bool, pub cursor: Option, @@ -17,17 +22,8 @@ pub struct ShellForm { impl TryFrom for ShellForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - let me = Self { - run: a.take_first().unwrap_or_default(), - cwd: a.take("cwd").ok(), - - block: a.bool("block"), - orphan: a.bool("orphan"), - interactive: a.bool("interactive"), - - cursor: a.get("cursor").ok(), - }; + fn try_from(a: ActionCow) -> Result { + let me: Self = a.deserialize()?; if me.cursor.is_some_and(|c| c > me.run.chars().count()) { bail!("The cursor position is out of bounds."); diff --git a/yazi-parser/src/mgr/sort.rs b/yazi-parser/src/mgr/sort.rs index 84e4a45d..4dd85f51 100644 --- a/yazi-parser/src/mgr/sort.rs +++ b/yazi-parser/src/mgr/sort.rs @@ -6,8 +6,10 @@ use yazi_shared::event::ActionCow; #[derive(Debug, Default, Deserialize, Serialize)] pub struct SortForm { + #[serde(alias = "0")] pub by: Option, pub reverse: Option, + #[serde(alias = "dir-first")] pub dir_first: Option, pub sensitive: Option, pub translit: Option, @@ -17,16 +19,7 @@ pub struct SortForm { impl TryFrom for SortForm { type Error = anyhow::Error; - fn try_from(a: ActionCow) -> Result { - Ok(Self { - by: a.first().ok().map(str::parse).transpose()?, - reverse: a.get("reverse").ok(), - dir_first: a.get("dir-first").ok(), - sensitive: a.get("sensitive").ok(), - translit: a.get("translit").ok(), - fallback: a.get("fallback").ok().map(str::parse).transpose()?, - }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for SortForm { diff --git a/yazi-parser/src/mgr/tab_rename.rs b/yazi-parser/src/mgr/tab_rename.rs index cc6abb2e..a79b9d48 100644 --- a/yazi-parser/src/mgr/tab_rename.rs +++ b/yazi-parser/src/mgr/tab_rename.rs @@ -1,25 +1,27 @@ use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::{SStr, event::ActionCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct TabRenameForm { + #[serde(alias = "0")] pub name: Option, + #[serde(default)] pub interactive: bool, } impl TryFrom for TabRenameForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - let name = a.take_first().ok(); - let interactive = a.bool("interactive"); + fn try_from(a: ActionCow) -> Result { + let me: Self = a.deserialize()?; - if name.is_none() && !interactive { + if me.name.is_none() && !me.interactive { bail!("either name or interactive must be specified in TabRenameForm"); } - Ok(Self { name, interactive }) + Ok(me) } } diff --git a/yazi-parser/src/mgr/update_mimes.rs b/yazi-parser/src/mgr/update_mimes.rs index c5f1d433..a6f5a8de 100644 --- a/yazi-parser/src/mgr/update_mimes.rs +++ b/yazi-parser/src/mgr/update_mimes.rs @@ -1,23 +1,17 @@ -use anyhow::bail; use hashbrown::HashMap; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{data::{Data, DataKey}, event::ActionCow}; +use serde::Deserialize; +use yazi_shared::{SStr, event::ActionCow, url::UrlCow}; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct UpdateMimesForm { - pub updates: HashMap, + pub updates: HashMap, SStr>, } impl TryFrom for UpdateMimesForm { type Error = anyhow::Error; - fn try_from(mut a: ActionCow) -> Result { - let Ok(updates) = a.take("updates") else { - bail!("Invalid 'updates' in UpdateMimesForm"); - }; - - Ok(Self { updates }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for UpdateMimesForm { diff --git a/yazi-scheduler/src/hook/in.rs b/yazi-scheduler/src/hook/in.rs index 19209925..4d1f7024 100644 --- a/yazi-scheduler/src/hook/in.rs +++ b/yazi-scheduler/src/hook/in.rs @@ -71,7 +71,7 @@ impl HookInOutCopy { pub(crate) fn reduce(self, task: &mut Task) { if let TaskProg::FileCopy(_) = &task.prog { - task.hook = Some(HookIn::from(self).with_id(task.id)); + task.with_hook(self); } } } @@ -94,7 +94,7 @@ impl HookInOutCut { pub(crate) fn reduce(self, task: &mut Task) { if let TaskProg::FileCut(_) = &task.prog { - task.hook = Some(HookIn::from(self).with_id(task.id)); + task.with_hook(self); } } } @@ -150,7 +150,7 @@ impl HookInOutLink { pub(crate) fn reduce(self, task: &mut Task) { if let TaskProg::FileLink(_) = &task.prog { - task.hook = Some(HookIn::from(self).with_id(task.id)); + task.with_hook(self); } } } @@ -174,7 +174,7 @@ impl HookInOutHardlink { pub(crate) fn reduce(self, task: &mut Task) { if let TaskProg::FileHardlink(_) = &task.prog { - task.hook = Some(HookIn::from(self).with_id(task.id)); + task.with_hook(self); } } } diff --git a/yazi-sftp/src/de.rs b/yazi-sftp/src/de.rs index 4e73ec0f..f479e126 100644 --- a/yazi-sftp/src/de.rs +++ b/yazi-sftp/src/de.rs @@ -136,7 +136,7 @@ impl<'de> serde::Deserializer<'de> for &mut Deserializer<'de> { let b = self.input.get(..len).ok_or(Error::serde("string not enough"))?; self.input = &self.input[len..]; - visitor.visit_str(str::from_utf8(b).map_err(|e| Error::serde(e.to_string()))?) + visitor.visit_borrowed_str(str::from_utf8(b).map_err(|e| Error::serde(e.to_string()))?) } fn deserialize_string(self, visitor: V) -> Result @@ -156,7 +156,7 @@ impl<'de> serde::Deserializer<'de> for &mut Deserializer<'de> { let b = self.input.get(4..4 + len).ok_or(Error::serde("bytes not enough"))?; self.input = &self.input[4 + len..]; - visitor.visit_bytes(b) + visitor.visit_borrowed_bytes(b) } fn deserialize_byte_buf(self, visitor: V) -> Result diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 889e4299..9ee73233 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -25,8 +25,10 @@ hashbrown = { workspace = true } memchr = "2.8.0" ordered-float = { workspace = true } parking_lot = { workspace = true } +paste = { workspace = true } percent-encoding = { workspace = true } serde = { workspace = true } +strum = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } typed-path = { workspace = true } diff --git a/yazi-shared/src/data/data.rs b/yazi-shared/src/data/data.rs index 583dd91c..c74cebf4 100644 --- a/yazi-shared/src/data/data.rs +++ b/yazi-shared/src/data/data.rs @@ -267,53 +267,5 @@ impl Data { } } -// --- Macros -macro_rules! impl_into_integer { - ($t:ty) => { - impl TryFrom<&Data> for $t { - type Error = anyhow::Error; - - fn try_from(value: &Data) -> Result { - Ok(match value { - Data::Integer(i) => <$t>::try_from(*i)?, - Data::String(s) => s.parse()?, - Data::Id(i) => <$t>::try_from(i.get())?, - _ => bail!("not an integer"), - }) - } - } - }; -} - -macro_rules! impl_into_number { - ($t:ty) => { - impl TryFrom<&Data> for $t { - type Error = anyhow::Error; - - fn try_from(value: &Data) -> Result { - Ok(match value { - Data::Integer(i) if *i == (*i as $t as _) => *i as $t, - Data::Number(n) if *n == (*n as $t as _) => *n as $t, - Data::String(s) => s.parse()?, - Data::Id(i) if i.get() == (i.get() as $t as _) => i.get() as $t, - _ => bail!("not a number"), - }) - } - } - }; -} - -impl_into_integer!(i8); -impl_into_integer!(i16); -impl_into_integer!(i32); -impl_into_integer!(i64); -impl_into_integer!(isize); -impl_into_integer!(u8); -impl_into_integer!(u16); -impl_into_integer!(u32); -impl_into_integer!(u64); -impl_into_integer!(usize); -impl_into_integer!(crate::Id); - -impl_into_number!(f32); -impl_into_number!(f64); +impl_into_integer!(Data, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, crate::Id); +impl_into_number!(Data, f32, f64); diff --git a/yazi-shared/src/data/de.rs b/yazi-shared/src/data/de.rs index 46b10919..2f988354 100644 --- a/yazi-shared/src/data/de.rs +++ b/yazi-shared/src/data/de.rs @@ -1,110 +1,110 @@ -use serde::de::{Error, IntoDeserializer, MapAccess, SeqAccess}; +use serde::{Deserializer, de::{self, Error, IntoDeserializer, MapAccess, SeqAccess}}; -use crate::data::{Data, DataKey}; +use crate::data::{BytesDeserializer, Data, DataKey, KeyDeserializer}; -impl<'de> serde::Deserializer<'de> for &Data { - type Error = serde::de::value::Error; +impl<'de> Deserializer<'de> for &'de Data { + type Error = de::value::Error; fn deserialize_any(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { match self { Data::Nil => visitor.visit_unit(), Data::Boolean(b) => visitor.visit_bool(*b), Data::Integer(i) => visitor.visit_i64(*i), Data::Number(n) => visitor.visit_f64(*n), - Data::String(s) => visitor.visit_str(s), - Data::List(l) => visitor.visit_seq(DataSeqAccess { iter: l.iter() }), - Data::Dict(d) => visitor.visit_map(DataMapAccess { iter: d.iter(), value: None }), + Data::String(s) => visitor.visit_borrowed_str(s), + Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.iter() }), + Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.iter(), value: None }), Data::Id(i) => visitor.visit_u64(i.get()), - Data::Url(_) => Err(Error::custom("url not supported")), + Data::Url(u) => u.into_deserializer().deserialize_any(visitor), Data::Path(_) => Err(Error::custom("path not supported")), - Data::Bytes(b) => visitor.visit_bytes(b), + Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_any(visitor), Data::Any(_) => Err(Error::custom("any not supported")), } } fn deserialize_bool(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_bool(self.try_into().map_err(Error::custom)?) } fn deserialize_i8(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_i8(self.try_into().map_err(Error::custom)?) } fn deserialize_i16(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_i16(self.try_into().map_err(Error::custom)?) } fn deserialize_i32(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_i32(self.try_into().map_err(Error::custom)?) } fn deserialize_i64(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_i64(self.try_into().map_err(Error::custom)?) } fn deserialize_u8(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_u8(self.try_into().map_err(Error::custom)?) } fn deserialize_u16(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_u16(self.try_into().map_err(Error::custom)?) } fn deserialize_u32(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_u32(self.try_into().map_err(Error::custom)?) } fn deserialize_u64(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_u64(self.try_into().map_err(Error::custom)?) } fn deserialize_f32(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_f32(self.try_into().map_err(Error::custom)?) } fn deserialize_f64(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_f64(self.try_into().map_err(Error::custom)?) } fn deserialize_char(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { let s: &str = self.try_into().map_err(Error::custom)?; let mut chars = s.chars(); @@ -116,37 +116,41 @@ impl<'de> serde::Deserializer<'de> for &Data { fn deserialize_str(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { - visitor.visit_str(self.try_into().map_err(Error::custom)?) + visitor.visit_borrowed_str(self.try_into().map_err(Error::custom)?) } fn deserialize_string(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { - let s: &str = self.try_into().map_err(Error::custom)?; - visitor.visit_string(s.to_owned()) + match self { + Data::Url(u) => visitor.visit_newtype_struct(u.into_deserializer()), + _ => self.deserialize_str(visitor), + } } fn deserialize_bytes(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { - visitor.visit_bytes(self.try_into().map_err(Error::custom)?) + match self { + Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_bytes(visitor), + _ => Err(Error::custom("not bytes")), + } } fn deserialize_byte_buf(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { - let bytes: &[u8] = self.try_into().map_err(Error::custom)?; - visitor.visit_byte_buf(bytes.to_vec()) + self.deserialize_bytes(visitor) } fn deserialize_option(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { match self { Data::Nil => visitor.visit_none(), @@ -156,7 +160,7 @@ impl<'de> serde::Deserializer<'de> for &Data { fn deserialize_unit(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { match self { Data::Nil => visitor.visit_unit(), @@ -170,7 +174,7 @@ impl<'de> serde::Deserializer<'de> for &Data { visitor: V, ) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { match self { Data::Nil => visitor.visit_unit(), @@ -184,25 +188,25 @@ impl<'de> serde::Deserializer<'de> for &Data { visitor: V, ) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { visitor.visit_newtype_struct(self) } fn deserialize_seq(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { match self { - Data::List(l) => visitor.visit_seq(DataSeqAccess { iter: l.iter() }), - Data::Bytes(b) => visitor.visit_seq(DataByteSeqAccess { iter: b.iter() }), + Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.iter() }), + Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_seq(visitor), _ => Err(Error::custom("not a sequence")), } } fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_seq(visitor) } @@ -214,19 +218,19 @@ impl<'de> serde::Deserializer<'de> for &Data { visitor: V, ) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_seq(visitor) } fn deserialize_map(self, visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { - if let Data::Dict(d) = self { - visitor.visit_map(DataMapAccess { iter: d.iter(), value: None }) - } else { - Err(Error::custom("not a map")) + match self { + Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.iter(), value: None }), + Data::Url(u) => u.into_deserializer().deserialize_map(visitor), + _ => Err(Error::custom("not a map")), } } @@ -237,7 +241,7 @@ impl<'de> serde::Deserializer<'de> for &Data { visitor: V, ) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { self.deserialize_map(visitor) } @@ -249,7 +253,7 @@ impl<'de> serde::Deserializer<'de> for &Data { visitor: V, ) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { match self { Data::String(s) => visitor.visit_enum((&**s).into_deserializer()), @@ -259,29 +263,36 @@ impl<'de> serde::Deserializer<'de> for &Data { fn deserialize_identifier(self, _visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { Err(Error::custom("identifier not supported")) } fn deserialize_ignored_any(self, _visitor: V) -> Result where - V: serde::de::Visitor<'de>, + V: de::Visitor<'de>, { Err(Error::custom("ignored any not supported")) } } -struct DataSeqAccess<'a> { +impl<'de> IntoDeserializer<'de, de::value::Error> for &'de Data { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { self } +} + +// --- Seq +struct SeqDeserializer<'a> { iter: std::slice::Iter<'a, Data>, } -impl<'de> SeqAccess<'de> for DataSeqAccess<'_> { - type Error = serde::de::value::Error; +impl<'de> SeqAccess<'de> for SeqDeserializer<'de> { + type Error = de::value::Error; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where - T: serde::de::DeserializeSeed<'de>, + T: de::DeserializeSeed<'de>, { self.iter.next().map(|value| seed.deserialize(value)).transpose() } @@ -289,51 +300,28 @@ impl<'de> SeqAccess<'de> for DataSeqAccess<'_> { fn size_hint(&self) -> Option { Some(self.iter.len()) } } -struct DataByteSeqAccess<'a> { - iter: std::slice::Iter<'a, u8>, -} - -impl<'de> SeqAccess<'de> for DataByteSeqAccess<'_> { - type Error = serde::de::value::Error; - - fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: serde::de::DeserializeSeed<'de>, - { - self.iter.next().map(|value| seed.deserialize((*value).into_deserializer())).transpose() - } - - fn size_hint(&self) -> Option { Some(self.iter.len()) } -} - -struct DataMapAccess<'a> { +// --- Map +struct MapDeserializer<'a> { iter: hashbrown::hash_map::Iter<'a, DataKey, Data>, value: Option<&'a Data>, } -impl<'de> MapAccess<'de> for DataMapAccess<'_> { - type Error = serde::de::value::Error; +impl<'de> MapAccess<'de> for MapDeserializer<'de> { + type Error = de::value::Error; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> where - K: serde::de::DeserializeSeed<'de>, + K: de::DeserializeSeed<'de>, { let Some((key, value)) = self.iter.next() else { return Ok(None) }; self.value = Some(value); - match key { - DataKey::Boolean(b) => seed.deserialize((*b).into_deserializer()).map(Some), - DataKey::Integer(i) => seed.deserialize((*i).into_deserializer()).map(Some), - DataKey::Number(n) => seed.deserialize((*n).into_deserializer()).map(Some), - DataKey::String(s) => seed.deserialize((&**s).into_deserializer()).map(Some), - DataKey::Id(id) => seed.deserialize(id.get().into_deserializer()).map(Some), - _ => Err(Error::custom("unsupported map key type")), - } + seed.deserialize(KeyDeserializer::Borrowed(key)).map(Some) } fn next_value_seed(&mut self, seed: V) -> Result where - V: serde::de::DeserializeSeed<'de>, + V: de::DeserializeSeed<'de>, { seed.deserialize(self.value.take().ok_or_else(|| Error::custom("value missing for key"))?) } diff --git a/yazi-shared/src/data/de_bytes.rs b/yazi-shared/src/data/de_bytes.rs new file mode 100644 index 00000000..3dcee3c4 --- /dev/null +++ b/yazi-shared/src/data/de_bytes.rs @@ -0,0 +1,71 @@ +use std::borrow::Cow; + +use serde::{Deserializer, de::{self, value::SeqDeserializer}}; + +pub(crate) struct BytesDeserializer<'a>(pub(crate) Cow<'a, [u8]>); + +impl<'de, 'a: 'de> Deserializer<'de> for BytesDeserializer<'a> { + type Error = de::value::Error; + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string unit unit_struct + map struct enum bytes byte_buf identifier ignored_any + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Cow::Borrowed(b) => visitor.visit_borrowed_bytes(b), + Cow::Owned(b) => visitor.visit_byte_buf(b), + } + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Cow::Borrowed(b) => visitor.visit_seq(SeqDeserializer::new(b.iter().copied())), + Cow::Owned(b) => visitor.visit_seq(SeqDeserializer::new(b.into_iter())), + } + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } +} diff --git a/yazi-shared/src/data/de_key.rs b/yazi-shared/src/data/de_key.rs new file mode 100644 index 00000000..cd31a638 --- /dev/null +++ b/yazi-shared/src/data/de_key.rs @@ -0,0 +1,114 @@ +use std::{borrow::Cow, ops::Deref}; + +use serde::{Deserializer, de::{self, Error, IntoDeserializer}}; + +use crate::data::DataKey; + +pub(crate) enum KeyDeserializer<'a> { + Borrowed(&'a DataKey), + Owned(DataKey), +} + +impl Deref for KeyDeserializer<'_> { + type Target = DataKey; + + fn deref(&self) -> &Self::Target { + match self { + Self::Borrowed(key) => key, + Self::Owned(key) => key, + } + } +} + +impl<'de, 'a: 'de> Deserializer<'de> for KeyDeserializer<'a> { + type Error = de::value::Error; + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i128 u8 u16 u32 u128 f32 f64 char bytes byte_buf + option unit unit_struct newtype_struct seq tuple tuple_struct map struct enum ignored_any + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Self::Borrowed(DataKey::Boolean(b)) => visitor.visit_bool(*b), + Self::Borrowed(DataKey::Integer(i)) => visitor.visit_i64(*i), + Self::Borrowed(DataKey::Number(n)) => visitor.visit_f64(n.0), + Self::Borrowed(DataKey::String(s)) => visitor.visit_borrowed_str(s), + Self::Borrowed(DataKey::Id(id)) => visitor.visit_u64(id.get()), + Self::Borrowed(DataKey::Url(u)) => u.into_deserializer().deserialize_any(visitor), + Self::Owned(DataKey::Boolean(b)) => visitor.visit_bool(b), + Self::Owned(DataKey::Integer(i)) => visitor.visit_i64(i), + Self::Owned(DataKey::Number(n)) => visitor.visit_f64(n.0), + Self::Owned(DataKey::String(Cow::Borrowed(s))) => visitor.visit_borrowed_str(s), + Self::Owned(DataKey::String(Cow::Owned(s))) => visitor.visit_string(s), + Self::Owned(DataKey::Id(id)) => visitor.visit_u64(id.get()), + Self::Owned(DataKey::Url(u)) => u.into_deserializer().deserialize_any(visitor), + _ => Err(Error::custom("unsupported map key type")), + } + } + + fn deserialize_i64(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_i64((&*self).try_into().map_err(Error::custom)?) + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_u64((&*self).try_into().map_err(Error::custom)?) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_identifier(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Self::Borrowed(DataKey::Url(u)) => visitor.visit_newtype_struct(u.into_deserializer()), + Self::Owned(DataKey::Url(u)) => visitor.visit_newtype_struct(u.into_deserializer()), + other => other.deserialize_str(visitor), + } + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Self::Borrowed(DataKey::Boolean(b)) => { + visitor.visit_borrowed_str(if *b { "true" } else { "false" }) + } + Self::Borrowed(DataKey::Integer(i)) => visitor.visit_string(i.to_string()), + Self::Borrowed(DataKey::Number(n)) => visitor.visit_string(n.0.to_string()), + Self::Borrowed(DataKey::String(s)) => visitor.visit_borrowed_str(s), + Self::Borrowed(DataKey::Id(id)) => visitor.visit_string(id.get().to_string()), + Self::Owned(DataKey::Boolean(b)) => { + visitor.visit_borrowed_str(if b { "true" } else { "false" }) + } + Self::Owned(DataKey::Integer(i)) => visitor.visit_string(i.to_string()), + Self::Owned(DataKey::Number(n)) => visitor.visit_string(n.0.to_string()), + Self::Owned(DataKey::String(Cow::Borrowed(s))) => visitor.visit_borrowed_str(s), + Self::Owned(DataKey::String(Cow::Owned(s))) => visitor.visit_string(s), + Self::Owned(DataKey::Id(id)) => visitor.visit_string(id.get().to_string()), + _ => Err(Error::custom("unsupported map key type")), + } + } +} + +impl<'de, 'a: 'de> IntoDeserializer<'de, de::value::Error> for KeyDeserializer<'a> { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { self } +} diff --git a/yazi-shared/src/data/de_owned.rs b/yazi-shared/src/data/de_owned.rs new file mode 100644 index 00000000..863c1150 --- /dev/null +++ b/yazi-shared/src/data/de_owned.rs @@ -0,0 +1,335 @@ +use std::borrow::Cow; + +use serde::{Deserializer, de::{self, Error, IntoDeserializer, MapAccess, SeqAccess}}; + +use crate::data::{BytesDeserializer, Data, DataKey, KeyDeserializer}; + +impl<'de> Deserializer<'de> for Data { + type Error = de::value::Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::Nil => visitor.visit_unit(), + Data::Boolean(b) => visitor.visit_bool(b), + Data::Integer(i) => visitor.visit_i64(i), + Data::Number(n) => visitor.visit_f64(n), + Data::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), + Data::String(Cow::Owned(s)) => visitor.visit_string(s), + Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }), + Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }), + Data::Id(i) => visitor.visit_u64(i.get()), + Data::Url(u) => u.into_deserializer().deserialize_any(visitor), + Data::Path(_) => Err(Error::custom("path not supported")), + Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_any(visitor), + Data::Any(_) => Err(Error::custom("any not supported")), + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_bool((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_i8(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_i8((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_i16(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_i16((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_i32(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_i32((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_i64(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_i64((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_u8(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_u8((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_u16(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_u16((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_u32(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_u32((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_u64((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_f32(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_f32((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_f64((&self).try_into().map_err(Error::custom)?) + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + let s: &str = (&self).try_into().map_err(Error::custom)?; + let mut chars = s.chars(); + match (chars.next(), chars.next()) { + (Some(ch), None) => visitor.visit_char(ch), + _ => Err(Error::custom("not a char")), + } + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), + Data::String(Cow::Owned(s)) => visitor.visit_string(s), + Data::Url(u) => visitor.visit_newtype_struct(u.into_deserializer()), + _ => Err(Error::custom("not a string")), + } + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_bytes(visitor), + _ => Err(Error::custom("not bytes")), + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::Nil => visitor.visit_none(), + other => visitor.visit_some(other), + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::Nil => visitor.visit_unit(), + _ => Err(Error::custom("expected unit")), + } + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::Nil => visitor.visit_unit(), + _ => Err(Error::custom("expected unit struct")), + } + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }), + Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_seq(visitor), + _ => Err(Error::custom("not a sequence")), + } + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }), + Data::Url(u) => Deserializer::deserialize_map(u.into_deserializer(), visitor), + _ => Err(Error::custom("not a map")), + } + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + match self { + Data::String(s) => visitor.visit_enum(s.into_deserializer()), + _ => Err(Error::custom("not an enum")), + } + } + + fn deserialize_identifier(self, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::custom("identifier not supported")) + } + + fn deserialize_ignored_any(self, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::custom("ignored any not supported")) + } +} + +impl<'de> IntoDeserializer<'de, de::value::Error> for Data { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { self } +} + +// --- Seq +struct SeqDeserializer { + iter: std::vec::IntoIter, +} + +impl<'de> SeqAccess<'de> for SeqDeserializer { + type Error = de::value::Error; + + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: de::DeserializeSeed<'de>, + { + self.iter.next().map(|value| seed.deserialize(value)).transpose() + } + + fn size_hint(&self) -> Option { Some(self.iter.len()) } +} + +// --- Map +struct MapDeserializer { + iter: hashbrown::hash_map::IntoIter, + value: Option, +} + +impl<'de> MapAccess<'de> for MapDeserializer { + type Error = de::value::Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: de::DeserializeSeed<'de>, + { + let Some((key, value)) = self.iter.next() else { return Ok(None) }; + self.value = Some(value); + + seed.deserialize(KeyDeserializer::Owned(key)).map(Some) + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + seed.deserialize(self.value.take().ok_or_else(|| Error::custom("value missing for key"))?) + } + + fn size_hint(&self) -> Option { Some(self.iter.len()) } +} diff --git a/yazi-shared/src/data/key.rs b/yazi-shared/src/data/key.rs index b66db94e..c88240b0 100644 --- a/yazi-shared/src/data/key.rs +++ b/yazi-shared/src/data/key.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize, de}; -use crate::{Id, SStr, path::PathBufDyn, url::{UrlBuf, UrlCow}}; +use crate::{Id, SStr, path::PathBufDyn, url::UrlBuf}; #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(untagged)] @@ -33,15 +33,6 @@ impl DataKey { } } - pub fn into_url(self) -> Option> { - match self { - Self::String(s) => s.try_into().ok(), - Self::Url(u) => Some(u.into()), - Self::Bytes(b) => b.try_into().ok(), - _ => None, - } - } - fn deserialize_integer<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, @@ -80,3 +71,6 @@ impl From<&'static str> for DataKey { impl From for DataKey { fn from(value: String) -> Self { Self::String(Cow::Owned(value)) } } + +impl_into_integer!(DataKey, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, crate::Id); +impl_into_number!(DataKey, f32, f64); diff --git a/yazi-shared/src/data/macros.rs b/yazi-shared/src/data/macros.rs new file mode 100644 index 00000000..25806a12 --- /dev/null +++ b/yazi-shared/src/data/macros.rs @@ -0,0 +1,62 @@ +use anyhow::bail; + +pub(crate) fn float_to_i64(value: T) -> anyhow::Result +where + T: Into, +{ + let value = value.into(); + if !value.is_finite() || value.fract() != 0.0 { + bail!("not an integer"); + } + + let integer = value as i64; + if integer as f64 != value { + bail!("not an integer"); + } + + Ok(integer) +} + +macro_rules! impl_into_integer { + ($a:ty, $($b:ty),+ $(,)?) => { + $( + impl TryFrom<&$a> for $b { + type Error = anyhow::Error; + + fn try_from(value: &$a) -> Result { + paste::paste! { + Ok(match value { + $a::Integer(i) => <$b>::try_from(*i)?, + $a::Number(n) => <$b>::try_from($crate::data::macros::float_to_i64(*n)?)?, + $a::String(s) => s.parse()?, + $a::Id(i) => <$b>::try_from(i.get())?, + _ => anyhow::bail!("not an integer"), + }) + } + } + } + )+ + }; +} + +macro_rules! impl_into_number { + ($a:ty, $($b:ty),+ $(,)?) => { + $( + impl TryFrom<&$a> for $b { + type Error = anyhow::Error; + + fn try_from(value: &$a) -> Result { + paste::paste! { + Ok(match value { + $a::Integer(i) if *i == (*i as $b as _) => *i as $b, + $a::Number(n) if f64::from(*n) == (f64::from(*n) as $b as _) => f64::from(*n) as $b, + $a::String(s) => s.parse()?, + $a::Id(i) if i.get() == (i.get() as $b as _) => i.get() as $b, + _ => anyhow::bail!("not a number"), + }) + } + } + } + )+ + }; +} diff --git a/yazi-shared/src/data/mod.rs b/yazi-shared/src/data/mod.rs index ca7fb9d8..1f290c8a 100644 --- a/yazi-shared/src/data/mod.rs +++ b/yazi-shared/src/data/mod.rs @@ -1 +1,4 @@ -yazi_macro::mod_flat!(any data de key); +#[macro_use] +mod macros; + +yazi_macro::mod_flat!(any data de de_bytes de_key de_owned key); diff --git a/yazi-shared/src/event/cow.rs b/yazi-shared/src/event/cow.rs index 1ae32cbe..e59b1449 100644 --- a/yazi-shared/src/event/cow.rs +++ b/yazi-shared/src/event/cow.rs @@ -1,6 +1,7 @@ use std::{iter, ops::Deref}; use anyhow::Result; +use serde::de::DeserializeOwned; use tokio::sync::mpsc; use super::Action; @@ -36,6 +37,16 @@ impl From<&'static Action> for ActionCow { } impl ActionCow { + pub fn deserialize(self) -> Result + where + T: DeserializeOwned, + { + match self { + Self::Owned(c) => T::deserialize(c), + Self::Borrowed(c) => T::deserialize(c), + } + } + pub fn take<'a, T>(&mut self, name: impl Into) -> Result where T: TryFrom + TryFrom<&'a Data>, diff --git a/yazi-shared/src/event/de.rs b/yazi-shared/src/event/de.rs new file mode 100644 index 00000000..dfc15603 --- /dev/null +++ b/yazi-shared/src/event/de.rs @@ -0,0 +1,43 @@ +use serde::de::value::MapDeserializer; + +use super::Action; +use crate::data::KeyDeserializer; + +impl<'de> serde::Deserializer<'de> for &'de Action { + type Error = serde::de::value::Error; + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf + unit struct unit_struct newtype_struct seq tuple tuple_struct enum identifier + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_map(MapDeserializer::new( + self.args.iter().map(|(key, value)| (KeyDeserializer::Borrowed(key), value)), + )) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_unit() + } +} diff --git a/yazi-shared/src/event/de_owned.rs b/yazi-shared/src/event/de_owned.rs new file mode 100644 index 00000000..d219e3a8 --- /dev/null +++ b/yazi-shared/src/event/de_owned.rs @@ -0,0 +1,42 @@ +use serde::de::value::MapDeserializer; + +use crate::{data::KeyDeserializer, event::Action}; + +impl<'de> serde::Deserializer<'de> for Action { + type Error = serde::de::value::Error; + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf + unit struct unit_struct newtype_struct seq tuple tuple_struct enum identifier + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_map(MapDeserializer::new( + self.args.into_iter().map(|(key, value)| (KeyDeserializer::Owned(key), value)), + )) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + visitor.visit_unit() + } +} diff --git a/yazi-shared/src/event/mod.rs b/yazi-shared/src/event/mod.rs index d3259125..437bef17 100644 --- a/yazi-shared/src/event/mod.rs +++ b/yazi-shared/src/event/mod.rs @@ -1,3 +1,3 @@ -yazi_macro::mod_flat!(action cow event); +yazi_macro::mod_flat!(action cow de de_owned event); pub static NEED_RENDER: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0); diff --git a/yazi-shared/src/loc/cow.rs b/yazi-shared/src/loc/cow.rs new file mode 100644 index 00000000..d5782928 --- /dev/null +++ b/yazi-shared/src/loc/cow.rs @@ -0,0 +1,124 @@ +use std::{borrow::Cow, fmt::{self, Debug}}; + +use crate::{loc::{Loc, LocAble, LocAbleImpl, LocBuf, LocBufAble}, path::{PathBufDyn, PathCow, PathDyn}}; + +#[derive(Clone)] +pub enum LocCow<'a, B = &'a std::path::Path, O = std::path::PathBuf> +where + B: LocAble<'a, Owned = O>, + O: LocBufAble, +{ + Borrowed(Loc<'a, B>), + Owned(LocBuf), +} + +impl<'a, B, O> Debug for LocCow<'a, B, O> +where + B: LocAble<'a, Owned = O>, + O: LocBufAble, + Loc<'a, B>: Debug, + LocBuf: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Borrowed(loc) => f.debug_tuple("Borrowed").field(loc).finish(), + Self::Owned(loc) => f.debug_tuple("Owned").field(loc).finish(), + } + } +} + +impl<'a, B, O> Default for LocCow<'a, B, O> +where + B: LocAble<'a, Owned = O> + LocAbleImpl<'a>, + O: LocBufAble, +{ + fn default() -> Self { Self::Borrowed(Default::default()) } +} + +impl<'a, B, O> From> for LocCow<'a, B, O> +where + B: LocAble<'a, Owned = O>, + O: LocBufAble, +{ + fn from(value: Loc<'a, B>) -> Self { Self::Borrowed(value) } +} + +impl<'a, B, O> From> for LocCow<'a, B, O> +where + B: LocAble<'a, Owned = O>, + O: LocBufAble, +{ + fn from(value: LocBuf) -> Self { Self::Owned(value) } +} + +impl<'a, B, O> From> for PathCow<'a> +where + B: LocAble<'a, Owned = O> + Into>, + O: LocBufAble + Into, +{ + fn from(value: LocCow<'a, B, O>) -> Self { value.into_path() } +} + +impl<'a> LocCow<'a> { + pub fn as_loc(&self) -> Loc<'_> { + match self { + Self::Borrowed(loc) => *loc, + Self::Owned(loc) => loc.as_loc(), + } + } + + pub fn into_inner(self) -> Cow<'a, std::path::Path> { + match self { + Self::Borrowed(loc) => Cow::Borrowed(loc.as_inner()), + Self::Owned(loc) => Cow::Owned(loc.into_inner()), + } + } +} + +impl<'a> LocCow<'a, &'a typed_path::UnixPath, typed_path::UnixPathBuf> { + pub fn as_loc(&self) -> Loc<'_, &'_ typed_path::UnixPath> { + match self { + Self::Borrowed(loc) => *loc, + Self::Owned(loc) => loc.as_loc(), + } + } + + pub fn into_inner(self) -> Cow<'a, typed_path::UnixPath> { + match self { + Self::Borrowed(loc) => Cow::Borrowed(loc.as_inner()), + Self::Owned(loc) => Cow::Owned(loc.into_inner()), + } + } +} + +impl<'a, B, O> LocCow<'a, B, O> +where + B: LocAble<'a, Owned = O> + LocAbleImpl<'a>, + O: LocBufAble, +{ + pub fn into_owned(self) -> LocBuf { + match self { + Self::Borrowed(loc) => { + LocBuf { inner: loc.inner.to_path_buf(), uri: loc.uri, urn: loc.urn } + } + Self::Owned(loc) => loc, + } + } + + pub fn is_borrowed(&self) -> bool { matches!(self, Self::Borrowed(_)) } + + pub fn is_owned(&self) -> bool { !self.is_borrowed() } +} + +impl<'a, B, O> LocCow<'a, B, O> +where + B: LocAble<'a, Owned = O> + Into>, + O: LocBufAble + Into, +{ + pub fn into_path(self) -> PathCow<'a> { + match self { + Self::Borrowed(loc) => PathCow::Borrowed(loc.inner.into()), + Self::Owned(loc) => PathCow::Owned(loc.inner.into()), + } + } +} diff --git a/yazi-shared/src/loc/mod.rs b/yazi-shared/src/loc/mod.rs index 5b4556e9..7b323505 100644 --- a/yazi-shared/src/loc/mod.rs +++ b/yazi-shared/src/loc/mod.rs @@ -1,3 +1,3 @@ #![allow(private_bounds)] -yazi_macro::mod_flat!(able buf loc); +yazi_macro::mod_flat!(able buf cow loc); diff --git a/yazi-shared/src/path/cow.rs b/yazi-shared/src/path/cow.rs index 170a99e9..96ac3053 100644 --- a/yazi-shared/src/path/cow.rs +++ b/yazi-shared/src/path/cow.rs @@ -4,7 +4,6 @@ use anyhow::Result; use crate::path::{AsPath, PathBufDyn, PathDyn, PathDynError, PathKind}; -// --- PathCow #[derive(Debug)] pub enum PathCow<'a> { Borrowed(PathDyn<'a>), @@ -45,6 +44,13 @@ impl PartialEq<&str> for PathCow<'_> { } impl<'a> PathCow<'a> { + pub fn into_encoded_bytes(self) -> Cow<'a, [u8]> { + match self { + Self::Borrowed(p) => Cow::Borrowed(p.encoded_bytes()), + Self::Owned(p) => Cow::Owned(p.into_encoded_bytes()), + } + } + pub fn into_owned(self) -> PathBufDyn { match self { Self::Borrowed(p) => p.to_owned(), diff --git a/yazi-shared/src/pool/symbol.rs b/yazi-shared/src/pool/symbol.rs index 1d83b091..60fc2fc8 100644 --- a/yazi-shared/src/pool/symbol.rs +++ b/yazi-shared/src/pool/symbol.rs @@ -1,6 +1,7 @@ -use std::{hash::{Hash, Hasher}, marker::PhantomData, mem::ManuallyDrop, ops::Deref, str}; +use std::{borrow::Cow, hash::{Hash, Hasher}, marker::PhantomData, mem::ManuallyDrop, ops::Deref, str}; use hashbrown::hash_map::RawEntryMut; +use serde::Deserialize; use crate::pool::{Pool, SYMBOLS, SymbolPtr, compute_hash}; @@ -133,6 +134,15 @@ impl std::fmt::Debug for Symbol { } } +impl<'de> Deserialize<'de> for Symbol { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Cow::::deserialize(deserializer).map(Pool::::intern) + } +} + impl Symbol { #[inline] pub(super) fn new(ptr: SymbolPtr) -> Self { Self { ptr, _phantom: PhantomData } } diff --git a/yazi-shared/src/scheme/kind.rs b/yazi-shared/src/scheme/kind.rs index 88bccc30..c5ddc3dc 100644 --- a/yazi-shared/src/scheme/kind.rs +++ b/yazi-shared/src/scheme/kind.rs @@ -1,8 +1,10 @@ use anyhow::{Result, bail}; +use strum::IntoStaticStr; use crate::{BytesExt, scheme::{AsScheme, SchemeRef}}; -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, IntoStaticStr)] +#[strum(serialize_all = "kebab-case")] pub enum SchemeKind { Regular, Search, @@ -39,16 +41,6 @@ impl TryFrom<&[u8]> for SchemeKind { } impl SchemeKind { - #[inline] - pub const fn as_str(self) -> &'static str { - match self { - Self::Regular => "regular", - Self::Search => "search", - Self::Archive => "archive", - Self::Sftp => "sftp", - } - } - #[inline] pub fn is_local(self) -> bool { match self { @@ -74,8 +66,8 @@ impl SchemeKind { } #[inline] - pub(super) const fn offset(self, tilde: bool) -> usize { - 3 + self.as_str().len() + tilde as usize + pub(super) fn offset(self, tilde: bool) -> usize { + 3 + Into::<&str>::into(self).len() + tilde as usize } pub fn parse(bytes: &[u8]) -> Result> { diff --git a/yazi-shared/src/scheme/ref.rs b/yazi-shared/src/scheme/ref.rs index a6a69beb..6259f699 100644 --- a/yazi-shared/src/scheme/ref.rs +++ b/yazi-shared/src/scheme/ref.rs @@ -13,7 +13,6 @@ pub enum SchemeRef<'a> { impl Deref for SchemeRef<'_> { type Target = SchemeKind; - #[inline] fn deref(&self) -> &Self::Target { match self { Self::Regular { .. } => &SchemeKind::Regular, @@ -42,13 +41,11 @@ impl From> for Scheme { } impl<'a> SchemeRef<'a> { - #[inline] pub fn covariant(self, other: impl AsScheme) -> bool { let other = other.as_scheme(); if self.is_virtual() || other.is_virtual() { self == other } else { true } } - #[inline] pub const fn domain(self) -> Option<&'a str> { match self { Self::Regular { .. } => None, @@ -58,7 +55,6 @@ impl<'a> SchemeRef<'a> { } } - #[inline] pub const fn kind(self) -> SchemeKind { match self { Self::Regular { .. } => SchemeKind::Regular, @@ -68,7 +64,6 @@ impl<'a> SchemeRef<'a> { } } - #[inline] pub const fn ports(self) -> (usize, usize) { match self { Self::Regular { uri, urn } => (uri, urn), @@ -96,6 +91,5 @@ impl<'a> SchemeRef<'a> { } } - #[inline] pub const fn zeroed(self) -> Self { self.with_ports(0, 0) } } diff --git a/yazi-shared/src/scheme/scheme.rs b/yazi-shared/src/scheme/scheme.rs index 1d5961f3..8148a431 100644 --- a/yazi-shared/src/scheme/scheme.rs +++ b/yazi-shared/src/scheme/scheme.rs @@ -1,8 +1,11 @@ use std::hash::{Hash, Hasher}; +use serde::Deserialize; + use crate::{pool::Symbol, scheme::{AsScheme, SchemeRef}}; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(tag = "kind", rename_all = "kebab-case")] pub enum Scheme { Regular { uri: usize, urn: usize }, Search { domain: Symbol, uri: usize, urn: usize }, diff --git a/yazi-shared/src/scheme/traits.rs b/yazi-shared/src/scheme/traits.rs index b8c64c21..b36cbc56 100644 --- a/yazi-shared/src/scheme/traits.rs +++ b/yazi-shared/src/scheme/traits.rs @@ -48,6 +48,8 @@ where { fn kind(&self) -> SchemeKind { *self.as_scheme() } + fn ports(&self) -> (usize, usize) { self.as_scheme().ports() } + fn domain(&self) -> Option<&str> { self.as_scheme().domain() } fn covariant(&self, other: impl AsScheme) -> bool { self.as_scheme().covariant(other) } diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index 0c690057..cf3bbc25 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -1,9 +1,9 @@ use std::{borrow::Cow, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, path::{Path, PathBuf}, str::FromStr}; use anyhow::Result; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::{self, IntoDeserializer}}; -use crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::Scheme, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlLike}}; +use crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::{Scheme, SchemeLike}, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlDeserializer, UrlLike}}; #[derive(Clone, Eq)] pub enum UrlBuf { @@ -203,11 +203,63 @@ impl<'de> Deserialize<'de> for UrlBuf { where D: serde::Deserializer<'de>, { - let s = String::deserialize(deserializer)?; - Self::try_from(s).map_err(serde::de::Error::custom) + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = UrlBuf; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a Url or URL string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + UrlBuf::from_str(value).map_err(E::custom) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + UrlBuf::try_from(value).map_err(E::custom) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Shadow { + #[serde(flatten)] + scheme: Scheme, + path: Vec, + } + + let Shadow { scheme, path } = Deserialize::deserialize(deserializer)?; + let path = PathBufDyn::with(scheme.kind(), path).map_err(de::Error::custom)?; + + UrlBuf::try_from((scheme, path)).map_err(de::Error::custom) + } + } + + deserializer.deserialize_string(Visitor) } } +impl<'de> IntoDeserializer<'de, de::value::Error> for UrlBuf { + type Deserializer = UrlDeserializer<'de>; + + fn into_deserializer(self) -> Self::Deserializer { UrlDeserializer(self.into()) } +} + +impl<'de> IntoDeserializer<'de, de::value::Error> for &'de UrlBuf { + type Deserializer = UrlDeserializer<'de>; + + fn into_deserializer(self) -> Self::Deserializer { UrlDeserializer(self.into()) } +} + // --- Tests #[cfg(test)] mod tests { diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 9734115e..b1de5301 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -2,34 +2,30 @@ use std::{borrow::Cow, hash::{Hash, Hasher}, path::PathBuf}; use anyhow::{Result, anyhow}; use serde::{Deserialize, Deserializer, Serialize}; +use typed_path::{UnixPath, UnixPathBuf}; -use crate::{loc::{Loc, LocBuf}, path::{PathBufDyn, PathCow, PathDyn}, pool::SymbolCow, scheme::{AsScheme, Scheme, SchemeCow, SchemeKind, SchemeRef}, url::{AsUrl, Url, UrlBuf}}; +use crate::{loc::{Loc, LocBuf, LocCow}, path::{PathBufDyn, PathCow, PathDyn}, pool::SymbolCow, scheme::{AsScheme, Scheme, SchemeCow, SchemeKind, SchemeRef}, url::{AsUrl, Url, UrlBuf}}; #[derive(Clone, Debug)] pub enum UrlCow<'a> { - Regular(LocBuf), - Search { loc: LocBuf, domain: SymbolCow<'a, str> }, - Archive { loc: LocBuf, domain: SymbolCow<'a, str> }, - Sftp { loc: LocBuf, domain: SymbolCow<'a, str> }, - - RegularRef(Loc<'a>), - SearchRef { loc: Loc<'a>, domain: SymbolCow<'a, str> }, - ArchiveRef { loc: Loc<'a>, domain: SymbolCow<'a, str> }, - SftpRef { loc: Loc<'a, &'a typed_path::UnixPath>, domain: SymbolCow<'a, str> }, + Regular(LocCow<'a>), + Search { loc: LocCow<'a>, domain: SymbolCow<'a, str> }, + Archive { loc: LocCow<'a>, domain: SymbolCow<'a, str> }, + Sftp { loc: LocCow<'a, &'a UnixPath, UnixPathBuf>, domain: SymbolCow<'a, str> }, } // FIXME: remove impl Default for UrlCow<'_> { - fn default() -> Self { Self::RegularRef(Default::default()) } + fn default() -> Self { Self::Regular(Default::default()) } } impl<'a> From> for UrlCow<'a> { fn from(value: Url<'a>) -> Self { match value { - Url::Regular(loc) => Self::RegularRef(loc), - Url::Search { loc, domain } => Self::SearchRef { loc, domain: domain.into() }, - Url::Archive { loc, domain } => Self::ArchiveRef { loc, domain: domain.into() }, - Url::Sftp { loc, domain } => Self::SftpRef { loc, domain: domain.into() }, + Url::Regular(loc) => Self::Regular(loc.into()), + Url::Search { loc, domain } => Self::Search { loc: loc.into(), domain: domain.into() }, + Url::Archive { loc, domain } => Self::Archive { loc: loc.into(), domain: domain.into() }, + Url::Sftp { loc, domain } => Self::Sftp { loc: loc.into(), domain: domain.into() }, } } } @@ -44,10 +40,12 @@ where impl From for UrlCow<'_> { fn from(value: UrlBuf) -> Self { match value { - UrlBuf::Regular(loc) => Self::Regular(loc), - UrlBuf::Search { loc, domain } => Self::Search { loc, domain: domain.into() }, - UrlBuf::Archive { loc, domain } => Self::Archive { loc, domain: domain.into() }, - UrlBuf::Sftp { loc, domain } => Self::Sftp { loc, domain: domain.into() }, + UrlBuf::Regular(loc) => Self::Regular(loc.into()), + UrlBuf::Search { loc, domain } => Self::Search { loc: loc.into(), domain: domain.into() }, + UrlBuf::Archive { loc, domain } => { + Self::Archive { loc: loc.into(), domain: domain.into() } + } + UrlBuf::Sftp { loc, domain } => Self::Sftp { loc: loc.into(), domain: domain.into() }, } } } @@ -146,17 +144,17 @@ impl<'a> TryFrom<(SchemeCow<'a>, PathDyn<'a>)> for UrlCow<'a> { let (uri, urn) = scheme.as_scheme().ports(); let domain = scheme.into_domain(); Ok(match kind { - SchemeKind::Regular => Self::RegularRef(Loc::bare(path.as_os()?)), - SchemeKind::Search => Self::SearchRef { - loc: Loc::with(path.as_os()?, uri, urn)?, + SchemeKind::Regular => Self::Regular(Loc::bare(path.as_os()?).into()), + SchemeKind::Search => Self::Search { + loc: Loc::with(path.as_os()?, uri, urn)?.into(), domain: domain.ok_or_else(|| anyhow!("missing domain for search scheme"))?, }, - SchemeKind::Archive => Self::ArchiveRef { - loc: Loc::with(path.as_os()?, uri, urn)?, + SchemeKind::Archive => Self::Archive { + loc: Loc::with(path.as_os()?, uri, urn)?.into(), domain: domain.ok_or_else(|| anyhow!("missing domain for archive scheme"))?, }, - SchemeKind::Sftp => Self::SftpRef { - loc: Loc::with(path.as_unix()?, uri, urn)?, + SchemeKind::Sftp => Self::Sftp { + loc: Loc::with(path.as_unix()?, uri, urn)?.into(), domain: domain.ok_or_else(|| anyhow!("missing domain for sftp scheme"))?, }, }) @@ -171,17 +169,19 @@ impl<'a> TryFrom<(SchemeCow<'a>, PathBufDyn)> for UrlCow<'a> { let (uri, urn) = scheme.as_scheme().ports(); let domain = scheme.into_domain(); Ok(match kind { - SchemeKind::Regular => Self::Regular(path.into_os()?.into()), + SchemeKind::Regular => { + Self::Regular(LocBuf::::from(path.into_os()?).into()) + } SchemeKind::Search => Self::Search { - loc: LocBuf::::with(path.try_into()?, uri, urn)?, + loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), domain: domain.ok_or_else(|| anyhow!("missing domain for search scheme"))?, }, SchemeKind::Archive => Self::Archive { - loc: LocBuf::::with(path.try_into()?, uri, urn)?, + loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), domain: domain.ok_or_else(|| anyhow!("missing domain for archive scheme"))?, }, SchemeKind::Sftp => Self::Sftp { - loc: LocBuf::::with(path.try_into()?, uri, urn)?, + loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), domain: domain.ok_or_else(|| anyhow!("missing domain for sftp scheme"))?, }, }) @@ -207,75 +207,75 @@ impl Hash for UrlCow<'_> { impl<'a> UrlCow<'a> { pub fn is_owned(&self) -> bool { match self { - Self::Regular(_) | Self::Search { .. } | Self::Archive { .. } | Self::Sftp { .. } => true, - Self::RegularRef(_) - | Self::SearchRef { .. } - | Self::ArchiveRef { .. } - | Self::SftpRef { .. } => false, + Self::Regular(loc) => loc.is_owned(), + Self::Search { loc, .. } => loc.is_owned(), + Self::Archive { loc, .. } => loc.is_owned(), + Self::Sftp { loc, .. } => loc.is_owned(), } } pub fn into_owned(self) -> UrlBuf { match self { - Self::Regular(loc) => UrlBuf::Regular(loc), - Self::Search { loc, domain } => UrlBuf::Search { loc, domain: domain.into() }, - Self::Archive { loc, domain } => UrlBuf::Archive { loc, domain: domain.into() }, - Self::Sftp { loc, domain } => UrlBuf::Sftp { loc, domain: domain.into() }, - - Self::RegularRef(loc) => UrlBuf::Regular(loc.into()), - Self::SearchRef { loc, domain } => { - UrlBuf::Search { loc: loc.into(), domain: domain.into() } + Self::Regular(loc) => UrlBuf::Regular(loc.into_owned()), + Self::Search { loc, domain } => { + UrlBuf::Search { loc: loc.into_owned(), domain: domain.into() } } - Self::ArchiveRef { loc, domain } => { - UrlBuf::Archive { loc: loc.into(), domain: domain.into() } + Self::Archive { loc, domain } => { + UrlBuf::Archive { loc: loc.into_owned(), domain: domain.into() } + } + Self::Sftp { loc, domain } => { + UrlBuf::Sftp { loc: loc.into_owned(), domain: domain.into() } } - Self::SftpRef { loc, domain } => UrlBuf::Sftp { loc: loc.into(), domain: domain.into() }, } } - pub fn into_scheme(self) -> SchemeCow<'a> { + pub fn into_pair(self) -> (SchemeCow<'a>, PathCow<'a>) { let (uri, urn) = self.as_url().scheme().ports(); match self { - Self::Regular(_) => Scheme::Regular { uri, urn }.into(), - Self::RegularRef(_) => SchemeRef::Regular { uri, urn }.into(), - Self::Search { domain, .. } | Self::SearchRef { domain, .. } => match domain { - SymbolCow::Borrowed(domain) => SchemeRef::Search { domain, uri, urn }.into(), - SymbolCow::Owned(domain) => Scheme::Search { domain, uri, urn }.into(), + Self::Regular(loc) => match loc { + LocCow::Borrowed(_) => (SchemeRef::Regular { uri, urn }.into(), loc.into_path()), + LocCow::Owned(_) => (Scheme::Regular { uri, urn }.into(), loc.into_path()), }, - Self::Archive { domain, .. } | Self::ArchiveRef { domain, .. } => match domain { - SymbolCow::Borrowed(domain) => SchemeRef::Archive { domain, uri, urn }.into(), - SymbolCow::Owned(domain) => Scheme::Archive { domain, uri, urn }.into(), + Self::Search { loc, domain } => match domain { + SymbolCow::Borrowed(domain) => { + (SchemeRef::Search { domain, uri, urn }.into(), loc.into_path()) + } + SymbolCow::Owned(domain) => (Scheme::Search { domain, uri, urn }.into(), loc.into_path()), }, - Self::Sftp { domain, .. } | Self::SftpRef { domain, .. } => match domain { - SymbolCow::Borrowed(domain) => SchemeRef::Sftp { domain, uri, urn }.into(), - SymbolCow::Owned(domain) => Scheme::Sftp { domain, uri, urn }.into(), + Self::Archive { loc, domain } => match domain { + SymbolCow::Borrowed(domain) => { + (SchemeRef::Archive { domain, uri, urn }.into(), loc.into_path()) + } + SymbolCow::Owned(domain) => (Scheme::Archive { domain, uri, urn }.into(), loc.into_path()), + }, + Self::Sftp { loc, domain } => match domain { + SymbolCow::Borrowed(domain) => { + (SchemeRef::Sftp { domain, uri, urn }.into(), loc.into_path()) + } + SymbolCow::Owned(domain) => (Scheme::Sftp { domain, uri, urn }.into(), loc.into_path()), }, } } + pub fn into_scheme(self) -> SchemeCow<'a> { self.into_pair().0 } + + 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), - UrlCow::Search { loc, domain } => UrlCow::Search { loc, domain: domain.into_owned().into() }, + 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, domain: domain.into_owned().into() } + UrlCow::Archive { loc: loc.into_owned().into(), domain: domain.into_owned().into() } } - UrlCow::Sftp { loc, domain } => UrlCow::Sftp { loc, domain: domain.into_owned().into() }, - - UrlCow::RegularRef(loc) => UrlCow::Regular(loc.into()), - UrlCow::SearchRef { loc, domain } => { - UrlCow::Search { loc: loc.into(), domain: domain.into_owned().into() } - } - UrlCow::ArchiveRef { loc, domain } => { - UrlCow::Archive { loc: loc.into(), domain: domain.into_owned().into() } - } - UrlCow::SftpRef { loc, domain } => { - UrlCow::Sftp { loc: loc.into(), domain: domain.into_owned().into() } + UrlCow::Sftp { loc, domain } => { + UrlCow::Sftp { loc: loc.into_owned().into(), domain: domain.into_owned().into() } } } } - #[inline] pub fn to_owned(&self) -> UrlBuf { self.as_url().into() } } diff --git a/yazi-shared/src/url/de.rs b/yazi-shared/src/url/de.rs new file mode 100644 index 00000000..306b0f0d --- /dev/null +++ b/yazi-shared/src/url/de.rs @@ -0,0 +1,113 @@ +use std::borrow::Cow; + +use serde::{Deserializer, de::{self, IntoDeserializer, MapAccess}}; + +use crate::{data::BytesDeserializer, pool::SymbolCow, scheme::SchemeLike, url::UrlCow}; + +pub struct UrlDeserializer<'a>(pub(super) UrlCow<'a>); + +impl<'de, 'a: 'de> Deserializer<'de> for UrlDeserializer<'a> { + type Error = de::value::Error; + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf option + unit unit_struct struct newtype_struct seq tuple tuple_struct enum identifier ignored_any + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_map(MapDeserializer::new(self.0)) + } +} + +// --- Map +struct MapDeserializer<'a> { + kind: Option<&'static str>, + domain: Option>, + uri: Option, + urn: Option, + path: Option>, +} + +impl<'a> MapDeserializer<'a> { + fn new(url: UrlCow<'a>) -> Self { + let (scheme, path) = url.into_pair(); + let kind: &'static str = scheme.kind().into(); + let (uri, urn) = scheme.ports(); + + Self { + kind: Some(kind), + domain: scheme.into_domain(), + uri: Some(uri), + urn: Some(urn), + path: Some(path.into_encoded_bytes()), + } + } +} + +impl<'de, 'a: 'de> MapAccess<'de> for MapDeserializer<'a> { + type Error = de::value::Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: de::DeserializeSeed<'de>, + { + let key = if self.kind.is_some() { + Some("kind") + } else if self.domain.is_some() { + Some("domain") + } else if self.uri.is_some() { + Some("uri") + } else if self.urn.is_some() { + Some("urn") + } else if self.path.is_some() { + Some("path") + } else { + None + }; + + key.map(|key| seed.deserialize(key.into_deserializer())).transpose() + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + if let Some(kind) = self.kind.take() { + return seed.deserialize(kind.into_deserializer()); + } + if let Some(domain) = self.domain.take() { + return seed.deserialize(domain.as_ref().into_deserializer()); + } + if let Some(uri) = self.uri.take() { + return seed.deserialize(uri.into_deserializer()); + } + if let Some(urn) = self.urn.take() { + return seed.deserialize(urn.into_deserializer()); + } + if let Some(path) = self.path.take() { + return seed.deserialize(BytesDeserializer(path)); + } + + Err(de::Error::custom("value missing for key")) + } + + fn size_hint(&self) -> Option { + Some( + self.kind.is_some() as usize + + self.domain.is_some() as usize + + self.uri.is_some() as usize + + self.urn.is_some() as usize + + self.path.is_some() as usize, + ) + } +} diff --git a/yazi-shared/src/url/mod.rs b/yazi-shared/src/url/mod.rs index 207f15eb..6d7f2bd8 100644 --- a/yazi-shared/src/url/mod.rs +++ b/yazi-shared/src/url/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(buf component components cov cow display encode like traits url); +yazi_macro::mod_flat!(buf component components cov cow de display encode like traits url); diff --git a/yazi-shared/src/url/traits.rs b/yazi-shared/src/url/traits.rs index 0b0c5d73..70ab0c29 100644 --- a/yazi-shared/src/url/traits.rs +++ b/yazi-shared/src/url/traits.rs @@ -61,11 +61,6 @@ impl AsUrl for UrlCow<'_> { Self::Search { loc, domain } => Url::Search { loc: loc.as_loc(), domain }, Self::Archive { loc, domain } => Url::Archive { loc: loc.as_loc(), domain }, Self::Sftp { loc, domain } => Url::Sftp { loc: loc.as_loc(), domain }, - - Self::RegularRef(loc) => Url::Regular(*loc), - Self::SearchRef { loc, domain } => Url::Search { loc: *loc, domain }, - Self::ArchiveRef { loc, domain } => Url::Archive { loc: *loc, domain }, - Self::SftpRef { loc, domain } => Url::Sftp { loc: *loc, domain }, } } }