From 8a0265ad64d9d4cef0cacc938e0ed229180ff736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Thu, 28 Aug 2025 22:55:33 +0800 Subject: [PATCH] perf: zero-copy `UrlBuf` to `Url` conversion (#3117) --- Cargo.lock | 68 ++++----- Cargo.toml | 6 +- README.md | 56 ++++---- flake.lock | 12 +- yazi-actor/src/cmp/trigger.rs | 6 +- yazi-actor/src/mgr/cd.rs | 2 +- yazi-actor/src/mgr/create.rs | 2 +- yazi-actor/src/mgr/reveal.rs | 2 +- yazi-binding/src/url.rs | 4 +- yazi-config/src/pattern.rs | 8 +- yazi-core/src/tab/selected.rs | 20 +-- yazi-fs/src/fns.rs | 2 +- yazi-fs/src/path/clean.rs | 5 +- yazi-fs/src/path/expand.rs | 10 +- yazi-fs/src/path/relative.rs | 8 +- yazi-scheduler/src/file/file.rs | 2 +- yazi-shared/src/lib.rs | 2 +- yazi-shared/src/pool/symbol.rs | 8 ++ .../src/{url/scheme.rs => scheme/cow.rs} | 131 +++++++++++------- yazi-shared/src/scheme/mod.rs | 1 + yazi-shared/src/scheme/ref.rs | 69 +++++++++ yazi-shared/src/scheme/scheme.rs | 36 +++++ yazi-shared/src/url/buf.rs | 43 +++--- yazi-shared/src/url/component.rs | 27 ++-- yazi-shared/src/url/cov.rs | 2 +- yazi-shared/src/url/cow.rs | 58 +++++--- yazi-shared/src/url/encode.rs | 44 +++--- yazi-shared/src/url/mod.rs | 2 +- yazi-shared/src/url/url.rs | 101 ++++++-------- yazi-watcher/src/backend/backend.rs | 4 +- 30 files changed, 442 insertions(+), 299 deletions(-) rename yazi-shared/src/{url/scheme.rs => scheme/cow.rs} (60%) create mode 100644 yazi-shared/src/scheme/mod.rs create mode 100644 yazi-shared/src/scheme/ref.rs create mode 100644 yazi-shared/src/scheme/scheme.rs diff --git a/Cargo.lock b/Cargo.lock index ed5798b3..5bc92796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,9 +243,9 @@ dependencies = [ [[package]] name = "bit_field" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -392,9 +392,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -895,6 +895,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1047,8 +1053,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -1069,7 +1075,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", "serde", ] @@ -1132,9 +1138,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ "byteorder-lite", "quick-error", @@ -1259,9 +1265,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -1967,9 +1973,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.38.2" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d200a41a7797e6461bd04e4e95c3347053a731c32c87f066f2f0dda22dbdbba8" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" dependencies = [ "memchr", ] @@ -2158,14 +2164,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -2179,13 +2185,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] @@ -2196,9 +2202,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "rgb" @@ -2267,9 +2273,9 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "safe_arch" -version = "0.7.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +checksum = "3fb5032219cc30e5bb98749b19a18ceb2cf15e24ba8d517a7e64dff4f1f1eca5" dependencies = [ "bytemuck", ] @@ -2519,7 +2525,7 @@ dependencies = [ "once_cell", "onig", "plist", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", "serde", "serde_derive", "serde_json", @@ -3127,9 +3133,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.33" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +checksum = "5a0ab08c041f0cbb00a12fd091b2877dcec2311f90f87a88391d4b0961ffb4fe" dependencies = [ "bytemuck", "safe_arch", @@ -3727,7 +3733,7 @@ dependencies = [ "bitflags 2.9.3", "core-foundation-sys", "dirs", - "foldhash", + "foldhash 0.2.0", "futures", "hashbrown", "libc", @@ -3859,7 +3865,7 @@ version = "25.6.11" dependencies = [ "anyhow", "crossterm 0.29.0", - "foldhash", + "foldhash 0.2.0", "futures", "hashbrown", "libc", diff --git a/Cargo.toml b/Cargo.toml index 608016f3..0546eab2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,11 @@ ansi-to-tui = "7.0.0" anyhow = "1.0.99" base64 = "0.22.1" bitflags = { version = "2.9.3", features = [ "serde" ] } -clap = { version = "4.5.45", features = [ "derive" ] } +clap = { version = "4.5.46", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.29.0", features = [ "event-stream" ] } dirs = "6.0.0" -foldhash = "0.1.5" +foldhash = "0.2.0" futures = "0.3.31" globset = "0.4.16" hashbrown = { version = "0.15.5", features = [ "serde" ] } @@ -41,7 +41,7 @@ ordered-float = { version = "5.0.0", features = [ "serde" ] } parking_lot = "0.12.4" paste = "1.0.15" ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] } -regex = "1.11.1" +regex = "1.11.2" scopeguard = "1.2.0" serde = { version = "1.0.219", features = [ "derive" ] } serde_json = "1.0.143" diff --git a/README.md b/README.md index c0d0bab9..67b220ee 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@
- Special thanks to: -

- - Warp sponsorship -
- Warp, the intelligent terminal -
- Yazi's AI-powered terminal of choice!
Available for macOS, Linux and Windows
-
+ + Special thanks to: +
+ Warp sponsorship +
+ Warp, built for coding with multiple AI agents +
+ Available for macOS, Linux and Windows +

@@ -52,25 +52,25 @@ Yazi is currently in heavy development, expect breaking changes. ## Image Preview -| Platform | Protocol | Support | -| ---------------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------- | -| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in | -| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in | -| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in | -| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in | -| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in | -| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in | -| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in | -| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in | -| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in | -| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in | -| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in | -| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ✅ Built-in | -| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in | -| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in | -| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in | -| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required | -| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required | +| Platform | Protocol | Support | +| ---------------------------------------------------------------------------- | -------------------------------------- | --------------------------------- | +| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in | +| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in | +| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in | +| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in | +| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in | +| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in | +| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in | +| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in | +| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in | +| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in | +| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in | +| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ✅ Built-in | +| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in | +| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in | +| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in | +| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required | +| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required | See https://yazi-rs.github.io/docs/image-preview for details. diff --git a/flake.lock b/flake.lock index 2f328745..98ef9989 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1752596105, - "narHash": "sha256-lFNVsu/mHLq3q11MuGkMhUUoSXEdQjCHvpReaGP1S2k=", + "lastModified": 1756288264, + "narHash": "sha256-Om8adB1lfkU7D33VpR+/haZ2gI5r3Q+ZbIPzE5sYnwE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dab3a6e781554f965bde3def0aa2fda4eb8f1708", + "rev": "ddd1826f294a0ee5fdc198ab72c8306a0ea73aa9", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1752633862, - "narHash": "sha256-Bj7ozT1+5P7NmvDcuAXJvj56txcXuAhk3Vd9FdWFQzk=", + "lastModified": 1756348497, + "narHash": "sha256-xJp3VnoYh4kpsaKFO/7SsGbwOz7pI1ZmjbqpXEuR2cw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "8668ca94858206ac3db0860a9dec471de0d995f8", + "rev": "0adf92c70d23fb4f703aea5d3ebb51ac65994f7f", "type": "github" }, "original": { diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index 324fc60e..066a6f67 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -79,8 +79,10 @@ impl Trigger { const SEP: char = std::path::MAIN_SEPARATOR; Some(match path.as_os_str().rsplit_once(SEP) { - Some((p, c)) if p.is_empty() => (UrlBuf { loc: MAIN_SEPARATOR_STR.into(), scheme }, c.into()), - Some((p, c)) => (expand_url(UrlBuf { loc: p.into(), scheme }), c.into()), + Some((p, c)) if p.is_empty() => { + (UrlBuf { loc: MAIN_SEPARATOR_STR.into(), scheme: scheme.into() }, c.into()) + } + Some((p, c)) => (expand_url(UrlBuf { loc: p.into(), scheme: scheme.into() }), c.into()), None => (CWD.load().as_ref().clone(), path.into()), }) } diff --git a/yazi-actor/src/mgr/cd.rs b/yazi-actor/src/mgr/cd.rs index 7b9a2160..8a623d81 100644 --- a/yazi-actor/src/mgr/cd.rs +++ b/yazi-actor/src/mgr/cd.rs @@ -53,7 +53,7 @@ impl Actor for Cd { // Parent if let Some(parent) = opt.target.parent() { - tab.parent = Some(tab.history.remove_or(&parent)); + tab.parent = Some(tab.history.remove_or(parent)); } err!(Pubsub::pub_after_cd(tab.id, tab.cwd())); diff --git a/yazi-actor/src/mgr/create.rs b/yazi-actor/src/mgr/create.rs index 094e8007..d79341aa 100644 --- a/yazi-actor/src/mgr/create.rs +++ b/yazi-actor/src/mgr/create.rs @@ -53,7 +53,7 @@ impl Create { FilesOp::Deleting(parent.into(), [urn.into()].into()).emit(); provider::create(&new).await?; } else if let Some(parent) = new.parent() { - provider::create_dir_all(&parent).await.ok(); + provider::create_dir_all(parent).await.ok(); ok_or_not_found(provider::remove_file(&new).await)?; provider::create(&new).await?; } else { diff --git a/yazi-actor/src/mgr/reveal.rs b/yazi-actor/src/mgr/reveal.rs index 4619532d..c95d414b 100644 --- a/yazi-actor/src/mgr/reveal.rs +++ b/yazi-actor/src/mgr/reveal.rs @@ -17,7 +17,7 @@ impl Actor for Reveal { let Some((parent, child)) = opt.target.pair() else { succ!() }; // Cd to the parent directory - act!(mgr:cd, cx, (parent.clone(), opt.source))?; + act!(mgr:cd, cx, (parent, opt.source))?; // Try to hover on the child file let tab = cx.tab_mut(); diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index 207ab075..4af92936 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -38,7 +38,9 @@ impl<'a> From<&'a Url> for yazi_shared::url::Url<'a> { } impl<'a> From<&'a Url> for UrlCow<'a> { - fn from(value: &'a Url) -> Self { UrlCow::Borrowed(value.as_url()) } + fn from(value: &'a Url) -> Self { + UrlCow::Borrowed { loc: value.loc.as_loc(), scheme: value.scheme.as_ref().into() } + } } impl From for yazi_shared::url::UrlBufCov { diff --git a/yazi-config/src/pattern.rs b/yazi-config/src/pattern.rs index 8d59c716..016b9f3e 100644 --- a/yazi-config/src/pattern.rs +++ b/yazi-config/src/pattern.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::Result; use globset::GlobBuilder; use serde::Deserialize; -use yazi_shared::url::{Scheme, UrlBuf}; +use yazi_shared::{scheme::{SchemeCow, SchemeRef}, url::UrlBuf}; #[derive(Debug, Deserialize)] #[serde(try_from = "String")] @@ -108,14 +108,16 @@ impl PatternScheme { }; if protocol != "*" { - me.0 = Some(Scheme::parse_kind(protocol.as_bytes())?); + me.0 = Some(SchemeCow::parse_kind(protocol.as_bytes())?); } Ok((me, protocol.len() + 3)) } #[inline] - fn matches(&self, scheme: &Scheme) -> bool { self.0.is_none_or(|s| s == scheme.kind()) } + fn matches<'a>(&self, scheme: impl Into>) -> bool { + self.0.is_none_or(|s| s == scheme.into().kind()) + } } // --- Tests diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index acb273e7..284c30b8 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -59,7 +59,7 @@ impl Selected { let mut parent = urls[0].parent(); let mut parents = vec![]; while let Some(u) = parent { - if self.inner.contains_key(&UrlCov::new(&u)) { + if self.inner.contains_key(&UrlCov::new(u)) { return 0; } @@ -117,11 +117,11 @@ impl Selected { // FIXME: use UrlCov::parent() instead let mut parent = first.parent(); while let Some(u) = parent { - let n = self.parents.get_mut(&UrlCov::new(&u)).unwrap(); + let n = self.parents.get_mut(&UrlCov::new(u)).unwrap(); *n -= count; if *n == 0 { - self.parents.remove(&UrlCov::new(&u)); + self.parents.remove(&UrlCov::new(u)); } parent = u.parent(); @@ -147,14 +147,14 @@ impl Selected { #[cfg(test)] mod tests { - use yazi_shared::url::UrlCow; + use yazi_shared::{scheme::SchemeCow, url::UrlCow}; use super::*; fn url(s: &str) -> Url<'_> { match UrlCow::try_from(s).unwrap() { - UrlCow::Borrowed(url) => url, - UrlCow::Owned(_) => unreachable!(), + UrlCow::Borrowed { loc, scheme: SchemeCow::Borrowed(scheme) } => Url { loc, scheme }, + _ => unreachable!(), } } @@ -275,16 +275,16 @@ mod tests { let child1 = url("/parent/child1"); let child2 = url("/parent/child2"); let child3 = url("/parent/child3"); - assert_eq!(3, s.add_same([&child1, &child2, &child3])); + assert_eq!(3, s.add_same([child1, child2, child3])); - assert!(s.remove(&child1)); + assert!(s.remove(child1)); assert_eq!(s.inner.len(), 2); assert!(!s.parents.is_empty()); - assert!(s.remove(&child2)); + assert!(s.remove(child2)); assert!(!s.parents.is_empty()); - assert!(s.remove(&child3)); + assert!(s.remove(child3)); assert!(s.inner.is_empty()); assert!(s.parents.is_empty()); } diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index 0f0ae499..92b0b963 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -172,7 +172,7 @@ pub fn max_common_root(urls: &[UrlBuf]) -> usize { .components() .zip(parent.components()) .take_while(|(a, b)| match (a, b) { - (Component::Scheme(a), Component::Scheme(b)) => a.covariant(b), + (Component::Scheme(a), Component::Scheme(b)) => a.covariant(*b), (a, b) => a == b, }) .count() diff --git a/yazi-fs/src/path/clean.rs b/yazi-fs/src/path/clean.rs index 51e6ee4d..3d893d78 100644 --- a/yazi-fs/src/path/clean.rs +++ b/yazi-fs/src/path/clean.rs @@ -8,10 +8,7 @@ pub fn clean_url<'a>(url: impl Into>) -> UrlBuf { clean_path_impl(&cow.loc(), cow.loc().base().count(), cow.loc().trail().count()); let loc = LocBuf::with(path, uri, urn).expect("Failed to create Loc from cleaned path"); - match cow { - UrlCow::Borrowed(u) => UrlBuf { loc, scheme: u.scheme.clone() }, - UrlCow::Owned(u) => UrlBuf { loc, scheme: u.scheme }, - } + UrlBuf { loc, scheme: cow.into_scheme().into() } } fn clean_path_impl(path: &Path, base: usize, trail: usize) -> (PathBuf, usize, usize) { diff --git a/yazi-fs/src/path/expand.rs b/yazi-fs/src/path/expand.rs index be0dbb4b..e39860c4 100644 --- a/yazi-fs/src/path/expand.rs +++ b/yazi-fs/src/path/expand.rs @@ -29,10 +29,10 @@ fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> { ) .expect("Failed to create Loc from expanded path"); - let url = UrlBuf { loc, scheme: url.scheme.clone() }; + let url = UrlBuf { loc, scheme: url.scheme.into() }; match absolute_url(url.as_url()) { - UrlCow::Borrowed(_) => url.into(), - UrlCow::Owned(u) => u.into(), + UrlCow::Borrowed { .. } => url.into(), + c @ UrlCow::Owned { .. } => c.into_owned().into(), } } @@ -73,7 +73,7 @@ fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> { if url.has_trail() { 0 } else { 2 }, ) .expect("Failed to create Loc from drive letter"); - UrlBuf { loc, scheme: url.scheme.clone() }.into() + UrlBuf { loc, scheme: url.scheme.into() }.into() } else if local && let Ok(rest) = url.loc.strip_prefix("~/") && let Some(home) = dirs::home_dir() @@ -86,7 +86,7 @@ fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> { url.urn().count() + if url.has_trail() { 0 } else { add }, ) .expect("Failed to create Loc from home directory"); - UrlBuf { loc, scheme: url.scheme.clone() }.into() + UrlBuf { loc, scheme: url.scheme.into() }.into() } else if !url.is_absolute() { let cwd = CWD.load(); let loc = LocBuf::with(cwd.loc.join(url.loc), url.uri().count(), url.urn().count()) diff --git a/yazi-fs/src/path/relative.rs b/yazi-fs/src/path/relative.rs index b6daf65d..72eaba9b 100644 --- a/yazi-fs/src/path/relative.rs +++ b/yazi-fs/src/path/relative.rs @@ -8,8 +8,8 @@ pub fn path_relative_to<'a>( to: &'a impl AsRef, ) -> Result> { Ok(match url_relative_to(Url::regular(&from).into(), Url::regular(to).into())? { - UrlCow::Borrowed(url) => Cow::Borrowed(url.loc.as_path()), - UrlCow::Owned(url) => Cow::Owned(url.loc.into_path()), + UrlCow::Borrowed { loc, .. } => Cow::Borrowed(loc.as_path()), + UrlCow::Owned { loc, .. } => Cow::Owned(loc.into_path()), }) } @@ -25,7 +25,7 @@ pub(super) fn url_relative_to<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result(from: UrlCow<'_>, to: UrlCow<'a>) -> Result PartialEq for Symbol { impl Eq for Symbol {} +impl PartialEq for Symbol { + fn eq(&self, other: &str) -> bool { self.as_ref() == other } +} + +impl PartialEq<[u8]> for Symbol<[u8]> { + fn eq(&self, other: &[u8]) -> bool { self.as_ref() == other } +} + // --- Hash impl Hash for Symbol { fn hash(&self, state: &mut H) { self.ptr.as_ptr().hash(state); } diff --git a/yazi-shared/src/url/scheme.rs b/yazi-shared/src/scheme/cow.rs similarity index 60% rename from yazi-shared/src/url/scheme.rs rename to yazi-shared/src/scheme/cow.rs index 0a952384..e8798934 100644 --- a/yazi-shared/src/url/scheme.rs +++ b/yazi-shared/src/scheme/cow.rs @@ -3,45 +3,75 @@ use std::{borrow::Cow, ops::Not, path::Path}; use anyhow::{Result, bail, ensure}; use percent_encoding::percent_decode; -use crate::{BytesExt, pool::{InternStr, Symbol}}; +use crate::{BytesExt, pool::InternStr, scheme::{Scheme, SchemeRef}}; -#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum Scheme { - #[default] - Regular, - - Search(Symbol), - - Archive(Symbol), - - Sftp(Symbol), +#[derive(Debug)] +pub enum SchemeCow<'a> { + Borrowed(SchemeRef<'a>), + Owned(Scheme), } -impl Scheme { - #[inline] - pub const fn kind(&self) -> &'static str { - match self { - Self::Regular => "regular", - Self::Search(_) => "search", - Self::Archive(_) => "archive", - Self::Sftp(_) => "sftp", +impl Default for SchemeCow<'_> { + fn default() -> Self { Self::Borrowed(SchemeRef::Regular) } +} + +impl From for SchemeCow<'_> { + fn from(value: Scheme) -> Self { Self::Owned(value) } +} + +impl<'a> From<&'a Scheme> for SchemeCow<'a> { + fn from(value: &'a Scheme) -> Self { Self::Borrowed(value.as_ref()) } +} + +impl<'a> From> for SchemeCow<'a> { + fn from(value: SchemeRef<'a>) -> Self { Self::Borrowed(value) } +} + +impl From> for Scheme { + fn from(value: SchemeCow<'_>) -> Self { + match value { + SchemeCow::Borrowed(s) => s.into(), + SchemeCow::Owned(s) => s, + } + } +} + +impl<'a> SchemeCow<'a> { + pub fn search(domain: impl Into>) -> Self { + match domain.into() { + Cow::Borrowed(s) => SchemeRef::Search(s).into(), + Cow::Owned(s) => Scheme::Search(s.intern()).into(), + } + } + + pub fn archive(domain: impl Into>) -> Self { + match domain.into() { + Cow::Borrowed(s) => SchemeRef::Archive(s).into(), + Cow::Owned(s) => Scheme::Archive(s.intern()).into(), + } + } + + pub fn sftp(domain: impl Into>) -> Self { + match domain.into() { + Cow::Borrowed(s) => SchemeRef::Sftp(s).into(), + Cow::Owned(s) => Scheme::Sftp(s.intern()).into(), } } #[inline] - pub fn domain(&self) -> Option<&str> { + pub fn as_ref(&self) -> SchemeRef<'_> { match self { - Self::Regular => None, - Self::Search(s) | Self::Archive(s) | Self::Sftp(s) => Some(s), + Self::Borrowed(s) => *s, + Self::Owned(s) => s.into(), } } - pub(super) fn parse( - bytes: &[u8], + pub(crate) fn parse( + bytes: &'a [u8], skip: &mut usize, ) -> Result<(Self, bool, Option, Option)> { let Some((mut protocol, rest)) = bytes.split_by_seq(b"://") else { - return Ok((Self::Regular, false, None, None)); + return Ok((Self::default(), false, None, None)); }; // Advance to the beginning of the path @@ -54,18 +84,18 @@ impl Scheme { } let (scheme, uri, urn) = match protocol { - b"regular" => (Self::Regular, None, None), + b"regular" => (Self::default(), None, None), b"search" => { let (domain, uri, urn) = Self::decode_param(rest, skip)?; - (Self::Search(domain), uri, urn) + (Self::search(domain), uri, urn) } b"archive" => { let (domain, uri, urn) = Self::decode_param(rest, skip)?; - (Self::Archive(domain), uri, urn) + (Self::archive(domain), uri, urn) } b"sftp" => { let (domain, uri, urn) = Self::decode_param(rest, skip)?; - (Self::Sftp(domain), uri, urn) + (Self::sftp(domain), uri, urn) } _ => bail!("Could not parse protocol from URL: {}", String::from_utf8_lossy(bytes)), }; @@ -84,31 +114,18 @@ impl Scheme { } } - #[inline] - pub fn covariant(&self, other: &Self) -> bool { - if self.is_virtual() || other.is_virtual() { self == other } else { true } - } - - #[inline] - pub fn is_virtual(&self) -> bool { - match self { - Self::Regular | Self::Search(_) => false, - Self::Archive(_) | Self::Sftp(_) => true, - } - } - fn decode_param( - bytes: &[u8], + bytes: &'a [u8], skip: &mut usize, - ) -> Result<(Symbol, Option, Option)> { + ) -> Result<(Cow<'a, str>, Option, Option)> { let mut len = bytes.iter().copied().take_while(|&b| b != b'/').count(); let slash = bytes.get(len).is_some_and(|&b| b == b'/'); *skip += len + slash as usize; let (uri, urn) = Self::decode_ports(&bytes[..len], &mut len)?; let domain = match Cow::from(percent_decode(&bytes[..len])) { - Cow::Borrowed(b) => str::from_utf8(b)?.intern(), - Cow::Owned(b) => String::from_utf8(b)?.intern(), + Cow::Borrowed(b) => str::from_utf8(b)?.into(), + Cow::Owned(b) => String::from_utf8(b)?.into(), }; Ok((domain, uri, urn)) @@ -131,24 +148,24 @@ impl Scheme { Ok((b, a)) } - pub(super) fn normalize_ports( + pub(crate) fn normalize_ports( &self, uri: Option, urn: Option, path: &Path, ) -> Result> { - Ok(match self { - Scheme::Regular => { + Ok(match self.as_ref() { + SchemeRef::Regular => { ensure!(uri.is_none() && urn.is_none(), "Regular scheme cannot have ports"); None } - Scheme::Search(_) => { + SchemeRef::Search(_) => { let (uri, urn) = (uri.unwrap_or(0), urn.unwrap_or(0)); ensure!(uri == urn, "Search scheme requires URI and URN to be equal"); Some((uri, urn)) } - Scheme::Archive(_) => Some((uri.unwrap_or(0), urn.unwrap_or(0))), - Scheme::Sftp(_) => { + SchemeRef::Archive(_) => Some((uri.unwrap_or(0), urn.unwrap_or(0))), + SchemeRef::Sftp(_) => { let uri = uri.unwrap_or(path.as_os_str().is_empty().not() as usize); let urn = urn.unwrap_or(path.file_name().is_some() as usize); Some((uri, urn)) @@ -157,6 +174,14 @@ impl Scheme { } } +impl SchemeCow<'_> { + #[inline] + pub fn is_virtual(&self) -> bool { self.as_ref().is_virtual() } + + #[inline] + pub fn into_owned(self) -> Scheme { self.into() } +} + #[cfg(test)] mod tests { use super::*; @@ -165,7 +190,7 @@ mod tests { fn test_decode_ports() -> Result<()> { fn assert(s: &str, len: usize, uri: Option, urn: Option) -> Result<()> { let mut n = usize::MAX; - let port = Scheme::decode_ports(s.as_bytes(), &mut n)?; + let port = SchemeCow::decode_ports(s.as_bytes(), &mut n)?; assert_eq!((usize::MAX - n, port.0, port.1), (len, uri, urn)); Ok(()) } diff --git a/yazi-shared/src/scheme/mod.rs b/yazi-shared/src/scheme/mod.rs new file mode 100644 index 00000000..adafbcf4 --- /dev/null +++ b/yazi-shared/src/scheme/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(cow r#ref scheme); diff --git a/yazi-shared/src/scheme/ref.rs b/yazi-shared/src/scheme/ref.rs new file mode 100644 index 00000000..341c774a --- /dev/null +++ b/yazi-shared/src/scheme/ref.rs @@ -0,0 +1,69 @@ +use crate::{pool::InternStr, scheme::Scheme}; + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub enum SchemeRef<'a> { + #[default] + Regular, + + Search(&'a str), + + Archive(&'a str), + + Sftp(&'a str), +} + +impl<'a> From<&'a Scheme> for SchemeRef<'a> { + fn from(value: &'a Scheme) -> Self { + match value { + Scheme::Regular => Self::Regular, + Scheme::Search(d) => Self::Search(d), + Scheme::Archive(d) => Self::Archive(d), + Scheme::Sftp(d) => Self::Sftp(d), + } + } +} + +impl From> for Scheme { + fn from(value: SchemeRef) -> Self { + match value { + SchemeRef::Regular => Self::Regular, + SchemeRef::Search(d) => Self::Search(d.intern()), + SchemeRef::Archive(d) => Self::Archive(d.intern()), + SchemeRef::Sftp(d) => Self::Sftp(d.intern()), + } + } +} + +impl<'a> SchemeRef<'a> { + #[inline] + pub const fn kind(self) -> &'static str { + match self { + Self::Regular => "regular", + Self::Search(_) => "search", + Self::Archive(_) => "archive", + Self::Sftp(_) => "sftp", + } + } + + #[inline] + pub const fn domain(self) -> Option<&'a str> { + match self { + Self::Regular => None, + Self::Search(s) | Self::Archive(s) | Self::Sftp(s) => Some(s), + } + } + + #[inline] + pub fn covariant(self, other: impl Into) -> bool { + let other = other.into(); + if self.is_virtual() || other.is_virtual() { self == other } else { true } + } + + #[inline] + pub fn is_virtual(&self) -> bool { + match self { + Self::Regular | Self::Search(_) => false, + Self::Archive(_) | Self::Sftp(_) => true, + } + } +} diff --git a/yazi-shared/src/scheme/scheme.rs b/yazi-shared/src/scheme/scheme.rs new file mode 100644 index 00000000..cd010873 --- /dev/null +++ b/yazi-shared/src/scheme/scheme.rs @@ -0,0 +1,36 @@ +use std::hash::{Hash, Hasher}; + +use crate::{pool::Symbol, scheme::SchemeRef}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub enum Scheme { + #[default] + Regular, + + Search(Symbol), + + Archive(Symbol), + + Sftp(Symbol), +} + +impl Hash for Scheme { + fn hash(&self, state: &mut H) { self.as_ref().hash(state); } +} + +impl Scheme { + #[inline] + pub fn as_ref(&self) -> SchemeRef<'_> { self.into() } + + #[inline] + pub fn kind(&self) -> &'static str { self.as_ref().kind() } + + #[inline] + pub fn domain(&self) -> Option<&str> { self.as_ref().domain() } + + #[inline] + pub fn covariant(&self, other: &Self) -> bool { self.as_ref().covariant(other) } + + #[inline] + pub fn is_virtual(&self) -> bool { self.as_ref().is_virtual() } +} diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index 5acbdece..13e4a4c0 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -3,9 +3,9 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::BuildHasher, p use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::{loc::LocBuf, pool::Pool, url::{Components, Display, Encode, EncodeTilded, Scheme, Uri, Url, UrlCow, Urn}}; +use crate::{loc::LocBuf, pool::Pool, scheme::{Scheme, SchemeRef}, url::{Components, Display, Encode, EncodeTilded, Uri, Url, UrlCow, Urn}}; -#[derive(Clone, Default, Eq, Ord, PartialOrd, PartialEq, Hash)] +#[derive(Clone, Default, Eq, Hash, PartialEq)] pub struct UrlBuf { pub loc: LocBuf, pub scheme: Scheme, @@ -20,11 +20,11 @@ impl From for UrlBuf { } impl From<&Url<'_>> for UrlBuf { - fn from(url: &Url<'_>) -> Self { Self { loc: url.loc.into(), scheme: url.scheme.clone() } } + fn from(url: &Url<'_>) -> Self { Self { loc: url.loc.into(), scheme: url.scheme.into() } } } impl From> for UrlBuf { - fn from(url: Url<'_>) -> Self { Self { loc: url.loc.into(), scheme: url.scheme } } + fn from(url: Url<'_>) -> Self { Self { loc: url.loc.into(), scheme: url.scheme.into() } } } impl From<&UrlBuf> for UrlBuf { @@ -107,34 +107,35 @@ impl UrlBuf { pub fn strip_prefix<'a>(&self, base: impl Into>) -> Option<&Urn> { use Scheme as S; + use SchemeRef as T; let base = base.into(); let prefix = self.loc.strip_prefix(base.loc).ok()?; - Some(Urn::new(match (&self.scheme, &base.scheme) { + Some(Urn::new(match (&self.scheme, base.scheme) { // Same scheme - (S::Regular, S::Regular) => Some(prefix), - (S::Search(_), S::Search(_)) => Some(prefix), - (S::Archive(a), S::Archive(b)) => Some(prefix).filter(|_| a == b), - (S::Sftp(a), S::Sftp(b)) => Some(prefix).filter(|_| a == b), + (S::Regular, T::Regular) => Some(prefix), + (S::Search(_), T::Search(_)) => Some(prefix), + (S::Archive(a), T::Archive(b)) => Some(prefix).filter(|_| a == b), + (S::Sftp(a), T::Sftp(b)) => Some(prefix).filter(|_| a == b), // Both are local files - (S::Regular, S::Search(_)) => Some(prefix), - (S::Search(_), S::Regular) => Some(prefix), + (S::Regular, T::Search(_)) => Some(prefix), + (S::Search(_), T::Regular) => Some(prefix), // Only the entry of archives is a local file - (S::Regular, S::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()), - (S::Search(_), S::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()), - (S::Archive(_), S::Regular) => Some(prefix).filter(|_| self.uri().is_empty()), - (S::Archive(_), S::Search(_)) => Some(prefix).filter(|_| self.uri().is_empty()), + (S::Regular, T::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()), + (S::Search(_), T::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()), + (S::Archive(_), T::Regular) => Some(prefix).filter(|_| self.uri().is_empty()), + (S::Archive(_), T::Search(_)) => Some(prefix).filter(|_| self.uri().is_empty()), // Independent virtual file space - (S::Regular, S::Sftp(_)) => None, - (S::Search(_), S::Sftp(_)) => None, - (S::Archive(_), S::Sftp(_)) => None, - (S::Sftp(_), S::Regular) => None, - (S::Sftp(_), S::Search(_)) => None, - (S::Sftp(_), S::Archive(_)) => None, + (S::Regular, T::Sftp(_)) => None, + (S::Search(_), T::Sftp(_)) => None, + (S::Archive(_), T::Sftp(_)) => None, + (S::Sftp(_), T::Regular) => None, + (S::Sftp(_), T::Search(_)) => None, + (S::Sftp(_), T::Archive(_)) => None, }?)) } diff --git a/yazi-shared/src/url/component.rs b/yazi-shared/src/url/component.rs index 16e3ddd5..fc1a73e5 100644 --- a/yazi-shared/src/url/component.rs +++ b/yazi-shared/src/url/component.rs @@ -1,10 +1,10 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not, path::{self, PathBuf, PrefixComponent}}; -use crate::{loc::Loc, url::{Encode, Scheme, Url, UrlBuf, UrlCow}}; +use crate::{loc::Loc, scheme::{Scheme, SchemeRef}, url::{Encode, Url, UrlBuf, UrlCow}}; -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Component<'a> { - Scheme(&'a Scheme), + Scheme(SchemeRef<'a>), Prefix(PrefixComponent<'a>), RootDir, CurDir, @@ -29,7 +29,7 @@ impl<'a> FromIterator> for UrlBuf { let mut scheme = Scheme::Regular; let mut buf = PathBuf::new(); iter.into_iter().for_each(|c| match c { - Component::Scheme(s) => scheme = s.clone(), + Component::Scheme(s) => scheme = s.into(), Component::Prefix(p) => buf.push(path::Component::Prefix(p)), Component::RootDir => buf.push(path::Component::RootDir), Component::CurDir => buf.push(path::Component::CurDir), @@ -61,16 +61,16 @@ impl<'a> FromIterator> for PathBuf { pub struct Components<'a> { inner: path::Components<'a>, loc: Loc<'a>, - scheme: &'a Scheme, + scheme: SchemeRef<'a>, scheme_yielded: bool, } -impl<'a> From<&'a Url<'a>> for Components<'a> { - fn from(value: &'a Url<'a>) -> Self { +impl<'a> From> for Components<'a> { + fn from(value: Url<'a>) -> Self { Self { - inner: value.loc.components(), + inner: value.loc.as_path().components(), loc: value.loc, - scheme: &value.scheme, + scheme: value.scheme, scheme_yielded: false, } } @@ -81,19 +81,14 @@ impl<'a> From<&'a UrlBuf> for Components<'a> { Self { inner: value.loc.components(), loc: value.loc.as_loc(), - scheme: &value.scheme, + scheme: value.scheme.as_ref(), scheme_yielded: false, } } } impl<'a> From<&'a UrlCow<'a>> for Components<'a> { - fn from(value: &'a UrlCow<'a>) -> Self { - match value { - UrlCow::Borrowed(url) => Self::from(url), - UrlCow::Owned(url) => Self::from(url), - } - } + fn from(value: &'a UrlCow<'a>) -> Self { Self::from(value.as_url()) } } impl<'a> Components<'a> { diff --git a/yazi-shared/src/url/cov.rs b/yazi-shared/src/url/cov.rs index 6023210a..dea9f120 100644 --- a/yazi-shared/src/url/cov.rs +++ b/yazi-shared/src/url/cov.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::url::{Url, UrlBuf, UrlCow}; -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct UrlCov<'a>(Url<'a>); impl<'a> Deref for UrlCov<'a> { diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 90046025..6f7def57 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -3,28 +3,30 @@ use std::{borrow::Cow, path::{Path, PathBuf}}; use anyhow::Result; use percent_encoding::percent_decode; -use crate::{IntoOsStr, loc::{Loc, LocBuf}, url::{Components, Scheme, Url, UrlBuf, Urn}}; +use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{SchemeCow, SchemeRef}, url::{Components, Url, UrlBuf, Urn}}; #[derive(Debug)] pub enum UrlCow<'a> { - Borrowed(Url<'a>), - Owned(UrlBuf), + Borrowed { loc: Loc<'a>, scheme: SchemeCow<'a> }, + Owned { loc: LocBuf, scheme: SchemeCow<'a> }, } impl Default for UrlCow<'_> { - fn default() -> Self { Self::Owned(UrlBuf::default()) } + fn default() -> Self { Self::Owned { loc: Default::default(), scheme: Default::default() } } } impl<'a> From> for UrlCow<'a> { - fn from(value: Url<'a>) -> Self { Self::Borrowed(value) } + fn from(value: Url<'a>) -> Self { Self::Borrowed { loc: value.loc, scheme: value.scheme.into() } } } impl<'a> From<&'a UrlBuf> for UrlCow<'a> { - fn from(value: &'a UrlBuf) -> Self { Self::Borrowed(value.into()) } + fn from(value: &'a UrlBuf) -> Self { + Self::Borrowed { loc: value.loc.as_loc(), scheme: SchemeCow::from(&value.scheme) } + } } impl From for UrlCow<'_> { - fn from(value: UrlBuf) -> Self { Self::Owned(value) } + fn from(value: UrlBuf) -> Self { Self::Owned { loc: value.loc, scheme: value.scheme.into() } } } impl<'a> From<&'a UrlCow<'a>> for Url<'a> { @@ -46,11 +48,13 @@ impl<'a> TryFrom<&'a [u8]> for UrlCow<'a> { let (scheme, path, port) = Self::parse(value)?; Ok(match (path, port) { - (Cow::Borrowed(p), None) => Url { loc: Loc::from(p), scheme }.into(), - (Cow::Borrowed(p), Some((uri, urn))) => Url { loc: Loc::with(p, uri, urn)?, scheme }.into(), - (Cow::Owned(p), None) => UrlBuf { loc: LocBuf::from(p), scheme }.into(), + (Cow::Borrowed(p), None) => Self::Borrowed { loc: Loc::from(p), scheme }, + (Cow::Borrowed(p), Some((uri, urn))) => { + Self::Borrowed { loc: Loc::with(p, uri, urn)?, scheme } + } + (Cow::Owned(p), None) => Self::Owned { loc: LocBuf::from(p), scheme }, (Cow::Owned(p), Some((uri, urn))) => { - UrlBuf { loc: LocBuf::with(p, uri, urn)?, scheme }.into() + Self::Owned { loc: LocBuf::with(p, uri, urn)?, scheme } } }) } @@ -94,36 +98,44 @@ impl PartialEq for UrlCow<'_> { fn eq(&self, other: &UrlBuf) -> bool { self.as_url() == other.as_url() } } -impl UrlCow<'_> { +impl<'a> UrlCow<'a> { #[inline] pub fn loc(&self) -> Loc<'_> { match self { - UrlCow::Borrowed(u) => u.loc.as_loc(), - UrlCow::Owned(u) => u.loc.as_loc(), + UrlCow::Borrowed { loc, .. } => *loc, + UrlCow::Owned { loc, .. } => loc.as_loc(), } } #[inline] - pub fn scheme(&self) -> &Scheme { + pub fn scheme(&self) -> SchemeRef<'_> { match self { - UrlCow::Borrowed(u) => &u.scheme, - UrlCow::Owned(u) => &u.scheme, + UrlCow::Borrowed { scheme, .. } => scheme.as_ref(), + UrlCow::Owned { scheme, .. } => scheme.as_ref(), } } #[inline] pub fn as_url(&self) -> Url<'_> { match self { - UrlCow::Borrowed(u) => u.as_url(), - UrlCow::Owned(u) => u.as_url(), + UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_ref() }, + UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_ref() }, } } #[inline] pub fn into_owned(self) -> UrlBuf { match self { - UrlCow::Borrowed(u) => u.into(), - UrlCow::Owned(u) => u, + UrlCow::Borrowed { loc, scheme } => UrlBuf { loc: loc.into(), scheme: scheme.into() }, + UrlCow::Owned { loc, scheme } => UrlBuf { loc, scheme: scheme.into() }, + } + } + + #[inline] + pub fn into_scheme(self) -> SchemeCow<'a> { + match self { + UrlCow::Borrowed { scheme, .. } => scheme, + UrlCow::Owned { scheme, .. } => scheme, } } @@ -133,9 +145,9 @@ impl UrlCow<'_> { #[inline] pub fn pair(&self) -> Option<(Url<'_>, &Urn)> { self.as_url().pair() } - pub fn parse(bytes: &[u8]) -> Result<(Scheme, Cow<'_, Path>, Option<(usize, usize)>)> { + pub fn parse(bytes: &[u8]) -> Result<(SchemeCow<'_>, Cow<'_, Path>, Option<(usize, usize)>)> { let mut skip = 0; - let (scheme, tilde, uri, urn) = Scheme::parse(bytes, &mut skip)?; + let (scheme, tilde, uri, urn) = SchemeCow::parse(bytes, &mut skip)?; let rest = if tilde { Cow::from(percent_decode(&bytes[skip..])).into_os_str()? diff --git a/yazi-shared/src/url/encode.rs b/yazi-shared/src/url/encode.rs index b37f332a..e24e756f 100644 --- a/yazi-shared/src/url/encode.rs +++ b/yazi-shared/src/url/encode.rs @@ -2,24 +2,24 @@ use std::{fmt::{self, Display}, ops::Not}; use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode}; -use crate::{loc::Loc, url::{Scheme, Url, UrlBuf}}; +use crate::{loc::Loc, scheme::SchemeRef, url::{Url, UrlBuf}}; pub struct Encode<'a> { loc: Loc<'a>, - scheme: &'a Scheme, + scheme: SchemeRef<'a>, } -impl<'a> From<&'a Url<'a>> for Encode<'a> { - fn from(url: &'a Url<'a>) -> Self { Self::new(url.loc, &url.scheme) } +impl<'a> From> for Encode<'a> { + fn from(url: Url<'a>) -> Self { Self::new(url.loc, url.scheme) } } impl<'a> From<&'a UrlBuf> for Encode<'a> { - fn from(url: &'a UrlBuf) -> Self { Self::new(url.loc.as_loc(), &url.scheme) } + fn from(url: &'a UrlBuf) -> Self { Self::new(url.loc.as_loc(), url.scheme.as_ref()) } } impl<'a> Encode<'a> { #[inline] - pub(super) fn new(loc: Loc<'a>, scheme: &'a Scheme) -> Self { Self { loc, scheme } } + pub(super) fn new(loc: Loc<'a>, scheme: SchemeRef<'a>) -> Self { Self { loc, scheme } } #[inline] fn domain<'s>(s: &'s str) -> PercentEncode<'s> { @@ -46,9 +46,9 @@ impl<'a> Encode<'a> { } match self.0.scheme { - Scheme::Regular => Ok(()), - Scheme::Search(_) | Scheme::Archive(_) => w!(0, 0), - Scheme::Sftp(_) => w!( + SchemeRef::Regular => Ok(()), + SchemeRef::Search(_) | SchemeRef::Archive(_) => w!(0, 0), + SchemeRef::Sftp(_) => w!( self.0.loc.as_os_str().is_empty().not() as usize, self.0.loc.file_name().is_some() as usize ), @@ -62,11 +62,12 @@ impl<'a> Encode<'a> { impl Display for Encode<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use SchemeRef as S; match self.scheme { - Scheme::Regular => write!(f, "regular://"), - Scheme::Search(d) => write!(f, "search://{}{}/", Self::domain(d), self.ports()), - Scheme::Archive(d) => write!(f, "archive://{}{}/", Self::domain(d), self.ports()), - Scheme::Sftp(d) => write!(f, "sftp://{}{}/", Self::domain(d), self.ports()), + S::Regular => write!(f, "regular://"), + S::Search(d) => write!(f, "search://{}{}/", Self::domain(d), self.ports()), + S::Archive(d) => write!(f, "archive://{}{}/", Self::domain(d), self.ports()), + S::Sftp(d) => write!(f, "sftp://{}{}/", Self::domain(d), self.ports()), } } } @@ -74,15 +75,11 @@ impl Display for Encode<'_> { // --- Tilded pub struct EncodeTilded<'a> { loc: Loc<'a>, - scheme: &'a Scheme, -} - -impl<'a> From<&'a Url<'a>> for EncodeTilded<'a> { - fn from(url: &'a Url<'a>) -> Self { Self { loc: url.loc, scheme: &url.scheme } } + scheme: SchemeRef<'a>, } impl<'a> From<&'a UrlBuf> for EncodeTilded<'a> { - fn from(url: &'a UrlBuf) -> Self { Self { loc: url.loc.as_loc(), scheme: &url.scheme } } + fn from(url: &'a UrlBuf) -> Self { Self { loc: url.loc.as_loc(), scheme: url.scheme.as_ref() } } } impl<'a> From<&EncodeTilded<'a>> for Encode<'a> { @@ -92,15 +89,16 @@ impl<'a> From<&EncodeTilded<'a>> for Encode<'a> { impl Display for EncodeTilded<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use Encode as E; + use SchemeRef as S; let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), CONTROLS); match self.scheme { - Scheme::Regular => write!(f, "regular~://{loc}"), - Scheme::Search(d) => write!(f, "search~://{}{}/{loc}", E::domain(d), E::ports(&self.into())), - Scheme::Archive(d) => { + S::Regular => write!(f, "regular~://{loc}"), + S::Search(d) => write!(f, "search~://{}{}/{loc}", E::domain(d), E::ports(&self.into())), + S::Archive(d) => { write!(f, "archive~://{}{}/{loc}", E::domain(d), E::ports(&self.into())) } - Scheme::Sftp(d) => write!(f, "sftp~://{}{}/{loc}", E::domain(d), E::ports(&self.into())), + S::Sftp(d) => write!(f, "sftp~://{}{}/{loc}", E::domain(d), E::ports(&self.into())), } } } diff --git a/yazi-shared/src/url/mod.rs b/yazi-shared/src/url/mod.rs index c9883db4..212e282c 100644 --- a/yazi-shared/src/url/mod.rs +++ b/yazi-shared/src/url/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(buf component cov cow display encode scheme uri url urn); +yazi_macro::mod_flat!(buf component cov cow display encode uri url urn); diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index fd8bf544..58f253cd 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -2,22 +2,17 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::Path}; use hashbrown::Equivalent; -use crate::{loc::{Loc, LocBuf}, url::{Components, Encode, Scheme, Uri, UrlBuf, Urn}}; +use crate::{loc::{Loc, LocBuf}, scheme::SchemeRef, url::{Components, Encode, Uri, UrlBuf, Urn}}; -#[derive(Clone, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Url<'a> { pub loc: Loc<'a>, - pub scheme: Scheme, -} - -// TODO: remove -impl<'a> From<&'a Url<'a>> for Url<'a> { - fn from(value: &'a Url) -> Self { Self { loc: value.loc.as_loc(), scheme: value.scheme.clone() } } + pub scheme: SchemeRef<'a>, } impl<'a> From<&'a UrlBuf> for Url<'a> { fn from(value: &'a UrlBuf) -> Self { - Self { loc: value.loc.as_loc(), scheme: value.scheme.clone() } + Self { loc: value.loc.as_loc(), scheme: value.scheme.as_ref() } } } @@ -38,10 +33,10 @@ impl Equivalent for Url<'_> { // --- Debug impl Debug for Url<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.scheme == Scheme::Regular { + if self.scheme == SchemeRef::Regular { write!(f, "{}", self.loc.display()) } else { - write!(f, "{}{}", Encode::from(self), self.loc.display()) + write!(f, "{}{}", Encode::from(*self), self.loc.display()) } } } @@ -49,34 +44,31 @@ impl Debug for Url<'_> { impl<'a> Url<'a> { #[inline] pub fn regular + ?Sized>(path: &'a T) -> Self { - Self { loc: path.as_ref().into(), scheme: Scheme::Regular } + Self { loc: path.as_ref().into(), scheme: SchemeRef::Regular } } #[inline] - pub fn is_regular(&self) -> bool { self.scheme == Scheme::Regular } + pub fn is_regular(self) -> bool { self.scheme == SchemeRef::Regular } #[inline] pub fn into_regular(self) -> Self { - Self { loc: self.loc.as_path().into(), scheme: Scheme::Regular } + Self { loc: self.loc.as_path().into(), scheme: SchemeRef::Regular } } #[inline] - pub fn is_search(&self) -> bool { matches!(self.scheme, Scheme::Search(_)) } + pub fn is_search(self) -> bool { matches!(self.scheme, SchemeRef::Search(_)) } #[inline] - pub fn is_absolute(&self) -> bool { self.loc.is_absolute() } + pub fn is_absolute(self) -> bool { self.loc.is_absolute() } #[inline] - pub fn has_root(&self) -> bool { self.loc.has_root() } + pub fn has_root(self) -> bool { self.loc.has_root() } #[inline] - pub fn as_url(&'a self) -> Url<'a> { Self::from(self) } + pub fn to_owned(self) -> UrlBuf { self.into() } - #[inline] - pub fn to_owned(&self) -> UrlBuf { self.into() } - - pub fn join(&self, path: impl AsRef) -> UrlBuf { - use Scheme as S; + pub fn join(self, path: impl AsRef) -> UrlBuf { + use SchemeRef as S; let join = self.loc.join(path); @@ -87,26 +79,26 @@ impl<'a> Url<'a> { S::Sftp(_) => join.into(), }; - UrlBuf { loc, scheme: self.scheme.clone() } + UrlBuf { loc, scheme: self.scheme.into() } } #[inline] - pub fn uri(&self) -> &'a Uri { self.loc.uri() } + pub fn uri(self) -> &'a Uri { self.loc.uri() } #[inline] - pub fn urn(&self) -> &'a Urn { self.loc.urn() } + pub fn urn(self) -> &'a Urn { self.loc.urn() } #[inline] pub fn name(self) -> Option<&'a OsStr> { self.loc.name() } #[inline] - pub fn stem(&self) -> Option<&'a OsStr> { self.loc.stem() } + pub fn stem(self) -> Option<&'a OsStr> { self.loc.stem() } #[inline] - pub fn ext(&self) -> Option<&'a OsStr> { self.loc.ext() } + pub fn ext(self) -> Option<&'a OsStr> { self.loc.ext() } - pub fn base(&self) -> Option { - use Scheme as S; + pub fn base(self) -> Option { + use SchemeRef as S; if !self.loc.has_base() { return None; @@ -115,14 +107,14 @@ impl<'a> Url<'a> { let loc: Loc = self.loc.base().into(); Some(match self.scheme { S::Regular => Self { loc, scheme: S::Regular }, - S::Search(_) => Self { loc, scheme: self.scheme.clone() }, - S::Archive(_) => Self { loc, scheme: self.scheme.clone() }, - S::Sftp(_) => Self { loc, scheme: self.scheme.clone() }, + S::Search(_) => Self { loc, scheme: self.scheme }, + S::Archive(_) => Self { loc, scheme: self.scheme }, + S::Sftp(_) => Self { loc, scheme: self.scheme }, }) } - pub fn parent(&self) -> Option { - use Scheme as S; + pub fn parent(self) -> Option { + use SchemeRef as S; let parent = self.loc.parent()?; let uri = self.loc.uri(); @@ -133,60 +125,57 @@ impl<'a> Url<'a> { // Search S::Search(_) if uri.is_empty() => Self { loc: parent.into(), scheme: S::Regular }, - S::Search(_) => Self { - loc: Loc::new(parent, self.loc.base(), self.loc.base()), - scheme: self.scheme.clone(), - }, + S::Search(_) => { + Self { loc: Loc::new(parent, self.loc.base(), self.loc.base()), scheme: self.scheme } + } // Archive S::Archive(_) if uri.is_empty() => Self { loc: parent.into(), scheme: S::Regular }, S::Archive(_) if uri.nth(1).is_none() => { - Self { loc: Loc::zeroed(parent), scheme: self.scheme.clone() } - } - S::Archive(_) => { - Self { loc: Loc::floated(parent, self.loc.base()), scheme: self.scheme.clone() } + Self { loc: Loc::zeroed(parent), scheme: self.scheme } } + S::Archive(_) => Self { loc: Loc::floated(parent, self.loc.base()), scheme: self.scheme }, // SFTP - S::Sftp(_) => Self { loc: parent.into(), scheme: self.scheme.clone() }, + S::Sftp(_) => Self { loc: parent.into(), scheme: self.scheme }, }) } #[inline] - pub fn starts_with<'b>(&self, base: impl Into>) -> bool { + pub fn starts_with<'b>(self, base: impl Into>) -> bool { let base: Url = base.into(); - self.scheme.covariant(&base.scheme) && self.loc.starts_with(base.loc) + self.scheme.covariant(base.scheme) && self.loc.starts_with(base.loc) } #[inline] - pub fn ends_with<'b>(&self, child: impl Into>) -> bool { + pub fn ends_with<'b>(self, child: impl Into>) -> bool { let child: Url = child.into(); - self.scheme.covariant(&child.scheme) && self.loc.ends_with(child.loc) + self.scheme.covariant(child.scheme) && self.loc.ends_with(child.loc) } #[inline] - pub fn components(&self) -> Components<'_> { Components::from(self) } + pub fn components(self) -> Components<'a> { Components::from(self) } #[inline] - pub fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() } + pub fn os_str(self) -> Cow<'a, OsStr> { self.components().os_str() } #[inline] - pub fn covariant(&self, other: impl Into) -> bool { + pub fn covariant(self, other: impl Into) -> bool { let other = other.into(); - self.scheme.covariant(&other.scheme) && self.loc == other.loc + self.scheme.covariant(other.scheme) && self.loc == other.loc } #[inline] - pub fn pair(&self) -> Option<(Url<'a>, &'a Urn)> { Some((self.parent()?, self.loc.urn())) } + pub fn pair(self) -> Option<(Url<'a>, &'a Urn)> { Some((self.parent()?, self.loc.urn())) } #[inline] - pub fn as_path(&self) -> Option<&'a Path> { + pub fn as_path(self) -> Option<&'a Path> { Some(self.loc.as_path()).filter(|_| !self.scheme.is_virtual()) } #[inline] - pub fn has_base(&self) -> bool { self.loc.has_base() } + pub fn has_base(self) -> bool { self.loc.has_base() } #[inline] - pub fn has_trail(&self) -> bool { self.loc.has_trail() } + pub fn has_trail(self) -> bool { self.loc.has_trail() } } diff --git a/yazi-watcher/src/backend/backend.rs b/yazi-watcher/src/backend/backend.rs index f71715e3..81067291 100644 --- a/yazi-watcher/src/backend/backend.rs +++ b/yazi-watcher/src/backend/backend.rs @@ -54,8 +54,8 @@ impl Backend { let Some(parent) = url.parent() else { continue }; if todo.contains(&parent) { todo.insert(url); - } else if watched.contains(&parent) - || LINKED.read().from_dir(&parent).any(|p| watched.contains(Url::regular(p))) + } else if watched.contains(parent) + || LINKED.read().from_dir(parent).any(|p| watched.contains(Url::regular(p))) { todo.insert(parent.to_owned()); todo.insert(url);