From e5ee31cb7e4afbfba953a7e557a62c1070b84269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Mon, 24 Nov 2025 11:35:10 +0800 Subject: [PATCH] feat: new Unix path backend (#3359) --- Cargo.lock | 67 ++-- Cargo.toml | 6 +- yazi-actor/src/cmp/trigger.rs | 4 +- yazi-actor/src/lives/file.rs | 4 +- yazi-actor/src/mgr/bulk_rename.rs | 18 +- yazi-actor/src/mgr/copy.rs | 8 +- yazi-actor/src/mgr/quit.rs | 17 +- yazi-actor/src/mgr/search.rs | 2 +- yazi-actor/src/mgr/shell.rs | 2 +- yazi-adapter/src/drivers/sixel.rs | 2 +- yazi-binding/src/elements/line.rs | 2 +- yazi-binding/src/error.rs | 2 +- yazi-binding/src/path.rs | 111 ++++++- yazi-binding/src/url.rs | 144 ++++---- yazi-codegen/Cargo.toml | 2 +- yazi-config/src/popup/options.rs | 24 +- yazi-dds/src/sendable.rs | 10 +- yazi-fm/src/app/commands/plugin.rs | 4 +- yazi-fm/src/app/commands/quit.rs | 12 +- yazi-fs/Cargo.toml | 1 + yazi-fs/src/path/clean.rs | 34 +- yazi-fs/src/path/expand.rs | 7 +- yazi-fs/src/path/path.rs | 10 +- yazi-fs/src/path/percent.rs | 2 + yazi-fs/src/path/relative.rs | 12 +- yazi-fs/src/provider/local/local.rs | 2 +- yazi-fs/src/sorter.rs | 7 +- yazi-fs/src/url.rs | 15 +- yazi-plugin/src/fs/op.rs | 2 +- yazi-plugin/src/isolate/peek.rs | 2 +- yazi-plugin/src/isolate/seek.rs | 2 +- yazi-plugin/src/process/command.rs | 10 +- yazi-plugin/src/runtime/plugin.rs | 8 +- yazi-plugin/src/utils/text.rs | 2 +- yazi-shared/Cargo.toml | 1 + yazi-shared/src/data/data.rs | 6 +- yazi-shared/src/data/key.rs | 2 +- yazi-shared/src/event/event.rs | 6 +- yazi-shared/src/lib.rs | 2 +- yazi-shared/src/osstr.rs | 60 ---- yazi-shared/src/path/buf.rs | 63 +++- yazi-shared/src/path/component.rs | 75 +++++ yazi-shared/src/path/components.rs | 49 +++ yazi-shared/src/path/conversion.rs | 19 +- yazi-shared/src/path/cow.rs | 20 +- yazi-shared/src/path/display.rs | 12 + yazi-shared/src/path/error.rs | 30 +- yazi-shared/src/path/kind.rs | 1 + yazi-shared/src/path/like.rs | 6 +- yazi-shared/src/path/mod.rs | 2 +- yazi-shared/src/path/path.rs | 79 +++-- yazi-shared/src/path/view.rs | 27 -- yazi-shared/src/rand.rs | 27 -- yazi-shared/src/scheme/cow.rs | 11 +- yazi-shared/src/scheme/scheme.rs | 10 + yazi-shared/src/strand/buf.rs | 86 ++++- yazi-shared/src/strand/conversion.rs | 47 ++- yazi-shared/src/strand/cow.rs | 36 +- yazi-shared/src/strand/error.rs | 16 +- yazi-shared/src/strand/extensions.rs | 61 ++++ yazi-shared/src/strand/kind.rs | 29 +- yazi-shared/src/strand/mod.rs | 2 +- yazi-shared/src/strand/{dyn.rs => strand.rs} | 64 +++- yazi-shared/src/strand/view.rs | 26 -- yazi-shared/src/string.rs | 37 --- yazi-shared/src/url/buf.rs | 22 +- yazi-shared/src/url/component.rs | 202 +++--------- yazi-shared/src/url/components.rs | 196 +++++++++++ yazi-shared/src/url/display.rs | 14 +- yazi-shared/src/url/encode.rs | 1 - yazi-shared/src/url/like.rs | 75 +++++ yazi-shared/src/url/mod.rs | 2 +- yazi-shared/src/url/traits.rs | 91 +---- yazi-shared/src/url/url.rs | 329 ++++++++++--------- yazi-vfs/src/fns.rs | 6 +- yazi-vfs/src/provider/sftp/sftp.rs | 2 +- 76 files changed, 1430 insertions(+), 979 deletions(-) delete mode 100644 yazi-shared/src/osstr.rs create mode 100644 yazi-shared/src/path/display.rs delete mode 100644 yazi-shared/src/rand.rs create mode 100644 yazi-shared/src/strand/extensions.rs rename yazi-shared/src/strand/{dyn.rs => strand.rs} (74%) delete mode 100644 yazi-shared/src/string.rs create mode 100644 yazi-shared/src/url/components.rs create mode 100644 yazi-shared/src/url/like.rs diff --git a/Cargo.lock b/Cargo.lock index 6eaccab4..184ceeea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,9 +505,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.46" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -1544,14 +1544,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] @@ -1712,12 +1713,12 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -2040,7 +2041,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -3468,9 +3469,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -3633,9 +3634,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -3961,6 +3962,12 @@ dependencies = [ "rand 0.9.2", ] +[[package]] +name = "typed-path" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7922f2cdc51280d47b491af9eafc41eb0cdab85eabcb390c854412fcbf26dbe8" + [[package]] name = "typeid" version = "1.0.3" @@ -4645,7 +4652,7 @@ dependencies = [ "anyhow", "crossterm 0.29.0", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "libc", "mlua", "paste", @@ -4698,7 +4705,7 @@ name = "yazi-binding" version = "25.9.15" dependencies = [ "ansi-to-tui", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "mlua", "paste", "ratatui", @@ -4722,7 +4729,7 @@ dependencies = [ "clap_complete_fig", "clap_complete_nushell", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "regex", "serde", "vergen-gitcl", @@ -4781,7 +4788,7 @@ dependencies = [ "crossterm 0.29.0", "dirs", "globset", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "indexmap", "ratatui", "regex", @@ -4802,7 +4809,7 @@ version = "25.9.15" dependencies = [ "anyhow", "crossterm 0.29.0", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "indexmap", "parking_lot", "ratatui", @@ -4831,7 +4838,7 @@ name = "yazi-dds" version = "25.9.15" dependencies = [ "anyhow", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "mlua", "ordered-float 5.1.0", "parking_lot", @@ -4913,11 +4920,12 @@ dependencies = [ "core-foundation-sys", "dirs", "foldhash 0.2.0", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "libc", "objc2", "parking_lot", "percent-encoding", + "rand 0.9.2", "regex", "scopeguard", "serde", @@ -4947,7 +4955,7 @@ dependencies = [ "anyhow", "bitflags 2.10.0", "crossterm 0.29.0", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "mlua", "ordered-float 5.1.0", "serde", @@ -4967,7 +4975,7 @@ dependencies = [ "anyhow", "crossterm 0.29.0", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "libc", "mlua", "parking_lot", @@ -5025,7 +5033,7 @@ dependencies = [ "async-priority-channel", "foldhash 0.2.0", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "libc", "lru 0.16.2", "mlua", @@ -5066,7 +5074,7 @@ dependencies = [ "crossterm 0.29.0", "foldhash 0.2.0", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "libc", "memchr", "ordered-float 5.1.0", @@ -5075,6 +5083,7 @@ dependencies = [ "serde", "thiserror 2.0.17", "tokio", + "typed-path", "uzers", "windows-sys 0.61.2", "yazi-macro", @@ -5108,7 +5117,7 @@ dependencies = [ "anyhow", "deadpool", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "parking_lot", "russh", "tokio", @@ -5125,7 +5134,7 @@ name = "yazi-watcher" version = "25.9.15" dependencies = [ "anyhow", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "notify", "parking_lot", "percent-encoding", @@ -5164,18 +5173,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 09e0246c..5b25ac8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,8 @@ dirs = "6.0.0" foldhash = "0.2.0" futures = "0.3.31" globset = "0.4.18" -hashbrown = { version = "0.16.0", features = [ "serde" ] } -indexmap = { version = "2.12.0", features = [ "serde" ] } +hashbrown = { version = "0.16.1", features = [ "serde" ] } +indexmap = { version = "2.12.1", features = [ "serde" ] } libc = "0.2.177" lru = "0.16.2" mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } @@ -41,6 +41,7 @@ ordered-float = { version = "5.1.0", features = [ "serde" ] } parking_lot = "0.12.5" paste = "1.0.15" percent-encoding = "2.3.2" +rand = { version = "0.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] } ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } regex = "1.12.2" russh = { version = "0.54.6", default-features = false, features = [ "ring", "rsa" ] } @@ -55,6 +56,7 @@ tokio-util = "0.7.17" toml = { version = "0.9.8" } tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] } twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] } +typed-path = "0.12.0" unicode-width = { version = "0.2.0", default-features = false } uzers = "0.12.1" diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index c092099f..c2f36b73 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -81,7 +81,7 @@ impl Trigger { Some(match path.rsplit_pred(sep) { Some((p, c)) if p.is_empty() => { - let root = PathDyn::with(scheme.kind(), MAIN_SEPARATOR_STR).expect("valid root"); + let root = PathDyn::with_str(scheme.kind(), MAIN_SEPARATOR_STR); (UrlCow::try_from((scheme, root)).ok()?.into_owned(), c.into()) } Some((p, c)) => (expand_url(UrlCow::try_from((scheme, p)).ok()?), c.into()), @@ -99,7 +99,7 @@ mod tests { fn compare(s: &str, parent: &str, child: &str) { let (mut p, c) = Trigger::split_url(s).unwrap(); if let Ok(u) = p.try_strip_prefix(yazi_fs::CWD.load().as_ref()) { - p = UrlBuf::from(u); + p = UrlBuf::Regular(u.as_os().unwrap().into()); } assert_eq!((p, c.to_str().unwrap()), (parent.parse().unwrap(), child)); } diff --git a/yazi-actor/src/lives/file.rs b/yazi-actor/src/lives/file.rs index ac978929..d3c22917 100644 --- a/yazi-actor/src/lives/file.rs +++ b/yazi-actor/src/lives/file.rs @@ -4,7 +4,7 @@ use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Valu use yazi_binding::Style; use yazi_config::THEME; use yazi_plugin::bindings::Range; -use yazi_shared::url::UrlLike; +use yazi_shared::{path::AsPath, url::UrlLike}; use super::Lives; use crate::lives::PtrCell; @@ -91,7 +91,7 @@ impl UserData for File { let mut comp = me.url.try_strip_prefix(me.url.trail()).unwrap_or(me.url.loc()).components(); comp.next_back(); - Some(lua.create_string(comp.as_path().as_os_str().as_encoded_bytes())).transpose() + Some(lua.create_string(comp.as_path().encoded_bytes())).transpose() }); methods.add_method("style", |lua, me, ()| { lua.named_registry_value::("cx")?.borrow_scoped(|core: &yazi_core::Core| { diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index 2742c867..c8308417 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -1,4 +1,4 @@ -use std::{ffi::{OsStr, OsString}, hash::Hash, io::{Read, Write}, ops::Deref, path::Path}; +use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path}; use anyhow::{Result, anyhow}; use crossterm::{execute, style::Print}; @@ -11,7 +11,7 @@ use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider use yazi_macro::{err, succ}; use yazi_parser::VoidOpt; use yazi_proxy::{AppProxy, HIDER, TasksProxy}; -use yazi_shared::{OsStrJoin, data::Data, path::PathDyn, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; +use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_term::tty::TTY; use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_watcher::WATCHER; @@ -47,7 +47,7 @@ impl Actor for BulkRename { .create_new(true) .open(&tmp) .await? - .write_all(old.join(OsStr::new("\n")).as_encoded_bytes()) + .write_all(old.join(Strand::Utf8("\n")).encoded_bytes()) .await?; defer! { @@ -160,7 +160,7 @@ impl BulkRename { YAZI.opener.block(YAZI.open.all(Path::new("bulk-rename.txt"), "text/plain")) } - fn replace_url(url: &UrlBuf, take: usize, rep: &OsStr) -> Result { + fn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result { Ok(url.try_replace(take, PathDyn::with(url.kind(), rep)?)?.into_owned()) } @@ -220,10 +220,10 @@ impl BulkRename { // --- Tuple #[derive(Clone, Debug)] -struct Tuple(usize, OsString); +struct Tuple(usize, StrandBuf); impl Deref for Tuple { - type Target = OsStr; + type Target = StrandBuf; fn deref(&self) -> &Self::Target { &self.1 } } @@ -238,12 +238,12 @@ impl Hash for Tuple { fn hash(&self, state: &mut H) { self.1.hash(state); } } -impl AsRef for Tuple { - fn as_ref(&self) -> &OsStr { &self.1 } +impl AsStrand for &Tuple { + fn as_strand(&self) -> Strand<'_> { self.1.as_strand() } } impl Tuple { - fn new(index: usize, inner: impl Into) -> Self { Self(index, inner.into()) } + fn new(index: usize, inner: impl Into) -> Self { Self(index, inner.into()) } } // --- Tests diff --git a/yazi-actor/src/mgr/copy.rs b/yazi-actor/src/mgr/copy.rs index 88402c93..0ccf4ff9 100644 --- a/yazi-actor/src/mgr/copy.rs +++ b/yazi-actor/src/mgr/copy.rs @@ -1,7 +1,7 @@ use anyhow::{Result, bail}; use yazi_macro::{act, succ}; use yazi_parser::mgr::CopyOpt; -use yazi_shared::{data::Data, url::UrlLike}; +use yazi_shared::{data::Data, strand::ToStrand, url::UrlLike}; use yazi_widgets::CLIPBOARD; use crate::{Actor, Ctx}; @@ -28,11 +28,11 @@ impl Actor for Copy { match opt.r#type.as_ref() { // TODO: rename to "url" "path" => { - s.extend_from_slice(&opt.separator.transform(&u.os_str())); + s.extend_from_slice(&opt.separator.transform(&u.to_strand())); } "dirname" => { if let Some(p) = u.parent() { - s.extend_from_slice(&opt.separator.transform(&p.os_str())); + s.extend_from_slice(&opt.separator.transform(&p.to_strand())); } } "filename" => { @@ -50,7 +50,7 @@ impl Actor for Copy { // Copy the CWD path regardless even if the directory is empty if s.is_empty() && opt.r#type == "dirname" { - s.extend_from_slice(&opt.separator.transform(&cx.cwd().os_str())); + s.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand())); } futures::executor::block_on(CLIPBOARD.set(s)); diff --git a/yazi-actor/src/mgr/quit.rs b/yazi-actor/src/mgr/quit.rs index 9eb28e5a..3014c7ef 100644 --- a/yazi-actor/src/mgr/quit.rs +++ b/yazi-actor/src/mgr/quit.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, time::Duration}; +use std::time::Duration; use anyhow::Result; use tokio::{select, time}; @@ -7,7 +7,7 @@ use yazi_dds::spark::SparkKind; use yazi_macro::{emit, succ}; use yazi_parser::mgr::QuitOpt; use yazi_proxy::ConfirmProxy; -use yazi_shared::{data::Data, event::EventQuit, url::{AsUrl, UrlCow}}; +use yazi_shared::{data::Data, event::EventQuit, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl}; use crate::{Actor, Ctx}; @@ -66,17 +66,12 @@ impl Actor for Quit { } impl Quit { - pub(super) fn with_selected<'a, I, T>(selected: I) + pub(super) fn with_selected(selected: I) where - I: IntoIterator, - T: Into>, + I: IntoIterator, + I::Item: AsUrl, { - let paths = selected.into_iter().fold(OsString::new(), |mut s, u| { - s.push(u.into().as_url().os_str()); - s.push("\n"); - s - }); - + let paths = selected.into_iter().join(Strand::Utf8("\n")); if !paths.is_empty() { emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() })); } diff --git a/yazi-actor/src/mgr/search.rs b/yazi-actor/src/mgr/search.rs index 44b7571b..33850f03 100644 --- a/yazi-actor/src/mgr/search.rs +++ b/yazi-actor/src/mgr/search.rs @@ -52,7 +52,7 @@ impl Actor for SearchDo { handle.abort(); } - let cwd = tab.cwd().to_search(opt.subject.as_ref())?; + let cwd = tab.cwd().to_search(&opt.subject)?; let hidden = tab.pref.show_hidden; tab.search = Some(tokio::spawn(async move { diff --git a/yazi-actor/src/mgr/shell.rs b/yazi-actor/src/mgr/shell.rs index 722788d1..2d1a0437 100644 --- a/yazi-actor/src/mgr/shell.rs +++ b/yazi-actor/src/mgr/shell.rs @@ -40,7 +40,7 @@ impl Actor for Shell { TasksProxy::open_shell_compat(ProcessOpenOpt { cwd: cwd.into(), - cmd: Splatter::new(&selected).splat(opt.run.as_ref()), + cmd: Splatter::new(&selected).splat(&*opt.run), args: selected, block: opt.block, orphan: opt.orphan, diff --git a/yazi-adapter/src/drivers/sixel.rs b/yazi-adapter/src/drivers/sixel.rs index 8c80d7e8..bcb4bfd8 100644 --- a/yazi-adapter/src/drivers/sixel.rs +++ b/yazi-adapter/src/drivers/sixel.rs @@ -55,7 +55,7 @@ impl Sixel { tokio::task::spawn_blocking(move || { let mut buf = vec![]; - write!(buf, "{START}P0;1;8q\"1;1;{};{}", img.width(), img.height())?; + write!(buf, "{START}P9;1q\"1;1;{};{}", img.width(), img.height())?; // Palette for (i, c) in qo.palette.iter().enumerate() { diff --git a/yazi-binding/src/elements/line.rs b/yazi-binding/src/elements/line.rs index 69df7adb..63e8a623 100644 --- a/yazi-binding/src/elements/line.rs +++ b/yazi-binding/src/elements/line.rs @@ -227,7 +227,7 @@ mod tests { .call(()) .unwrap(); - line.spans.iter().map(|s| s.content.as_ref()).collect() + line.spans.iter().map(|s| &*s.content).collect() } #[test] diff --git a/yazi-binding/src/error.rs b/yazi-binding/src/error.rs index 382326f9..eef599a3 100644 --- a/yazi-binding/src/error.rs +++ b/yazi-binding/src/error.rs @@ -66,7 +66,7 @@ impl UserData for Error { methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| { Ok(match me { Self::Io(_) | Self::Fs(_) | Self::Serde(_) => lua.create_string(me.to_string()), - Self::Custom(s) => lua.create_string(s.as_ref()), + Self::Custom(s) => lua.create_string(&**s), }) }); methods.add_meta_function(MetaMethod::Concat, |lua, (lhs, rhs): (Value, Value)| { diff --git a/yazi-binding/src/path.rs b/yazi-binding/src/path.rs index 03288668..a18dbd5e 100644 --- a/yazi-binding/src/path.rs +++ b/yazi-binding/src/path.rs @@ -1,18 +1,97 @@ use std::ops::Deref; -use mlua::{ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataMethods, Value}; -use yazi_shared::path::{PathBufDyn, PathLike}; +use mlua::{ExternalError, ExternalResult, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; +use yazi_shared::{path::{PathBufDyn, PathLike, StripPrefixError}, strand::{AsStrand, Strand, StrandCow}}; -pub struct Path(pub PathBufDyn); +use crate::cached_field; + +pub type PathRef = UserDataRef; + +pub struct Path { + inner: PathBufDyn, + + v_ext: Option, + v_name: Option, + v_parent: Option, + v_stem: Option, +} impl Deref for Path { type Target = PathBufDyn; - fn deref(&self) -> &Self::Target { &self.0 } + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl From for PathBufDyn { + fn from(value: Path) -> Self { value.inner } +} + +impl AsStrand for Path { + fn as_strand(&self) -> Strand<'_> { self.inner.as_strand() } +} + +impl AsStrand for &Path { + fn as_strand(&self) -> Strand<'_> { self.inner.as_strand() } } impl Path { - pub fn new(urn: impl Into) -> Self { Self(urn.into()) } + pub fn new(path: impl Into) -> Self { + Self { + inner: path.into(), + + v_ext: None, + v_name: None, + v_parent: None, + v_stem: None, + } + } + + fn ends_with(&self, child: Value) -> mlua::Result { + match child { + Value::String(s) => { + self.try_ends_with(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err() + } + Value::UserData(ud) => self.try_ends_with(&*ud.borrow::()?).into_lua_err(), + _ => Err("must be a string or Path".into_lua_err())?, + } + } + + fn join(&self, other: Value) -> mlua::Result { + Ok(Self::new(match other { + Value::String(s) => { + self.try_join(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()? + } + Value::UserData(ref ud) => { + let path = ud.borrow::()?; + self.try_join(&*path).into_lua_err()? + } + _ => Err("must be a string or Path".into_lua_err())?, + })) + } + + fn starts_with(&self, base: Value) -> mlua::Result { + match base { + Value::String(s) => { + self.try_starts_with(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err() + } + Value::UserData(ud) => self.try_starts_with(&*ud.borrow::()?).into_lua_err(), + _ => Err("must be a string or Path".into_lua_err())?, + } + } + + fn strip_prefix(&self, base: Value) -> mlua::Result> { + let strip = match base { + Value::String(s) => self.try_strip_prefix(StrandCow::with(self.kind(), &*s.as_bytes())?), + Value::UserData(ud) => self.try_strip_prefix(&*ud.borrow::()?), + _ => Err("must be a string or Path".into_lua_err())?, + }; + + Ok(match strip { + Ok(p) => Some(Path::new(p)), + Err(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None, + Err(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?, + }) + } } impl FromLua for Path { @@ -25,7 +104,29 @@ impl FromLua for Path { } impl UserData for Path { + fn add_fields>(fields: &mut F) { + cached_field!(fields, ext, |lua, me| { + me.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose() + }); + cached_field!(fields, name, |lua, me| { + me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose() + }); + cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new))); + cached_field!(fields, stem, |lua, me| { + me.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose() + }); + } + fn add_methods>(methods: &mut M) { + methods.add_method("ends_with", |_, me, child: Value| me.ends_with(child)); + methods.add_method("join", |_, me, other: Value| me.join(other)); + methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base)); + methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base)); + + methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| { + lua.create_string([lhs.encoded_bytes(), &rhs.as_bytes()].concat()) + }); + methods.add_meta_method(MetaMethod::Eq, |_, me, other: PathRef| Ok(me.inner == other.inner)); methods .add_meta_method(MetaMethod::ToString, |lua, me, ()| lua.create_string(me.encoded_bytes())); } diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index 7577d8b0..f787c7f3 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -1,16 +1,17 @@ use std::ops::Deref; -use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; +use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use yazi_fs::{FsHash64, FsHash128}; -use yazi_shared::{path::StripPrefixError, strand::StrandCow, url::{AsUrl, UrlCow, UrlLike}}; +use yazi_shared::{path::StripPrefixError, scheme::SchemeCow, strand::{StrandLike, ToStrand}, url::{AsUrl, UrlCow, UrlLike}}; -use crate::{Scheme, cached_field, deprecate}; +use crate::{Path, Scheme, cached_field, deprecate}; pub type UrlRef = UserDataRef; pub struct Url { inner: yazi_shared::url::UrlBuf, + v_path: Option, v_name: Option, v_stem: Option, v_ext: Option, @@ -61,6 +62,7 @@ impl Url { Self { inner: url.into(), + v_path: None, v_name: None, v_stem: None, v_ext: None, @@ -78,13 +80,74 @@ impl Url { "Url", lua.create_function(|_, value: Value| { Ok(match value { - Value::String(s) => Self::try_from(s.as_bytes().as_ref())?, - Value::UserData(ud) => Self::new(ud.borrow::()?.inner.clone()), + Value::String(s) => Self::try_from(&*s.as_bytes())?, + Value::UserData(ud) => Self::new(&ud.borrow::()?.inner), _ => Err("Expected a string or a Url".into_lua_err())?, }) })?, ) } + + fn ends_with(&self, child: Value) -> mlua::Result { + match child { + Value::String(s) => self.try_ends_with(UrlCow::try_from(&*s.as_bytes())?).into_lua_err(), + Value::UserData(ud) => self.try_ends_with(&*ud.borrow::()?).into_lua_err(), + _ => Err("must be a string or Url".into_lua_err())?, + } + } + + fn hash(&self, long: Option) -> mlua::Result { + Ok(if long.unwrap_or(false) { + format!("{:x}", self.hash_u128()) + } else { + format!("{:x}", self.hash_u64()) + }) + } + + fn join(&self, lua: &Lua, other: Value) -> mlua::Result { + match other { + Value::String(s) => { + let b = s.as_bytes(); + let (scheme, path) = SchemeCow::parse(&*b)?; + if scheme == self.scheme() { + Self::new(self.try_join(path).into_lua_err()?).into_lua(lua) + } else { + Self::new(UrlCow::try_from((scheme, path))?).into_lua(lua) + } + } + Value::UserData(ref ud) => { + let url = ud.borrow::()?; + if url.scheme() == self.scheme() { + Self::new(self.try_join(url.loc()).into_lua_err()?).into_lua(lua) + } else { + Ok(other) + } + } + _ => Err("must be a string or Url".into_lua_err())?, + } + } + + fn starts_with(&self, base: Value) -> mlua::Result { + match base { + Value::String(s) => self.try_starts_with(UrlCow::try_from(&*s.as_bytes())?).into_lua_err(), + Value::UserData(ud) => self.try_starts_with(&*ud.borrow::()?).into_lua_err(), + _ => Err("must be a string or Url".into_lua_err())?, + } + } + + fn strip_prefix(&self, base: Value) -> mlua::Result> { + let strip = match base { + Value::String(s) => self.try_strip_prefix(UrlCow::try_from(&*s.as_bytes())?), + Value::UserData(ud) => self.try_strip_prefix(&*ud.borrow::()?), + _ => Err("must be a string or Url".into_lua_err())?, + }; + + Ok(match strip { + Ok(p) => Some(Path::new(p)), + Err(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None, + Err(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?, + }) + } } impl FromLua for Url { @@ -98,6 +161,7 @@ impl FromLua for Url { impl UserData for Url { fn add_fields>(fields: &mut F) { + cached_field!(fields, path, |_, me| Ok(Path::new(me.loc()))); cached_field!(fields, name, |lua, me| { me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose() }); @@ -108,7 +172,7 @@ impl UserData for Url { me.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose() }); cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new))); - cached_field!(fields, urn, |_, me| Ok(super::Path::new(me.urn()))); + cached_field!(fields, urn, |_, me| Ok(Path::new(me.urn()))); cached_field!(fields, base, |_, me| { Ok(Some(me.base()).filter(|u| !u.loc().is_empty()).map(Self::new)) }); @@ -131,65 +195,11 @@ impl UserData for Url { } fn add_methods>(methods: &mut M) { - methods.add_method("hash", |_, me, long: Option| { - Ok(if long.unwrap_or(false) { - format!("{:x}", me.hash_u128()) - } else { - format!("{:x}", me.hash_u64()) - }) - }); - methods.add_method("join", |_, me, other: Value| { - Ok(Self::new(match other { - Value::String(s) => { - let b = s.as_bytes(); - me.try_join(StrandCow::with(me.kind(), &*b).into_lua_err()?).into_lua_err()? - } - Value::UserData(ud) => { - let url = ud.borrow::()?; - if !me.scheme().covariant(url.scheme()) { - return Err("cannot join Urls with different schemes".into_lua_err()); - } - me.try_join(url.loc()).into_lua_err()? - } - _ => Err("must be a string or Url".into_lua_err())?, - })) - }); - methods.add_method("starts_with", |_, me, base: Value| { - Ok(match base { - Value::String(s) => { - let b = s.as_bytes(); - me.loc().try_starts_with(&StrandCow::with(me.kind(), &*b).into_lua_err()?).into_lua_err() - } - Value::UserData(ud) => me.try_starts_with(&*ud.borrow::()?).into_lua_err(), - _ => Err("must be a string or Url".into_lua_err())?, - }) - }); - methods.add_method("ends_with", |_, me, child: Value| { - Ok(match child { - Value::String(s) => { - let b = s.as_bytes(); - me.loc().try_ends_with(&StrandCow::with(me.kind(), &*b).into_lua_err()?).into_lua_err() - } - Value::UserData(ud) => me.try_ends_with(&*ud.borrow::()?).into_lua_err(), - _ => Err("must be a string or Url".into_lua_err())?, - }) - }); - methods.add_method("strip_prefix", |_, me, base: Value| { - let strip = match base { - Value::String(s) => { - let b = s.as_bytes(); - me.loc().try_strip_prefix(&StrandCow::with(me.kind(), &*b).into_lua_err()?) - } - Value::UserData(ud) => me.try_strip_prefix(&*ud.borrow::()?), - _ => Err("must be a string or Url".into_lua_err())?, - }; - - Ok(match strip { - Ok(p) => Some(Self::new(p)), // TODO: return `Path` instead of `Url` - Err(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None, - Err(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?, - }) - }); + methods.add_method("ends_with", |_, me, child: Value| me.ends_with(child)); + methods.add_method("hash", |_, me, long: Option| me.hash(long)); + methods.add_method("join", |lua, me, other: Value| me.join(lua, other)); + methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base)); + methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base)); methods.add_function_mut("into_search", |_, (ud, domain): (AnyUserData, mlua::String)| { let url = ud.take::()?.inner.into_search(domain.to_str()?).into_lua_err()?; @@ -198,10 +208,10 @@ impl UserData for Url { methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner)); methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| { - lua.create_string(me.os_str().as_encoded_bytes()) + lua.create_string(me.to_strand().encoded_bytes()) }); methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| { - lua.create_string([lhs.os_str().as_encoded_bytes(), &rhs.as_bytes()].concat()) + lua.create_string([lhs.to_strand().encoded_bytes(), &rhs.as_bytes()].concat()) }); } } diff --git a/yazi-codegen/Cargo.toml b/yazi-codegen/Cargo.toml index c39c5fce..b22d5f82 100644 --- a/yazi-codegen/Cargo.toml +++ b/yazi-codegen/Cargo.toml @@ -13,5 +13,5 @@ proc-macro = true [dependencies] # External dependencies -syn = { version = "2.0.110", features = [ "full" ] } +syn = { version = "2.0.111", features = [ "full" ] } quote = "1.0.42" diff --git a/yazi-config/src/popup/options.rs b/yazi-config/src/popup/options.rs index 85fd42f7..17dcf238 100644 --- a/yazi-config/src/popup/options.rs +++ b/yazi-config/src/popup/options.rs @@ -1,5 +1,5 @@ use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}}; -use yazi_shared::{IntoStringLossy, url::UrlBuf}; +use yazi_shared::{strand::ToStrand, url::UrlBuf}; use super::{Offset, Position}; use crate::YAZI; @@ -123,7 +123,7 @@ impl ConfirmCfg { Self::replace_number(&YAZI.confirm.trash_title, urls.len()), YAZI.confirm.trash_position(), None, - Self::truncate_list(urls.iter(), urls.len(), 100), + Self::truncate_list(urls, urls.len(), 100), ) } @@ -132,7 +132,7 @@ impl ConfirmCfg { Self::replace_number(&YAZI.confirm.delete_title, urls.len()), YAZI.confirm.delete_position(), None, - Self::truncate_list(urls.iter(), urls.len(), 100), + Self::truncate_list(urls, urls.len(), 100), ) } @@ -141,7 +141,7 @@ impl ConfirmCfg { YAZI.confirm.overwrite_title.clone(), YAZI.confirm.overwrite_position(), Some(Text::raw(&YAZI.confirm.overwrite_body)), - Some(url.into_string_lossy().into()), + Some(url.to_strand().into_string_lossy().into()), ) } @@ -150,7 +150,7 @@ impl ConfirmCfg { Self::replace_number(&YAZI.confirm.quit_title, len), YAZI.confirm.quit_position(), Some(Text::raw(&YAZI.confirm.quit_body)), - Self::truncate_list(names.into_iter(), len, 10), + Self::truncate_list(names, len, 10), ) } @@ -158,18 +158,18 @@ impl ConfirmCfg { tpl.replace("{n}", &n.to_string()).replace("{s}", if n > 1 { "s" } else { "" }) } - fn truncate_list( - it: impl Iterator, - len: usize, - max: usize, - ) -> Option> { + fn truncate_list(it: I, len: usize, max: usize) -> Option> + where + I: IntoIterator, + I::Item: ToStrand, + { let mut lines = Vec::with_capacity(len.min(max + 1)); - for (i, s) in it.enumerate() { + for (i, s) in it.into_iter().enumerate() { if i >= max { lines.push(format!("... and {} more", len - max)); break; } - lines.push(s.into_string_lossy()); + lines.push(s.to_strand().into_string_lossy()); } Some(Text::from_iter(lines)) } diff --git a/yazi-dds/src/sendable.rs b/yazi-dds/src/sendable.rs index c04b6af7..3b8953ed 100644 --- a/yazi-dds/src/sendable.rs +++ b/yazi-dds/src/sendable.rs @@ -47,7 +47,7 @@ impl Sendable { Data::Url(ud.take::()?.into()) } Some(t) if t == TypeId::of::() => { - Data::Path(ud.take::()?.0) + Data::Path(ud.take::()?.into()) } Some(t) if t == TypeId::of::() => { Data::Id(**ud.borrow::()?) @@ -103,7 +103,7 @@ impl Sendable { Data::Boolean(b) => Value::Boolean(*b), Data::Integer(i) => Value::Integer(*i), Data::Number(n) => Value::Number(*n), - Data::String(s) => Value::String(lua.create_string(s.as_ref())?), + Data::String(s) => Value::String(lua.create_string(&**s)?), Data::List(l) => { let mut vec = Vec::with_capacity(l.len()); for v in l { @@ -175,7 +175,7 @@ impl Sendable { match k { DataKey::Integer(i) => tbl.raw_set(i + 1, Self::data_to_value_ref(lua, v)?), DataKey::String(s) => { - tbl.raw_set(replace_cow(s.as_ref(), "-", "_"), Self::data_to_value_ref(lua, v)?) + tbl.raw_set(replace_cow(&**s, "-", "_"), Self::data_to_value_ref(lua, v)?) } _ => Err("invalid key in Data".into_lua_err()), }?; @@ -215,7 +215,7 @@ impl Sendable { DataKey::Url(ud.take::()?.into()) } Some(t) if t == TypeId::of::() => { - DataKey::Path(ud.take::()?.0) + DataKey::Path(ud.take::()?.into()) } Some(t) if t == TypeId::of::() => { DataKey::Id(**ud.borrow::()?) @@ -241,7 +241,7 @@ impl Sendable { DataKey::Boolean(b) => Value::Boolean(*b), DataKey::Integer(i) => Value::Integer(*i), DataKey::Number(n) => Value::Number(n.0), - DataKey::String(s) => Value::String(lua.create_string(s.as_ref())?), + DataKey::String(s) => Value::String(lua.create_string(&**s)?), DataKey::Id(i) => yazi_binding::Id(*i).into_lua(lua)?, DataKey::Url(u) => yazi_binding::Url::new(u.clone()).into_lua(lua)?, DataKey::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?, diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index e8986f19..48bc16ff 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -16,7 +16,7 @@ use crate::app::App; impl App { pub(crate) fn plugin(&mut self, mut opt: PluginOpt) -> Result { let mut hits = false; - if let Some(chunk) = LOADER.read().get(opt.id.as_ref()) { + if let Some(chunk) = LOADER.read().get(&*opt.id) { hits = true; opt.mode = opt.mode.auto_then(chunk.sync_entry); } @@ -38,7 +38,7 @@ impl App { pub(crate) fn plugin_do(&mut self, opt: PluginOpt) -> Result { let loader = LOADER.read(); - let Some(chunk) = loader.get(opt.id.as_ref()) else { + let Some(chunk) = loader.get(&*opt.id) else { succ!(warn!("plugin `{}` not found", opt.id)); }; diff --git a/yazi-fm/src/app/commands/quit.rs b/yazi-fm/src/app/commands/quit.rs index ecdc1342..807f0b99 100644 --- a/yazi-fm/src/app/commands/quit.rs +++ b/yazi-fm/src/app/commands/quit.rs @@ -1,8 +1,6 @@ -use std::ffi::OsString; - use yazi_boot::ARGS; use yazi_fs::provider::{Provider, local::Local}; -use yazi_shared::{event::EventQuit, url::UrlLike}; +use yazi_shared::{event::EventQuit, strand::{StrandBuf, StrandLike, ToStrand}}; use crate::{Term, app::App}; @@ -25,14 +23,14 @@ impl App { async fn cwd_to_file(&self, no: bool) { if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) { - let cwd = self.core.mgr.cwd().os_str(); - Local::regular(p).write(cwd.as_encoded_bytes()).await.ok(); + let cwd = self.core.mgr.cwd().to_strand(); + Local::regular(p).write(cwd.encoded_bytes()).await.ok(); } } - async fn selected_to_file(&self, selected: Option) { + async fn selected_to_file(&self, selected: Option) { if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) { - Local::regular(p).write(s.as_encoded_bytes()).await.ok(); + Local::regular(p).write(s.encoded_bytes()).await.ok(); } } } diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index deba1893..2fb51961 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -26,6 +26,7 @@ foldhash = { workspace = true } hashbrown = { workspace = true } parking_lot = { workspace = true } percent-encoding = { workspace = true } +rand = { workspace = true } regex = { workspace = true } scopeguard = { workspace = true } serde = { workspace = true } diff --git a/yazi-fs/src/path/clean.rs b/yazi-fs/src/path/clean.rs index 7e5b2e9a..a5b9bc29 100644 --- a/yazi-fs/src/path/clean.rs +++ b/yazi-fs/src/path/clean.rs @@ -1,6 +1,4 @@ -use std::path::PathBuf; - -use yazi_shared::{loc::LocBuf, path::PathDyn, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}}; +use yazi_shared::{path::{PathBufDyn, PathDyn}, url::{UrlBuf, UrlCow, UrlLike}}; pub fn clean_url<'a>(url: impl Into>) -> UrlBuf { let cow: UrlCow = url.into(); @@ -10,25 +8,12 @@ pub fn clean_url<'a>(url: impl Into>) -> UrlBuf { cow.trail().components().count() - 1, ); - match cow.as_url() { - Url::Regular(_) => UrlBuf::Regular(path.into()), - Url::Search { domain, .. } => UrlBuf::Search { - loc: LocBuf::::with(path, uri, urn).expect("create Loc from cleaned path"), - domain: domain.intern(), - }, - Url::Archive { domain, .. } => UrlBuf::Archive { - loc: LocBuf::::with(path, uri, urn).expect("create Loc from cleaned path"), - domain: domain.intern(), - }, - Url::Sftp { domain, .. } => UrlBuf::Sftp { - loc: LocBuf::::with(path, uri, urn).expect("create Loc from cleaned path"), - domain: domain.intern(), - }, - } + let scheme = cow.scheme().to_owned().with_ports(uri, urn); + (scheme, path).try_into().expect("UrlBuf from cleaned path") } -fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBuf, usize, usize) { - use std::path::Component::*; +fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usize, usize) { + use yazi_shared::path::Component::*; let mut out = vec![]; let mut uri_count = 0; @@ -58,7 +43,14 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBuf, usize, } } - (if out.is_empty() { PathBuf::from(".") } else { out.iter().collect() }, uri_count, urn_count) + let kind = path.kind(); + let path = if out.is_empty() { + PathBufDyn::with_str(kind, ".") + } else { + PathBufDyn::from_components(kind, out).expect("components with same kind") + }; + + (path, uri_count, urn_count) } #[cfg(test)] diff --git a/yazi-fs/src/path/expand.rs b/yazi-fs/src/path/expand.rs index 562f06db..f5bde9ff 100644 --- a/yazi-fs/src/path/expand.rs +++ b/yazi-fs/src/path/expand.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, path::PathBuf}; -use yazi_shared::{FromWtf8Vec, loc::LocBuf, path::{PathBufDyn, PathCow, PathDyn, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}}; +use yazi_shared::{FromWtf8Vec, loc::LocBuf, path::{PathBufDyn, PathCow, PathDyn, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}}; use crate::{CWD, path::clean_url}; @@ -60,11 +60,12 @@ fn expand_variables<'a>(p: PathDyn<'a>) -> PathCow<'a> { .map_or_else(|| caps.get(0).unwrap().as_bytes().to_owned(), |s| s.into_encoded_bytes()) }); - match (b, p) { + match (b, p.kind()) { (Cow::Borrowed(_), _) => p.into(), - (Cow::Owned(b), PathDyn::Os(_)) => { + (Cow::Owned(b), PathKind::Os) => { PathBufDyn::Os(std::path::PathBuf::from_wtf8_vec(b).expect("valid WTF-8 path")).into() } + (Cow::Owned(b), PathKind::Unix) => PathBufDyn::Unix(b.into()).into(), } } diff --git a/yazi-fs/src/path/path.rs b/yazi-fs/src/path/path.rs index 088cf438..f59eb489 100644 --- a/yazi-fs/src/path/path.rs +++ b/yazi-fs/src/path/path.rs @@ -1,15 +1,13 @@ -use std::{borrow::Cow, ffi::OsStr}; +use yazi_shared::{strand::StrandCow, url::{UrlBuf, UrlLike}}; -use yazi_shared::url::{UrlBuf, UrlLike}; - -pub fn skip_url(url: &UrlBuf, n: usize) -> Cow<'_, OsStr> { +pub fn skip_url(url: &UrlBuf, n: usize) -> StrandCow<'_> { let mut it = url.components(); for _ in 0..n { if it.next().is_none() { - return OsStr::new("").into(); + return StrandCow::default(); } } - it.os_str() + it.strand() } #[cfg(test)] diff --git a/yazi-fs/src/path/percent.rs b/yazi-fs/src/path/percent.rs index 98a5e299..6728d086 100644 --- a/yazi-fs/src/path/percent.rs +++ b/yazi-fs/src/path/percent.rs @@ -32,12 +32,14 @@ impl PercentEncoding for PathDyn<'_> { fn percent_encode(&self) -> Cow<'_, Path> { match self { PathDyn::Os(p) => p.percent_encode(), + PathDyn::Unix(_) => todo!(), } } fn percent_decode(&self) -> Cow<'_, [u8]> { match self { PathDyn::Os(p) => p.percent_decode(), + PathDyn::Unix(_) => todo!(), } } } diff --git a/yazi-fs/src/path/relative.rs b/yazi-fs/src/path/relative.rs index 674ccb69..172ec187 100644 --- a/yazi-fs/src/path/relative.rs +++ b/yazi-fs/src/path/relative.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use anyhow::{Result, bail}; use yazi_shared::{path::PathBufDyn, url::{UrlCow, UrlLike}}; @@ -23,7 +21,10 @@ fn url_relative_to_<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result> } if from.covariant(&to) { - return UrlCow::try_from((to.scheme().zeroed().to_owned(), PathBufDyn::with(to.kind(), ".")?)); + return UrlCow::try_from(( + to.scheme().zeroed().to_owned(), + PathBufDyn::with_str(to.kind(), "."), + )); } let (mut f_it, mut t_it) = (from.components(), to.components()); @@ -44,7 +45,8 @@ fn url_relative_to_<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result> let dots = f_head.into_iter().chain(f_it).map(|_| ParentDir); let rest = t_head.into_iter().chain(t_it); - let buf: PathBuf = dots.chain(rest).collect(); // FIXME: remove PathBuf - let buf = PathBufDyn::from(buf); + let iter = dots.chain(rest).map(|c| c.downgrade().expect("path component from dot or normal")); + let buf = PathBufDyn::from_components(to.kind(), iter)?; + UrlCow::try_from((to.scheme().zeroed().to_owned(), buf)) } diff --git a/yazi-fs/src/provider/local/local.rs b/yazi-fs/src/provider/local/local.rs index abfaaf71..8c51fed0 100644 --- a/yazi-fs/src/provider/local/local.rs +++ b/yazi-fs/src/provider/local/local.rs @@ -4,7 +4,7 @@ use yazi_shared::{path::{AsPath, PathBufDyn}, scheme::SchemeKind, url::{Url, Url use crate::{cha::Cha, path::absolute_url, provider::{Attrs, Provider}}; -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct Local<'a> { url: Url<'a>, path: &'a Path, diff --git a/yazi-fs/src/sorter.rs b/yazi-fs/src/sorter.rs index debac466..2d745dc7 100644 --- a/yazi-fs/src/sorter.rs +++ b/yazi-fs/src/sorter.rs @@ -1,7 +1,8 @@ use std::cmp::Ordering; use hashbrown::HashMap; -use yazi_shared::{LcgRng, natsort, path::PathBufDyn, translit::Transliterator, url::UrlLike}; +use rand::{RngCore, SeedableRng, rngs::SmallRng}; +use yazi_shared::{natsort, path::PathBufDyn, translit::Transliterator, url::UrlLike}; use crate::{File, SortBy}; @@ -59,8 +60,8 @@ impl FilesSorter { if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord } }), SortBy::Random => { - let mut rng = LcgRng::default(); - items.sort_unstable_by(|a, b| self.cmp(rng.next(), rng.next(), self.promote(a, b))) + let mut rng = SmallRng::from_os_rng(); + items.sort_unstable_by(|a, b| self.cmp(rng.next_u64(), rng.next_u64(), self.promote(a, b))) } } } diff --git a/yazi-fs/src/url.rs b/yazi-fs/src/url.rs index 958fbfd2..b8ff40da 100644 --- a/yazi-fs/src/url.rs +++ b/yazi-fs/src/url.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; use yazi_shared::{path::PathDyn, url::{AsUrl, Url, UrlBuf, UrlCow}}; -use crate::{FsHash128, FsScheme, path::PercentEncoding}; +use crate::{FsHash128, FsScheme}; pub trait FsUrl<'a> { fn cache(&self) -> Option; @@ -26,12 +26,13 @@ impl<'a> FsUrl<'a> for Url<'a> { fn cache(&self) -> Option { fn with_loc(loc: PathDyn, mut root: PathBuf) -> PathBuf { let mut it = loc.components(); - if it.next() == Some(std::path::Component::RootDir) { - root.push(it.as_path().percent_encode()); - } else { - root.push(".%2F"); - root.push(loc.percent_encode()); - } + todo!(); + // if it.next() == Some(std::path::Component::RootDir) { + // root.push(it.as_path().percent_encode()); + // } else { + // root.push(".%2F"); + // root.push(loc.percent_encode()); + // } root } diff --git a/yazi-plugin/src/fs/op.rs b/yazi-plugin/src/fs/op.rs index e2cb6c7d..4888e92a 100644 --- a/yazi-plugin/src/fs/op.rs +++ b/yazi-plugin/src/fs/op.rs @@ -35,7 +35,7 @@ impl FilesOp { url.into(), sizes .pairs::() - .map(|r| r.map(|(urn, size)| (urn.0, size))) + .map(|r| r.map(|(urn, size)| (urn.into(), size))) .collect::>()?, ))) } diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index 9c212f90..146061a0 100644 --- a/yazi-plugin/src/isolate/peek.rs +++ b/yazi-plugin/src/isolate/peek.rs @@ -64,7 +64,7 @@ fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol, skip: us plugin.call_method("peek", job) }); - AppProxy::plugin(PluginOpt::new_callback(cmd.name.as_ref(), cb)); + AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb)); } fn peek_async( diff --git a/yazi-plugin/src/isolate/seek.rs b/yazi-plugin/src/isolate/seek.rs index e51a4b5f..9085b82d 100644 --- a/yazi-plugin/src/isolate/seek.rs +++ b/yazi-plugin/src/isolate/seek.rs @@ -16,5 +16,5 @@ pub fn seek_sync(cmd: &'static Cmd, file: yazi_fs::File, units: i16) { plugin.call_method("seek", job) }); - AppProxy::plugin(PluginOpt::new_callback(cmd.name.as_ref(), cb)); + AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb)); } diff --git a/yazi-plugin/src/process/command.rs b/yazi-plugin/src/process/command.rs index ac66cd71..1e63f64f 100644 --- a/yazi-plugin/src/process/command.rs +++ b/yazi-plugin/src/process/command.rs @@ -1,9 +1,9 @@ -use std::{any::TypeId, io, process::Stdio}; +use std::{any::TypeId, ffi::OsStr, io, process::Stdio}; use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value}; use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; use yazi_binding::Error; -use yazi_shared::IntoOsStr; +use yazi_shared::FromWtf8; use super::{Child, output::Output}; use crate::process::Status; @@ -144,11 +144,11 @@ impl UserData for Command { let mut me = ud.borrow_mut::()?; match arg { Value::String(s) => { - me.inner.arg(s.as_bytes().into_os_str()?); + me.inner.arg(OsStr::from_wtf8(&*s.as_bytes())?); } Value::Table(t) => { for s in t.sequence_values::() { - me.inner.arg(s?.as_bytes().into_os_str()?); + me.inner.arg(OsStr::from_wtf8(&*s?.as_bytes())?); } } _ => return Err("arg must be a string or table of strings".into_lua_err()), @@ -165,7 +165,7 @@ impl UserData for Command { |_, (ud, key, value): (AnyUserData, mlua::String, mlua::String)| { ud.borrow_mut::()? .inner - .env(key.as_bytes().into_os_str()?, value.as_bytes().into_os_str()?); + .env(OsStr::from_wtf8(&*key.as_bytes())?, OsStr::from_wtf8(&*value.as_bytes())?); Ok(ud) }, ); diff --git a/yazi-plugin/src/runtime/plugin.rs b/yazi-plugin/src/runtime/plugin.rs index 3978e6cc..58760bde 100644 --- a/yazi-plugin/src/runtime/plugin.rs +++ b/yazi-plugin/src/runtime/plugin.rs @@ -56,7 +56,7 @@ impl Fetcher { impl UserData for Fetcher { fn add_fields>(fields: &mut F) { - cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref())); + cached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name)); } } @@ -73,7 +73,7 @@ impl Spotter { impl UserData for Spotter { fn add_fields>(fields: &mut F) { - cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref())); + cached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name)); } } @@ -90,7 +90,7 @@ impl Preloader { impl UserData for Preloader { fn add_fields>(fields: &mut F) { - cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref())); + cached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name)); } } @@ -107,6 +107,6 @@ impl Previewer { impl UserData for Previewer { fn add_fields>(fields: &mut F) { - cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref())); + cached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name)); } } diff --git a/yazi-plugin/src/utils/text.rs b/yazi-plugin/src/utils/text.rs index 296d0d1f..22b861e2 100644 --- a/yazi-plugin/src/utils/text.rs +++ b/yazi-plugin/src/utils/text.rs @@ -21,7 +21,7 @@ impl Utils { Some(false) => yazi_shared::shell::escape_windows(s.as_ref()), None => yazi_shared::shell::escape_native(s.as_ref()), }; - lua.create_string(s.as_ref()) + lua.create_string(&*s) }) } diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 5544bcb2..7a4cece6 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -28,6 +28,7 @@ percent-encoding = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } +typed-path = { workspace = true } [target."cfg(unix)".dependencies] libc = { workspace = true } diff --git a/yazi-shared/src/data/data.rs b/yazi-shared/src/data/data.rs index 261322ed..6f77c30a 100644 --- a/yazi-shared/src/data/data.rs +++ b/yazi-shared/src/data/data.rs @@ -20,7 +20,7 @@ pub enum Data { Id(Id), #[serde(skip_deserializing)] Url(UrlBuf), - #[serde(skip_deserializing)] + #[serde(skip)] Path(PathBufDyn), #[serde(skip)] Bytes(Vec), @@ -150,7 +150,7 @@ impl<'a> TryFrom<&'a Data> for Cow<'a, str> { fn try_from(value: &'a Data) -> Result { match value { - Data::String(s) => Ok(s.as_ref().into()), + Data::String(s) => Ok(Cow::Borrowed(s)), _ => bail!("not a string"), } } @@ -193,7 +193,7 @@ impl<'a> TryFrom<&'a Data> for UrlCow<'a> { fn try_from(value: &'a Data) -> Result { match value { - Data::String(s) => s.as_ref().try_into(), + Data::String(s) => Self::try_from(&**s), Data::Url(u) => Ok(u.into()), Data::Bytes(b) => b.as_slice().try_into(), _ => bail!("not a URL"), diff --git a/yazi-shared/src/data/key.rs b/yazi-shared/src/data/key.rs index 005416ac..61b6c5e1 100644 --- a/yazi-shared/src/data/key.rs +++ b/yazi-shared/src/data/key.rs @@ -17,7 +17,7 @@ pub enum DataKey { Id(Id), #[serde(skip_deserializing)] Url(UrlBuf), - #[serde(skip_deserializing)] + #[serde(skip)] Path(PathBufDyn), #[serde(skip)] Bytes(Vec), diff --git a/yazi-shared/src/event/event.rs b/yazi-shared/src/event/event.rs index 4ca69480..5a6c3606 100644 --- a/yazi-shared/src/event/event.rs +++ b/yazi-shared/src/event/event.rs @@ -1,10 +1,8 @@ -use std::ffi::OsString; - use crossterm::event::{KeyEvent, MouseEvent}; use tokio::sync::mpsc; use super::CmdCow; -use crate::RoCell; +use crate::{RoCell, strand::StrandBuf}; static TX: RoCell> = RoCell::new(); static RX: RoCell> = RoCell::new(); @@ -25,7 +23,7 @@ pub enum Event { pub struct EventQuit { pub code: i32, pub no_cwd_file: bool, - pub selected: Option, + pub selected: Option, } impl Event { diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 3b1af6dc..aff60ffa 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -2,7 +2,7 @@ yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url); -yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os osstr predictor rand ro_cell source string sync_cell terminal tests throttle time utf8 wtf8); +yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8 wtf8); pub fn init() { pool::init(); diff --git a/yazi-shared/src/osstr.rs b/yazi-shared/src/osstr.rs deleted file mode 100644 index 7fc560af..00000000 --- a/yazi-shared/src/osstr.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::{borrow::Cow, ffi::{OsStr, OsString}}; - -pub trait IntoOsStr<'a> { - type Error; - - fn into_os_str(self) -> Result, Self::Error>; -} - -impl<'a> IntoOsStr<'a> for Cow<'a, [u8]> { - type Error = anyhow::Error; - - fn into_os_str(self) -> Result, Self::Error> { - #[cfg(unix)] - { - use std::os::unix::ffi::{OsStrExt, OsStringExt}; - Ok(match self { - Cow::Borrowed(b) => Cow::Borrowed(OsStr::from_bytes(b)), - Cow::Owned(b) => Cow::Owned(OsString::from_vec(b)), - }) - } - #[cfg(windows)] - { - Ok(match self { - Cow::Borrowed(b) => Cow::Borrowed(OsStr::new(str::from_utf8(b)?)), - Cow::Owned(b) => Cow::Owned(OsString::from(String::from_utf8(b)?)), - }) - } - } -} - -impl<'a> IntoOsStr<'a> for &'a [u8] { - type Error = anyhow::Error; - - fn into_os_str(self) -> Result, Self::Error> { Cow::Borrowed(self).into_os_str() } -} - -// --- OsStrJoin -pub trait OsStrJoin { - fn join(&self, sep: &OsStr) -> OsString; -} - -impl OsStrJoin for Vec -where - T: AsRef, -{ - fn join(&self, sep: &OsStr) -> OsString { - if self.is_empty() { - return OsString::new(); - } - - let mut result = OsString::new(); - for (i, item) in self.iter().enumerate() { - if i > 0 { - result.push(sep); - } - result.push(item.as_ref()); - } - result - } -} diff --git a/yazi-shared/src/path/buf.rs b/yazi-shared/src/path/buf.rs index d9273700..9d42ca69 100644 --- a/yazi-shared/src/path/buf.rs +++ b/yazi-shared/src/path/buf.rs @@ -1,15 +1,14 @@ use std::ffi::{OsStr, OsString}; use hashbrown::Equivalent; -use serde::Serialize; -use crate::{FromWtf8, path::{AsPath, PathBufDynError, PathDyn, PathKind, SetNameError}, strand::{AsStrand, Strand, StrandError}}; +use crate::{FromWtf8, FromWtf8Vec, path::{AsPath, Component, PathDyn, PathDynError, PathKind, SetNameError}, strand::{AsStrand, Strand}}; // --- PathBufDyn -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] -#[serde(untagged)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum PathBufDyn { Os(std::path::PathBuf), + Unix(typed_path::UnixPathBuf), } impl From for PathBufDyn { @@ -21,7 +20,7 @@ impl From> for PathBufDyn { } impl TryFrom for std::path::PathBuf { - type Error = PathBufDynError; + type Error = PathDynError; fn try_from(value: PathBufDyn) -> Result { value.into_os() } } @@ -46,19 +45,34 @@ impl PathBufDyn { { match kind.into() { PathKind::Os => Self::Os(unsafe { OsString::from_encoded_bytes_unchecked(bytes) }.into()), + PathKind::Unix => Self::Unix(bytes.into()), } } + #[inline] + pub fn from_components<'a, K, I>(kind: K, iter: I) -> Result + where + K: Into, + I: IntoIterator>, + { + Ok(match kind.into() { + PathKind::Os => Self::Os(iter.into_iter().collect::>()?), + PathKind::Unix => Self::Unix(iter.into_iter().collect::>()?), + }) + } + pub fn into_encoded_bytes(self) -> Vec { match self { Self::Os(p) => p.into_os_string().into_encoded_bytes(), + Self::Unix(p) => p.into_vec(), } } #[inline] - pub fn into_os(self) -> Result { + pub fn into_os(self) -> Result { Ok(match self { Self::Os(p) => p, + Self::Unix(_) => Err(PathDynError::AsOs)?, }) } @@ -66,13 +80,11 @@ impl PathBufDyn { pub fn new(kind: PathKind) -> Self { match kind { PathKind::Os => Self::Os(std::path::PathBuf::new()), + PathKind::Unix => Self::Unix(typed_path::UnixPathBuf::new()), } } - #[inline] - pub fn os(path: impl Into) -> Self { Self::Os(path.into()) } - - pub fn try_extend(&mut self, paths: T) -> Result<(), StrandError> + pub fn try_extend(&mut self, paths: T) -> Result<(), PathDynError> where T: IntoIterator, T::Item: AsPath, @@ -83,13 +95,14 @@ impl PathBufDyn { Ok(()) } - pub fn try_push(&mut self, path: T) -> Result<(), StrandError> + pub fn try_push(&mut self, path: T) -> Result<(), PathDynError> where T: AsPath, { let path = path.as_path(); Ok(match self { Self::Os(p) => p.push(path.as_os()?), + Self::Unix(p) => p.push(path.encoded_bytes()), }) } @@ -98,22 +111,25 @@ impl PathBufDyn { T: AsStrand, { Ok(match (self, name.as_strand()) { - (Self::Os(p), Strand::Os(q)) => p.set_file_name(q), - (Self::Os(p), Strand::Utf8(q)) => p.set_file_name(q), + (Self::Os(p), Strand::Os(s)) => p.set_file_name(s), + (Self::Os(p), Strand::Utf8(s)) => p.set_file_name(s), (Self::Os(p), Strand::Bytes(b)) => { p.set_file_name(OsStr::from_wtf8(b).map_err(|_| SetNameError::FromWtf8)?) } + + (Self::Unix(p), s) => p.set_file_name(s.encoded_bytes()), }) } - pub fn with(kind: K, strand: T) -> Result + pub fn with(kind: K, bytes: Vec) -> Result where K: Into, - T: AsStrand, { - let s = strand.as_strand(); Ok(match kind.into() { - PathKind::Os => Self::Os(std::path::PathBuf::from(s.as_os()?)), + PathKind::Os => { + Self::Os(std::path::PathBuf::from_wtf8_vec(bytes).map_err(|_| PathDynError::AsOs)?) + } + PathKind::Unix => Self::Unix(bytes.into()), }) } @@ -123,6 +139,19 @@ impl PathBufDyn { { match kind.into() { PathKind::Os => Self::Os(std::path::PathBuf::with_capacity(capacity)), + PathKind::Unix => Self::Unix(typed_path::UnixPathBuf::with_capacity(capacity)), + } + } + + pub fn with_str(kind: K, s: S) -> Self + where + K: Into, + S: Into, + { + let s = s.into(); + match kind.into() { + PathKind::Os => Self::Os(s.into()), + PathKind::Unix => Self::Unix(s.into()), } } } diff --git a/yazi-shared/src/path/component.rs b/yazi-shared/src/path/component.rs index e69de29b..98b73bdb 100644 --- a/yazi-shared/src/path/component.rs +++ b/yazi-shared/src/path/component.rs @@ -0,0 +1,75 @@ +use std::path::PrefixComponent; + +use crate::{path::PathDynError, strand::Strand}; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Component<'a> { + Prefix(PrefixComponent<'a>), + RootDir, + CurDir, + ParentDir, + Normal(Strand<'a>), +} + +impl<'a> From> for Component<'a> { + fn from(value: std::path::Component<'a>) -> Self { + match value { + std::path::Component::Prefix(p) => Self::Prefix(p), + std::path::Component::RootDir => Self::RootDir, + std::path::Component::CurDir => Self::CurDir, + std::path::Component::ParentDir => Self::ParentDir, + std::path::Component::Normal(s) => Self::Normal(Strand::Os(s)), + } + } +} + +impl<'a> From> for Component<'a> { + fn from(value: typed_path::UnixComponent<'a>) -> Self { + match value { + typed_path::UnixComponent::RootDir => Self::RootDir, + typed_path::UnixComponent::CurDir => Self::CurDir, + typed_path::UnixComponent::ParentDir => Self::ParentDir, + typed_path::UnixComponent::Normal(b) => Self::Normal(Strand::Bytes(b)), + } + } +} + +impl<'a> TryFrom> for std::path::Component<'a> { + type Error = PathDynError; + + fn try_from(value: Component<'a>) -> Result { + Ok(match value { + Component::Prefix(p) => Self::Prefix(p), + Component::RootDir => Self::RootDir, + Component::CurDir => Self::CurDir, + Component::ParentDir => Self::ParentDir, + Component::Normal(s) => Self::Normal(s.as_os()?), + }) + } +} + +impl<'a> TryFrom> for typed_path::UnixComponent<'a> { + type Error = PathDynError; + + fn try_from(value: Component<'a>) -> Result { + Ok(match value { + Component::Prefix(_) => Err(PathDynError::AsUnix)?, + Component::RootDir => Self::RootDir, + Component::CurDir => Self::CurDir, + Component::ParentDir => Self::ParentDir, + Component::Normal(s) => Self::Normal(s.encoded_bytes()), + }) + } +} + +impl<'a> FromIterator> for Result { + fn from_iter>>(iter: I) -> Self { + iter.into_iter().map(std::path::Component::try_from).collect() + } +} + +impl<'a> FromIterator> for Result { + fn from_iter>>(iter: I) -> Self { + iter.into_iter().map(typed_path::UnixComponent::try_from).collect() + } +} diff --git a/yazi-shared/src/path/components.rs b/yazi-shared/src/path/components.rs index e69de29b..37273b3b 100644 --- a/yazi-shared/src/path/components.rs +++ b/yazi-shared/src/path/components.rs @@ -0,0 +1,49 @@ +use std::iter::FusedIterator; + +use typed_path::Components as _; + +use crate::{path::{Component, PathDyn}, strand::Strand}; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Components<'a> { + Os(std::path::Components<'a>), + Unix(typed_path::UnixComponents<'a>), +} + +impl<'a> Components<'a> { + pub fn path(&self) -> PathDyn<'a> { + match self { + Self::Os(c) => PathDyn::Os(c.as_path()), + Self::Unix(c) => PathDyn::Unix(c.as_path()), + } + } + + pub fn strand(&self) -> Strand<'a> { + match self { + Self::Os(c) => Strand::Os(c.as_path().as_os_str()), + Self::Unix(c) => Strand::Bytes(c.as_bytes()), + } + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + match self { + Self::Os(c) => c.next().map(Into::into), + Self::Unix(c) => c.next().map(Into::into), + } + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + match self { + Self::Os(c) => c.next_back().map(Into::into), + Self::Unix(c) => c.next_back().map(Into::into), + } + } +} + +impl FusedIterator for Components<'_> {} diff --git a/yazi-shared/src/path/conversion.rs b/yazi-shared/src/path/conversion.rs index 963fef8b..9dbccb4d 100644 --- a/yazi-shared/src/path/conversion.rs +++ b/yazi-shared/src/path/conversion.rs @@ -1,5 +1,3 @@ -use std::ffi::OsStr; - use super::{PathBufDyn, PathDyn}; use crate::path::PathCow; @@ -8,14 +6,6 @@ pub trait AsPath { fn as_path(&self) -> PathDyn<'_>; } -impl AsPath for OsStr { - fn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self.as_ref()) } -} - -impl AsPath for &OsStr { - fn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self.as_ref()) } -} - impl AsPath for std::path::Path { fn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self) } } @@ -32,14 +22,11 @@ impl AsPath for PathBufDyn { fn as_path(&self) -> PathDyn<'_> { match self { Self::Os(p) => PathDyn::Os(p), + Self::Unix(p) => PathDyn::Unix(p), } } } -impl AsPath for &PathBufDyn { - fn as_path(&self) -> PathDyn<'_> { (*self).as_path() } -} - impl AsPath for PathCow<'_> { fn as_path(&self) -> PathDyn<'_> { match self { @@ -49,6 +36,10 @@ impl AsPath for PathCow<'_> { } } +impl AsPath for super::Components<'_> { + fn as_path(&self) -> PathDyn<'_> { self.path() } +} + // --- AsPathRef pub trait AsPathRef<'a> { fn as_path_ref(self) -> PathDyn<'a>; diff --git a/yazi-shared/src/path/cow.rs b/yazi-shared/src/path/cow.rs index a2523944..3e1414b7 100644 --- a/yazi-shared/src/path/cow.rs +++ b/yazi-shared/src/path/cow.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use anyhow::Result; -use crate::{IntoOsStr, path::{AsPath, PathBufDyn, PathDyn}}; +use crate::path::{AsPath, PathBufDyn, PathDyn, PathKind}; // --- PathCow pub enum PathCow<'a> { @@ -32,17 +32,21 @@ impl PartialEq<&str> for PathCow<'_> { } impl<'a> PathCow<'a> { - pub fn from_os_bytes(bytes: impl Into>) -> Result { - Ok(match bytes.into().into_os_str()? { - Cow::Borrowed(s) => PathDyn::os(s).into(), - Cow::Owned(s) => PathBufDyn::os(s).into(), - }) - } - pub fn into_owned(self) -> PathBufDyn { match self { Self::Borrowed(s) => s.to_owned(), Self::Owned(s) => s, } } + + pub fn with(kind: K, bytes: T) -> Result + where + K: Into, + T: Into>, + { + Ok(match bytes.into() { + Cow::Borrowed(b) => PathDyn::with(kind, b)?.into(), + Cow::Owned(b) => PathBufDyn::with(kind, b)?.into(), + }) + } } diff --git a/yazi-shared/src/path/display.rs b/yazi-shared/src/path/display.rs new file mode 100644 index 00000000..dbdb5df3 --- /dev/null +++ b/yazi-shared/src/path/display.rs @@ -0,0 +1,12 @@ +use crate::path::PathDyn; + +pub struct Display<'a>(pub PathDyn<'a>); + +impl std::fmt::Display for Display<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + PathDyn::Os(p) => write!(f, "{}", p.display()), + PathDyn::Unix(p) => write!(f, "{}", p.display()), + } + } +} diff --git a/yazi-shared/src/path/error.rs b/yazi-shared/src/path/error.rs index 8b28d274..772d7250 100644 --- a/yazi-shared/src/path/error.rs +++ b/yazi-shared/src/path/error.rs @@ -12,7 +12,7 @@ pub struct EndsWithError; #[error("calling join on paths with different encodings")] pub enum JoinError { FromWtf8, - FromPathBufDyn(#[from] PathBufDynError), + FromPathDyn(#[from] PathDynError), } impl From for std::io::Error { @@ -22,27 +22,33 @@ impl From for std::io::Error { // --- PathDynError #[derive(Debug, Error)] pub enum PathDynError { - #[error("conversion to OsStr failed")] + #[error("conversion to OS path failed")] AsOs, + #[error("conversion to Unix path failed")] + AsUnix, + #[error("conversion to UTF-8 path failed")] + AsUtf8, +} + +impl From for PathDynError { + fn from(err: StrandError) -> Self { + match err { + StrandError::AsOs => Self::AsOs, + StrandError::AsUtf8 => Self::AsUtf8, + } + } } impl From for std::io::Error { fn from(err: PathDynError) -> Self { Self::other(err) } } -// --- PathBufDynError -#[derive(Debug, Error)] -pub enum PathBufDynError { - #[error("conversion to OsString failed")] - IntoOs, -} - // --- SetNameError #[derive(Debug, Error)] #[error("calling set_name on paths with different encodings")] pub enum SetNameError { FromWtf8, - FromStrandDyn(#[from] StrandError), + FromStrand(#[from] StrandError), } impl From for std::io::Error { @@ -89,3 +95,7 @@ pub enum StripPrefixError { impl From for StripPrefixError { fn from(_: std::path::StripPrefixError) -> Self { Self::NotPrefix } } + +impl From for StripPrefixError { + fn from(_: typed_path::StripPrefixError) -> Self { Self::NotPrefix } +} diff --git a/yazi-shared/src/path/kind.rs b/yazi-shared/src/path/kind.rs index 4c9e1920..a0dd82ba 100644 --- a/yazi-shared/src/path/kind.rs +++ b/yazi-shared/src/path/kind.rs @@ -2,6 +2,7 @@ use crate::scheme::SchemeKind; pub enum PathKind { Os, + Unix, } impl From for PathKind { diff --git a/yazi-shared/src/path/like.rs b/yazi-shared/src/path/like.rs index 99ee4b5e..0c8ca02c 100644 --- a/yazi-shared/src/path/like.rs +++ b/yazi-shared/src/path/like.rs @@ -2,14 +2,14 @@ use std::borrow::Cow; use anyhow::Result; -use crate::{Utf8BytePredictor, path::{AsPath, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError}, strand::{AsStrand, Strand}}; +use crate::{Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError}, strand::{AsStrand, Strand}}; pub trait PathLike: AsPath { fn as_os(&self) -> Result<&std::path::Path, PathDynError> { self.as_path().as_os() } - fn components(&self) -> std::path::Components<'_> { self.as_path().components() } + fn components(&self) -> Components<'_> { self.as_path().components() } - fn display(&self) -> std::path::Display<'_> { self.as_path().display() } + fn display(&self) -> Display<'_> { self.as_path().display() } fn encoded_bytes(&self) -> &[u8] { self.as_path().encoded_bytes() } diff --git a/yazi-shared/src/path/mod.rs b/yazi-shared/src/path/mod.rs index e7691f0c..a3d7f658 100644 --- a/yazi-shared/src/path/mod.rs +++ b/yazi-shared/src/path/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(buf component components conversion cow error kind like path view); +yazi_macro::mod_flat!(buf component components conversion cow display error kind like path view); diff --git a/yazi-shared/src/path/path.rs b/yazi-shared/src/path/path.rs index 3ac95899..eb895296 100644 --- a/yazi-shared/src/path/path.rs +++ b/yazi-shared/src/path/path.rs @@ -4,17 +4,22 @@ use anyhow::Result; use hashbrown::Equivalent; use super::{RsplitOnceError, StartsWithError}; -use crate::{BytesExt, FromWtf8, Utf8BytePredictor, path::{AsPath, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError}, strand::{AsStrand, Strand, StrandError}}; +use crate::{BytesExt, FromWtf8, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError}, strand::{AsStrand, Strand, StrandError}}; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum PathDyn<'p> { Os(&'p std::path::Path), + Unix(&'p typed_path::UnixPath), } impl<'a> From<&'a std::path::Path> for PathDyn<'a> { fn from(value: &'a std::path::Path) -> Self { Self::Os(value) } } +impl<'a> From<&'a typed_path::UnixPath> for PathDyn<'a> { + fn from(value: &'a typed_path::UnixPath) -> Self { Self::Unix(value) } +} + impl<'a> From<&'a PathBufDyn> for PathDyn<'a> { fn from(value: &'a PathBufDyn) -> Self { value.as_path() } } @@ -33,8 +38,9 @@ impl PartialEq<&std::path::Path> for PathDyn<'_> { impl PartialEq<&str> for PathDyn<'_> { fn eq(&self, other: &&str) -> bool { - match self { - PathDyn::Os(p) => p == other, + match *self { + PathDyn::Os(p) => p == *other, + PathDyn::Unix(p) => p == typed_path::UnixPath::new(other), } } } @@ -48,30 +54,30 @@ impl<'p> PathDyn<'p> { pub fn as_os(self) -> Result<&'p std::path::Path, PathDynError> { match self { Self::Os(p) => Ok(p), + Self::Unix(_) => Err(PathDynError::AsOs), } } - pub fn components(self) -> std::path::Components<'p> { + pub fn components(self) -> Components<'p> { match self { - Self::Os(p) => p.components(), + Self::Os(p) => Components::Os(p.components()), + Self::Unix(p) => Components::Unix(p.components()), } } - pub fn display(self) -> std::path::Display<'p> { - match self { - Self::Os(p) => p.display(), - } - } + pub fn display(self) -> Display<'p> { Display(self) } pub fn encoded_bytes(self) -> &'p [u8] { match self { Self::Os(p) => p.as_os_str().as_encoded_bytes(), + Self::Unix(p) => p.as_bytes(), } } pub fn ext(self) -> Option> { Some(match self { Self::Os(p) => p.extension()?.into(), + Self::Unix(p) => p.extension()?.into(), }) } @@ -82,18 +88,21 @@ impl<'p> PathDyn<'p> { { match kind.into() { PathKind::Os => Self::Os(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }.as_ref()), + PathKind::Unix => Self::Unix(typed_path::UnixPath::new(bytes)), } } pub fn has_root(self) -> bool { match self { Self::Os(p) => p.has_root(), + Self::Unix(p) => p.has_root(), } } pub fn is_absolute(self) -> bool { match self { Self::Os(p) => p.is_absolute(), + Self::Unix(p) => p.is_absolute(), } } @@ -107,6 +116,7 @@ impl<'p> PathDyn<'p> { pub fn kind(self) -> PathKind { match self { Self::Os(_) => PathKind::Os, + Self::Unix(_) => PathKind::Unix, } } @@ -115,20 +125,14 @@ impl<'p> PathDyn<'p> { pub fn name(self) -> Option> { Some(match self { Self::Os(p) => p.file_name()?.into(), + Self::Unix(p) => p.file_name()?.into(), }) } - #[inline] - pub fn os(path: &'p T) -> Self - where - T: ?Sized + AsRef, - { - Self::Os(path.as_ref()) - } - pub fn parent(self) -> Option { Some(match self { Self::Os(p) => Self::Os(p.parent()?), + Self::Unix(p) => Self::Unix(p.parent()?), }) } @@ -145,6 +149,7 @@ impl<'p> PathDyn<'p> { pub fn stem(self) -> Option> { Some(match self { Self::Os(p) => p.file_stem()?.into(), + Self::Unix(p) => p.file_stem()?.into(), }) } @@ -152,12 +157,14 @@ impl<'p> PathDyn<'p> { pub fn to_os_owned(self) -> Result { match self { Self::Os(p) => Ok(p.to_owned()), + Self::Unix(_) => Err(PathDynError::AsOs), } } pub fn to_owned(self) -> PathBufDyn { match self { - Self::Os(p) => PathBufDyn::Os(p.to_path_buf()), + Self::Os(p) => PathBufDyn::Os(p.to_owned()), + Self::Unix(p) => PathBufDyn::Unix(p.to_owned()), } } @@ -177,6 +184,8 @@ impl<'p> PathDyn<'p> { (Self::Os(p), Strand::Bytes(b)) => { p.ends_with(OsStr::from_wtf8(b).map_err(|_| EndsWithError)?) } + + (Self::Unix(p), s) => p.ends_with(s.encoded_bytes()), }) } @@ -190,6 +199,8 @@ impl<'p> PathDyn<'p> { (Self::Os(p), Strand::Bytes(b)) => { PathBufDyn::Os(p.join(OsStr::from_wtf8(b).map_err(|_| JoinError::FromWtf8)?)) } + + (Self::Unix(p), s) => PathBufDyn::Unix(p.join(s.encoded_bytes())), }) } @@ -203,6 +214,7 @@ impl<'p> PathDyn<'p> { PathDyn::Os(p) => { p.as_os_str().as_encoded_bytes().rsplit_seq_once(pat.as_os()?.as_encoded_bytes()) } + PathDyn::Unix(p) => p.as_bytes().rsplit_seq_once(pat.encoded_bytes()), } .ok_or(RsplitOnceError::NotFound)?; @@ -216,11 +228,13 @@ impl<'p> PathDyn<'p> { T: AsStrand, { Ok(match (self, base.as_strand()) { - (Self::Os(p), Strand::Os(q)) => p.starts_with(q), - (Self::Os(p), Strand::Utf8(q)) => p.starts_with(q), + (Self::Os(p), Strand::Os(s)) => p.starts_with(s), + (Self::Os(p), Strand::Utf8(s)) => p.starts_with(s), (Self::Os(p), Strand::Bytes(b)) => { p.starts_with(OsStr::from_wtf8(b).map_err(|_| StartsWithError)?) } + + (Self::Unix(p), s) => p.starts_with(s.encoded_bytes()), }) } @@ -229,22 +243,37 @@ impl<'p> PathDyn<'p> { T: AsStrand, { Ok(match (self, base.as_strand()) { - (Self::Os(p), Strand::Os(q)) => Self::Os(p.strip_prefix(q)?), - (Self::Os(p), Strand::Utf8(q)) => Self::Os(p.strip_prefix(q)?), + (Self::Os(p), Strand::Os(s)) => Self::Os(p.strip_prefix(s)?), + (Self::Os(p), Strand::Utf8(s)) => Self::Os(p.strip_prefix(s)?), (Self::Os(p), Strand::Bytes(b)) => { Self::Os(p.strip_prefix(OsStr::from_wtf8(b).map_err(|_| StripPrefixError::WrongEncoding)?)?) } + + (Self::Unix(p), s) => Self::Unix(p.strip_prefix(s.encoded_bytes())?), }) } - pub fn with(kind: K, strand: &'p T) -> Result + pub fn with(kind: K, strand: &'p S) -> Result where K: Into, - T: ?Sized + AsStrand, + S: ?Sized + AsStrand, { let s = strand.as_strand(); Ok(match kind.into() { PathKind::Os => Self::Os(std::path::Path::new(s.as_os()?)), + PathKind::Unix => Self::Unix(typed_path::UnixPath::new(s.encoded_bytes())), }) } + + pub fn with_str(kind: K, s: &'p S) -> Self + where + K: Into, + S: ?Sized + AsRef, + { + let s = s.as_ref(); + match kind.into() { + PathKind::Os => Self::Os(s.as_ref()), + PathKind::Unix => Self::Unix(s.as_ref()), + } + } } diff --git a/yazi-shared/src/path/view.rs b/yazi-shared/src/path/view.rs index 883977d9..601682a5 100644 --- a/yazi-shared/src/path/view.rs +++ b/yazi-shared/src/path/view.rs @@ -1,16 +1,8 @@ -use std::{borrow::Cow, ffi::OsStr}; - -use crate::path::{AsPath, PathDyn}; - // --- AsPathView pub trait AsPathView<'a, T> { fn as_path_view(self) -> T; } -impl<'a> AsPathView<'a, &'a std::path::Path> for &'a OsStr { - fn as_path_view(self) -> &'a std::path::Path { std::path::Path::new(self) } -} - impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::Path { fn as_path_view(self) -> &'a std::path::Path { self } } @@ -18,22 +10,3 @@ impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::Path { impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::PathBuf { fn as_path_view(self) -> &'a std::path::Path { self } } - -impl<'a> AsPathView<'a, &'a std::path::Path> for std::path::Components<'a> { - fn as_path_view(self) -> &'a std::path::Path { self.as_path() } -} - -impl<'a, T> AsPathView<'a, PathDyn<'a>> for &'a T -where - T: AsPath, -{ - fn as_path_view(self) -> PathDyn<'a> { self.as_path() } -} - -impl<'a> AsPathView<'a, &'a std::path::Path> for &'a Cow<'_, OsStr> { - fn as_path_view(self) -> &'a std::path::Path { std::path::Path::new(self) } -} - -impl<'a> AsPathView<'a, &'a std::path::Path> for crate::loc::Loc<'a, &'a std::path::Path> { - fn as_path_view(self) -> &'a std::path::Path { *self } -} diff --git a/yazi-shared/src/rand.rs b/yazi-shared/src/rand.rs deleted file mode 100644 index 20271405..00000000 --- a/yazi-shared/src/rand.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -pub struct LcgRng { - seed: u64, -} - -impl LcgRng { - const A: u64 = 6364136223846793005; - const C: u64 = 1; - const M: u64 = u64::MAX; -} - -impl Iterator for LcgRng { - type Item = u64; - - fn next(&mut self) -> Option { - self.seed = Self::A.wrapping_mul(self.seed).wrapping_add(Self::C) % Self::M; - Some(self.seed) - } -} - -impl Default for LcgRng { - fn default() -> Self { - let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards"); - Self { seed: time.as_secs() ^ time.subsec_nanos() as u64 } - } -} diff --git a/yazi-shared/src/scheme/cow.rs b/yazi-shared/src/scheme/cow.rs index 641c298d..7c92c371 100644 --- a/yazi-shared/src/scheme/cow.rs +++ b/yazi-shared/src/scheme/cow.rs @@ -30,6 +30,10 @@ impl From> for Scheme { fn from(value: SchemeCow<'_>) -> Self { value.into_owned() } } +impl PartialEq> for SchemeCow<'_> { + fn eq(&self, other: &SchemeRef) -> bool { self.as_scheme() == *other } +} + impl<'a> SchemeCow<'a> { pub fn regular(uri: usize, urn: usize) -> Self { SchemeRef::Regular { uri, urn }.into() } @@ -130,12 +134,7 @@ impl<'a> SchemeCow<'a> { fn decode_path(kind: SchemeKind, tilde: bool, bytes: &'a [u8]) -> Result> { let bytes: Cow<_> = if tilde { percent_decode(bytes).into() } else { bytes.into() }; - Ok(match kind { - SchemeKind::Regular => PathCow::from_os_bytes(bytes)?, - SchemeKind::Search => PathCow::from_os_bytes(bytes)?, - SchemeKind::Archive => PathCow::from_os_bytes(bytes)?, - SchemeKind::Sftp => PathCow::from_os_bytes(bytes)?, - }) + PathCow::with(kind, bytes) } fn normalize_ports( diff --git a/yazi-shared/src/scheme/scheme.rs b/yazi-shared/src/scheme/scheme.rs index 886dbca0..71d6a84f 100644 --- a/yazi-shared/src/scheme/scheme.rs +++ b/yazi-shared/src/scheme/scheme.rs @@ -28,4 +28,14 @@ impl Scheme { } } } + + #[inline] + pub fn with_ports(self, uri: usize, urn: usize) -> Self { + match self { + Self::Regular { .. } => Self::Regular { uri, urn }, + Self::Search { domain, .. } => Self::Search { domain, uri, urn }, + Self::Archive { domain, .. } => Self::Archive { domain, uri, urn }, + Self::Sftp { domain, .. } => Self::Sftp { domain, uri, urn }, + } + } } diff --git a/yazi-shared/src/strand/buf.rs b/yazi-shared/src/strand/buf.rs index f5b53849..f75f093d 100644 --- a/yazi-shared/src/strand/buf.rs +++ b/yazi-shared/src/strand/buf.rs @@ -1,8 +1,9 @@ -use std::ffi::OsString; +use std::{borrow::Cow, ffi::OsString}; +use anyhow::Result; use serde::Serialize; -use crate::{path::PathDyn, scheme::SchemeKind, strand::{AsStrand, Strand, StrandError, StrandKind}}; +use crate::{FromWtf8Vec, path::PathDyn, strand::{AsStrand, Strand, StrandCow, StrandError, StrandKind}}; // --- StrandBuf #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] @@ -21,6 +22,10 @@ impl From for StrandBuf { fn from(value: OsString) -> Self { Self::Os(value) } } +impl From<&str> for StrandBuf { + fn from(value: &str) -> Self { Self::Utf8(value.to_owned()) } +} + impl From for StrandBuf { fn from(value: String) -> Self { Self::Utf8(value) } } @@ -29,10 +34,15 @@ impl From> for StrandBuf { fn from(value: PathDyn) -> Self { match value { PathDyn::Os(p) => Self::Os(p.as_os_str().to_owned()), + PathDyn::Unix(p) => Self::Bytes(p.as_bytes().to_owned()), } } } +impl From> for StrandBuf { + fn from(value: StrandCow<'_>) -> Self { value.into_owned() } +} + impl PartialEq> for StrandBuf { fn eq(&self, other: &Strand<'_>) -> bool { self.as_strand() == *other } } @@ -49,8 +59,8 @@ impl StrandBuf { #[inline] pub unsafe fn from_encoded_bytes(kind: impl Into, bytes: Vec) -> Self { match kind.into() { - StrandKind::Os => Self::Os(unsafe { OsString::from_encoded_bytes_unchecked(bytes) }), StrandKind::Utf8 => Self::Utf8(unsafe { String::from_utf8_unchecked(bytes) }), + StrandKind::Os => Self::Os(unsafe { OsString::from_encoded_bytes_unchecked(bytes) }), StrandKind::Bytes => Self::Bytes(bytes), } } @@ -63,6 +73,39 @@ impl StrandBuf { } } + pub fn into_string_lossy(self) -> String { + match self { + Self::Os(s) => match s.to_string_lossy() { + Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(s.into_encoded_bytes()) }, + Cow::Owned(s) => s, + }, + Self::Utf8(s) => s, + Self::Bytes(b) => match String::from_utf8_lossy(&b) { + Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(b) }, + Cow::Owned(s) => s, + }, + } + } + + pub fn new(kind: impl Into) -> Self { Self::with_str(kind, "") } + + pub fn push_str(&mut self, s: impl AsRef) { + let s = s.as_ref(); + match self { + Self::Os(buf) => buf.push(s), + Self::Utf8(buf) => buf.push_str(s), + Self::Bytes(buf) => buf.extend(s.as_bytes()), + } + } + + pub fn reserve_exact(&mut self, additional: usize) { + match self { + Self::Os(buf) => buf.reserve_exact(additional), + Self::Utf8(buf) => buf.reserve_exact(additional), + Self::Bytes(buf) => buf.reserve_exact(additional), + } + } + pub fn try_push(&mut self, s: T) -> Result<(), StrandError> where T: AsStrand, @@ -75,11 +118,38 @@ impl StrandBuf { }) } - pub fn with_capacity(kind: SchemeKind, capacity: usize) -> Self { - use SchemeKind as K; - match kind { - K::Regular | K::Search | K::Archive => Self::Os(OsString::with_capacity(capacity)), - K::Sftp => Self::Os(OsString::with_capacity(capacity)), // FIXME + pub fn with(kind: K, bytes: Vec) -> Result + where + K: Into, + { + Ok(match kind.into() { + StrandKind::Utf8 => Self::Utf8(String::from_utf8(bytes)?), + StrandKind::Os => Self::Os(OsString::from_wtf8_vec(bytes)?), + StrandKind::Bytes => Self::Bytes(bytes), + }) + } + + pub fn with_capacity(kind: K, capacity: usize) -> Self + where + K: Into, + { + match kind.into() { + StrandKind::Utf8 => Self::Utf8(String::with_capacity(capacity)), + StrandKind::Os => Self::Os(OsString::with_capacity(capacity)), + StrandKind::Bytes => Self::Bytes(Vec::with_capacity(capacity)), + } + } + + pub fn with_str(kind: K, s: S) -> Self + where + K: Into, + S: Into, + { + let s = s.into(); + match kind.into() { + StrandKind::Utf8 => Self::Utf8(s), + StrandKind::Os => Self::Os(s.into()), + StrandKind::Bytes => Self::Bytes(s.into_bytes()), } } } diff --git a/yazi-shared/src/strand/conversion.rs b/yazi-shared/src/strand/conversion.rs index 5f21c101..e8e628e6 100644 --- a/yazi-shared/src/strand/conversion.rs +++ b/yazi-shared/src/strand/conversion.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}}; -use crate::{path::{PathBufDyn, PathDyn}, strand::{Strand, StrandBuf, StrandCow}}; +use crate::{path::{PathBufDyn, PathCow, PathDyn}, strand::{Strand, StrandBuf, StrandCow}, url::AsUrl}; // --- AsStrand pub trait AsStrand { @@ -11,14 +11,6 @@ impl AsStrand for [u8] { fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) } } -impl AsStrand for &[u8] { - fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) } -} - -impl AsStrand for Vec { - fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) } -} - impl AsStrand for str { fn as_strand(&self) -> Strand<'_> { Strand::Utf8(self) } } @@ -51,6 +43,10 @@ impl AsStrand for &std::path::Path { fn as_strand(&self) -> Strand<'_> { Strand::Os(self.as_os_str()) } } +impl AsStrand for crate::path::Components<'_> { + fn as_strand(&self) -> Strand<'_> { self.strand() } +} + impl AsStrand for Cow<'_, OsStr> { fn as_strand(&self) -> Strand<'_> { Strand::Os(self) } } @@ -59,14 +55,29 @@ impl AsStrand for PathDyn<'_> { fn as_strand(&self) -> Strand<'_> { match self { Self::Os(p) => Strand::Os(p.as_os_str()), + Self::Unix(p) => Strand::Bytes(p.as_bytes()), + } + } +} + +impl AsStrand for PathBufDyn { + fn as_strand(&self) -> Strand<'_> { + match self { + PathBufDyn::Os(p) => Strand::Os(p.as_os_str()), + PathBufDyn::Unix(p) => Strand::Bytes(p.as_bytes()), } } } impl AsStrand for &PathBufDyn { + fn as_strand(&self) -> Strand<'_> { (**self).as_strand() } +} + +impl AsStrand for PathCow<'_> { fn as_strand(&self) -> Strand<'_> { match self { - PathBufDyn::Os(p) => Strand::Os(p.as_os_str()), + Self::Borrowed(p) => p.as_strand(), + Self::Owned(p) => p.as_strand(), } } } @@ -101,3 +112,19 @@ impl AsStrand for StrandCow<'_> { impl AsStrand for &StrandCow<'_> { fn as_strand(&self) -> Strand<'_> { (**self).as_strand() } } + +// --- ToStrand +pub trait ToStrand { + fn to_strand(&self) -> StrandCow<'_>; +} + +impl ToStrand for String { + fn to_strand(&self) -> StrandCow<'_> { self.as_strand().into() } +} + +impl ToStrand for T +where + T: AsUrl, +{ + fn to_strand(&self) -> StrandCow<'_> { self.as_url().components().strand() } +} diff --git a/yazi-shared/src/strand/cow.rs b/yazi-shared/src/strand/cow.rs index 58b85aa1..f18f099b 100644 --- a/yazi-shared/src/strand/cow.rs +++ b/yazi-shared/src/strand/cow.rs @@ -2,14 +2,17 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}}; use anyhow::Result; -use crate::{IntoOsStr, scheme::SchemeKind, strand::{Strand, StrandBuf}}; +use crate::strand::{Strand, StrandBuf, StrandKind}; -// --- StrandCow pub enum StrandCow<'a> { Borrowed(Strand<'a>), Owned(StrandBuf), } +impl Default for StrandCow<'_> { + fn default() -> Self { Self::Borrowed(Strand::default()) } +} + impl From for StrandCow<'_> { fn from(value: OsString) -> Self { Self::Owned(StrandBuf::Os(value)) } } @@ -41,13 +44,6 @@ impl PartialEq> for StrandCow<'_> { } impl<'a> StrandCow<'a> { - pub fn from_os_bytes(bytes: impl Into>) -> Result { - Ok(match bytes.into().into_os_str()? { - Cow::Borrowed(s) => Strand::Os(s).into(), - Cow::Owned(s) => StrandBuf::Os(s).into(), - }) - } - pub fn into_owned(self) -> StrandBuf { match self { Self::Borrowed(s) => s.to_owned(), @@ -55,13 +51,21 @@ impl<'a> StrandCow<'a> { } } - pub fn with(kind: SchemeKind, bytes: T) -> Result - where - T: Into>, - { - match kind { - SchemeKind::Regular | SchemeKind::Search | SchemeKind::Archive => Self::from_os_bytes(bytes), - SchemeKind::Sftp => Self::from_os_bytes(bytes), // FIXME + pub fn into_string_lossy(self) -> String { + match self { + Self::Borrowed(s) => s.to_string_lossy().into_owned(), + Self::Owned(s) => s.into_string_lossy(), } } + + pub fn with(kind: K, bytes: T) -> Result + where + K: Into, + T: Into>, + { + Ok(match bytes.into() { + Cow::Borrowed(b) => Strand::with(kind, b)?.into(), + Cow::Owned(b) => StrandBuf::with(kind, b)?.into(), + }) + } } diff --git a/yazi-shared/src/strand/error.rs b/yazi-shared/src/strand/error.rs index 83e521da..d66e86d1 100644 --- a/yazi-shared/src/strand/error.rs +++ b/yazi-shared/src/strand/error.rs @@ -1,24 +1,14 @@ use thiserror::Error; -use crate::path::PathDynError; - -// --- StrandDynError +// --- StrandError #[derive(Debug, Error)] pub enum StrandError { - #[error("conversion to OsStr failed")] + #[error("conversion to OS string failed")] AsOs, - #[error("conversion to UTF-8 str failed")] + #[error("conversion to UTF-8 string failed")] AsUtf8, } -impl From for StrandError { - fn from(err: PathDynError) -> Self { - match err { - PathDynError::AsOs => Self::AsOs, - } - } -} - impl From for std::io::Error { fn from(err: StrandError) -> Self { Self::other(err) } } diff --git a/yazi-shared/src/strand/extensions.rs b/yazi-shared/src/strand/extensions.rs new file mode 100644 index 00000000..24e53cf4 --- /dev/null +++ b/yazi-shared/src/strand/extensions.rs @@ -0,0 +1,61 @@ +use crate::strand::{AsStrand, Strand, StrandBuf, StrandLike, ToStrand}; + +// --- StrandJoin +pub trait AsStrandJoin { + fn join(self, sep: Strand) -> StrandBuf; +} + +impl AsStrandJoin for T +where + T: IntoIterator, + T::Item: AsStrand, +{ + fn join(self, sep: Strand) -> StrandBuf { + let mut kind = sep.kind(); + let mut buf = Vec::new(); + for (i, item) in self.into_iter().enumerate() { + if i > 0 { + buf.extend(sep.encoded_bytes()); + } + + let s = item.as_strand(); + buf.extend(s.encoded_bytes()); + + if s.kind() > kind { + kind = s.kind(); + } + } + + unsafe { StrandBuf::from_encoded_bytes(kind, buf) } + } +} + +// --- ToStrandJoin +pub trait ToStrandJoin { + fn join(self, sep: Strand) -> StrandBuf; +} + +impl ToStrandJoin for T +where + T: IntoIterator, + T::Item: ToStrand, +{ + fn join(self, sep: Strand) -> StrandBuf { + let mut kind = sep.kind(); + let mut buf = Vec::new(); + for (i, item) in self.into_iter().enumerate() { + if i > 0 { + buf.extend(sep.encoded_bytes()); + } + + let s = item.to_strand(); + buf.extend(s.encoded_bytes()); + + if s.kind() > kind { + kind = s.kind(); + } + } + + unsafe { StrandBuf::from_encoded_bytes(kind, buf) } + } +} diff --git a/yazi-shared/src/strand/kind.rs b/yazi-shared/src/strand/kind.rs index 77c050aa..dd2e0883 100644 --- a/yazi-shared/src/strand/kind.rs +++ b/yazi-shared/src/strand/kind.rs @@ -1,5 +1,28 @@ +use crate::{path::PathKind, scheme::SchemeKind}; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum StrandKind { - Os, - Utf8, - Bytes, + Utf8 = 0, + Os = 1, + Bytes = 2, +} + +impl From for StrandKind { + fn from(value: PathKind) -> Self { + match value { + PathKind::Os => StrandKind::Os, + PathKind::Unix => StrandKind::Bytes, + } + } +} + +impl From for StrandKind { + fn from(value: SchemeKind) -> Self { + match value { + SchemeKind::Regular => Self::Os, + SchemeKind::Search => Self::Os, + SchemeKind::Archive => Self::Os, + SchemeKind::Sftp => Self::Os, // FIXME + } + } } diff --git a/yazi-shared/src/strand/mod.rs b/yazi-shared/src/strand/mod.rs index ca1ffd07..af6a08c4 100644 --- a/yazi-shared/src/strand/mod.rs +++ b/yazi-shared/src/strand/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(buf conversion cow error kind like r#dyn view); +yazi_macro::mod_flat!(buf conversion cow error extensions kind like strand view); diff --git a/yazi-shared/src/strand/dyn.rs b/yazi-shared/src/strand/strand.rs similarity index 74% rename from yazi-shared/src/strand/dyn.rs rename to yazi-shared/src/strand/strand.rs index 5512cb7a..92dc418f 100644 --- a/yazi-shared/src/strand/dyn.rs +++ b/yazi-shared/src/strand/strand.rs @@ -1,9 +1,11 @@ use std::{borrow::Cow, ffi::OsStr, fmt::Display}; +use anyhow::Result; + use crate::{BytesExt, FromWtf8, strand::{AsStrand, StrandBuf, StrandError, StrandKind}}; // --- Strand -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd)] pub enum Strand<'p> { Os(&'p OsStr), Utf8(&'p str), @@ -22,6 +24,10 @@ impl<'a> From<&'a str> for Strand<'a> { fn from(value: &'a str) -> Self { Self::Utf8(value) } } +impl<'a> From<&'a [u8]> for Strand<'a> { + fn from(value: &'a [u8]) -> Self { Self::Bytes(value) } +} + impl<'a> From<&'a StrandBuf> for Strand<'a> { fn from(value: &'a StrandBuf) -> Self { match value { @@ -32,12 +38,42 @@ impl<'a> From<&'a StrandBuf> for Strand<'a> { } } +impl PartialEq for Strand<'_> { + fn eq(&self, other: &Self) -> bool { + match *other { + Self::Os(s) => *self == s, + Self::Utf8(s) => *self == s, + Self::Bytes(b) => *self == b, + } + } +} + +impl PartialEq<&OsStr> for Strand<'_> { + fn eq(&self, other: &&OsStr) -> bool { + match *self { + Self::Os(s) => s == *other, + Self::Utf8(s) => s == *other, + Self::Bytes(b) => b == other.as_encoded_bytes(), + } + } +} + impl PartialEq<&str> for Strand<'_> { fn eq(&self, other: &&str) -> bool { - match self { - Self::Os(s) => s == other, - Self::Utf8(s) => s == other, - Self::Bytes(b) => *b == other.as_bytes(), + match *self { + Self::Os(s) => s == *other, + Self::Utf8(s) => s == *other, + Self::Bytes(b) => b == other.as_bytes(), + } + } +} + +impl PartialEq<&[u8]> for Strand<'_> { + fn eq(&self, other: &&[u8]) -> bool { + match *self { + Self::Os(s) => s.as_encoded_bytes() == *other, + Self::Utf8(s) => s.as_bytes() == *other, + Self::Bytes(b) => b == *other, } } } @@ -102,6 +138,7 @@ impl<'a> Strand<'a> { D(self) } + #[inline] pub fn encoded_bytes(self) -> &'a [u8] { match self { Self::Os(s) => s.as_encoded_bytes(), @@ -117,8 +154,8 @@ impl<'a> Strand<'a> { #[inline] pub unsafe fn from_encoded_bytes(kind: impl Into, bytes: &'a [u8]) -> Self { match kind.into() { - StrandKind::Os => Self::Os(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }), StrandKind::Utf8 => Self::Utf8(unsafe { str::from_utf8_unchecked(bytes) }), + StrandKind::Os => Self::Os(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }), StrandKind::Bytes => Self::Bytes(bytes), } } @@ -127,8 +164,8 @@ impl<'a> Strand<'a> { pub fn kind(self) -> StrandKind { match self { - Self::Os(_) => StrandKind::Os, Self::Utf8(_) => StrandKind::Utf8, + Self::Os(_) => StrandKind::Os, Self::Bytes(_) => StrandKind::Bytes, } } @@ -152,4 +189,17 @@ impl<'a> Strand<'a> { } pub fn to_string_lossy(self) -> Cow<'a, str> { String::from_utf8_lossy(self.encoded_bytes()) } + + pub fn with(kind: K, strand: &'a S) -> Result + where + K: Into, + S: ?Sized + AsStrand, + { + let strand = strand.as_strand(); + Ok(match kind.into() { + StrandKind::Utf8 => Self::Utf8(strand.as_utf8()?), + StrandKind::Os => Self::Os(strand.as_os()?), + StrandKind::Bytes => Self::Bytes(strand.encoded_bytes()), + }) + } } diff --git a/yazi-shared/src/strand/view.rs b/yazi-shared/src/strand/view.rs index 454f713b..3a3e7a35 100644 --- a/yazi-shared/src/strand/view.rs +++ b/yazi-shared/src/strand/view.rs @@ -1,16 +1,10 @@ use std::ffi::OsStr; -use crate::{path::PathDyn, strand::Strand}; - // --- AsStrandView pub trait AsStrandView<'a, T> { fn as_strand_view(self) -> T; } -impl<'a> AsStrandView<'a, &'a OsStr> for &'a str { - fn as_strand_view(self) -> &'a OsStr { OsStr::new(self) } -} - impl<'a> AsStrandView<'a, &'a OsStr> for &'a OsStr { fn as_strand_view(self) -> &'a OsStr { self } } @@ -22,23 +16,3 @@ impl<'a> AsStrandView<'a, &'a OsStr> for &'a std::path::Path { impl<'a> AsStrandView<'a, &'a OsStr> for std::path::Components<'a> { fn as_strand_view(self) -> &'a OsStr { self.as_path().as_os_str() } } - -impl<'a> AsStrandView<'a, Strand<'a>> for &'a str { - fn as_strand_view(self) -> Strand<'a> { Strand::Utf8(self) } -} - -impl<'a> AsStrandView<'a, Strand<'a>> for &'a std::path::Path { - fn as_strand_view(self) -> Strand<'a> { Strand::Os(self.as_os_str()) } -} - -impl<'a> AsStrandView<'a, Strand<'a>> for std::path::Components<'a> { - fn as_strand_view(self) -> Strand<'a> { Strand::Os(self.as_path().as_os_str()) } -} - -impl<'a> AsStrandView<'a, Strand<'a>> for PathDyn<'a> { - fn as_strand_view(self) -> Strand<'a> { - match self { - Self::Os(p) => Strand::Os(p.as_os_str()), - } - } -} diff --git a/yazi-shared/src/string.rs b/yazi-shared/src/string.rs deleted file mode 100644 index 20f961a5..00000000 --- a/yazi-shared/src/string.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::{borrow::Cow, ffi::{OsStr, OsString}}; - -use crate::url::{UrlBuf, UrlLike}; - -pub trait IntoStringLossy { - fn into_string_lossy(self) -> String; -} - -impl IntoStringLossy for String { - fn into_string_lossy(self) -> String { self } -} - -impl IntoStringLossy for &OsStr { - fn into_string_lossy(self) -> String { self.to_string_lossy().into_owned() } -} - -impl IntoStringLossy for OsString { - fn into_string_lossy(self) -> String { - match self.to_string_lossy() { - Cow::Owned(s) => s, - Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(self.into_encoded_bytes()) }, - } - } -} - -impl IntoStringLossy for Cow<'_, OsStr> { - fn into_string_lossy(self) -> String { - match self { - Cow::Owned(s) => s.into_string_lossy(), - Cow::Borrowed(s) => s.into_string_lossy(), - } - } -} - -impl IntoStringLossy for &UrlBuf { - fn into_string_lossy(self) -> String { self.os_str().into_string_lossy() } -} diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index 4206a177..9986ab46 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::{Debug, Formatter}, path::{Path, PathBuf}, str::From use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::{loc::LocBuf, path::{PathBufDyn, PathBufDynError, PathDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::SchemeKind, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlLike}}; +use crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::{Scheme, SchemeKind}, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlLike}}; #[derive(Clone, Eq, Hash, PartialEq)] pub enum UrlBuf { @@ -53,14 +53,6 @@ impl From<&Path> for UrlBuf { fn from(path: &Path) -> Self { path.to_path_buf().into() } } -impl From> for UrlBuf { - fn from(path: PathDyn) -> Self { - match path { - PathDyn::Os(p) => p.to_path_buf().into(), - } - } -} - impl FromStr for UrlBuf { type Err = anyhow::Error; @@ -75,6 +67,14 @@ impl TryFrom for UrlBuf { } } +impl TryFrom<(Scheme, PathBufDyn)> for UrlBuf { + type Error = anyhow::Error; + + fn try_from(value: (Scheme, PathBufDyn)) -> Result { + Ok(UrlCow::try_from(value)?.into_owned()) + } +} + impl AsRef for UrlBuf { fn as_ref(&self) -> &Self { self } } @@ -155,7 +155,7 @@ impl UrlBuf { pub fn to_regular(&self) -> Result { Ok(self.as_url().as_regular()?.into()) } #[inline] - pub fn into_regular(self) -> Result { + pub fn into_regular(self) -> Result { Ok(Self::Regular(self.into_loc().into_os()?.into())) } @@ -172,7 +172,7 @@ impl UrlBuf { } #[inline] - pub fn into_search(self, domain: impl AsRef) -> Result { + pub fn into_search(self, domain: impl AsRef) -> Result { Ok(Self::Search { loc: LocBuf::::zeroed(self.into_loc().into_os()?), domain: Pool::::intern(domain), diff --git a/yazi-shared/src/url/component.rs b/yazi-shared/src/url/component.rs index fdba0425..37835a61 100644 --- a/yazi-shared/src/url/component.rs +++ b/yazi-shared/src/url/component.rs @@ -1,8 +1,8 @@ -use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not, path::{self, PathBuf, PrefixComponent}}; +use std::path::PrefixComponent; use anyhow::Result; -use crate::{path::{PathBufDyn, PathCow}, scheme::SchemeRef, url::{Encode, Url, UrlBuf, UrlCow}}; +use crate::{path::{self, PathDynError}, scheme::SchemeRef, strand::Strand}; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Component<'a> { @@ -11,171 +11,67 @@ pub enum Component<'a> { RootDir, CurDir, ParentDir, - Normal(&'a OsStr), + Normal(Strand<'a>), } impl<'a> From> for Component<'a> { - fn from(comp: path::Component<'a>) -> Self { - match comp { - path::Component::Prefix(p) => Component::Prefix(p), - path::Component::RootDir => Component::RootDir, - path::Component::CurDir => Component::CurDir, - path::Component::ParentDir => Component::ParentDir, - path::Component::Normal(s) => Component::Normal(s), + fn from(value: path::Component<'a>) -> Self { + match value { + path::Component::Prefix(p) => Self::Prefix(p), + path::Component::RootDir => Self::RootDir, + path::Component::CurDir => Self::CurDir, + path::Component::ParentDir => Self::ParentDir, + path::Component::Normal(s) => Self::Normal(s), } } } -impl<'a> FromIterator> for Result { - fn from_iter>>(iter: I) -> Self { - let mut buf = PathBuf::new(); - let mut scheme = None; - iter.into_iter().for_each(|c| match c { - Component::Scheme(s) => scheme = Some(s), - Component::Prefix(p) => buf.push(path::Component::Prefix(p)), - Component::RootDir => buf.push(path::Component::RootDir), - Component::CurDir => buf.push(path::Component::CurDir), - Component::ParentDir => buf.push(path::Component::ParentDir), - Component::Normal(s) => buf.push(path::Component::Normal(s)), - }); - - Ok(if let Some(s) = scheme { - UrlCow::try_from((s, PathCow::Owned(PathBufDyn::Os(buf))))?.into_owned() - } else { - buf.into() +impl<'a> Component<'a> { + pub fn downgrade(self) -> Option> { + Some(match self { + Self::Scheme(_) => None?, + Self::Prefix(p) => path::Component::Prefix(p), + Self::RootDir => path::Component::RootDir, + Self::CurDir => path::Component::CurDir, + Self::ParentDir => path::Component::ParentDir, + Self::Normal(s) => path::Component::Normal(s), }) } } -impl<'a> FromIterator> for PathBuf { +// impl<'a> FromIterator> for Result { +// fn from_iter>>(iter: I) -> Self { +// let mut buf = PathBuf::new(); +// let mut scheme = None; +// iter.into_iter().for_each(|c| match c { +// Component::Scheme(s) => scheme = Some(s), +// Component::Prefix(p) => buf.push(path::Component::Prefix(p)), +// Component::RootDir => buf.push(path::Component::RootDir), +// Component::CurDir => buf.push(path::Component::CurDir), +// Component::ParentDir => buf.push(path::Component::ParentDir), +// Component::Normal(s) => buf.push(path::Component::Normal(s)), +// }); + +// Ok(if let Some(s) = scheme { +// UrlCow::try_from((s, PathCow::Owned(PathBufDyn::Os(buf))))?.into_owned() +// } else { +// buf.into() +// }) +// } +// } + +impl<'a> FromIterator> for Result { fn from_iter>>(iter: I) -> Self { - let mut buf = Self::new(); - iter.into_iter().for_each(|c| match c { - Component::Scheme(_) => {} - Component::Prefix(p) => buf.push(path::Component::Prefix(p)), - Component::RootDir => buf.push(path::Component::RootDir), - Component::CurDir => buf.push(path::Component::CurDir), - Component::ParentDir => buf.push(path::Component::ParentDir), - Component::Normal(s) => buf.push(path::Component::Normal(s)), - }); - buf + iter.into_iter().filter_map(|c| c.downgrade()).map(std::path::Component::try_from).collect() } } -// --- Components -#[derive(Clone)] -pub struct Components<'a> { - inner: path::Components<'a>, - url: Url<'a>, - scheme_yielded: bool, -} - -impl<'a> From> for Components<'a> { - fn from(value: Url<'a>) -> Self { - Self { inner: value.loc().components(), url: value, scheme_yielded: false } - } -} - -impl<'a> Components<'a> { - pub fn os_str(&self) -> Cow<'a, OsStr> { - let path = self.inner.as_path(); - if self.url.kind().is_local() || self.scheme_yielded { - return path.as_os_str().into(); - } - - let mut s = OsString::from(Encode(self.url).to_string()); - s.reserve_exact(path.as_os_str().len()); - s.push(path); - s.into() - } - - pub fn covariant(&self, other: &Self) -> bool { - match (self.scheme_yielded, other.scheme_yielded) { - (false, false) => {} - (true, true) if self.url.scheme().covariant(other.url.scheme()) => {} - _ => return false, - } - self.inner == other.inner - } -} - -impl<'a> Iterator for Components<'a> { - type Item = Component<'a>; - - fn next(&mut self) -> Option { - if !self.scheme_yielded { - self.scheme_yielded = true; - Some(Component::Scheme(self.url.scheme())) - } else { - self.inner.next().map(Into::into) - } - } - - fn size_hint(&self) -> (usize, Option) { - let (min, max) = self.inner.size_hint(); - let scheme = self.scheme_yielded.not() as usize; - - (min + scheme, max.map(|n| n + scheme)) - } -} - -impl<'a> DoubleEndedIterator for Components<'a> { - fn next_back(&mut self) -> Option { - if let Some(comp) = self.inner.next_back() { - Some(comp.into()) - } else if !self.scheme_yielded { - self.scheme_yielded = true; - Some(Component::Scheme(self.url.scheme())) - } else { - None - } - } -} - -impl<'a> FusedIterator for Components<'a> {} - -impl<'a> PartialEq for Components<'a> { - fn eq(&self, other: &Self) -> bool { - Some(self.url.scheme()).filter(|_| !self.scheme_yielded) - == Some(other.url.scheme()).filter(|_| !other.scheme_yielded) - && self.inner == other.inner - } -} - -// --- Tests -#[cfg(test)] -mod tests { - use std::path::Path; - - use anyhow::Result; - - use super::*; - use crate::url::UrlLike; - - #[test] - fn test_collect() -> Result<()> { - crate::init_tests(); - - let search: UrlBuf = "search://keyword//root/projects/yazi".parse()?; - assert_eq!(search.uri(), ""); - assert_eq!(search.scheme(), SchemeRef::Search { domain: "keyword", uri: 0, urn: 0 }); - - let item = search.try_join("main.rs")?; - assert_eq!(item.uri(), "main.rs"); - assert_eq!(item.scheme(), SchemeRef::Search { domain: "keyword", uri: 1, urn: 1 }); - - let u: UrlBuf = item.components().take(4).collect::>()?; - assert_eq!(u.scheme(), SchemeRef::Search { domain: "keyword", uri: 0, urn: 0 }); - assert_eq!(u.loc(), Path::new("/root/projects")); - - let u: UrlBuf = item - .components() - .take(5) - .chain([Component::Normal(OsStr::new("target/release/yazi"))]) - .collect::>()?; - assert_eq!(u.scheme(), SchemeRef::Search { domain: "keyword", uri: 0, urn: 0 }); - assert_eq!(u.loc(), Path::new("/root/projects/yazi/target/release/yazi")); - - Ok(()) +impl<'a> FromIterator> for Result { + fn from_iter>>(iter: I) -> Self { + iter + .into_iter() + .filter_map(|c| c.downgrade()) + .map(typed_path::UnixComponent::try_from) + .collect() } } diff --git a/yazi-shared/src/url/components.rs b/yazi-shared/src/url/components.rs new file mode 100644 index 00000000..987bbf53 --- /dev/null +++ b/yazi-shared/src/url/components.rs @@ -0,0 +1,196 @@ +use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not}; + +use crate::{loc::Loc, path, scheme::{Encode as EncodeScheme, SchemeCow, SchemeRef}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}}; + +#[derive(Clone)] +pub struct Components<'a> { + inner: path::Components<'a>, + url: Url<'a>, + back_yields: usize, + scheme_yielded: bool, +} + +impl<'a> From> for Components<'a> { + fn from(value: Url<'a>) -> Self { + Self { + inner: value.loc().components(), + url: value, + back_yields: 0, + scheme_yielded: false, + } + } +} + +impl<'a> Components<'a> { + pub fn covariant(&self, other: &Self) -> bool { + match (self.scheme_yielded, other.scheme_yielded) { + (true, true) => {} + (false, false) if self.scheme().covariant(other.scheme()) => {} + _ => return false, + } + self.inner == other.inner + } + + pub fn os_str(&self) -> Cow<'a, OsStr> { + let Ok(path) = self.inner.path().as_os() else { + return OsString::from(EncodeUrl(self.url()).to_string()).into(); + }; + + if self.url.is_regular() || self.scheme_yielded { + return path.as_os_str().into(); + } + + let mut s = OsString::from(EncodeScheme(self.url()).to_string()); + s.reserve_exact(path.as_os_str().len()); + s.push(path); + s.into() + } + + pub fn scheme(&self) -> SchemeRef<'a> { + let left = self.inner.clone().count(); + + let (uri, urn) = SchemeCow::retrieve_ports(self.url); + let (uri, urn) = ( + uri.saturating_sub(self.back_yields).min(left), + urn.saturating_sub(self.back_yields).min(left), + ); + + match self.url { + Url::Regular(_) => SchemeRef::Regular { uri, urn }, + Url::Search { domain, .. } => SchemeRef::Search { domain, uri, urn }, + Url::Archive { domain, .. } => SchemeRef::Archive { domain, uri, urn }, + Url::Sftp { domain, .. } => SchemeRef::Sftp { domain, uri, urn }, + } + } + + pub fn strand(&self) -> StrandCow<'a> { + let s = self.inner.strand(); + if self.url.is_regular() || self.scheme_yielded { + return s.into(); + } + + let mut buf = StrandBuf::with_str(s.kind(), EncodeScheme(self.url()).to_string()); + buf.reserve_exact(s.len()); + buf.try_push(s).expect("strand with same kind"); + buf.into() + } + + pub fn url(&self) -> Url<'a> { + let path = self.inner.path(); + let (uri, urn) = self.scheme().ports(); + match self.url { + Url::Regular(_) => Url::Regular(Loc::with(path.as_os().unwrap(), uri, urn).unwrap()), + Url::Search { domain, .. } => { + Url::Search { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } + } + Url::Archive { domain, .. } => { + Url::Archive { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } + } + Url::Sftp { domain, .. } => { + Url::Sftp { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } + } + } + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option { + if !self.scheme_yielded { + self.scheme_yielded = true; + Some(Component::Scheme(self.scheme())) + } else { + self.inner.next().map(Into::into) + } + } + + fn size_hint(&self) -> (usize, Option) { + let (min, max) = self.inner.size_hint(); + let scheme = self.scheme_yielded.not() as usize; + + (min + scheme, max.map(|n| n + scheme)) + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option { + if let Some(c) = self.inner.next_back() { + self.back_yields += 1; + Some(c.into()) + } else if !self.scheme_yielded { + self.scheme_yielded = true; + Some(Component::Scheme(self.scheme())) + } else { + None + } + } +} + +impl<'a> FusedIterator for Components<'a> {} + +impl<'a> PartialEq for Components<'a> { + fn eq(&self, other: &Self) -> bool { + if self.inner != other.inner { + return false; + } + match (self.scheme_yielded, other.scheme_yielded) { + (true, true) => true, + (false, false) if self.scheme() == other.scheme() => true, + _ => false, + } + } +} + +// --- Tests +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::{scheme::SchemeRef, url::{Component, UrlBuf, UrlLike}}; + + #[test] + fn test_url() -> Result<()> { + use Component::*; + use SchemeRef as S; + + crate::init_tests(); + + let search: UrlBuf = "search://keyword//root/projects/yazi".parse()?; + assert_eq!(search.uri(), ""); + assert_eq!(search.scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + + let src = search.try_join("src")?; + assert_eq!(src.uri(), "src"); + assert_eq!(src.scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 }); + + let main = src.try_join("main.rs")?; + assert_eq!(main.urn(), "src/main.rs"); + assert_eq!(main.scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 }); + + let mut it = main.components(); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 }); + assert_eq!(it.next_back(), Some(Normal("main.rs".into()))); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 }); + assert_eq!(it.next_back(), Some(Normal("src".into()))); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(it.next_back(), Some(Normal("yazi".into()))); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + + let mut it = main.components(); + assert_eq!(it.next(), Some(Scheme(S::Search { domain: "keyword", uri: 2, urn: 2 }))); + assert_eq!(it.next(), Some(RootDir)); + assert_eq!(it.next(), Some(Normal("root".into()))); + assert_eq!(it.next(), Some(Normal("projects".into()))); + assert_eq!(it.next(), Some(Normal("yazi".into()))); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 }); + assert_eq!(it.next(), Some(Normal("src".into()))); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 }); + assert_eq!(it.next_back(), Some(Normal("main.rs".into()))); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(it.next(), None); + assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + + Ok(()) + } +} diff --git a/yazi-shared/src/url/display.rs b/yazi-shared/src/url/display.rs index 176600b6..ad797709 100644 --- a/yazi-shared/src/url/display.rs +++ b/yazi-shared/src/url/display.rs @@ -1,18 +1,12 @@ use crate::{scheme::Encode, url::Url}; -pub struct Display<'a> { - inner: Url<'a>, -} +pub struct Display<'a>(pub Url<'a>); -impl<'a> From> for Display<'a> { - fn from(value: Url<'a>) -> Self { Self { inner: value } } -} - -impl<'a> std::fmt::Display for Display<'a> { +impl std::fmt::Display for Display<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let (kind, loc) = (self.inner.kind(), self.inner.loc()); + let (kind, loc) = (self.0.kind(), self.0.loc()); if kind.is_virtual() { - Encode(self.inner).fmt(f)?; + Encode(self.0).fmt(f)?; } loc.display().fmt(f) } diff --git a/yazi-shared/src/url/encode.rs b/yazi-shared/src/url/encode.rs index fe2a9d3f..1695e50e 100644 --- a/yazi-shared/src/url/encode.rs +++ b/yazi-shared/src/url/encode.rs @@ -4,7 +4,6 @@ use percent_encoding::{CONTROLS, percent_encode}; use crate::url::Url; -// --- Tilded #[derive(Clone, Copy)] pub struct Encode<'a>(pub Url<'a>); diff --git a/yazi-shared/src/url/like.rs b/yazi-shared/src/url/like.rs new file mode 100644 index 00000000..46c3544f --- /dev/null +++ b/yazi-shared/src/url/like.rs @@ -0,0 +1,75 @@ +use std::{borrow::Cow, ffi::OsStr, path::Path}; + +use anyhow::Result; + +use crate::{path::{AsPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, scheme::{SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, Display, Url, UrlBuf, UrlCow}}; + +pub trait UrlLike +where + Self: AsUrl, +{ + fn as_local(&self) -> Option<&Path> { self.as_url().as_local() } + + fn base(&self) -> Url<'_> { self.as_url().base() } + + fn components(&self) -> Components<'_> { self.as_url().into() } + + fn covariant(&self, other: impl AsUrl) -> bool { self.as_url().covariant(other) } + + fn display(&self) -> Display<'_> { Display(self.as_url()) } + + fn ext(&self) -> Option> { self.as_url().ext() } + + fn has_base(&self) -> bool { self.as_url().has_base() } + + fn has_root(&self) -> bool { self.as_url().has_root() } + + fn has_trail(&self) -> bool { self.as_url().has_trail() } + + fn is_absolute(&self) -> bool { self.as_url().is_absolute() } + + fn kind(&self) -> SchemeKind { self.as_url().kind() } + + fn loc(&self) -> PathDyn<'_> { self.as_url().loc() } + + fn name(&self) -> Option> { self.as_url().name() } + + fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() } + + fn pair(&self) -> Option<(Url<'_>, PathDyn<'_>)> { self.as_url().pair() } + + fn parent(&self) -> Option> { self.as_url().parent() } + + fn scheme(&self) -> SchemeRef<'_> { self.as_url().scheme() } + + fn stem(&self) -> Option> { self.as_url().stem() } + + fn trail(&self) -> Url<'_> { self.as_url().trail() } + + fn try_ends_with(&self, child: impl AsUrl) -> Result { + self.as_url().try_ends_with(child) + } + + fn try_join(&self, path: impl AsStrand) -> Result { + self.as_url().try_join(path) + } + + fn try_replace<'a>(&self, take: usize, path: impl AsPathRef<'a>) -> Result> { + self.as_url().try_replace(take, path) + } + + fn try_starts_with(&self, base: impl AsUrl) -> Result { + self.as_url().try_starts_with(base) + } + + fn try_strip_prefix(&self, base: impl AsUrl) -> Result, StripPrefixError> { + self.as_url().try_strip_prefix(base) + } + + fn uri(&self) -> PathDyn<'_> { self.as_url().uri() } + + fn urn(&self) -> PathDyn<'_> { self.as_url().urn() } +} + +impl UrlLike for UrlBuf {} +impl UrlLike for UrlCow<'_> {} diff --git a/yazi-shared/src/url/mod.rs b/yazi-shared/src/url/mod.rs index c94d033a..207f15eb 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 traits url); +yazi_macro::mod_flat!(buf component components cov cow display encode like traits url); diff --git a/yazi-shared/src/url/traits.rs b/yazi-shared/src/url/traits.rs index 717cb0b7..0b0c5d73 100644 --- a/yazi-shared/src/url/traits.rs +++ b/yazi-shared/src/url/traits.rs @@ -1,8 +1,6 @@ -use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; +use std::path::{Path, PathBuf}; -use anyhow::Result; - -use crate::{loc::Loc, path::{AsPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, scheme::{SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{Components, Display, Url, UrlBuf, UrlCow}}; +use crate::{loc::Loc, url::{Url, UrlBuf, UrlCow}}; // --- AsUrl pub trait AsUrl { @@ -29,15 +27,6 @@ impl AsUrl for &PathBuf { fn as_url(&self) -> Url<'_> { (*self).as_path().as_url() } } -impl AsUrl for PathDyn<'_> { - #[inline] - fn as_url(&self) -> Url<'_> { - match *self { - Self::Os(p) => p.as_url(), - } - } -} - impl AsUrl for Url<'_> { #[inline] fn as_url(&self) -> Url<'_> { *self } @@ -82,10 +71,13 @@ impl AsUrl for UrlCow<'_> { } impl AsUrl for &UrlCow<'_> { - #[inline] fn as_url(&self) -> Url<'_> { (**self).as_url() } } +impl AsUrl for super::Components<'_> { + fn as_url(&self) -> Url<'_> { self.url() } +} + impl<'a, T> From<&'a T> for Url<'a> where T: AsUrl + ?Sized, @@ -99,74 +91,3 @@ where { fn from(value: &'a mut T) -> Self { value.as_url() } } - -// UrlLike -pub trait UrlLike -where - Self: AsUrl, -{ - fn as_local(&self) -> Option<&Path> { self.as_url().as_local() } - - fn base(&self) -> Url<'_> { self.as_url().base() } - - fn components(&self) -> Components<'_> { self.as_url().into() } - - fn covariant(&self, other: impl AsUrl) -> bool { self.as_url().covariant(other) } - - fn display(&self) -> Display<'_> { self.as_url().into() } - - fn ext(&self) -> Option> { self.as_url().ext() } - - fn has_base(&self) -> bool { self.as_url().has_base() } - - fn has_root(&self) -> bool { self.as_url().has_root() } - - fn has_trail(&self) -> bool { self.as_url().has_trail() } - - fn is_absolute(&self) -> bool { self.as_url().is_absolute() } - - fn kind(&self) -> SchemeKind { self.as_url().kind() } - - fn loc(&self) -> PathDyn<'_> { self.as_url().loc() } - - fn name(&self) -> Option> { self.as_url().name() } - - fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() } - - fn pair(&self) -> Option<(Url<'_>, PathDyn<'_>)> { self.as_url().pair() } - - fn parent(&self) -> Option> { self.as_url().parent() } - - fn scheme(&self) -> SchemeRef<'_> { self.as_url().scheme() } - - fn stem(&self) -> Option> { self.as_url().stem() } - - fn trail(&self) -> Url<'_> { self.as_url().trail() } - - fn try_ends_with(&self, child: impl AsUrl) -> Result { - self.as_url().try_ends_with(child) - } - - fn try_join(&self, path: impl AsStrand) -> Result { - self.as_url().try_join(path) - } - - fn try_replace<'a>(&self, take: usize, path: impl AsPathRef<'a>) -> Result> { - self.as_url().try_replace(take, path) - } - - fn try_starts_with(&self, base: impl AsUrl) -> Result { - self.as_url().try_starts_with(base) - } - - fn try_strip_prefix(&self, base: impl AsUrl) -> Result, StripPrefixError> { - self.as_url().try_strip_prefix(base) - } - - fn uri(&self) -> PathDyn<'_> { self.as_url().uri() } - - fn urn(&self) -> PathDyn<'_> { self.as_url().urn() } -} - -impl UrlLike for UrlBuf {} -impl UrlLike for UrlCow<'_> {} diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index d0e86fbb..86795dbf 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -39,15 +39,85 @@ impl Debug for Url<'_> { impl Serialize for Url<'_> { fn serialize(&self, serializer: S) -> Result { let (kind, loc) = (self.kind(), self.loc()); - match (kind.is_virtual(), loc.to_str()) { - (false, Ok(s)) => serializer.serialize_str(s), - (true, Ok(s)) => serializer.serialize_str(&format!("{}{s}", EncodeScheme(*self))), + match (kind == SchemeKind::Regular, loc.to_str()) { + (true, Ok(s)) => serializer.serialize_str(s), + (false, Ok(s)) => serializer.serialize_str(&format!("{}{s}", EncodeScheme(*self))), (_, Err(_)) => serializer.collect_str(&EncodeUrl(*self)), } } } impl<'a> Url<'a> { + #[inline] + pub fn as_local(self) -> Option<&'a Path> { + self.loc().as_os().ok().filter(|_| self.kind().is_local()) + } + + #[inline] + pub fn as_regular(self) -> Result { + Ok(Self::Regular(Loc::bare(self.loc().as_os()?))) + } + + pub fn base(self) -> Self { + match self { + Self::Regular(loc) => Self::Regular(Loc::bare(loc.base())), + Self::Search { loc, domain } => Self::Search { loc: Loc::bare(loc.base()), domain }, + Self::Archive { loc, domain } => Self::Archive { loc: Loc::bare(loc.base()), domain }, + Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.base()), domain }, + } + } + + #[inline] + pub fn components(self) -> Components<'a> { Components::from(self) } + + #[inline] + pub fn covariant(self, other: impl AsUrl) -> bool { + let other = other.as_url(); + self.loc() == other.loc() && self.scheme().covariant(other.scheme()) + } + + #[inline] + pub fn ext(self) -> Option> { + Some(match self { + Self::Regular(loc) => loc.extension()?.as_strand(), + Self::Search { loc, .. } => loc.extension()?.as_strand(), + Self::Archive { loc, .. } => loc.extension()?.as_strand(), + Self::Sftp { loc, .. } => loc.extension()?.as_strand(), + }) + } + + #[inline] + pub fn has_base(self) -> bool { + match self { + Self::Regular(loc) => loc.has_base(), + Self::Search { loc, .. } => loc.has_base(), + Self::Archive { loc, .. } => loc.has_base(), + Self::Sftp { loc, .. } => loc.has_base(), + } + } + + #[inline] + pub fn has_root(self) -> bool { self.loc().has_root() } + + #[inline] + pub fn has_trail(self) -> bool { + match self { + Self::Regular(loc) => loc.has_trail(), + Self::Search { loc, .. } => loc.has_trail(), + Self::Archive { loc, .. } => loc.has_trail(), + Self::Sftp { loc, .. } => loc.has_trail(), + } + } + + #[inline] + pub fn is_absolute(self) -> bool { self.loc().is_absolute() } + + #[inline] + pub fn is_regular(self) -> bool { matches!(self, Self::Regular(_)) } + + #[inline] + pub fn is_search(self) -> bool { matches!(self, Self::Search { .. }) } + #[inline] pub fn kind(&self) -> SchemeKind { match self { @@ -68,6 +138,54 @@ impl<'a> Url<'a> { } } + #[inline] + pub fn name(self) -> Option> { + Some(match self { + Self::Regular(loc) => loc.file_name()?.as_strand(), + Self::Search { loc, .. } => loc.file_name()?.as_strand(), + Self::Archive { loc, .. } => loc.file_name()?.as_strand(), + Self::Sftp { loc, .. } => loc.file_name()?.as_strand(), + }) + } + + #[inline] + pub fn os_str(self) -> Cow<'a, OsStr> { self.components().os_str() } + + #[inline] + pub fn pair(self) -> Option<(Self, PathDyn<'a>)> { Some((self.parent()?, self.urn())) } + + pub fn parent(self) -> Option { + let uri = self.uri(); + + Some(match self { + // Regular + Self::Regular(loc) => Self::regular(loc.parent()?), + + // Search + Self::Search { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), + Self::Search { loc, domain } => { + Self::Search { loc: Loc::new(loc.parent()?, loc.base(), loc.base()), domain } + } + + // Archive + Self::Archive { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), + Self::Archive { loc, domain } if uri.components().nth(1).is_none() => { + Self::Archive { loc: Loc::zeroed(loc.parent()?), domain } + } + Self::Archive { loc, domain } => { + Self::Archive { loc: Loc::floated(loc.parent()?, loc.base()), domain } + } + + // SFTP + Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.parent()?), domain }, + }) + } + + #[inline] + pub fn regular + ?Sized>(path: &'a T) -> Self { + Self::Regular(Loc::bare(path.as_ref())) + } + #[inline] pub fn scheme(self) -> SchemeRef<'a> { let (uri, urn) = SchemeCow::retrieve_ports(self); @@ -80,30 +198,47 @@ impl<'a> Url<'a> { } #[inline] - pub fn regular + ?Sized>(path: &'a T) -> Self { - Self::Regular(Loc::bare(path.as_ref())) + pub fn stem(self) -> Option> { + Some(match self { + Self::Regular(loc) => loc.file_stem()?.as_strand(), + Self::Search { loc, .. } => loc.file_stem()?.as_strand(), + Self::Archive { loc, .. } => loc.file_stem()?.as_strand(), + Self::Sftp { loc, .. } => loc.file_stem()?.as_strand(), + }) } - #[inline] - pub fn is_regular(self) -> bool { matches!(self, Self::Regular(_)) } - - #[inline] - pub fn as_regular(self) -> Result { - Ok(Self::Regular(Loc::bare(self.loc().as_os()?))) - } - - #[inline] - pub fn is_search(self) -> bool { matches!(self, Self::Search { .. }) } - - #[inline] - pub fn is_absolute(self) -> bool { self.loc().is_absolute() } - - #[inline] - pub fn has_root(self) -> bool { self.loc().has_root() } - #[inline] pub fn to_owned(self) -> UrlBuf { self.into() } + #[inline] + pub fn trail(self) -> Self { + match self { + Self::Regular(loc) => Self::Regular(Loc::bare(loc.trail())), + Self::Search { loc, domain } => Self::Search { loc: Loc::bare(loc.trail()), domain }, + Self::Archive { loc, domain } => Self::Archive { loc: Loc::bare(loc.trail()), domain }, + Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.trail()), domain }, + } + } + + pub fn triple(self) -> (PathDyn<'a>, PathDyn<'a>, PathDyn<'a>) { + match self { + Self::Regular(loc) | Self::Search { loc, .. } | Self::Archive { loc, .. } => { + let (base, rest, urn) = loc.triple(); + (base.as_path(), rest.as_path(), urn.as_path()) + } + Self::Sftp { loc, .. } => { + let (base, rest, urn) = loc.triple(); + (base.as_path(), rest.as_path(), urn.as_path()) + } + } + } + + #[inline] + pub fn try_ends_with(self, child: impl AsUrl) -> Result { + let child = child.as_url(); + Ok(self.loc().try_ends_with(child.loc())? && self.scheme().covariant(child.scheme())) + } + pub fn try_join(self, path: impl AsStrand) -> Result { let joined = self.loc().try_join(path)?; @@ -135,7 +270,8 @@ impl<'a> Url<'a> { return UrlCow::try_from(b); } - let mut path = PathBufDyn::Os(self.loc().components().take(take - 1).collect()); // FIXME + let loc = self.loc(); + let mut path = PathBufDyn::from_components(loc.kind(), loc.components().take(take - 1))?; path.try_push(rep)?; let url = match self { @@ -171,6 +307,12 @@ impl<'a> Url<'a> { Ok(url.into()) } + #[inline] + pub fn try_starts_with(self, base: impl AsUrl) -> Result { + let base = base.as_url(); + Ok(self.loc().try_starts_with(base.loc())? && self.scheme().covariant(base.scheme())) + } + pub fn try_strip_prefix(self, base: impl AsUrl) -> Result, StripPrefixError> { use StripPrefixError::{Exotic, NotPrefix}; use Url as U; @@ -236,145 +378,4 @@ impl<'a> Url<'a> { Self::Sftp { loc, .. } => loc.urn().as_path(), } } - - #[inline] - pub fn name(self) -> Option> { - Some(match self { - Self::Regular(loc) => loc.file_name()?.as_strand(), - Self::Search { loc, .. } => loc.file_name()?.as_strand(), - Self::Archive { loc, .. } => loc.file_name()?.as_strand(), - Self::Sftp { loc, .. } => loc.file_name()?.as_strand(), - }) - } - - #[inline] - pub fn stem(self) -> Option> { - Some(match self { - Self::Regular(loc) => loc.file_stem()?.as_strand(), - Self::Search { loc, .. } => loc.file_stem()?.as_strand(), - Self::Archive { loc, .. } => loc.file_stem()?.as_strand(), - Self::Sftp { loc, .. } => loc.file_stem()?.as_strand(), - }) - } - - #[inline] - pub fn ext(self) -> Option> { - Some(match self { - Self::Regular(loc) => loc.extension()?.as_strand(), - Self::Search { loc, .. } => loc.extension()?.as_strand(), - Self::Archive { loc, .. } => loc.extension()?.as_strand(), - Self::Sftp { loc, .. } => loc.extension()?.as_strand(), - }) - } - - pub fn base(self) -> Self { - match self { - Self::Regular(loc) => Self::Regular(Loc::bare(loc.base())), - Self::Search { loc, domain } => Self::Search { loc: Loc::bare(loc.base()), domain }, - Self::Archive { loc, domain } => Self::Archive { loc: Loc::bare(loc.base()), domain }, - Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.base()), domain }, - } - } - - #[inline] - pub fn trail(self) -> Self { - match self { - Self::Regular(loc) => Self::Regular(Loc::bare(loc.trail())), - Self::Search { loc, domain } => Self::Search { loc: Loc::bare(loc.trail()), domain }, - Self::Archive { loc, domain } => Self::Archive { loc: Loc::bare(loc.trail()), domain }, - Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.trail()), domain }, - } - } - - pub fn triple(self) -> (PathDyn<'a>, PathDyn<'a>, PathDyn<'a>) { - match self { - Self::Regular(loc) | Self::Search { loc, .. } | Self::Archive { loc, .. } => { - let (base, rest, urn) = loc.triple(); - (base.as_path(), rest.as_path(), urn.as_path()) - } - Self::Sftp { loc, .. } => { - let (base, rest, urn) = loc.triple(); - (base.as_path(), rest.as_path(), urn.as_path()) - } - } - } - - pub fn parent(self) -> Option { - let uri = self.uri(); - - Some(match self { - // Regular - Self::Regular(loc) => Self::regular(loc.parent()?), - - // Search - Self::Search { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), - Self::Search { loc, domain } => { - Self::Search { loc: Loc::new(loc.parent()?, loc.base(), loc.base()), domain } - } - - // Archive - Self::Archive { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), - Self::Archive { loc, domain } if uri.components().nth(1).is_none() => { - Self::Archive { loc: Loc::zeroed(loc.parent()?), domain } - } - Self::Archive { loc, domain } => { - Self::Archive { loc: Loc::floated(loc.parent()?, loc.base()), domain } - } - - // SFTP - Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.parent()?), domain }, - }) - } - - #[inline] - pub fn try_starts_with(self, base: impl AsUrl) -> Result { - let base = base.as_url(); - Ok(self.loc().try_starts_with(base.loc())? && self.scheme().covariant(base.scheme())) - } - - #[inline] - pub fn try_ends_with(self, child: impl AsUrl) -> Result { - let child = child.as_url(); - Ok(self.loc().try_ends_with(child.loc())? && self.scheme().covariant(child.scheme())) - } - - #[inline] - pub fn components(self) -> Components<'a> { Components::from(self) } - - #[inline] - pub fn os_str(self) -> Cow<'a, OsStr> { self.components().os_str() } - - #[inline] - pub fn covariant(self, other: impl AsUrl) -> bool { - let other = other.as_url(); - self.loc() == other.loc() && self.scheme().covariant(other.scheme()) - } - - #[inline] - pub fn pair(self) -> Option<(Self, PathDyn<'a>)> { Some((self.parent()?, self.urn())) } - - #[inline] - pub fn as_local(self) -> Option<&'a Path> { - self.loc().as_os().ok().filter(|_| self.kind().is_local()) - } - - #[inline] - pub fn has_base(self) -> bool { - match self { - Self::Regular(loc) => loc.has_base(), - Self::Search { loc, .. } => loc.has_base(), - Self::Archive { loc, .. } => loc.has_base(), - Self::Sftp { loc, .. } => loc.has_base(), - } - } - - #[inline] - pub fn has_trail(self) -> bool { - match self { - Self::Regular(loc) => loc.has_trail(), - Self::Search { loc, .. } => loc.has_trail(), - Self::Archive { loc, .. } => loc.has_trail(), - Self::Sftp { loc, .. } => loc.has_trail(), - } - } } diff --git a/yazi-vfs/src/fns.rs b/yazi-vfs/src/fns.rs index 25115c72..a1f3cb6c 100644 --- a/yazi-vfs/src/fns.rs +++ b/yazi-vfs/src/fns.rs @@ -39,7 +39,7 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result { let dot_ext = match url.ext() { Some(e) => { let mut s = StrandBuf::with_capacity(url.kind(), e.len() + 1); - s.try_push(".")?; + s.push_str("."); s.try_push(e)?; s } @@ -53,9 +53,9 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result { if append { name.try_push(&dot_ext)?; - name.try_push(format!("_{i}"))?; + name.push_str(format!("_{i}")); } else { - name.try_push(format!("_{i}"))?; + name.push_str(format!("_{i}")); name.try_push(&dot_ext)?; } diff --git a/yazi-vfs/src/provider/sftp/sftp.rs b/yazi-vfs/src/provider/sftp/sftp.rs index 6bb37475..531edc75 100644 --- a/yazi-vfs/src/provider/sftp/sftp.rs +++ b/yazi-vfs/src/provider/sftp/sftp.rs @@ -9,7 +9,7 @@ use yazi_shared::{path::{AsPath, PathBufDyn}, pool::InternStr, url::{Url, UrlBuf use super::Cha; use crate::provider::sftp::Conn; -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct Sftp<'a> { url: Url<'a>, path: &'a Path,