feat: new Unix path backend (#3359)

This commit is contained in:
三咲雅 misaki masa 2025-11-24 11:35:10 +08:00 committed by GitHub
parent a08b345a02
commit e5ee31cb7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 1430 additions and 979 deletions

67
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {

View file

@ -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<UrlBuf> {
fn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result<UrlBuf> {
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<H: std::hash::Hasher>(&self, state: &mut H) { self.1.hash(state); }
}
impl AsRef<OsStr> 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<OsString>) -> Self { Self(index, inner.into()) }
fn new(index: usize, inner: impl Into<StrandBuf>) -> Self { Self(index, inner.into()) }
}
// --- Tests

View file

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

View file

@ -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<I>(selected: I)
where
I: IntoIterator<Item = T>,
T: Into<UrlCow<'a>>,
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() }));
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Path>;
pub struct Path {
inner: PathBufDyn,
v_ext: Option<Value>,
v_name: Option<Value>,
v_parent: Option<Value>,
v_stem: Option<Value>,
}
impl Deref for Path {
type Target = PathBufDyn;
fn deref(&self) -> &Self::Target { &self.0 }
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<Path> 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<PathBufDyn>) -> Self { Self(urn.into()) }
pub fn new(path: impl Into<PathBufDyn>) -> 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<bool> {
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::<Self>()?).into_lua_err(),
_ => Err("must be a string or Path".into_lua_err())?,
}
}
fn join(&self, other: Value) -> mlua::Result<Self> {
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>()?;
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<bool> {
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::<Self>()?).into_lua_err(),
_ => Err("must be a string or Path".into_lua_err())?,
}
}
fn strip_prefix(&self, base: Value) -> mlua::Result<Option<Path>> {
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::<Self>()?),
_ => 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<F: UserDataFields<Self>>(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<M: UserDataMethods<Self>>(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()));
}

View file

@ -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<Url>;
pub struct Url {
inner: yazi_shared::url::UrlBuf,
v_path: Option<Value>,
v_name: Option<Value>,
v_stem: Option<Value>,
v_ext: Option<Value>,
@ -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::<Self>()?.inner.clone()),
Value::String(s) => Self::try_from(&*s.as_bytes())?,
Value::UserData(ud) => Self::new(&ud.borrow::<Self>()?.inner),
_ => Err("Expected a string or a Url".into_lua_err())?,
})
})?,
)
}
fn ends_with(&self, child: Value) -> mlua::Result<bool> {
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::<Self>()?).into_lua_err(),
_ => Err("must be a string or Url".into_lua_err())?,
}
}
fn hash(&self, long: Option<bool>) -> mlua::Result<String> {
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<Value> {
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::<Self>()?;
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<bool> {
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::<Self>()?).into_lua_err(),
_ => Err("must be a string or Url".into_lua_err())?,
}
}
fn strip_prefix(&self, base: Value) -> mlua::Result<Option<Path>> {
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::<Self>()?),
_ => 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<F: UserDataFields<Self>>(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<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("hash", |_, me, long: Option<bool>| {
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::<Self>()?;
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::<Self>()?).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::<Self>()?).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::<Self>()?),
_ => 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<bool>| 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::<Self>()?.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())
});
}
}

View file

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

View file

@ -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<Item = impl IntoStringLossy>,
len: usize,
max: usize,
) -> Option<Text<'static>> {
fn truncate_list<I>(it: I, len: usize, max: usize) -> Option<Text<'static>>
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))
}

View file

@ -47,7 +47,7 @@ impl Sendable {
Data::Url(ud.take::<yazi_binding::Url>()?.into())
}
Some(t) if t == TypeId::of::<yazi_binding::Path>() => {
Data::Path(ud.take::<yazi_binding::Path>()?.0)
Data::Path(ud.take::<yazi_binding::Path>()?.into())
}
Some(t) if t == TypeId::of::<yazi_binding::Id>() => {
Data::Id(**ud.borrow::<yazi_binding::Id>()?)
@ -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::<yazi_binding::Url>()?.into())
}
Some(t) if t == TypeId::of::<yazi_binding::Path>() => {
DataKey::Path(ud.take::<yazi_binding::Path>()?.0)
DataKey::Path(ud.take::<yazi_binding::Path>()?.into())
}
Some(t) if t == TypeId::of::<yazi_binding::Id>() => {
DataKey::Id(**ud.borrow::<yazi_binding::Id>()?)
@ -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)?,

View file

@ -16,7 +16,7 @@ use crate::app::App;
impl App {
pub(crate) fn plugin(&mut self, mut opt: PluginOpt) -> Result<Data> {
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<Data> {
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));
};

View file

@ -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<OsString>) {
async fn selected_to_file(&self, selected: Option<StrandBuf>) {
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();
}
}
}

View file

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

View file

@ -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<UrlCow<'a>>) -> UrlBuf {
let cow: UrlCow = url.into();
@ -10,25 +8,12 @@ pub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {
cow.trail().components().count() - 1,
);
match cow.as_url() {
Url::Regular(_) => UrlBuf::Regular(path.into()),
Url::Search { domain, .. } => UrlBuf::Search {
loc: LocBuf::<PathBuf>::with(path, uri, urn).expect("create Loc from cleaned path"),
domain: domain.intern(),
},
Url::Archive { domain, .. } => UrlBuf::Archive {
loc: LocBuf::<PathBuf>::with(path, uri, urn).expect("create Loc from cleaned path"),
domain: domain.intern(),
},
Url::Sftp { domain, .. } => UrlBuf::Sftp {
loc: LocBuf::<PathBuf>::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)]

View file

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

View file

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

View file

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

View file

@ -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<UrlCow<'a>>
}
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<UrlCow<'a>>
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))
}

View file

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

View file

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

View file

@ -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<PathBuf>;
@ -26,12 +26,13 @@ impl<'a> FsUrl<'a> for Url<'a> {
fn cache(&self) -> Option<PathBuf> {
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
}

View file

@ -35,7 +35,7 @@ impl FilesOp {
url.into(),
sizes
.pairs::<Path, u64>()
.map(|r| r.map(|(urn, size)| (urn.0, size)))
.map(|r| r.map(|(urn, size)| (urn.into(), size)))
.collect::<mlua::Result<_>>()?,
)))
}

View file

@ -64,7 +64,7 @@ fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol<str>, 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(

View file

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

View file

@ -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::<Self>()?;
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::<mlua::String>() {
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::<Self>()?
.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)
},
);

View file

@ -56,7 +56,7 @@ impl Fetcher {
impl UserData for Fetcher {
fn add_fields<F: mlua::UserDataFields<Self>>(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<F: mlua::UserDataFields<Self>>(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<F: mlua::UserDataFields<Self>>(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<F: mlua::UserDataFields<Self>>(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));
}
}

View file

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

View file

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

View file

@ -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<u8>),
@ -150,7 +150,7 @@ impl<'a> TryFrom<&'a Data> for Cow<'a, str> {
fn try_from(value: &'a Data) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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"),

View file

@ -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<u8>),

View file

@ -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<mpsc::UnboundedSender<Event>> = RoCell::new();
static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();
@ -25,7 +23,7 @@ pub enum Event {
pub struct EventQuit {
pub code: i32,
pub no_cwd_file: bool,
pub selected: Option<OsString>,
pub selected: Option<StrandBuf>,
}
impl Event {

View file

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

View file

@ -1,60 +0,0 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}};
pub trait IntoOsStr<'a> {
type Error;
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error>;
}
impl<'a> IntoOsStr<'a> for Cow<'a, [u8]> {
type Error = anyhow::Error;
fn into_os_str(self) -> Result<Cow<'a, OsStr>, 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<Cow<'a, OsStr>, Self::Error> { Cow::Borrowed(self).into_os_str() }
}
// --- OsStrJoin
pub trait OsStrJoin {
fn join(&self, sep: &OsStr) -> OsString;
}
impl<T> OsStrJoin for Vec<T>
where
T: AsRef<OsStr>,
{
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
}
}

View file

@ -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<std::path::PathBuf> for PathBufDyn {
@ -21,7 +20,7 @@ impl From<PathDyn<'_>> for PathBufDyn {
}
impl TryFrom<PathBufDyn> for std::path::PathBuf {
type Error = PathBufDynError;
type Error = PathDynError;
fn try_from(value: PathBufDyn) -> Result<Self, Self::Error> { 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<Self, PathDynError>
where
K: Into<PathKind>,
I: IntoIterator<Item = Component<'a>>,
{
Ok(match kind.into() {
PathKind::Os => Self::Os(iter.into_iter().collect::<Result<_, PathDynError>>()?),
PathKind::Unix => Self::Unix(iter.into_iter().collect::<Result<_, PathDynError>>()?),
})
}
pub fn into_encoded_bytes(self) -> Vec<u8> {
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<std::path::PathBuf, PathBufDynError> {
pub fn into_os(self) -> Result<std::path::PathBuf, PathDynError> {
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<std::path::PathBuf>) -> Self { Self::Os(path.into()) }
pub fn try_extend<T>(&mut self, paths: T) -> Result<(), StrandError>
pub fn try_extend<T>(&mut self, paths: T) -> Result<(), PathDynError>
where
T: IntoIterator,
T::Item: AsPath,
@ -83,13 +95,14 @@ impl PathBufDyn {
Ok(())
}
pub fn try_push<T>(&mut self, path: T) -> Result<(), StrandError>
pub fn try_push<T>(&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<K, T>(kind: K, strand: T) -> Result<Self, StrandError>
pub fn with<K>(kind: K, bytes: Vec<u8>) -> Result<Self, PathDynError>
where
K: Into<PathKind>,
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<K, S>(kind: K, s: S) -> Self
where
K: Into<PathKind>,
S: Into<String>,
{
let s = s.into();
match kind.into() {
PathKind::Os => Self::Os(s.into()),
PathKind::Unix => Self::Unix(s.into()),
}
}
}

View file

@ -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<std::path::Component<'a>> 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<typed_path::UnixComponent<'a>> 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<Component<'a>> for std::path::Component<'a> {
type Error = PathDynError;
fn try_from(value: Component<'a>) -> Result<Self, Self::Error> {
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<Component<'a>> for typed_path::UnixComponent<'a> {
type Error = PathDynError;
fn try_from(value: Component<'a>) -> Result<Self, Self::Error> {
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<Component<'a>> for Result<std::path::PathBuf, PathDynError> {
fn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {
iter.into_iter().map(std::path::Component::try_from).collect()
}
}
impl<'a> FromIterator<Component<'a>> for Result<typed_path::UnixPathBuf, PathDynError> {
fn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {
iter.into_iter().map(typed_path::UnixComponent::try_from).collect()
}
}

View file

@ -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<Component<'a>> {
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<Component<'a>> {
match self {
Self::Os(c) => c.next_back().map(Into::into),
Self::Unix(c) => c.next_back().map(Into::into),
}
}
}
impl FusedIterator for Components<'_> {}

View file

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

View file

@ -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<Cow<'a, [u8]>>) -> Result<Self> {
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<K, T>(kind: K, bytes: T) -> Result<Self>
where
K: Into<PathKind>,
T: Into<Cow<'a, [u8]>>,
{
Ok(match bytes.into() {
Cow::Borrowed(b) => PathDyn::with(kind, b)?.into(),
Cow::Owned(b) => PathBufDyn::with(kind, b)?.into(),
})
}
}

View file

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

View file

@ -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<JoinError> for std::io::Error {
@ -22,27 +22,33 @@ impl From<JoinError> 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<StrandError> for PathDynError {
fn from(err: StrandError) -> Self {
match err {
StrandError::AsOs => Self::AsOs,
StrandError::AsUtf8 => Self::AsUtf8,
}
}
}
impl From<PathDynError> 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<SetNameError> for std::io::Error {
@ -89,3 +95,7 @@ pub enum StripPrefixError {
impl From<std::path::StripPrefixError> for StripPrefixError {
fn from(_: std::path::StripPrefixError) -> Self { Self::NotPrefix }
}
impl From<typed_path::StripPrefixError> for StripPrefixError {
fn from(_: typed_path::StripPrefixError) -> Self { Self::NotPrefix }
}

View file

@ -2,6 +2,7 @@ use crate::scheme::SchemeKind;
pub enum PathKind {
Os,
Unix,
}
impl From<SchemeKind> for PathKind {

View file

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

View file

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

View file

@ -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<Strand<'p>> {
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<Strand<'p>> {
Some(match self {
Self::Os(p) => p.file_name()?.into(),
Self::Unix(p) => p.file_name()?.into(),
})
}
#[inline]
pub fn os<T>(path: &'p T) -> Self
where
T: ?Sized + AsRef<std::path::Path>,
{
Self::Os(path.as_ref())
}
pub fn parent(self) -> Option<Self> {
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<Strand<'p>> {
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<std::path::PathBuf, PathDynError> {
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<K, T>(kind: K, strand: &'p T) -> Result<Self, StrandError>
pub fn with<K, S>(kind: K, strand: &'p S) -> Result<Self, StrandError>
where
K: Into<PathKind>,
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<K, S>(kind: K, s: &'p S) -> Self
where
K: Into<PathKind>,
S: ?Sized + AsRef<str>,
{
let s = s.as_ref();
match kind.into() {
PathKind::Os => Self::Os(s.as_ref()),
PathKind::Unix => Self::Unix(s.as_ref()),
}
}
}

View file

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

View file

@ -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::Item> {
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 }
}
}

View file

@ -30,6 +30,10 @@ impl From<SchemeCow<'_>> for Scheme {
fn from(value: SchemeCow<'_>) -> Self { value.into_owned() }
}
impl PartialEq<SchemeRef<'_>> 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<PathCow<'a>> {
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(

View file

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

View file

@ -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<OsString> 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<String> for StrandBuf {
fn from(value: String) -> Self { Self::Utf8(value) }
}
@ -29,10 +34,15 @@ impl From<PathDyn<'_>> 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<StrandCow<'_>> for StrandBuf {
fn from(value: StrandCow<'_>) -> Self { value.into_owned() }
}
impl PartialEq<Strand<'_>> 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<StrandKind>, bytes: Vec<u8>) -> 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<StrandKind>) -> Self { Self::with_str(kind, "") }
pub fn push_str(&mut self, s: impl AsRef<str>) {
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<T>(&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<K>(kind: K, bytes: Vec<u8>) -> Result<Self>
where
K: Into<StrandKind>,
{
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<K>(kind: K, capacity: usize) -> Self
where
K: Into<StrandKind>,
{
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<K, S>(kind: K, s: S) -> Self
where
K: Into<StrandKind>,
S: Into<String>,
{
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()),
}
}
}

View file

@ -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<u8> {
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<T> ToStrand for T
where
T: AsUrl,
{
fn to_strand(&self) -> StrandCow<'_> { self.as_url().components().strand() }
}

View file

@ -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<OsString> for StrandCow<'_> {
fn from(value: OsString) -> Self { Self::Owned(StrandBuf::Os(value)) }
}
@ -41,13 +44,6 @@ impl PartialEq<Strand<'_>> for StrandCow<'_> {
}
impl<'a> StrandCow<'a> {
pub fn from_os_bytes(bytes: impl Into<Cow<'a, [u8]>>) -> Result<Self> {
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<T>(kind: SchemeKind, bytes: T) -> Result<Self>
where
T: Into<Cow<'a, [u8]>>,
{
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<K, T>(kind: K, bytes: T) -> Result<Self>
where
K: Into<StrandKind>,
T: Into<Cow<'a, [u8]>>,
{
Ok(match bytes.into() {
Cow::Borrowed(b) => Strand::with(kind, b)?.into(),
Cow::Owned(b) => StrandBuf::with(kind, b)?.into(),
})
}
}

View file

@ -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<PathDynError> for StrandError {
fn from(err: PathDynError) -> Self {
match err {
PathDynError::AsOs => Self::AsOs,
}
}
}
impl From<StrandError> for std::io::Error {
fn from(err: StrandError) -> Self { Self::other(err) }
}

View file

@ -0,0 +1,61 @@
use crate::strand::{AsStrand, Strand, StrandBuf, StrandLike, ToStrand};
// --- StrandJoin
pub trait AsStrandJoin {
fn join(self, sep: Strand) -> StrandBuf;
}
impl<T> 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<T> 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) }
}
}

View file

@ -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<PathKind> for StrandKind {
fn from(value: PathKind) -> Self {
match value {
PathKind::Os => StrandKind::Os,
PathKind::Unix => StrandKind::Bytes,
}
}
}
impl From<SchemeKind> 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
}
}
}

View file

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

View file

@ -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<StrandKind>, 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<K, S>(kind: K, strand: &'a S) -> Result<Self>
where
K: Into<StrandKind>,
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()),
})
}
}

View file

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

View file

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

View file

@ -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<PathDyn<'_>> 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<String> for UrlBuf {
}
}
impl TryFrom<(Scheme, PathBufDyn)> for UrlBuf {
type Error = anyhow::Error;
fn try_from(value: (Scheme, PathBufDyn)) -> Result<Self, Self::Error> {
Ok(UrlCow::try_from(value)?.into_owned())
}
}
impl AsRef<Self> for UrlBuf {
fn as_ref(&self) -> &Self { self }
}
@ -155,7 +155,7 @@ impl UrlBuf {
pub fn to_regular(&self) -> Result<Self, PathDynError> { Ok(self.as_url().as_regular()?.into()) }
#[inline]
pub fn into_regular(self) -> Result<Self, PathBufDynError> {
pub fn into_regular(self) -> Result<Self, PathDynError> {
Ok(Self::Regular(self.into_loc().into_os()?.into()))
}
@ -172,7 +172,7 @@ impl UrlBuf {
}
#[inline]
pub fn into_search(self, domain: impl AsRef<str>) -> Result<Self, PathBufDynError> {
pub fn into_search(self, domain: impl AsRef<str>) -> Result<Self, PathDynError> {
Ok(Self::Search {
loc: LocBuf::<PathBuf>::zeroed(self.into_loc().into_os()?),
domain: Pool::<str>::intern(domain),

View file

@ -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<path::Component<'a>> 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<Component<'a>> for Result<UrlBuf> {
fn from_iter<I: IntoIterator<Item = Component<'a>>>(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<path::Component<'a>> {
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<Component<'a>> for PathBuf {
// impl<'a> FromIterator<Component<'a>> for Result<UrlBuf> {
// fn from_iter<I: IntoIterator<Item = Component<'a>>>(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<Component<'a>> for Result<std::path::PathBuf, PathDynError> {
fn from_iter<I: IntoIterator<Item = Component<'a>>>(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<Url<'a>> 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<Self::Item> {
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<usize>) {
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<Self::Item> {
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::<Result<_>>()?;
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::<Result<_>>()?;
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<Component<'a>> for Result<typed_path::UnixPathBuf, PathDynError> {
fn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {
iter
.into_iter()
.filter_map(|c| c.downgrade())
.map(typed_path::UnixComponent::try_from)
.collect()
}
}

View file

@ -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<Url<'a>> 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<Self::Item> {
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<usize>) {
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<Self::Item> {
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(())
}
}

View file

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

View file

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

View file

@ -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<Strand<'_>> { 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<Strand<'_>> { 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<Url<'_>> { self.as_url().parent() }
fn scheme(&self) -> SchemeRef<'_> { self.as_url().scheme() }
fn stem(&self) -> Option<Strand<'_>> { self.as_url().stem() }
fn trail(&self) -> Url<'_> { self.as_url().trail() }
fn try_ends_with(&self, child: impl AsUrl) -> Result<bool, EndsWithError> {
self.as_url().try_ends_with(child)
}
fn try_join(&self, path: impl AsStrand) -> Result<UrlBuf, JoinError> {
self.as_url().try_join(path)
}
fn try_replace<'a>(&self, take: usize, path: impl AsPathRef<'a>) -> Result<UrlCow<'a>> {
self.as_url().try_replace(take, path)
}
fn try_starts_with(&self, base: impl AsUrl) -> Result<bool, StartsWithError> {
self.as_url().try_starts_with(base)
}
fn try_strip_prefix(&self, base: impl AsUrl) -> Result<PathDyn<'_>, 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<'_> {}

View file

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

View file

@ -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<Strand<'_>> { 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<Strand<'_>> { 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<Url<'_>> { self.as_url().parent() }
fn scheme(&self) -> SchemeRef<'_> { self.as_url().scheme() }
fn stem(&self) -> Option<Strand<'_>> { self.as_url().stem() }
fn trail(&self) -> Url<'_> { self.as_url().trail() }
fn try_ends_with(&self, child: impl AsUrl) -> Result<bool, EndsWithError> {
self.as_url().try_ends_with(child)
}
fn try_join(&self, path: impl AsStrand) -> Result<UrlBuf, JoinError> {
self.as_url().try_join(path)
}
fn try_replace<'a>(&self, take: usize, path: impl AsPathRef<'a>) -> Result<UrlCow<'a>> {
self.as_url().try_replace(take, path)
}
fn try_starts_with(&self, base: impl AsUrl) -> Result<bool, StartsWithError> {
self.as_url().try_starts_with(base)
}
fn try_strip_prefix(&self, base: impl AsUrl) -> Result<PathDyn<'_>, 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<'_> {}

View file

@ -39,15 +39,85 @@ impl Debug for Url<'_> {
impl Serialize for Url<'_> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<Self, PathDynError> {
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<Strand<'a>> {
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<Strand<'a>> {
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<Self> {
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<T: AsRef<Path> + ?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<T: AsRef<Path> + ?Sized>(path: &'a T) -> Self {
Self::Regular(Loc::bare(path.as_ref()))
pub fn stem(self) -> Option<Strand<'a>> {
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<Self, PathDynError> {
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<bool, EndsWithError> {
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<UrlBuf, JoinError> {
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<bool, StartsWithError> {
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<PathDyn<'a>, 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<Strand<'a>> {
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<Strand<'a>> {
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<Strand<'a>> {
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<Self> {
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<bool, StartsWithError> {
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<bool, EndsWithError> {
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(),
}
}
}

View file

@ -39,7 +39,7 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result<UrlBuf> {
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<UrlBuf> {
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)?;
}

View file

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