perf: zero-copy UrlBuf to Url conversion (#3117)

This commit is contained in:
三咲雅 misaki masa 2025-08-28 22:55:33 +08:00 committed by sxyazi
parent dd7afaa64a
commit 8a0265ad64
No known key found for this signature in database
30 changed files with 442 additions and 299 deletions

68
Cargo.lock generated
View file

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

View file

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

View file

@ -1,13 +1,13 @@
<div align="center">
<sup>Special thanks to:</sup>
<br><br>
<a href="https://www.warp.dev/?utm_source=github&utm_medium=referral&utm_campaign=yazi" target="_blank">
<img alt="Warp sponsorship" width="300" src="https://github.com/user-attachments/assets/c7f141e7-9751-407d-bb0e-d6f2c487b34f">
<br>
<b>Warp, the intelligent terminal</b>
<br>
<sup>Yazi's AI-powered terminal of choice!<br>Available for macOS, Linux and Windows</sup>
</a>
<a href="https://go.warp.dev/yazi" target="_blank">
<sup>Special thanks to:</sup>
<br>
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png">
<br>
<h>Warp, built for coding with multiple AI agents</b>
<br>
<sup>Available for macOS, Linux and Windows</sup>
</a>
</div>
<br>
@ -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.

12
flake.lock generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Url> for yazi_shared::url::UrlBufCov {

View file

@ -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<SchemeRef<'a>>) -> bool {
self.0.is_none_or(|s| s == scheme.into().kind())
}
}
// --- Tests

View file

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

View file

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

View file

@ -8,10 +8,7 @@ pub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> 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) {

View file

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

View file

@ -8,8 +8,8 @@ pub fn path_relative_to<'a>(
to: &'a impl AsRef<Path>,
) -> Result<Cow<'a, Path>> {
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<Ur
}
if from.covariant(&to) {
return Ok(UrlBuf { loc: LocBuf::zeroed("."), scheme: to.scheme().clone() }.into());
return Ok(UrlBuf { loc: LocBuf::zeroed("."), scheme: to.scheme().into() }.into());
}
let (mut f_it, mut t_it) = (from.components(), to.components());
@ -47,5 +47,5 @@ pub(super) fn url_relative_to<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result<Ur
let rest = t_head.into_iter().chain(t_it);
let buf: PathBuf = dots.chain(rest).collect();
Ok(UrlBuf { loc: LocBuf::zeroed(buf), scheme: to.scheme().clone() }.into())
Ok(UrlBuf { loc: LocBuf::zeroed(buf), scheme: to.scheme().into() }.into())
}

View file

@ -77,7 +77,7 @@ impl File {
};
let src = if task.relative {
path_relative_to(provider::canonicalize(&task.to.parent().unwrap()).await?.loc, &src)?
path_relative_to(provider::canonicalize(task.to.parent().unwrap()).await?.loc, &src)?
} else {
src
};

View file

@ -1,6 +1,6 @@
#![allow(clippy::option_map_unit_fn)]
yazi_macro::mod_pub!(errors event loc pool shell translit url);
yazi_macro::mod_pub!(errors event loc pool shell translit url scheme);
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os osstr rand ro_cell source string sync_cell terminal tests throttle time utf8);

View file

@ -78,6 +78,14 @@ impl<T: ?Sized> PartialEq for Symbol<T> {
impl<T: ?Sized> Eq for Symbol<T> {}
impl PartialEq<str> for Symbol<str> {
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<T: ?Sized> Hash for Symbol<T> {
fn hash<H: Hasher>(&self, state: &mut H) { self.ptr.as_ptr().hash(state); }

View file

@ -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<str>),
Archive(Symbol<str>),
Sftp(Symbol<str>),
#[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<Scheme> 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<SchemeRef<'a>> for SchemeCow<'a> {
fn from(value: SchemeRef<'a>) -> Self { Self::Borrowed(value) }
}
impl From<SchemeCow<'_>> 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<Cow<'a, str>>) -> 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<Cow<'a, str>>) -> 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<Cow<'a, str>>) -> 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<usize>, Option<usize>)> {
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<str>, Option<usize>, Option<usize>)> {
) -> Result<(Cow<'a, str>, Option<usize>, Option<usize>)> {
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<usize>,
urn: Option<usize>,
path: &Path,
) -> Result<Option<(usize, usize)>> {
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<usize>, urn: Option<usize>) -> 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(())
}

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(cow r#ref scheme);

View file

@ -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<SchemeRef<'_>> 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<Self>) -> 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,
}
}
}

View file

@ -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<str>),
Archive(Symbol<str>),
Sftp(Symbol<str>),
}
impl Hash for Scheme {
fn hash<H: Hasher>(&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() }
}

View file

@ -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<PathBuf> 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<Url<'_>> 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<Url<'a>>) -> 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,
}?))
}

View file

@ -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<Component<'a>> 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<Component<'a>> 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<Url<'a>> 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> {

View file

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

View file

@ -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<Url<'a>> 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<UrlBuf> 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<UrlBuf> 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()?

View file

@ -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<Url<'a>> 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())),
}
}
}

View file

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

View file

@ -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<UrlBuf> 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<T: AsRef<Path> + ?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<Path>) -> UrlBuf {
use Scheme as S;
pub fn join(self, path: impl AsRef<Path>) -> 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<Self> {
use Scheme as S;
pub fn base(self) -> Option<Self> {
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<Self> {
use Scheme as S;
pub fn parent(self) -> Option<Self> {
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<Url<'b>>) -> bool {
pub fn starts_with<'b>(self, base: impl Into<Url<'b>>) -> 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<Url<'b>>) -> bool {
pub fn ends_with<'b>(self, child: impl Into<Url<'b>>) -> 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<Self>) -> bool {
pub fn covariant(self, other: impl Into<Self>) -> 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() }
}

View file

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