diff --git a/Cargo.lock b/Cargo.lock index 3a582eab..0db822ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1079,6 +1079,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", + "serde", ] [[package]] @@ -3534,6 +3535,7 @@ version = "25.6.11" dependencies = [ "ansi-to-tui", "foldhash", + "hashbrown", "mlua", "paste", "ratatui", @@ -3634,6 +3636,7 @@ dependencies = [ "anyhow", "crossterm 0.29.0", "futures", + "hashbrown", "indexmap", "libc", "notify", @@ -3662,6 +3665,7 @@ name = "yazi-dds" version = "25.6.11" dependencies = [ "anyhow", + "hashbrown", "mlua", "ordered-float 5.0.0", "parking_lot", @@ -3773,6 +3777,7 @@ dependencies = [ "anyhow", "bitflags 2.9.1", "crossterm 0.29.0", + "hashbrown", "mlua", "serde", "tokio", @@ -3872,6 +3877,7 @@ dependencies = [ "crossterm 0.29.0", "foldhash", "futures", + "hashbrown", "libc", "memchr", "ordered-float 5.0.0", diff --git a/Cargo.toml b/Cargo.toml index 8952cd0f..21674699 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ dirs = "6.0.0" foldhash = "0.1.5" futures = "0.3.31" globset = "0.4.16" +hashbrown = { version = "0.15.5", features = [ "serde" ] } indexmap = { version = "2.10.0", features = [ "serde" ] } libc = "0.2.175" lru = "0.16.0" diff --git a/cspell.json b/cspell.json index d3ef44a8..8ea8ec81 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir"],"language":"en","flagWords":[],"version":"0.2"} \ No newline at end of file +{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown"],"flagWords":[],"version":"0.2","language":"en"} \ No newline at end of file diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index 04172b80..324fc60e 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -5,7 +5,7 @@ use yazi_fs::{CWD, path::expand_url, provider}; use yazi_macro::{act, render, succ}; use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt}; use yazi_proxy::CmpProxy; -use yazi_shared::{OsStrSplit, event::Data, natsort, url::{UrlBuf, UrnBuf}}; +use yazi_shared::{OsStrSplit, event::Data, natsort, url::{UrlBuf, UrlCow, UrnBuf}}; use crate::{Actor, Ctx}; @@ -67,7 +67,7 @@ impl Actor for Trigger { impl Trigger { fn split_url(s: &str) -> Option<(UrlBuf, UrnBuf)> { - let (scheme, path, ..) = UrlBuf::parse(s.as_bytes()).ok()?; + let (scheme, path, ..) = UrlCow::parse(s.as_bytes()).ok()?; if !scheme.is_virtual() && path.as_os_str() == "~" { return None; // We don't autocomplete a `~`, but `~/` diff --git a/yazi-actor/src/lives/tab.rs b/yazi-actor/src/lives/tab.rs index b22c5821..bcdef269 100644 --- a/yazi-actor/src/lives/tab.rs +++ b/yazi-actor/src/lives/tab.rs @@ -64,7 +64,7 @@ impl UserData for Tab { fn add_methods>(methods: &mut M) { methods.add_method("history", |_, me, url: UrlRef| { - me.history.get(&url).map(|f| Folder::make(None, f, me)).transpose() + me.history.get(url.as_ref()).map(|f| Folder::make(None, f, me)).transpose() }); } } diff --git a/yazi-actor/src/mgr/cd.rs b/yazi-actor/src/mgr/cd.rs index e7c0714c..09f65b1c 100644 --- a/yazi-actor/src/mgr/cd.rs +++ b/yazi-actor/src/mgr/cd.rs @@ -39,10 +39,10 @@ impl Actor for Cd { // Backstack if opt.source.big_jump() { if tab.current.url.is_regular() { - tab.backstack.push(&tab.current.url); + tab.backstack.push(tab.current.url.as_url()); } if opt.target.is_regular() { - tab.backstack.push(&opt.target); + tab.backstack.push(opt.target.as_url()); } } diff --git a/yazi-actor/src/mgr/escape.rs b/yazi-actor/src/mgr/escape.rs index 59d8ee5c..d6c9a349 100644 --- a/yazi-actor/src/mgr/escape.rs +++ b/yazi-actor/src/mgr/escape.rs @@ -74,8 +74,8 @@ impl Actor for EscapeVisual { indices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect(); if !select { - tab.selected.remove_many(&urls); - } else if tab.selected.add_many(&urls) != urls.len() { + tab.selected.remove_many(urls); + } else if urls.len() != tab.selected.add_many(urls) { AppProxy::notify_warn( "Escape visual mode", "Some files cannot be selected, due to path nesting conflict.", diff --git a/yazi-actor/src/mgr/follow.rs b/yazi-actor/src/mgr/follow.rs index 38e3854b..ee83ae95 100644 --- a/yazi-actor/src/mgr/follow.rs +++ b/yazi-actor/src/mgr/follow.rs @@ -18,7 +18,7 @@ impl Actor for Follow { let Some(link_to) = &file.link_to else { succ!() }; if link_to.is_absolute() { - act!(mgr:reveal, cx, link_to.to_owned()) + act!(mgr:reveal, cx, link_to.clone()) } else if let Some(p) = file.url.parent_url() { act!(mgr:reveal, cx, clean_url(p.join(link_to))) } else { diff --git a/yazi-actor/src/mgr/paste.rs b/yazi-actor/src/mgr/paste.rs index b4f482c6..a53eeffb 100644 --- a/yazi-actor/src/mgr/paste.rs +++ b/yazi-actor/src/mgr/paste.rs @@ -15,15 +15,15 @@ impl Actor for Paste { fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let mgr = &mut cx.core.mgr; let tab = &mgr.tabs[cx.tab]; - let (src, dest) = (mgr.yanked.iter().collect::>(), tab.cwd()); + let dest = tab.cwd(); if mgr.yanked.cut { - cx.core.tasks.file_cut(&src, dest, opt.force); + cx.core.tasks.file_cut(&mgr.yanked, dest, opt.force); - mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src)); + mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(mgr.yanked.iter())); act!(mgr:unyank, cx) } else { - succ!(cx.core.tasks.file_copy(&src, dest, opt.force, opt.follow)); + succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, opt.force, opt.follow)); } } } diff --git a/yazi-actor/src/mgr/rename.rs b/yazi-actor/src/mgr/rename.rs index 4bd35b20..285016e0 100644 --- a/yazi-actor/src/mgr/rename.rs +++ b/yazi-actor/src/mgr/rename.rs @@ -68,7 +68,7 @@ impl Rename { provider::rename(&old, &new).await?; if let Some(o) = overwritten { - ok_or_not_found(provider::rename(p_new.join(&o), &new).await)?; + ok_or_not_found(provider::rename(&p_new.join(&o), &new).await)?; FilesOp::Deleting(p_new.clone(), [UrnBuf::from(o)].into()).emit(); } diff --git a/yazi-actor/src/mgr/shell.rs b/yazi-actor/src/mgr/shell.rs index 69f922e9..1bbf900b 100644 --- a/yazi-actor/src/mgr/shell.rs +++ b/yazi-actor/src/mgr/shell.rs @@ -19,7 +19,7 @@ impl Actor for Shell { fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result { act!(mgr:escape_visual, cx)?; - let cwd = opt.cwd.take().unwrap_or_else(|| cx.cwd().clone()); + let cwd = opt.cwd.take().unwrap_or(cx.cwd().into()).into_owned(); let selected = cx.tab().hovered_and_selected().cloned().collect(); let input = opt.interactive.then(|| { diff --git a/yazi-actor/src/mgr/tab_create.rs b/yazi-actor/src/mgr/tab_create.rs index 7401e06c..5d9d4af8 100644 --- a/yazi-actor/src/mgr/tab_create.rs +++ b/yazi-actor/src/mgr/tab_create.rs @@ -26,7 +26,7 @@ impl Actor for TabCreate { let mut tab = Tab::default(); let (cd, url) = if let Some(wd) = opt.wd { - (true, wd) + (true, wd.into_owned()) } else if let Some(h) = cx.hovered() { tab.pref = cx.tab().pref.clone(); (false, h.url.to_regular()) diff --git a/yazi-actor/src/mgr/toggle.rs b/yazi-actor/src/mgr/toggle.rs index c0c8dce0..ef7fd091 100644 --- a/yazi-actor/src/mgr/toggle.rs +++ b/yazi-actor/src/mgr/toggle.rs @@ -2,7 +2,7 @@ use anyhow::Result; use yazi_macro::{render_and, succ}; use yazi_parser::mgr::ToggleOpt; use yazi_proxy::AppProxy; -use yazi_shared::event::Data; +use yazi_shared::{event::Data, url::UrlCow}; use crate::{Actor, Ctx}; @@ -15,12 +15,14 @@ impl Actor for Toggle { fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let tab = cx.tab_mut(); - let Some(url) = opt.url.as_ref().or(tab.current.hovered().map(|h| &h.url)) else { succ!() }; + let Some(url) = opt.url.or(tab.current.hovered().map(|h| UrlCow::from(&h.url))) else { + succ!(); + }; let b = match opt.state { - Some(true) => render_and!(tab.selected.add(url)), - Some(false) => render_and!(tab.selected.remove(url)) | true, - None => render_and!(tab.selected.remove(url) || tab.selected.add(url)), + Some(true) => render_and!(tab.selected.add(&url)), + Some(false) => render_and!(tab.selected.remove(&url)) | true, + None => render_and!(tab.selected.remove(&url) || tab.selected.add(&url)), }; if !b { diff --git a/yazi-actor/src/mgr/toggle_all.rs b/yazi-actor/src/mgr/toggle_all.rs index ec2d5101..80dbc889 100644 --- a/yazi-actor/src/mgr/toggle_all.rs +++ b/yazi-actor/src/mgr/toggle_all.rs @@ -29,8 +29,8 @@ impl Actor for ToggleAll { let warn = match either { Left((removal, addition)) => { - render!(tab.selected.remove_many(&removal) > 0); - render!(tab.selected.add_many(&addition), > 0) != addition.len() + render!(tab.selected.remove_many(removal) > 0); + addition.len() != render!(tab.selected.add_many(addition), > 0) } Right((removal, addition)) => { render!(tab.selected.remove_many(&removal) > 0); diff --git a/yazi-actor/src/mgr/update_files.rs b/yazi-actor/src/mgr/update_files.rs index 2914e515..d26f5594 100644 --- a/yazi-actor/src/mgr/update_files.rs +++ b/yazi-actor/src/mgr/update_files.rs @@ -85,7 +85,12 @@ impl UpdateFiles { fn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result { let (id, url) = (cx.tab().id, op.cwd()); - let folder = cx.tab_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(url)); + let (_, folder) = cx + .tab_mut() + .history + .raw_entry_mut() + .from_key(url) + .or_insert_with(|| (url.clone(), Folder::from(url))); if folder.update_pub(id, op) { act!(mgr:peek, cx, true)?; @@ -101,8 +106,10 @@ impl UpdateFiles { tab .history - .entry(op.cwd().clone()) - .or_insert_with(|| Folder::from(op.cwd())) + .raw_entry_mut() + .from_key(op.cwd()) + .or_insert_with(|| (op.cwd().clone(), Folder::from(op.cwd()))) + .1 .update_pub(tab.id, op); if leave { diff --git a/yazi-actor/src/mgr/update_mimes.rs b/yazi-actor/src/mgr/update_mimes.rs index 2ec0addc..684530a3 100644 --- a/yazi-actor/src/mgr/update_mimes.rs +++ b/yazi-actor/src/mgr/update_mimes.rs @@ -23,10 +23,10 @@ impl Actor for UpdateMimes { .flat_map(|(key, value)| key.into_url().zip(value.into_string())) .filter(|(url, mime)| cx.mgr.mimetype.by_url(url) != Some(mime)) .fold(HashMap::new(), |mut map, (u, m)| { - for u in linked.from_file(&u) { + for u in linked.from_file(u.as_url()) { map.insert(u, m.to_string()); } - map.insert(u, m.into_owned()); + map.insert(u.into_owned(), m.into_owned()); map }); diff --git a/yazi-actor/src/mgr/yank.rs b/yazi-actor/src/mgr/yank.rs index 5afc94a4..4926cbaf 100644 --- a/yazi-actor/src/mgr/yank.rs +++ b/yazi-actor/src/mgr/yank.rs @@ -2,7 +2,7 @@ use anyhow::Result; use yazi_core::mgr::Yanked; use yazi_macro::{act, render}; use yazi_parser::mgr::YankOpt; -use yazi_shared::{event::Data, url::UrlCov}; +use yazi_shared::{event::Data, url::UrlBufCov}; use crate::{Actor, Ctx}; @@ -17,7 +17,7 @@ impl Actor for Yank { act!(mgr:escape_visual, cx)?; cx.mgr.yanked = - Yanked::new(opt.cut, cx.tab().selected_or_hovered().cloned().map(UrlCov).collect()); + Yanked::new(opt.cut, cx.tab().selected_or_hovered().cloned().map(UrlBufCov).collect()); render!(cx.mgr.yanked.catchup_revision(true)); act!(mgr:escape_select, cx) diff --git a/yazi-adapter/src/image.rs b/yazi-adapter/src/image.rs index 8d2ab112..bd599c57 100644 --- a/yazi-adapter/src/image.rs +++ b/yazi-adapter/src/image.rs @@ -108,7 +108,7 @@ impl Image { limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32); } - let mut reader = ImageReader::new(provider::open(&url).await?.reader_sync().await); + let mut reader = ImageReader::new(provider::open(url).await?.reader_sync().await); if let Ok(format) = ImageFormat::from_path(url) { reader.set_format(format); } diff --git a/yazi-binding/Cargo.toml b/yazi-binding/Cargo.toml index 1fb7e0c2..80dbeb65 100644 --- a/yazi-binding/Cargo.toml +++ b/yazi-binding/Cargo.toml @@ -22,6 +22,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.6.11" } # External dependencies ansi-to-tui = { workspace = true } foldhash = { workspace = true } +hashbrown = { workspace = true } mlua = { workspace = true } paste = { workspace = true } ratatui = { workspace = true } diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index bd490a07..44604b4c 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -1,7 +1,7 @@ -use std::{borrow::Cow, ops::Deref}; +use std::ops::Deref; use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; -use yazi_shared::url::UrlCov; +use yazi_shared::url::{UrlBufCov, UrlCow}; use crate::{Urn, cached_field, deprecate}; @@ -33,23 +33,23 @@ impl From for yazi_shared::url::UrlBuf { fn from(value: Url) -> Self { value.inner } } -impl From for Cow<'_, yazi_shared::url::UrlBuf> { - fn from(value: Url) -> Self { Cow::Owned(value.inner) } +impl<'a> From<&'a Url> for yazi_shared::url::Url<'a> { + fn from(value: &'a Url) -> Self { value.as_url() } } -impl<'a> From<&'a Url> for Cow<'a, yazi_shared::url::UrlBuf> { - fn from(value: &'a Url) -> Self { Cow::Borrowed(&value.inner) } +impl<'a> From<&'a Url> for UrlCow<'a> { + fn from(value: &'a Url) -> Self { UrlCow::Borrowed(value.as_url()) } } -impl From for yazi_shared::url::UrlCov { - fn from(value: Url) -> Self { UrlCov(value.inner) } +impl From for yazi_shared::url::UrlBufCov { + fn from(value: Url) -> Self { UrlBufCov(value.inner) } } impl TryFrom<&[u8]> for Url { type Error = mlua::Error; fn try_from(value: &[u8]) -> mlua::Result { - Ok(Self::new(yazi_shared::url::UrlBuf::try_from(value)?)) + Ok(Self::new(UrlCow::try_from(value)?.into_owned())) } } diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index 2a291f7e..dd6b8e57 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -25,7 +25,7 @@ impl Boot { return (vec![CWD.load().as_ref().clone()], vec![UrnBuf::default()]); } - async fn go<'a>(entry: UrlBuf) -> (UrlBuf, UrnBuf) { + async fn go(entry: UrlBuf) -> (UrlBuf, UrnBuf) { let Some((parent, child)) = entry.pair() else { return (entry, UrnBuf::default()); }; diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index 8bfa6848..00b48693 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use yazi_codegen::DeserializeOver2; use yazi_fs::{Xdg, path::expand_url}; -use yazi_shared::{SStr, timestamp_us, url::UrlBuf}; +use yazi_shared::{SStr, timestamp_us, url::Url}; use super::PreviewWrap; @@ -53,7 +53,7 @@ impl Preview { self.cache_dir = if self.cache_dir.as_os_str().is_empty() { Xdg::cache_dir() - } else if let Some(p) = expand_url(UrlBuf::from(&self.cache_dir)).into_path() { + } else if let Some(p) = expand_url(Url::regular(&self.cache_dir)).into_path() { p } else { bail!("[preview].cache_dir must be a path within local filesystem."); diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index e29df4db..b527453d 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -25,6 +25,7 @@ yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } anyhow = { workspace = true } crossterm = { workspace = true } futures = { workspace = true } +hashbrown = { workspace = true } indexmap = { workspace = true } notify = { version = "8.2.0", default-features = false, features = [ "macos_fsevent" ] } parking_lot = { workspace = true } diff --git a/yazi-core/src/mgr/linked.rs b/yazi-core/src/mgr/linked.rs index 96da051c..522b313d 100644 --- a/yazi-core/src/mgr/linked.rs +++ b/yazi-core/src/mgr/linked.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, iter, ops::{Deref, DerefMut}}; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{Url, UrlBuf}; #[derive(Default)] pub struct Linked(HashMap /* from ==> to */); @@ -29,7 +29,7 @@ impl Linked { } } - pub fn from_file(&self, url: &UrlBuf) -> Vec { + pub fn from_file(&self, url: Url) -> Vec { if url.scheme.is_virtual() { vec![] } else if let Some((parent, urn)) = url.pair() { diff --git a/yazi-core/src/mgr/mimetype.rs b/yazi-core/src/mgr/mimetype.rs index ad689a19..4f61e64f 100644 --- a/yazi-core/src/mgr/mimetype.rs +++ b/yazi-core/src/mgr/mimetype.rs @@ -1,19 +1,20 @@ -use std::{borrow::Cow, collections::HashMap}; +use std::borrow::Cow; +use hashbrown::HashMap; use yazi_fs::File; -use yazi_shared::{MIME_DIR, SStr, url::{UrlBuf, UrlCov}}; +use yazi_shared::{MIME_DIR, SStr, url::{Url, UrlBuf, UrlBufCov, UrlCov}}; #[derive(Default)] -pub struct Mimetype(HashMap); +pub struct Mimetype(HashMap); impl Mimetype { #[inline] - pub fn by_url(&self, url: &UrlBuf) -> Option<&str> { - self.0.get(UrlCov::new(url)).map(|s| s.as_str()) + pub fn by_url<'a>(&self, url: impl Into>) -> Option<&str> { + self.0.get(&UrlCov::new(url)).map(|s| s.as_str()) } #[inline] - pub fn by_url_owned(&self, url: &UrlBuf) -> Option { + pub fn by_url_owned<'a>(&self, url: impl Into>) -> Option { self.by_url(url).map(|s| Cow::Owned(s.to_owned())) } @@ -28,10 +29,12 @@ impl Mimetype { } #[inline] - pub fn contains(&self, url: &UrlBuf) -> bool { self.0.contains_key(UrlCov::new(url)) } + pub fn contains<'a>(&self, url: impl Into>) -> bool { + self.0.contains_key(&UrlCov::new(url)) + } #[inline] pub fn extend(&mut self, iter: impl IntoIterator) { - self.0.extend(iter.into_iter().map(|(u, m)| (UrlCov(u), m))); + self.0.extend(iter.into_iter().map(|(u, m)| (UrlBufCov(u), m))); } } diff --git a/yazi-core/src/mgr/yanked.rs b/yazi-core/src/mgr/yanked.rs index bc11d424..0e11b53d 100644 --- a/yazi-core/src/mgr/yanked.rs +++ b/yazi-core/src/mgr/yanked.rs @@ -1,30 +1,33 @@ -use std::{collections::HashSet, ops::Deref}; +use std::ops::Deref; +use hashbrown::HashSet; use yazi_dds::Pubsub; use yazi_fs::FilesOp; use yazi_macro::err; -use yazi_shared::url::{UrlBuf, UrlCov}; +use yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov}; #[derive(Debug, Default)] pub struct Yanked { pub cut: bool, - urls: HashSet, + urls: HashSet, version: u64, revision: u64, } impl Deref for Yanked { - type Target = HashSet; + type Target = HashSet; fn deref(&self) -> &Self::Target { &self.urls } } impl Yanked { - pub fn new(cut: bool, urls: HashSet) -> Self { Self { cut, urls, ..Default::default() } } + pub fn new(cut: bool, urls: HashSet) -> Self { + Self { cut, urls, ..Default::default() } + } - pub fn remove(&mut self, url: &UrlBuf) { - if self.urls.remove(UrlCov::new(url)) { + pub fn remove<'a>(&mut self, url: impl Into>) { + if self.urls.remove(&UrlCov::new(url)) { self.revision += 1; } } @@ -39,7 +42,9 @@ impl Yanked { } #[inline] - pub fn contains(&self, url: impl AsRef) -> bool { self.urls.contains(UrlCov::new(&url)) } + pub fn contains<'a>(&self, url: impl Into>) -> bool { + self.urls.contains(&UrlCov::new(url)) + } pub fn contains_in(&self, dir: &UrlBuf) -> bool { self.urls.iter().any(|u| { @@ -60,7 +65,7 @@ impl Yanked { if !addition.is_empty() { let old = self.urls.len(); - self.urls.extend(addition.into_iter().map(UrlCov)); + self.urls.extend(addition.into_iter().map(UrlBufCov)); self.revision += (old != self.urls.len()) as u64; } } diff --git a/yazi-core/src/tab/backstack.rs b/yazi-core/src/tab/backstack.rs index 4bd8afa6..b3f3f79e 100644 --- a/yazi-core/src/tab/backstack.rs +++ b/yazi-core/src/tab/backstack.rs @@ -1,29 +1,31 @@ +use yazi_shared::url::{Url, UrlBuf}; + #[derive(Default)] -pub struct Backstack { +pub struct Backstack { cursor: usize, - stack: Vec, + stack: Vec, } -impl Backstack { - pub fn push(&mut self, item: &T) { +impl Backstack { + pub fn push(&mut self, url: Url) { if self.stack.is_empty() { - self.stack.push(item.clone()); + self.stack.push(url.to_owned()); return; } - if self.stack[self.cursor] == *item { + if self.stack[self.cursor] == url { return; } self.cursor += 1; if self.cursor == self.stack.len() { - self.stack.push(item.clone()); + self.stack.push(url.to_owned()); } else { - self.stack[self.cursor] = item.clone(); + self.stack[self.cursor] = url.to_owned(); self.stack.truncate(self.cursor + 1); } - // Only keep 30 items before the cursor, the cleanup threshold is 60 + // Only keep 30 URLs before the cursor, the cleanup threshold is 60 if self.stack.len() > 60 { let start = self.cursor.saturating_sub(30); self.stack.drain(..start); @@ -31,7 +33,7 @@ impl Backstack { } } - pub fn shift_backward(&mut self) -> Option<&T> { + pub fn shift_backward(&mut self) -> Option<&UrlBuf> { if self.cursor > 0 { self.cursor -= 1; Some(&self.stack[self.cursor]) @@ -40,7 +42,7 @@ impl Backstack { } } - pub fn shift_forward(&mut self) -> Option<&T> { + pub fn shift_forward(&mut self) -> Option<&UrlBuf> { if self.cursor + 1 >= self.stack.len() { None } else { @@ -56,30 +58,30 @@ mod tests { #[test] fn test_backstack() { - let mut bs: Backstack = Backstack::default(); + let mut bs: Backstack = Backstack::default(); assert_eq!(bs.shift_forward(), None); - bs.push(&1); - assert_eq!(bs.stack[bs.cursor], 1); + bs.push(Url::regular("1")); + assert_eq!(bs.stack[bs.cursor], Url::regular("1")); - bs.push(&2); - bs.push(&3); - assert_eq!(bs.stack[bs.cursor], 3); + bs.push(Url::regular("2")); + bs.push(Url::regular("3")); + assert_eq!(bs.stack[bs.cursor], Url::regular("3")); - assert_eq!(bs.shift_backward(), Some(&2)); - assert_eq!(bs.shift_backward(), Some(&1)); + assert_eq!(bs.shift_backward().unwrap(), Url::regular("2")); + assert_eq!(bs.shift_backward().unwrap(), Url::regular("1")); assert_eq!(bs.shift_backward(), None); assert_eq!(bs.shift_backward(), None); - assert_eq!(bs.stack[bs.cursor], 1); - assert_eq!(bs.shift_forward(), Some(&2)); - assert_eq!(bs.shift_forward(), Some(&3)); + assert_eq!(bs.stack[bs.cursor], Url::regular("1")); + assert_eq!(bs.shift_forward().unwrap(), Url::regular("2")); + assert_eq!(bs.shift_forward().unwrap(), Url::regular("3")); assert_eq!(bs.shift_forward(), None); bs.shift_backward(); - bs.push(&4); + bs.push(Url::regular("4")); - assert_eq!(bs.stack[bs.cursor], 4); + assert_eq!(bs.stack[bs.cursor], Url::regular("4")); assert_eq!(bs.shift_forward(), None); - assert_eq!(bs.shift_backward(), Some(&2)); + assert_eq!(bs.shift_backward().unwrap(), Url::regular("2")); } } diff --git a/yazi-core/src/tab/folder.rs b/yazi-core/src/tab/folder.rs index b2e66239..5187bbc1 100644 --- a/yazi-core/src/tab/folder.rs +++ b/yazi-core/src/tab/folder.rs @@ -37,8 +37,8 @@ impl Default for Folder { } } -impl From<&UrlBuf> for Folder { - fn from(url: &UrlBuf) -> Self { Self { url: url.clone(), ..Default::default() } } +impl> From for Folder { + fn from(value: T) -> Self { Self { url: value.into(), ..Default::default() } } } impl Folder { diff --git a/yazi-core/src/tab/history.rs b/yazi-core/src/tab/history.rs index e4074102..9dcf7f3f 100644 --- a/yazi-core/src/tab/history.rs +++ b/yazi-core/src/tab/history.rs @@ -1,6 +1,7 @@ -use std::{collections::HashMap, ops::{Deref, DerefMut}}; +use std::ops::{Deref, DerefMut}; -use yazi_shared::url::UrlBuf; +use hashbrown::HashMap; +use yazi_shared::url::{Url, UrlBuf}; use super::Folder; @@ -21,7 +22,8 @@ impl DerefMut for History { impl History { #[inline] - pub fn remove_or(&mut self, url: &UrlBuf) -> Folder { - self.0.remove(url).unwrap_or_else(|| Folder::from(url)) + pub fn remove_or<'a>(&mut self, url: impl Into>) -> Folder { + let url = url.into(); + self.0.remove(&url).unwrap_or_else(|| Folder::from(url)) } } diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index ae74d1d2..9fa4660e 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -1,13 +1,14 @@ -use std::{collections::HashMap, ops::Deref}; +use std::ops::Deref; +use hashbrown::HashMap; use indexmap::IndexMap; use yazi_fs::FilesOp; -use yazi_shared::{timestamp_us, url::{UrlBuf, UrlCov}}; +use yazi_shared::{timestamp_us, url::{Url, UrlBuf, UrlBufCov, UrlCov}}; #[derive(Default)] pub struct Selected { - inner: IndexMap, - parents: HashMap, + inner: IndexMap, + parents: HashMap, } impl Selected { @@ -21,27 +22,36 @@ impl Selected { pub fn values(&self) -> impl Iterator { self.inner.keys().map(Deref::deref) } #[inline] - pub fn contains(&self, url: impl AsRef) -> bool { - self.inner.contains_key(UrlCov::new(&url)) + pub fn contains<'a>(&self, url: impl Into>) -> bool { + self.inner.contains_key(&UrlCov::new(url)) } #[inline] - pub fn add(&mut self, url: &UrlBuf) -> bool { self.add_same(&[url]) == 1 } + pub fn add<'a>(&mut self, url: impl Into>) -> bool { self.add_same([url]) == 1 } - pub fn add_many(&mut self, urls: &[impl AsRef]) -> usize { + pub fn add_many<'a, I, T>(&mut self, urls: I) -> usize + where + I: IntoIterator, + T: Into>, + { let mut grouped: HashMap<_, Vec<_>> = Default::default(); - for u in urls { - if let Some(p) = u.as_ref().parent_url() { + for url in urls { + let u = url.into(); + if let Some(p) = u.parent_url() { grouped.entry(p).or_default().push(u); } } - grouped.into_values().map(|v| self.add_same(&v)).sum() + grouped.into_values().map(|v| self.add_same(v)).sum() } - fn add_same(&mut self, urls: &[impl AsRef]) -> usize { + fn add_same<'a, I, T>(&mut self, urls: I) -> usize + where + I: IntoIterator, + T: Into>, + { // If it has appeared as a parent let urls: Vec<_> = - urls.iter().map(UrlCov::new).filter(|&u| !self.parents.contains_key(u)).collect(); + urls.into_iter().map(UrlCov::new).filter(|u| !self.parents.contains_key(u)).collect(); if urls.is_empty() { return 0; } @@ -50,7 +60,7 @@ impl Selected { let mut parent = urls[0].parent_url(); let mut parents = vec![]; while let Some(u) = parent { - if self.inner.contains_key(&u) { + if self.inner.contains_key(&UrlCov::new(&u)) { return 0; } @@ -59,26 +69,33 @@ impl Selected { } let (now, len) = (timestamp_us(), self.inner.len()); - self.inner.extend(urls.iter().enumerate().map(|(i, &u)| (u.clone(), now + i as u64))); + self.inner.extend(urls.iter().enumerate().map(|(i, u)| (u.into(), now + i as u64))); for u in parents { - *self.parents.entry(u).or_insert(0) += self.inner.len() - len; + *self.parents.entry(UrlBufCov(u)).or_insert(0) += self.inner.len() - len; } urls.len() } #[inline] - pub fn remove(&mut self, url: &UrlBuf) -> bool { self.remove_same(&[url]) == 1 } + pub fn remove<'a>(&mut self, url: impl Into> + Clone) -> bool { + self.remove_same([url]) == 1 + } - pub fn remove_many(&mut self, urls: &[impl AsRef]) -> usize { + pub fn remove_many<'a, I, T>(&mut self, urls: I) -> usize + where + I: IntoIterator, + T: Into>, + { let mut grouped: HashMap<_, Vec<_>> = Default::default(); - for u in urls { - if let Some(p) = u.as_ref().parent_url() { + for url in urls { + let u = url.into(); + if let Some(p) = u.parent_url() { grouped.entry(p).or_default().push(u); } } - let affected = grouped.into_values().map(|v| self.remove_same(&v)).sum(); + let affected = grouped.into_values().map(|v| self.remove_same(v)).sum(); if affected > 0 { self.inner.sort_unstable_by(|_, a, _, b| a.cmp(b)); } @@ -86,19 +103,27 @@ impl Selected { affected } - fn remove_same(&mut self, urls: &[impl AsRef]) -> usize { - let count = urls.iter().filter_map(|u| self.inner.swap_remove(UrlCov::new(u))).count(); + fn remove_same<'a, I, T>(&mut self, urls: I) -> usize + where + I: IntoIterator, + T: Into> + Clone, + { + let mut it = urls.into_iter().peekable(); + let Some(first) = it.peek().cloned().map(UrlCov::new) else { return 0 }; + + let count = it.filter_map(|u| self.inner.swap_remove(&UrlCov::new(u))).count(); if count == 0 { return 0; } - let mut parent = UrlCov::new(&urls[0]).parent_url(); + // FIXME: use UrlCov::parent_url() instead + let mut parent = first.parent_url(); while let Some(u) = parent { - let n = self.parents.get_mut(&u).unwrap(); + let n = self.parents.get_mut(&UrlCov::new(&u)).unwrap(); *n -= count; if *n == 0 { - self.parents.remove(&u); + self.parents.remove(&UrlCov::new(&u)); } parent = u.parent_url(); @@ -124,9 +149,16 @@ impl Selected { #[cfg(test)] mod tests { + use yazi_shared::url::UrlCow; + use super::*; - fn url(s: &str) -> UrlBuf { s.parse().unwrap() } + fn url(s: &str) -> Url<'_> { + match UrlCow::try_from(s).unwrap() { + UrlCow::Borrowed(url) => url, + UrlCow::Owned(_) => unreachable!(), + } + } #[test] fn test_insert_non_conflicting() { @@ -172,7 +204,7 @@ mod tests { assert_eq!( 3, - s.add_same(&[&url("/parent/child1"), &url("/parent/child2"), &url("/parent/child3")]) + s.add_same([&url("/parent/child1"), &url("/parent/child2"), &url("/parent/child3")]) ); } @@ -181,7 +213,7 @@ mod tests { let mut s = Selected::default(); s.add(&url("/parent")); - assert_eq!(0, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")])); + assert_eq!(0, s.add_same([&url("/parent/child1"), &url("/parent/child2")])); } #[test] @@ -189,14 +221,14 @@ mod tests { let mut s = Selected::default(); s.add(&url("/parent/child1")); - assert_eq!(2, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")])); + assert_eq!(2, s.add_same([&url("/parent/child1"), &url("/parent/child2")])); } #[test] fn insert_many_empty_urls_list() { let mut s = Selected::default(); - assert_eq!(0, s.add_same(&[] as &[&UrlBuf])); + assert_eq!(0, s.add_same([] as [&Url; 0])); } #[test] @@ -204,14 +236,14 @@ mod tests { let mut s = Selected::default(); s.add(&url("/parent/child")); - assert_eq!(0, s.add_same(&[&url("/parent/child/child1"), &url("/parent/child/child2")])); + assert_eq!(0, s.add_same([&url("/parent/child/child1"), &url("/parent/child/child2")])); } #[test] fn insert_many_with_direct_parent_fails() { let mut s = Selected::default(); s.add(&url("/a")); - assert_eq!(0, s.add_same(&[&url("/a/b")])); + assert_eq!(0, s.add_same([&url("/a/b")])); } #[test] @@ -219,15 +251,15 @@ mod tests { let mut s = Selected::default(); s.add(&url("/a/b")); - assert_eq!(0, s.add_same(&[&url("/a")])); - assert_eq!(1, s.add_same(&[&url("/b"), &url("/a")])); + assert_eq!(0, s.add_same([&url("/a")])); + assert_eq!(1, s.add_same([&url("/b"), &url("/a")])); } #[test] fn insert_many_sibling_directories_success() { let mut s = Selected::default(); - assert_eq!(2, s.add_same(&[&url("/a/b"), &url("/a/c")])); + assert_eq!(2, s.add_same([&url("/a/b"), &url("/a/c")])); } #[test] @@ -235,7 +267,7 @@ mod tests { let mut s = Selected::default(); s.add(&url("/a/b")); - assert_eq!(0, s.add_same(&[&url("/a/b/c")])); + assert_eq!(0, s.add_same([&url("/a/b/c")])); } #[test] @@ -245,7 +277,7 @@ mod tests { let child1 = url("/parent/child1"); let child2 = url("/parent/child2"); let child3 = url("/parent/child3"); - assert_eq!(3, s.add_same(&[&child1, &child2, &child3])); + assert_eq!(3, s.add_same([&child1, &child2, &child3])); assert!(s.remove(&child1)); assert_eq!(s.inner.len(), 2); diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index 60fc1bd9..99f5558c 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -18,7 +18,7 @@ pub struct Tab { pub current: Folder, pub parent: Option, - pub backstack: Backstack, + pub backstack: Backstack, pub history: History, pub selected: Selected, diff --git a/yazi-core/src/tasks/file.rs b/yazi-core/src/tasks/file.rs index b2fb9375..7b8b38eb 100644 --- a/yazi-core/src/tasks/file.rs +++ b/yazi-core/src/tasks/file.rs @@ -1,13 +1,13 @@ -use std::collections::HashSet; - +use hashbrown::HashSet; use tracing::debug; -use yazi_shared::url::{UrlBuf, UrlCov}; +use yazi_shared::url::{UrlBuf, UrlBufCov}; use super::Tasks; +use crate::mgr::Yanked; impl Tasks { - pub fn file_cut(&self, src: &[&UrlCov], dest: &UrlBuf, force: bool) { - for &u in src { + pub fn file_cut(&self, src: &Yanked, dest: &UrlBuf, force: bool) { + for u in src.iter() { let to = dest.join(u.file_name().unwrap()); if force && *u == to { debug!("file_cut: same file, skipping {:?}", to); @@ -17,8 +17,8 @@ impl Tasks { } } - pub fn file_copy(&self, src: &[&UrlCov], dest: &UrlBuf, force: bool, follow: bool) { - for &u in src { + pub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: bool) { + for u in src.iter() { let to = dest.join(u.file_name().unwrap()); if force && *u == to { debug!("file_copy: same file, skipping {:?}", to); @@ -28,7 +28,7 @@ impl Tasks { } } - pub fn file_link(&self, src: &HashSet, dest: &UrlBuf, relative: bool, force: bool) { + pub fn file_link(&self, src: &HashSet, dest: &UrlBuf, relative: bool, force: bool) { for u in src { let to = dest.join(u.file_name().unwrap()); if force && *u == to { @@ -39,7 +39,7 @@ impl Tasks { } } - pub fn file_hardlink(&self, src: &HashSet, dest: &UrlBuf, force: bool, follow: bool) { + pub fn file_hardlink(&self, src: &HashSet, dest: &UrlBuf, force: bool, follow: bool) { for u in src { let to = dest.join(u.file_name().unwrap()); if force && *u == to { diff --git a/yazi-dds/Cargo.toml b/yazi-dds/Cargo.toml index 11808838..e8766dbd 100644 --- a/yazi-dds/Cargo.toml +++ b/yazi-dds/Cargo.toml @@ -22,6 +22,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.6.11" } # External dependencies anyhow = { workspace = true } +hashbrown = { workspace = true } mlua = { workspace = true } ordered-float = { workspace = true } parking_lot = { workspace = true } diff --git a/yazi-dds/src/ember/yank.rs b/yazi-dds/src/ember/yank.rs index c412a57a..d57ee948 100644 --- a/yazi-dds/src/ember/yank.rs +++ b/yazi-dds/src/ember/yank.rs @@ -1,9 +1,10 @@ -use std::{borrow::Cow, collections::HashSet}; +use std::borrow::Cow; +use hashbrown::HashSet; use mlua::{IntoLua, Lua, Value}; use serde::{Deserialize, Serialize}; use yazi_parser::mgr::UpdateYankedOpt; -use yazi_shared::url::UrlCov; +use yazi_shared::url::UrlBufCov; use super::Ember; @@ -11,13 +12,13 @@ use super::Ember; pub struct EmberYank<'a>(UpdateYankedOpt<'a>); impl<'a> EmberYank<'a> { - pub fn borrowed(cut: bool, urls: &'a HashSet) -> Ember<'a> { + pub fn borrowed(cut: bool, urls: &'a HashSet) -> Ember<'a> { Self(UpdateYankedOpt { cut, urls: Cow::Borrowed(urls) }).into() } } impl EmberYank<'static> { - pub fn owned(cut: bool, _: &HashSet) -> Ember<'static> { + pub fn owned(cut: bool, _: &HashSet) -> Ember<'static> { Self(UpdateYankedOpt { cut, urls: Default::default() }).into() } } diff --git a/yazi-dds/src/pubsub.rs b/yazi-dds/src/pubsub.rs index f907502a..cbc08833 100644 --- a/yazi-dds/src/pubsub.rs +++ b/yazi-dds/src/pubsub.rs @@ -1,11 +1,12 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use anyhow::Result; +use hashbrown::HashSet; use mlua::Function; use parking_lot::RwLock; use yazi_boot::BOOT; use yazi_fs::FolderStage; -use yazi_shared::{Id, RoCell, url::{UrlBuf, UrlCov}}; +use yazi_shared::{Id, RoCell, url::{UrlBuf, UrlBufCov}}; use crate::{Client, ID, PEERS, ember::{BodyMoveItem, Ember, EmberBulk, EmberHi}}; @@ -157,7 +158,7 @@ impl Pubsub { pub_after!(rename(tab: Id, from: &UrlBuf, to: &UrlBuf), (tab, from, to)); - pub_after!(@yank(cut: bool, urls: &HashSet), (cut, urls)); + pub_after!(@yank(cut: bool, urls: &HashSet), (cut, urls)); pub_after!(move(items: Vec), (&items), (items)); diff --git a/yazi-fs/src/calculator.rs b/yazi-fs/src/calculator.rs index 6dd26c7f..32e77171 100644 --- a/yazi-fs/src/calculator.rs +++ b/yazi-fs/src/calculator.rs @@ -21,7 +21,7 @@ impl SizeCalculator { return Ok(Self::Idle((VecDeque::new(), Some(meta.len())))); } - let mut buf = VecDeque::from([Either::Right(provider::read_dir_sync(u)?)]); + let mut buf = VecDeque::from([Either::Right(provider::read_dir_sync(&u)?)]); let size = Self::next_chunk(&mut buf); Ok(Self::Idle((buf, size))) }) diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index 652b7ffe..7005d547 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -36,7 +36,8 @@ impl File { } #[inline] - pub fn from_dummy(url: UrlBuf, ft: Option) -> Self { + pub fn from_dummy(url: impl Into, ft: Option) -> Self { + let url = url.into(); let cha = Cha::from_dummy(&url, ft); Self { url, cha, link_to: None } } diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index bd8c36e7..63ec74dc 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -4,21 +4,21 @@ use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, use anyhow::{Result, bail}; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; -use yazi_shared::url::{Component, UrlBuf}; +use yazi_shared::url::{Component, Url, UrlBuf}; use crate::{cha::Cha, provider}; #[inline] -pub async fn maybe_exists(u: impl AsRef) -> bool { - match provider::symlink_metadata(u).await { +pub async fn maybe_exists<'a>(url: impl Into>) -> bool { + match provider::symlink_metadata(url).await { Ok(_) => true, Err(e) => e.kind() != io::ErrorKind::NotFound, } } #[inline] -pub async fn must_be_dir(u: impl AsRef) -> bool { - provider::metadata(u).await.is_ok_and(|m| m.is_dir()) +pub async fn must_be_dir<'a>(url: impl Into>) -> bool { + provider::metadata(url).await.is_ok_and(|m| m.is_dir()) } #[inline] @@ -267,7 +267,7 @@ pub async fn remove_dir_clean(dir: &UrlBuf) { if entry.file_type().await.is_ok_and(|t| t.is_dir()) { let url = entry.url(); Box::pin(remove_dir_clean(&url)).await; - provider::remove_dir(url).await.ok(); + provider::remove_dir(&url).await.ok(); } } diff --git a/yazi-fs/src/path/clean.rs b/yazi-fs/src/path/clean.rs index 5c380c09..2fd394a8 100644 --- a/yazi-fs/src/path/clean.rs +++ b/yazi-fs/src/path/clean.rs @@ -1,15 +1,16 @@ -use std::{borrow::Cow, path::{Path, PathBuf}}; +use std::path::{Path, PathBuf}; -use yazi_shared::{loc::LocBuf, url::UrlBuf}; +use yazi_shared::{loc::LocBuf, url::{UrlBuf, UrlCow}}; -pub fn clean_url<'a>(url: impl Into>) -> UrlBuf { - let cow = url.into(); - let (path, uri, urn) = clean_path_impl(&cow.loc, cow.loc.base().count(), cow.loc.trail().count()); +pub fn clean_url<'a>(url: impl Into>) -> UrlBuf { + let cow: UrlCow = url.into(); + let (path, uri, urn) = + clean_path_impl(&cow.loc(), cow.loc().base().count(), cow.loc().trail().count()); let loc = LocBuf::with(path, uri, urn).expect("Failed to create Loc from cleaned path"); match cow { - Cow::Borrowed(u) => UrlBuf { loc, scheme: u.scheme.clone() }, - Cow::Owned(u) => UrlBuf { loc, scheme: u.scheme }, + UrlCow::Borrowed(u) => UrlBuf { loc, scheme: u.scheme.clone() }, + UrlCow::Owned(u) => UrlBuf { loc, scheme: u.scheme }, } } diff --git a/yazi-fs/src/path/expand.rs b/yazi-fs/src/path/expand.rs index 536fa2ba..e81f9878 100644 --- a/yazi-fs/src/path/expand.rs +++ b/yazi-fs/src/path/expand.rs @@ -1,15 +1,15 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, path::{Path, PathBuf}}; -use yazi_shared::{loc::LocBuf, url::UrlBuf}; +use yazi_shared::{loc::LocBuf, url::{Url, UrlBuf, UrlCow}}; use crate::{CWD, path::clean_url}; #[inline] -pub fn expand_url<'a>(url: impl AsRef) -> UrlBuf { - clean_url(expand_url_impl(url.as_ref())) +pub fn expand_url<'a>(url: impl Into>) -> UrlBuf { + clean_url(expand_url_impl(url.into().as_url())) } -fn expand_url_impl(url: &UrlBuf) -> Cow<'_, UrlBuf> { +fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> { let (o_base, o_rest, o_urn) = url.loc.triple(); let n_base = expand_variables(o_base); diff --git a/yazi-fs/src/path/path.rs b/yazi-fs/src/path/path.rs index 1c91f4f5..98d715de 100644 --- a/yazi-fs/src/path/path.rs +++ b/yazi-fs/src/path/path.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, future::Future, io, path::PathBuf}; use anyhow::{Result, bail}; -use yazi_shared::{loc::LocBuf, url::UrlBuf}; +use yazi_shared::{loc::LocBuf, url::{UrlBuf, UrlCow}}; use crate::provider; @@ -63,7 +63,14 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result { Ok(url) } -pub fn url_relative_to<'a>(from: &UrlBuf, to: &'a UrlBuf) -> Result> { +pub fn url_relative_to<'a>( + from: impl Into>, + to: impl Into>, +) -> Result> { + url_relative_to_impl(from.into(), to.into()) +} + +pub fn url_relative_to_impl<'a>(from: UrlCow<'a>, to: UrlCow<'a>) -> Result> { use yazi_shared::url::Component::*; if from.is_absolute() != to.is_absolute() { @@ -74,8 +81,8 @@ pub fn url_relative_to<'a>(from: &UrlBuf, to: &'a UrlBuf) -> Result(from: &UrlBuf, to: &'a UrlBuf) -> Result Cow<'_, std::path::Path> { #[cfg(test)] mod tests { - use std::borrow::Cow; - use super::url_relative_to; #[test] @@ -132,7 +137,7 @@ mod tests { fn assert(from: &str, to: &str, ret: &str) { assert_eq!( url_relative_to(&from.parse().unwrap(), &to.parse().unwrap()).unwrap(), - Cow::Owned(ret.parse().unwrap()) + ret.parse().unwrap() ); } diff --git a/yazi-fs/src/provider/provider.rs b/yazi-fs/src/provider/provider.rs index 6ef31783..911b1dfa 100644 --- a/yazi-fs/src/provider/provider.rs +++ b/yazi-fs/src/provider/provider.rs @@ -1,12 +1,12 @@ use std::io; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{Url, UrlBuf}; use crate::provider::{ReadDir, ReadDirSync, RwFile, local::Local}; #[inline] -pub async fn canonicalize(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn canonicalize<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::canonicalize(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -14,8 +14,8 @@ pub async fn canonicalize(url: impl AsRef) -> io::Result { } #[inline] -pub async fn create(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn create<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::create(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -23,8 +23,8 @@ pub async fn create(url: impl AsRef) -> io::Result { } #[inline] -pub async fn create_dir(url: impl AsRef) -> io::Result<()> { - if let Some(path) = url.as_ref().as_path() { +pub async fn create_dir<'a>(url: impl Into>) -> io::Result<()> { + if let Some(path) = url.into().as_path() { Local::create_dir(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -32,8 +32,8 @@ pub async fn create_dir(url: impl AsRef) -> io::Result<()> { } #[inline] -pub async fn create_dir_all(url: impl AsRef) -> io::Result<()> { - if let Some(path) = url.as_ref().as_path() { +pub async fn create_dir_all<'a>(url: impl Into>) -> io::Result<()> { + if let Some(path) = url.into().as_path() { Local::create_dir_all(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -41,8 +41,11 @@ pub async fn create_dir_all(url: impl AsRef) -> io::Result<()> { } #[inline] -pub async fn hard_link(original: impl AsRef, link: impl AsRef) -> io::Result<()> { - if let (Some(original), Some(link)) = (original.as_ref().as_path(), link.as_ref().as_path()) { +pub async fn hard_link<'a>( + original: impl Into>, + link: impl Into>, +) -> io::Result<()> { + if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) { Local::hard_link(original, link).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -50,8 +53,8 @@ pub async fn hard_link(original: impl AsRef, link: impl AsRef) - } #[inline] -pub async fn metadata(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn metadata<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::metadata(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -59,8 +62,8 @@ pub async fn metadata(url: impl AsRef) -> io::Result } #[inline] -pub async fn open(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn open<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::open(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -68,8 +71,8 @@ pub async fn open(url: impl AsRef) -> io::Result { } #[inline] -pub async fn read_dir(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn read_dir<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::read_dir(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -77,8 +80,8 @@ pub async fn read_dir(url: impl AsRef) -> io::Result { } #[inline] -pub fn read_dir_sync(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub fn read_dir_sync<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::read_dir_sync(path).map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -86,8 +89,8 @@ pub fn read_dir_sync(url: impl AsRef) -> io::Result { } #[inline] -pub async fn read_link(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn read_link<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::read_link(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -95,8 +98,8 @@ pub async fn read_link(url: impl AsRef) -> io::Result { } #[inline] -pub async fn remove_dir(url: impl AsRef) -> io::Result<()> { - if let Some(path) = url.as_ref().as_path() { +pub async fn remove_dir<'a>(url: impl Into>) -> io::Result<()> { + if let Some(path) = url.into().as_path() { Local::remove_dir(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -104,8 +107,8 @@ pub async fn remove_dir(url: impl AsRef) -> io::Result<()> { } #[inline] -pub async fn remove_dir_all(url: impl AsRef) -> io::Result<()> { - if let Some(path) = url.as_ref().as_path() { +pub async fn remove_dir_all<'a>(url: impl Into>) -> io::Result<()> { + if let Some(path) = url.into().as_path() { Local::remove_dir_all(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -113,8 +116,8 @@ pub async fn remove_dir_all(url: impl AsRef) -> io::Result<()> { } #[inline] -pub async fn remove_file(url: impl AsRef) -> io::Result<()> { - if let Some(path) = url.as_ref().as_path() { +pub async fn remove_file<'a>(url: impl Into>) -> io::Result<()> { + if let Some(path) = url.into().as_path() { Local::remove_file(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -122,8 +125,8 @@ pub async fn remove_file(url: impl AsRef) -> io::Result<()> { } #[inline] -pub async fn rename(from: impl AsRef, to: impl AsRef) -> io::Result<()> { - if let (Some(from), Some(to)) = (from.as_ref().as_path(), to.as_ref().as_path()) { +pub async fn rename<'a>(from: impl Into>, to: impl Into>) -> io::Result<()> { + if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) { Local::rename(from, to).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -131,8 +134,11 @@ pub async fn rename(from: impl AsRef, to: impl AsRef) -> io::Res } #[inline] -pub async fn symlink_dir(original: impl AsRef, link: impl AsRef) -> io::Result<()> { - if let (Some(original), Some(link)) = (original.as_ref().as_path(), link.as_ref().as_path()) { +pub async fn symlink_dir<'a>( + original: impl Into>, + link: impl Into>, +) -> io::Result<()> { + if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) { Local::symlink_dir(original, link).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -140,11 +146,11 @@ pub async fn symlink_dir(original: impl AsRef, link: impl AsRef) } #[inline] -pub async fn symlink_file( - original: impl AsRef, - link: impl AsRef, +pub async fn symlink_file<'a>( + original: impl Into>, + link: impl Into>, ) -> io::Result<()> { - if let (Some(original), Some(link)) = (original.as_ref().as_path(), link.as_ref().as_path()) { + if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) { Local::symlink_file(original, link).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -152,16 +158,16 @@ pub async fn symlink_file( } #[inline] -pub async fn symlink_metadata(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub async fn symlink_metadata<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::symlink_metadata(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } } -pub fn symlink_metadata_sync(url: impl AsRef) -> io::Result { - if let Some(path) = url.as_ref().as_path() { +pub fn symlink_metadata_sync<'a>(url: impl Into>) -> io::Result { + if let Some(path) = url.into().as_path() { Local::symlink_metadata_sync(path) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) @@ -169,8 +175,8 @@ pub fn symlink_metadata_sync(url: impl AsRef) -> io::Result, contents: impl AsRef<[u8]>) -> io::Result<()> { - if let Some(path) = url.as_ref().as_path() { +pub async fn write<'a>(url: impl Into>, contents: impl AsRef<[u8]>) -> io::Result<()> { + if let Some(path) = url.into().as_path() { Local::write(path, contents).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) diff --git a/yazi-parser/Cargo.toml b/yazi-parser/Cargo.toml index 0e88a45e..e7efb0de 100644 --- a/yazi-parser/Cargo.toml +++ b/yazi-parser/Cargo.toml @@ -24,6 +24,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.6.11" } anyhow = { workspace = true } bitflags = { workspace = true } crossterm = { workspace = true } +hashbrown = { workspace = true } mlua = { workspace = true } serde = { workspace = true } tokio = { workspace = true } diff --git a/yazi-parser/src/mgr/cd.rs b/yazi-parser/src/mgr/cd.rs index 1a7fc949..09a2a015 100644 --- a/yazi-parser/src/mgr/cd.rs +++ b/yazi-parser/src/mgr/cd.rs @@ -1,10 +1,10 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_fs::path::expand_url; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; #[derive(Debug)] pub struct CdOpt { - pub target: UrlBuf, + pub target: UrlCow<'static>, pub interactive: bool, pub source: CdSource, } @@ -14,16 +14,22 @@ impl From for CdOpt { let mut target = c.take_first_url().unwrap_or_default(); if !c.bool("raw") { - target = expand_url(&target); + target = expand_url(target).into(); } Self { target, interactive: c.bool("interactive"), source: CdSource::Cd } } } +impl From<(UrlCow<'static>, CdSource)> for CdOpt { + fn from((target, source): (UrlCow<'static>, CdSource)) -> Self { + Self { target, interactive: false, source } + } +} + impl From<(UrlBuf, CdSource)> for CdOpt { fn from((target, source): (UrlBuf, CdSource)) -> Self { - Self { target, interactive: false, source } + Self::from((UrlCow::from(target), source)) } } diff --git a/yazi-parser/src/mgr/peek.rs b/yazi-parser/src/mgr/peek.rs index 22b54672..d92a4181 100644 --- a/yazi-parser/src/mgr/peek.rs +++ b/yazi-parser/src/mgr/peek.rs @@ -1,11 +1,11 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{event::{CmdCow, Data}, url::UrlBuf}; +use yazi_shared::{event::{CmdCow, Data}, url::UrlCow}; #[derive(Debug, Default)] pub struct PeekOpt { pub skip: Option, pub force: bool, - pub only_if: Option, + pub only_if: Option>, pub upper_bound: bool, } diff --git a/yazi-parser/src/mgr/reveal.rs b/yazi-parser/src/mgr/reveal.rs index 76db1c81..a3fe73f8 100644 --- a/yazi-parser/src/mgr/reveal.rs +++ b/yazi-parser/src/mgr/reveal.rs @@ -1,12 +1,12 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_fs::path::expand_url; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; use crate::mgr::CdSource; #[derive(Debug)] pub struct RevealOpt { - pub target: UrlBuf, + pub target: UrlCow<'static>, pub source: CdSource, pub no_dummy: bool, } @@ -16,19 +16,33 @@ impl From for RevealOpt { let mut target = c.take_first_url().unwrap_or_default(); if !c.bool("raw") { - target = expand_url(&target); + target = expand_url(target).into(); } Self { target, source: CdSource::Reveal, no_dummy: c.bool("no-dummy") } } } +impl From> for RevealOpt { + fn from(target: UrlCow<'static>) -> Self { + Self { target, source: CdSource::Reveal, no_dummy: false } + } +} + +impl From<(UrlCow<'static>, CdSource)> for RevealOpt { + fn from((target, source): (UrlCow<'static>, CdSource)) -> Self { + Self { target, source, no_dummy: false } + } +} + impl From for RevealOpt { - fn from(target: UrlBuf) -> Self { Self { target, source: CdSource::Reveal, no_dummy: false } } + fn from(target: UrlBuf) -> Self { Self::from(UrlCow::from(target)) } } impl From<(UrlBuf, CdSource)> for RevealOpt { - fn from((target, source): (UrlBuf, CdSource)) -> Self { Self { target, source, no_dummy: false } } + fn from((target, source): (UrlBuf, CdSource)) -> Self { + Self::from((UrlCow::from(target), source)) + } } impl FromLua for RevealOpt { diff --git a/yazi-parser/src/mgr/shell.rs b/yazi-parser/src/mgr/shell.rs index b705904c..f295621f 100644 --- a/yazi-parser/src/mgr/shell.rs +++ b/yazi-parser/src/mgr/shell.rs @@ -1,11 +1,11 @@ use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{SStr, event::{CmdCow, Data}, url::UrlBuf}; +use yazi_shared::{SStr, event::{CmdCow, Data}, url::UrlCow}; #[derive(Debug)] pub struct ShellOpt { pub run: SStr, - pub cwd: Option, + pub cwd: Option>, pub block: bool, pub orphan: bool, diff --git a/yazi-parser/src/mgr/tab_create.rs b/yazi-parser/src/mgr/tab_create.rs index f974ed67..10f7ce17 100644 --- a/yazi-parser/src/mgr/tab_create.rs +++ b/yazi-parser/src/mgr/tab_create.rs @@ -1,11 +1,11 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_boot::BOOT; use yazi_fs::path::expand_url; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::UrlCow}; #[derive(Debug)] pub struct TabCreateOpt { - pub wd: Option, + pub wd: Option>, } impl From for TabCreateOpt { @@ -14,10 +14,10 @@ impl From for TabCreateOpt { return Self { wd: None }; } let Some(mut wd) = c.take_first_url() else { - return Self { wd: Some(BOOT.cwds[0].clone()) }; + return Self { wd: Some(UrlCow::from(&BOOT.cwds[0])) }; }; if !c.bool("raw") { - wd = expand_url(&wd); + wd = expand_url(wd).into(); } Self { wd: Some(wd) } } diff --git a/yazi-parser/src/mgr/toggle.rs b/yazi-parser/src/mgr/toggle.rs index f86d185e..f01a23e3 100644 --- a/yazi-parser/src/mgr/toggle.rs +++ b/yazi-parser/src/mgr/toggle.rs @@ -1,9 +1,9 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::UrlCow}; #[derive(Debug)] pub struct ToggleOpt { - pub url: Option, + pub url: Option>, pub state: Option, } diff --git a/yazi-parser/src/mgr/toggle_all.rs b/yazi-parser/src/mgr/toggle_all.rs index d49fda82..9cebbeae 100644 --- a/yazi-parser/src/mgr/toggle_all.rs +++ b/yazi-parser/src/mgr/toggle_all.rs @@ -1,9 +1,9 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::UrlCow}; #[derive(Debug)] pub struct ToggleAllOpt { - pub urls: Vec, + pub urls: Vec>, pub state: Option, } diff --git a/yazi-parser/src/mgr/update_paged.rs b/yazi-parser/src/mgr/update_paged.rs index 720f8600..85f793a5 100644 --- a/yazi-parser/src/mgr/update_paged.rs +++ b/yazi-parser/src/mgr/update_paged.rs @@ -1,10 +1,10 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; -use yazi_shared::{event::{CmdCow, Data}, url::UrlBuf}; +use yazi_shared::{event::{CmdCow, Data}, url::UrlCow}; #[derive(Debug, Default)] pub struct UpdatePagedOpt { pub page: Option, - pub only_if: Option, + pub only_if: Option>, } impl From for UpdatePagedOpt { diff --git a/yazi-parser/src/mgr/update_yanked.rs b/yazi-parser/src/mgr/update_yanked.rs index c1771443..3a65b9cb 100644 --- a/yazi-parser/src/mgr/update_yanked.rs +++ b/yazi-parser/src/mgr/update_yanked.rs @@ -1,20 +1,21 @@ -use std::{borrow::Cow, collections::HashSet}; +use std::borrow::Cow; use anyhow::bail; +use hashbrown::HashSet; use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods, Value}; use serde::{Deserialize, Serialize}; use yazi_binding::get_metatable; -use yazi_shared::{event::CmdCow, url::UrlCov}; +use yazi_shared::{event::CmdCow, url::UrlBufCov}; type Iter = yazi_binding::Iter< - std::iter::Map, fn(UrlCov) -> yazi_binding::Url>, + std::iter::Map, fn(UrlBufCov) -> yazi_binding::Url>, yazi_binding::Url, >; #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct UpdateYankedOpt<'a> { pub cut: bool, - pub urls: Cow<'a, HashSet>, + pub urls: Cow<'a, HashSet>, } impl TryFrom for UpdateYankedOpt<'_> { diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 6c76bbec..29d46e73 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -4,6 +4,7 @@ use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value}; use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef}; use yazi_config::Pattern; use yazi_fs::{mounts::PARTITIONS, provider, remove_dir_clean}; +use yazi_shared::url::UrlCow; use crate::bindings::SizeCalculator; @@ -157,7 +158,7 @@ fn expand_url(lua: &Lua) -> mlua::Result { lua.create_function(|_, value: Value| { use yazi_fs::path::expand_url; Ok(Url::new(match value { - Value::String(s) => expand_url(Url::try_from(s.as_bytes().as_ref())?), + Value::String(s) => expand_url(UrlCow::try_from(s.as_bytes().as_ref())?), Value::UserData(ud) => expand_url(&*ud.borrow::()?), _ => Err("must be a string or a Url".into_lua_err())?, })) diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 321bcdcf..5b607c43 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -1,11 +1,11 @@ -use std::{borrow::Cow, collections::VecDeque}; +use std::collections::VecDeque; use anyhow::{Result, anyhow}; use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; use yazi_config::YAZI; use yazi_fs::{SizeCalculator, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path::{skip_url, url_relative_to}, provider::{self, DirEntry}}; -use yazi_shared::{Id, url::UrlBuf}; +use yazi_shared::{Id, url::{UrlBuf, UrlCow}}; use super::{FileIn, FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; use crate::{LOW, NORMAL, TaskOp, TaskProg}; @@ -63,7 +63,7 @@ impl File { let src = if task.resolve { match provider::read_link(&task.from).await { - Ok(u) => Cow::Owned(u), + Ok(u) => UrlCow::from(u), Err(e) if e.kind() == NotFound => { warn!("Link task partially done: {task:?}"); return Ok(self.prog.send(TaskProg::Adv(task.id, 1, cha.len))?); @@ -71,20 +71,20 @@ impl File { Err(e) => Err(e)?, } } else { - Cow::Borrowed(&task.from) + UrlCow::from(&task.from) }; let src = if task.relative { - url_relative_to(&provider::canonicalize(task.to.parent_url().unwrap()).await?, &src)? + url_relative_to(provider::canonicalize(&task.to.parent_url().unwrap()).await?, src)? } else { src }; ok_or_not_found(provider::remove_file(&task.to).await)?; if cha.is_dir() { - provider::symlink_dir(src, &task.to).await?; + provider::symlink_dir(&src, &task.to).await?; } else { - provider::symlink_file(src, &task.to).await?; + provider::symlink_file(&src, &task.to).await?; } if task.delete { @@ -95,15 +95,15 @@ impl File { FileIn::Hardlink(task) => { let cha = task.cha.unwrap(); let src = if !task.follow { - Cow::Borrowed(&task.from) + UrlCow::from(&task.from) } else if let Ok(p) = provider::canonicalize(&task.from).await { - Cow::Owned(p) + UrlCow::from(p) } else { - Cow::Borrowed(&task.from) + UrlCow::from(&task.from) }; ok_or_not_found(provider::remove_file(&task.to).await)?; - match provider::hard_link(src, &task.to).await { + match provider::hard_link(&src, &task.to).await { Err(e) if e.kind() == NotFound => { warn!("Hardlink task partially done: {task:?}"); } @@ -291,7 +291,7 @@ impl File { let mut dirs = VecDeque::from([task.target]); while let Some(target) = dirs.pop_front() { - let Ok(mut it) = provider::read_dir(target).await else { continue }; + let Ok(mut it) = provider::read_dir(&target).await else { continue }; while let Ok(Some(entry)) = it.next_entry().await { let Ok(meta) = entry.metadata().await else { continue }; diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 1ea6c36e..888dbd3c 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -17,6 +17,7 @@ anyhow = { workspace = true } crossterm = { workspace = true } foldhash = { workspace = true } futures = { workspace = true } +hashbrown = { workspace = true } memchr = "2.7.5" ordered-float = { workspace = true } parking_lot = { workspace = true } diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index c4bd739b..cb9cdff0 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -4,7 +4,7 @@ use anyhow::{Result, anyhow, bail}; use serde::{Deserialize, de}; use super::{Data, DataKey}; -use crate::{Id, Layer, SStr, Source, url::UrlBuf}; +use crate::{Id, Layer, SStr, Source, url::UrlCow}; #[derive(Debug, Default)] pub struct Cmd { @@ -99,6 +99,9 @@ impl Cmd { #[inline] pub fn id(&self, name: impl Into) -> Option { self.get(name)?.as_id() } + #[inline] + pub fn url(&self, name: impl Into) -> Option> { self.get(name)?.to_url() } + #[inline] pub fn first(&self) -> Option<&Data> { self.get(0) } @@ -131,7 +134,7 @@ impl Cmd { } #[inline] - pub fn take_first_url(&mut self) -> Option { self.take_first()?.into_url() } + pub fn take_first_url(&mut self) -> Option> { self.take_first()?.into_url() } #[inline] pub fn take_any(&mut self, name: impl Into) -> Option { diff --git a/yazi-shared/src/event/cow.rs b/yazi-shared/src/event/cow.rs index 277944a6..1fb1775c 100644 --- a/yazi-shared/src/event/cow.rs +++ b/yazi-shared/src/event/cow.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, ops::Deref}; use anyhow::Result; use super::{Cmd, Data, DataKey}; -use crate::{SStr, url::UrlBuf}; +use crate::{SStr, url::UrlCow}; #[derive(Debug)] pub enum CmdCow { @@ -48,10 +48,10 @@ impl CmdCow { } #[inline] - pub fn take_url(&mut self, name: impl Into) -> Option { + pub fn take_url(&mut self, name: impl Into) -> Option> { match self { Self::Owned(c) => c.take(name).and_then(Data::into_url), - Self::Borrowed(c) => c.get(name).and_then(Data::to_url), + Self::Borrowed(c) => c.url(name), } } @@ -63,7 +63,7 @@ impl CmdCow { } } - pub fn take_first_url(&mut self) -> Option { + pub fn take_first_url(&mut self) -> Option> { match self { Self::Owned(c) => c.take_first_url(), Self::Borrowed(c) => c.first().and_then(Data::to_url), diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 64dfff88..5c41cee6 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -4,7 +4,7 @@ use anyhow::{Result, bail}; use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize, de}; -use crate::{Id, SStr, url::{UrlBuf, UrnBuf}}; +use crate::{Id, SStr, url::{UrlBuf, UrlCow, UrnBuf}}; // --- Data #[derive(Debug, Serialize, Deserialize)] @@ -64,11 +64,11 @@ impl Data { } #[inline] - pub fn into_url(self) -> Option { + pub fn into_url(self) -> Option> { match self { - Self::String(s) => s.parse().ok(), - Self::Url(u) => Some(u), - Self::Bytes(b) => b.as_slice().try_into().ok(), + Self::String(s) => s.try_into().ok(), + Self::Url(u) => Some(u.into()), + Self::Bytes(b) => b.try_into().ok(), _ => None, } } @@ -94,10 +94,10 @@ impl Data { } #[inline] - pub fn to_url(&self) -> Option { + pub fn to_url(&self) -> Option> { match self { - Self::String(s) => s.parse().ok(), - Self::Url(u) => Some(u.clone()), + Self::String(s) => s.as_ref().try_into().ok(), + Self::Url(u) => Some(u.into()), Self::Bytes(b) => b.as_slice().try_into().ok(), _ => None, } @@ -184,11 +184,11 @@ impl DataKey { } #[inline] - pub fn into_url(self) -> Option { + pub fn into_url(self) -> Option> { match self { - Self::String(s) => s.parse().ok(), - Self::Url(u) => Some(u), - Self::Bytes(b) => b.as_slice().try_into().ok(), + Self::String(s) => s.try_into().ok(), + Self::Url(u) => Some(u.into()), + Self::Bytes(b) => b.try_into().ok(), _ => None, } } diff --git a/yazi-shared/src/loc/buf.rs b/yazi-shared/src/loc/buf.rs index 4eee9d95..1a58a2cf 100644 --- a/yazi-shared/src/loc/buf.rs +++ b/yazi-shared/src/loc/buf.rs @@ -33,8 +33,9 @@ impl PartialOrd for LocBuf { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } +// --- Hash impl Hash for LocBuf { - fn hash(&self, state: &mut H) { self.inner.hash(state) } + fn hash(&self, state: &mut H) { self.as_loc().hash(state) } } impl Debug for LocBuf { @@ -137,23 +138,6 @@ impl LocBuf { loc } - #[inline] - pub fn triple(&self) -> (&Path, &Path, &Path) { - let len = self.bytes().len(); - - let base = ..len - self.uri; - let rest = len - self.uri..len - self.urn; - let urn = len - self.urn..; - - unsafe { - ( - Path::new(OsStr::from_encoded_bytes_unchecked(self.bytes().get_unchecked(base))), - Path::new(OsStr::from_encoded_bytes_unchecked(self.bytes().get_unchecked(rest))), - Path::new(OsStr::from_encoded_bytes_unchecked(self.bytes().get_unchecked(urn))), - ) - } - } - #[inline] fn bytes(&self) -> &[u8] { self.inner.as_os_str().as_encoded_bytes() } @@ -174,7 +158,7 @@ impl LocBuf { pub fn urn(&self) -> &Urn { self.as_loc().urn() } #[inline] - pub fn urn_owned(&self) -> UrnBuf { self.urn().to_owned() } + pub fn urn_owned(&self) -> UrnBuf { self.as_loc().urn_owned() } #[inline] pub fn base(&self) -> &Urn { self.as_loc().base() } diff --git a/yazi-shared/src/loc/loc.rs b/yazi-shared/src/loc/loc.rs index f214a62b..42280a92 100644 --- a/yazi-shared/src/loc/loc.rs +++ b/yazi-shared/src/loc/loc.rs @@ -1,9 +1,10 @@ -use std::{ffi::OsStr, ops::Deref, path::Path}; +use std::{ffi::OsStr, hash::{Hash, Hasher}, ops::Deref, path::Path}; use anyhow::{Result, bail}; -use crate::{loc::LocBuf, url::{Uri, Urn}}; +use crate::{loc::LocBuf, url::{Uri, Urn, UrnBuf}}; +#[derive(Clone, Copy, Debug)] pub struct Loc<'a> { pub(super) inner: &'a Path, pub(super) uri: usize, @@ -22,6 +23,11 @@ impl<'a> From<&'a LocBuf> for Loc<'a> { } } +// --- Hash +impl Hash for Loc<'_> { + fn hash(&self, state: &mut H) { self.inner.hash(state) } +} + impl<'a, T: ?Sized + AsRef> From<&'a T> for Loc<'a> { fn from(value: &'a T) -> Self { let path = Path::new(value.as_ref()); @@ -55,6 +61,13 @@ impl From> for LocBuf { } } +// --- Eq +impl PartialEq for Loc<'_> { + fn eq(&self, other: &Self) -> bool { self.inner == other.inner } +} + +impl Eq for Loc<'_> {} + impl<'a> Loc<'a> { pub fn new(path: &'a T, base: &Path, trail: &Path) -> Self where @@ -98,7 +111,10 @@ impl<'a> Loc<'a> { } #[inline] - pub fn uri(&self) -> &'a Uri { + pub fn as_loc(self) -> Loc<'a> { self } + + #[inline] + pub fn uri(self) -> &'a Uri { Uri::new(unsafe { OsStr::from_encoded_bytes_unchecked( self.bytes().get_unchecked(self.bytes().len() - self.uri..), @@ -107,7 +123,7 @@ impl<'a> Loc<'a> { } #[inline] - pub fn urn(&self) -> &'a Urn { + pub fn urn(self) -> &'a Urn { Urn::new(unsafe { OsStr::from_encoded_bytes_unchecked( self.bytes().get_unchecked(self.bytes().len() - self.urn..), @@ -116,7 +132,10 @@ impl<'a> Loc<'a> { } #[inline] - pub fn base(&self) -> &'a Urn { + pub fn urn_owned(self) -> UrnBuf { self.urn().to_owned() } + + #[inline] + pub fn base(self) -> &'a Urn { Urn::new(unsafe { OsStr::from_encoded_bytes_unchecked( self.bytes().get_unchecked(..self.bytes().len() - self.uri), @@ -125,10 +144,10 @@ impl<'a> Loc<'a> { } #[inline] - pub fn has_base(&self) -> bool { self.bytes().len() != self.uri } + pub fn has_base(self) -> bool { self.bytes().len() != self.uri } #[inline] - pub fn trail(&self) -> &'a Urn { + pub fn trail(self) -> &'a Urn { Urn::new(unsafe { OsStr::from_encoded_bytes_unchecked( self.bytes().get_unchecked(..self.bytes().len() - self.urn), @@ -137,11 +156,28 @@ impl<'a> Loc<'a> { } #[inline] - pub fn has_trail(&self) -> bool { self.bytes().len() != self.urn } + pub fn has_trail(self) -> bool { self.bytes().len() != self.urn } #[inline] - pub fn name(&self) -> &'a OsStr { self.inner.file_name().unwrap_or(OsStr::new("")) } + pub fn name(self) -> &'a OsStr { self.inner.file_name().unwrap_or(OsStr::new("")) } #[inline] - fn bytes(&self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() } + pub fn triple(self) -> (&'a Path, &'a Path, &'a Path) { + let len = self.bytes().len(); + + let base = ..len - self.uri; + let rest = len - self.uri..len - self.urn; + let urn = len - self.urn..; + + unsafe { + ( + Path::new(OsStr::from_encoded_bytes_unchecked(self.bytes().get_unchecked(base))), + Path::new(OsStr::from_encoded_bytes_unchecked(self.bytes().get_unchecked(rest))), + Path::new(OsStr::from_encoded_bytes_unchecked(self.bytes().get_unchecked(urn))), + ) + } + } + + #[inline] + fn bytes(self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() } } diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index baee1f9d..2f3497a8 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -1,11 +1,10 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::BuildHasher, ops::Deref, path::{Path, PathBuf}, str::FromStr}; use anyhow::Result; -use percent_encoding::percent_decode; use serde::{Deserialize, Serialize}; use super::UrnBuf; -use crate::{IntoOsStr, loc::LocBuf, url::{Components, Display, Encode, EncodeTilded, Scheme, Url, Urn}}; +use crate::{loc::LocBuf, url::{Components, Display, Encode, EncodeTilded, Scheme, Url, UrlCow, Urn}}; #[derive(Clone, Default, Eq, Ord, PartialOrd, PartialEq, Hash)] pub struct UrlBuf { @@ -27,6 +26,14 @@ impl From for UrlBuf { fn from(path: PathBuf) -> Self { LocBuf::from(path).into() } } +impl From<&Url<'_>> for UrlBuf { + fn from(url: &Url<'_>) -> Self { Self { loc: url.loc.into(), scheme: url.scheme.clone() } } +} + +impl From> for UrlBuf { + fn from(value: Url<'_>) -> Self { Self::from(&value) } +} + impl From<&UrlBuf> for UrlBuf { fn from(url: &UrlBuf) -> Self { url.clone() } } @@ -39,29 +46,18 @@ impl From<&Path> for UrlBuf { fn from(path: &Path) -> Self { path.to_path_buf().into() } } -impl TryFrom<&[u8]> for UrlBuf { - type Error = anyhow::Error; - - fn try_from(bytes: &[u8]) -> Result { - let (scheme, path, port) = Self::parse(bytes)?; - - let loc = - if let Some((uri, urn)) = port { LocBuf::with(path, uri, urn)? } else { LocBuf::from(path) }; - - Ok(Self { loc, scheme }) - } -} - impl FromStr for UrlBuf { type Err = anyhow::Error; - fn from_str(s: &str) -> Result { s.as_bytes().try_into() } + fn from_str(s: &str) -> Result { Ok(UrlCow::try_from(s)?.into_owned()) } } impl TryFrom for UrlBuf { type Error = anyhow::Error; - fn try_from(value: String) -> Result { value.as_bytes().try_into() } + fn try_from(value: String) -> Result { + Ok(UrlCow::try_from(value)?.into_owned()) + } } impl AsRef for UrlBuf { @@ -85,6 +81,15 @@ impl From> for UrlBuf { fn from(url: Cow<'_, UrlBuf>) -> Self { url.into_owned() } } +// --- Eq +impl PartialEq> for UrlBuf { + fn eq(&self, other: &Url) -> bool { self.as_url() == *other } +} + +impl PartialEq> for &UrlBuf { + fn eq(&self, other: &Url) -> bool { self.as_url() == *other } +} + impl UrlBuf { pub fn join(&self, path: impl AsRef) -> Self { use Scheme as S; @@ -102,12 +107,10 @@ impl UrlBuf { } #[inline] - pub fn components(&self) -> Components<'_> { Components::new(self) } + pub fn components(&self) -> Components<'_> { Components::from(self) } #[inline] - pub fn covariant(&self, other: &Self) -> bool { - self.scheme.covariant(&other.scheme) && self.loc == other.loc - } + pub fn covariant(&self, other: &Self) -> bool { self.as_url().covariant(other.as_url()) } #[inline] pub fn display(&self) -> Display<'_> { Display::new(self) } @@ -115,36 +118,8 @@ impl UrlBuf { #[inline] pub fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() } - pub fn parent_url(&self) -> Option { - use Scheme as S; - - let parent = self.loc.parent()?; - let uri = self.loc.uri(); - - Some(match self.scheme { - // Regular - S::Regular => Self { loc: parent.into(), scheme: S::Regular }, - - // Search - S::Search(_) if uri.is_empty() => Self { loc: parent.into(), scheme: S::Regular }, - S::Search(_) => Self { - loc: LocBuf::new(parent, self.loc.base(), self.loc.base()), - scheme: self.scheme.clone(), - }, - - // Archive - S::Archive(_) if uri.is_empty() => Self { loc: parent.into(), scheme: S::Regular }, - S::Archive(_) if uri.nth(1).is_none() => { - Self { loc: LocBuf::zeroed(parent), scheme: self.scheme.clone() } - } - S::Archive(_) => { - Self { loc: LocBuf::floated(parent, self.loc.base()), scheme: self.scheme.clone() } - } - - // SFTP - S::Sftp(_) => Self { loc: parent.into(), scheme: self.scheme.clone() }, - }) - } + #[inline] + pub fn parent_url(&self) -> Option { self.as_url().parent_url() } pub fn strip_prefix(&self, base: impl AsRef) -> Option<&Urn> { use Scheme as S; @@ -202,26 +177,6 @@ impl UrlBuf { #[inline] pub fn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self) } - - pub fn parse(bytes: &[u8]) -> Result<(Scheme, PathBuf, Option<(usize, usize)>)> { - let mut skip = 0; - let (scheme, tilde, uri, urn) = Scheme::parse(bytes, &mut skip)?; - - let rest = if tilde { - Cow::from(percent_decode(&bytes[skip..])).into_os_str()? - } else { - bytes[skip..].into_os_str()? - }; - - let path = match rest { - Cow::Borrowed(s) => Path::new(s).to_owned(), - Cow::Owned(s) => PathBuf::from(s), - }; - - let ports = scheme.normalize_ports(uri, urn, &path)?; - - Ok((scheme, path, ports)) - } } impl UrlBuf { @@ -235,7 +190,7 @@ impl UrlBuf { impl UrlBuf { // --- Regular #[inline] - pub fn is_regular(&self) -> bool { self.scheme == Scheme::Regular } + pub fn is_regular(&self) -> bool { self.as_url().is_regular() } #[inline] pub fn to_regular(&self) -> Self { diff --git a/yazi-shared/src/url/component.rs b/yazi-shared/src/url/component.rs index 4dc19dbb..d0044c9a 100644 --- a/yazi-shared/src/url/component.rs +++ b/yazi-shared/src/url/component.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not, path::{self, PathBuf, PrefixComponent}}; -use crate::{loc::LocBuf, url::{Encode, Scheme, UrlBuf}}; +use crate::{loc::Loc, url::{Encode, Scheme, Url, UrlBuf, UrlCow}}; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Component<'a> { @@ -60,21 +60,43 @@ impl<'a> FromIterator> for PathBuf { #[derive(Clone)] pub struct Components<'a> { inner: path::Components<'a>, - loc: &'a LocBuf, + loc: Loc<'a>, scheme: &'a Scheme, scheme_yielded: bool, } -impl<'a> Components<'a> { - pub fn new(url: &'a UrlBuf) -> Self { +impl<'a> From<&'a Url<'a>> for Components<'a> { + fn from(value: &'a Url<'a>) -> Self { Self { - inner: url.loc.components(), - loc: &url.loc, - scheme: &url.scheme, + inner: value.loc.components(), + loc: value.loc, + scheme: &value.scheme, scheme_yielded: false, } } +} +impl<'a> From<&'a UrlBuf> for Components<'a> { + fn from(value: &'a UrlBuf) -> Self { + Self { + inner: value.loc.components(), + loc: value.loc.as_loc(), + scheme: &value.scheme, + scheme_yielded: false, + } + } +} + +impl<'a> From<&'a UrlCow<'a>> for Components<'a> { + fn from(value: &'a UrlCow<'a>) -> Self { + match value { + UrlCow::Borrowed(url) => Self::from(url), + UrlCow::Owned(url) => Self::from(url), + } + } +} + +impl<'a> Components<'a> { pub fn os_str(&self) -> Cow<'a, OsStr> { let path = self.inner.as_path(); if !self.scheme.is_virtual() || self.scheme_yielded { diff --git a/yazi-shared/src/url/cov.rs b/yazi-shared/src/url/cov.rs index 238e7e1e..2fbd8afe 100644 --- a/yazi-shared/src/url/cov.rs +++ b/yazi-shared/src/url/cov.rs @@ -1,54 +1,87 @@ use std::{hash::{Hash, Hasher}, ops::Deref}; +use hashbrown::Equivalent; use serde::{Deserialize, Serialize}; -use crate::url::UrlBuf; +use crate::url::{Url, UrlBuf}; +#[derive(Clone)] +pub struct UrlCov<'a>(Url<'a>); + +impl<'a> Deref for UrlCov<'a> { + type Target = Url<'a>; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl<'a> From<&'a UrlBufCov> for UrlCov<'a> { + fn from(value: &'a UrlBufCov) -> Self { Self(value.0.as_url()) } +} + +impl PartialEq for UrlCov<'_> { + fn eq(&self, other: &UrlBufCov) -> bool { self.0.covariant(other.0.as_url()) } +} + +impl Hash for UrlCov<'_> { + fn hash(&self, state: &mut H) { + self.0.loc.hash(state); + if self.0.scheme.is_virtual() { + self.0.scheme.hash(state); + } + } +} + +impl Equivalent for UrlCov<'_> { + fn equivalent(&self, key: &UrlBufCov) -> bool { self == key } +} + +impl<'a> UrlCov<'a> { + #[inline] + pub fn new(url: impl Into>) -> Self { Self(url.into()) } +} + +// --- Buf #[derive(Clone, Debug, Deserialize, Eq, Serialize)] -#[repr(transparent)] -pub struct UrlCov(pub UrlBuf); +pub struct UrlBufCov(pub UrlBuf); -impl Deref for UrlCov { +impl Deref for UrlBufCov { type Target = UrlBuf; fn deref(&self) -> &Self::Target { &self.0 } } -impl AsRef for UrlCov { - fn as_ref(&self) -> &UrlBuf { &self.0 } -} - -impl From for UrlCov { +impl From for UrlBufCov { fn from(value: UrlBuf) -> Self { Self(value) } } -impl From for UrlBuf { - fn from(value: UrlCov) -> Self { value.0 } +impl From<&UrlCov<'_>> for UrlBufCov { + fn from(value: &UrlCov<'_>) -> Self { Self(UrlBuf::from(&value.0)) } } -impl Hash for UrlCov { - fn hash(&self, state: &mut H) { - self.loc.hash(state); - if self.scheme.is_virtual() { - self.scheme.hash(state); - } - } +impl From for UrlBuf { + fn from(value: UrlBufCov) -> Self { value.0 } } -impl PartialEq for UrlCov { +impl<'a> From<&'a UrlBufCov> for Url<'a> { + fn from(value: &'a UrlBufCov) -> Self { value.0.as_url() } +} + +impl Hash for UrlBufCov { + fn hash(&self, state: &mut H) { self.as_url().hash(state) } +} + +impl PartialEq for UrlBufCov { fn eq(&self, other: &Self) -> bool { self.covariant(other) } } -impl PartialEq for UrlCov { +impl PartialEq for UrlBufCov { fn eq(&self, other: &UrlBuf) -> bool { self.covariant(other) } } -impl UrlCov { +impl UrlBufCov { #[inline] - pub fn new>(u: &T) -> &Self { - unsafe { &*(u.as_ref() as *const UrlBuf as *const Self) } - } + pub fn as_url(&self) -> UrlCov<'_> { UrlCov::from(self) } #[inline] - pub fn parent_url(&self) -> Option { self.0.parent_url().map(UrlCov) } + pub fn parent_url(&self) -> Option { self.0.parent_url().map(UrlBufCov) } } diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs new file mode 100644 index 00000000..cc92902a --- /dev/null +++ b/yazi-shared/src/url/cow.rs @@ -0,0 +1,159 @@ +use std::{borrow::Cow, path::{Path, PathBuf}}; + +use anyhow::Result; +use percent_encoding::percent_decode; + +use crate::{IntoOsStr, loc::{Loc, LocBuf}, url::{Components, Scheme, Url, UrlBuf, UrnBuf}}; + +#[derive(Debug)] +pub enum UrlCow<'a> { + Borrowed(Url<'a>), + Owned(UrlBuf), +} + +impl Default for UrlCow<'_> { + fn default() -> Self { Self::Owned(UrlBuf::default()) } +} + +impl<'a> From> for UrlCow<'a> { + fn from(value: Url<'a>) -> Self { Self::Borrowed(value) } +} + +impl<'a> From<&'a UrlBuf> for UrlCow<'a> { + fn from(value: &'a UrlBuf) -> Self { Self::Borrowed(value.into()) } +} + +impl From for UrlCow<'_> { + fn from(value: UrlBuf) -> Self { Self::Owned(value) } +} + +impl<'a> From<&'a UrlCow<'a>> for Url<'a> { + fn from(value: &'a UrlCow<'a>) -> Self { value.as_url() } +} + +impl From> for UrlBuf { + fn from(value: UrlCow<'_>) -> Self { value.into_owned() } +} + +impl<'a> TryFrom<&'a [u8]> for UrlCow<'a> { + type Error = anyhow::Error; + + fn try_from(value: &'a [u8]) -> Result { + let (scheme, path, port) = Self::parse(value)?; + + Ok(match (path, port) { + (Cow::Borrowed(p), None) => Url { loc: Loc::from(p), scheme }.into(), + (Cow::Borrowed(p), Some((uri, urn))) => Url { loc: Loc::with(p, uri, urn)?, scheme }.into(), + (Cow::Owned(p), None) => UrlBuf { loc: LocBuf::from(p), scheme }.into(), + (Cow::Owned(p), Some((uri, urn))) => { + UrlBuf { loc: LocBuf::with(p, uri, urn)?, scheme }.into() + } + }) + } +} + +impl TryFrom> for UrlCow<'_> { + type Error = anyhow::Error; + + fn try_from(value: Vec) -> Result { + Ok(UrlCow::try_from(value.as_slice())?.into_owned().into()) + } +} + +impl<'a> TryFrom<&'a str> for UrlCow<'a> { + type Error = anyhow::Error; + + fn try_from(value: &'a str) -> Result { Self::try_from(value.as_bytes()) } +} + +impl TryFrom for UrlCow<'_> { + type Error = anyhow::Error; + + fn try_from(value: String) -> Result { + Ok(UrlCow::try_from(value.as_str())?.into_owned().into()) + } +} + +impl<'a> TryFrom> for UrlCow<'a> { + type Error = anyhow::Error; + + fn try_from(value: Cow<'a, str>) -> Result { + match value { + Cow::Borrowed(s) => UrlCow::try_from(s), + Cow::Owned(s) => UrlCow::try_from(s), + } + } +} + +// --- Eq +impl PartialEq for UrlCow<'_> { + fn eq(&self, other: &UrlBuf) -> bool { self.as_url() == other.as_url() } +} + +impl UrlCow<'_> { + #[inline] + pub fn loc(&self) -> Loc<'_> { + match self { + UrlCow::Borrowed(u) => u.loc.as_loc(), + UrlCow::Owned(u) => u.loc.as_loc(), + } + } + + #[inline] + pub fn scheme(&self) -> &Scheme { + match self { + UrlCow::Borrowed(u) => &u.scheme, + UrlCow::Owned(u) => &u.scheme, + } + } + + pub fn as_url(&self) -> Url<'_> { + match self { + UrlCow::Borrowed(u) => u.as_url(), + UrlCow::Owned(u) => u.as_url(), + } + } + + #[inline] + pub fn into_owned(self) -> UrlBuf { self.as_url().into() } + + #[inline] + pub fn parent_url(&self) -> Option { self.as_url().parent_url() } + + #[inline] + pub fn pair(&self) -> Option<(UrlBuf, UrnBuf)> { self.as_url().pair() } + + pub fn parse(bytes: &[u8]) -> Result<(Scheme, Cow<'_, Path>, Option<(usize, usize)>)> { + let mut skip = 0; + let (scheme, tilde, uri, urn) = Scheme::parse(bytes, &mut skip)?; + + let rest = if tilde { + Cow::from(percent_decode(&bytes[skip..])).into_os_str()? + } else { + bytes[skip..].into_os_str()? + }; + + let path: Cow<_> = match rest { + Cow::Borrowed(s) => Path::new(s).into(), + Cow::Owned(s) => PathBuf::from(s).into(), + }; + + let ports = scheme.normalize_ports(uri, urn, &path)?; + + Ok((scheme, path, ports)) + } +} + +impl UrlCow<'_> { + #[inline] + pub fn is_regular(&self) -> bool { self.as_url().is_regular() } + + #[inline] + pub fn is_absolute(&self) -> bool { self.as_url().is_absolute() } + + #[inline] + pub fn components(&self) -> Components<'_> { Components::from(self) } + + #[inline] + pub fn covariant(&self, other: &Self) -> bool { self.as_url().covariant(other) } +} diff --git a/yazi-shared/src/url/encode.rs b/yazi-shared/src/url/encode.rs index 8416d922..9d9f1a90 100644 --- a/yazi-shared/src/url/encode.rs +++ b/yazi-shared/src/url/encode.rs @@ -2,20 +2,24 @@ use std::{fmt::{self, Display}, ops::Not}; use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode}; -use crate::{loc::LocBuf, url::{Scheme, UrlBuf}}; +use crate::{loc::Loc, url::{Scheme, Url, UrlBuf}}; pub struct Encode<'a> { - loc: &'a LocBuf, + loc: Loc<'a>, scheme: &'a Scheme, } +impl<'a> From<&'a Url<'a>> for Encode<'a> { + fn from(url: &'a Url<'a>) -> Self { Self::new(url.loc, &url.scheme) } +} + impl<'a> From<&'a UrlBuf> for Encode<'a> { - fn from(url: &'a UrlBuf) -> Self { Self::new(&url.loc, &url.scheme) } + fn from(url: &'a UrlBuf) -> Self { Self::new(url.loc.as_loc(), &url.scheme) } } impl<'a> Encode<'a> { #[inline] - pub(super) fn new(loc: &'a LocBuf, scheme: &'a Scheme) -> Self { Self { loc, scheme } } + pub(super) fn new(loc: Loc<'a>, scheme: &'a Scheme) -> Self { Self { loc, scheme } } #[inline] fn domain<'s>(s: &'s str) -> PercentEncode<'s> { @@ -69,12 +73,16 @@ impl Display for Encode<'_> { // --- Tilded pub struct EncodeTilded<'a> { - loc: &'a LocBuf, + loc: Loc<'a>, scheme: &'a Scheme, } +impl<'a> From<&'a Url<'a>> for EncodeTilded<'a> { + fn from(url: &'a Url<'a>) -> Self { Self { loc: url.loc, scheme: &url.scheme } } +} + impl<'a> From<&'a UrlBuf> for EncodeTilded<'a> { - fn from(url: &'a UrlBuf) -> Self { Self { loc: &url.loc, scheme: &url.scheme } } + fn from(url: &'a UrlBuf) -> Self { Self { loc: url.loc.as_loc(), scheme: &url.scheme } } } impl<'a> From<&'a EncodeTilded<'a>> for Encode<'a> { diff --git a/yazi-shared/src/url/mod.rs b/yazi-shared/src/url/mod.rs index 3a71854e..c9883db4 100644 --- a/yazi-shared/src/url/mod.rs +++ b/yazi-shared/src/url/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(buf component cov display encode scheme uri url urn); +yazi_macro::mod_flat!(buf component cov cow display encode scheme uri url urn); diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index 354a3edb..d345aa79 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -1,22 +1,60 @@ -use std::path::Path; +use std::{ops::Deref, path::Path}; -use crate::{loc::Loc, url::{Scheme, UrlBuf}}; +use hashbrown::Equivalent; +use crate::{loc::{Loc, LocBuf}, url::{Components, Scheme, UrlBuf, UrnBuf}}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Url<'a> { pub loc: Loc<'a>, - pub scheme: &'a Scheme, + pub scheme: Scheme, +} + +impl<'a> Deref for Url<'a> { + type Target = Loc<'a>; + + fn deref(&self) -> &Self::Target { &self.loc } +} + +// TODO: remove +impl<'a> From<&'a Url<'a>> for Url<'a> { + fn from(value: &'a Url) -> Self { Self { loc: value.loc.as_loc(), scheme: value.scheme.clone() } } } impl<'a> From<&'a UrlBuf> for Url<'a> { - fn from(value: &'a UrlBuf) -> Self { Self { loc: value.loc.as_loc(), scheme: &value.scheme } } + fn from(value: &'a UrlBuf) -> Self { + Self { loc: value.loc.as_loc(), scheme: value.scheme.clone() } + } } -impl From> for UrlBuf { - fn from(value: Url<'_>) -> Self { Self { loc: value.loc.into(), scheme: value.scheme.clone() } } +impl<'a> From<&'a mut UrlBuf> for Url<'a> { + fn from(value: &'a mut UrlBuf) -> Self { Self::from(&*value) } +} + +// --- Eq +impl PartialEq for Url<'_> { + fn eq(&self, other: &UrlBuf) -> bool { *self == other.as_url() } +} + +// --- Hash +impl Equivalent for Url<'_> { + fn equivalent(&self, key: &UrlBuf) -> bool { self == key } } impl<'a> Url<'a> { - pub fn regular(path: &'a Path) -> Self { Self { loc: path.into(), scheme: &Scheme::Regular } } + #[inline] + pub fn regular + ?Sized>(path: &'a T) -> Self { + Self { loc: path.as_ref().into(), scheme: Scheme::Regular } + } + + #[inline] + pub fn is_regular(&self) -> bool { self.scheme == Scheme::Regular } + + #[inline] + pub fn as_url(&'a self) -> Url<'a> { Self::from(self) } + + #[inline] + pub fn to_owned(&self) -> UrlBuf { self.into() } pub fn base(&self) -> Option { use Scheme as S; @@ -27,10 +65,60 @@ impl<'a> Url<'a> { let loc: Loc = self.loc.base().into(); Some(match self.scheme { - S::Regular => Self { loc, scheme: &S::Regular }, - S::Search(_) => Self { loc, scheme: &self.scheme }, - S::Archive(_) => Self { loc, scheme: &self.scheme }, - S::Sftp(_) => Self { loc, scheme: &self.scheme }, + S::Regular => Self { loc, scheme: S::Regular }, + S::Search(_) => Self { loc, scheme: self.scheme.clone() }, + S::Archive(_) => Self { loc, scheme: self.scheme.clone() }, + S::Sftp(_) => Self { loc, scheme: self.scheme.clone() }, }) } + + // TODO: use Url instead as the return type + pub fn parent_url(&self) -> Option { + use Scheme as S; + + let parent = self.loc.parent()?; + let uri = self.loc.uri(); + + Some(match self.scheme { + // Regular + S::Regular => UrlBuf { loc: parent.into(), scheme: S::Regular }, + + // Search + S::Search(_) if uri.is_empty() => UrlBuf { loc: parent.into(), scheme: S::Regular }, + S::Search(_) => UrlBuf { + loc: LocBuf::new(parent, self.loc.base(), self.loc.base()), + scheme: self.scheme.clone(), + }, + + // Archive + S::Archive(_) if uri.is_empty() => UrlBuf { loc: parent.into(), scheme: S::Regular }, + S::Archive(_) if uri.nth(1).is_none() => { + UrlBuf { loc: LocBuf::zeroed(parent), scheme: self.scheme.clone() } + } + S::Archive(_) => { + UrlBuf { loc: LocBuf::floated(parent, self.loc.base()), scheme: self.scheme.clone() } + } + + // SFTP + S::Sftp(_) => UrlBuf { loc: parent.into(), scheme: self.scheme.clone() }, + }) + } + + #[inline] + pub fn components(&self) -> Components<'_> { Components::from(self) } + + #[inline] + pub fn covariant(&self, other: impl Into) -> bool { + let other = other.into(); + self.scheme.covariant(&other.scheme) && self.loc == other.loc + } + + // TODO: use Urn instead as the return type + #[inline] + pub fn pair(&self) -> Option<(UrlBuf, UrnBuf)> { + Some((self.parent_url()?, self.loc.urn_owned())) + } + + #[inline] + pub fn as_path(&self) -> Option<&Path> { Some(&*self.loc).filter(|_| !self.scheme.is_virtual()) } }