refactor: introduce UrlBuf (#3072)

This commit is contained in:
三咲雅 misaki masa 2025-08-18 02:25:59 +08:00 committed by GitHub
parent 9810196565
commit 3ca33c1f84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 815 additions and 425 deletions

6
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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 `~/`

View file

@ -64,7 +64,7 @@ impl UserData for Tab {
fn add_methods<M: UserDataMethods<Self>>(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()
});
}
}

View file

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

View file

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

View file

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

View file

@ -15,15 +15,15 @@ impl Actor for Paste {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let mgr = &mut cx.core.mgr;
let tab = &mgr.tabs[cx.tab];
let (src, dest) = (mgr.yanked.iter().collect::<Vec<_>>(), 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));
}
}
}

View file

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

View file

@ -19,7 +19,7 @@ impl Actor for Shell {
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
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(|| {

View file

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

View file

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

View file

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

View file

@ -85,7 +85,12 @@ impl UpdateFiles {
fn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
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 {

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Url> for yazi_shared::url::UrlBuf {
fn from(value: Url) -> Self { value.inner }
}
impl From<Url> 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<Url> for yazi_shared::url::UrlCov {
fn from(value: Url) -> Self { UrlCov(value.inner) }
impl From<Url> 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<Self> {
Ok(Self::new(yazi_shared::url::UrlBuf::try_from(value)?))
Ok(Self::new(UrlCow::try_from(value)?.into_owned()))
}
}

View file

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

View file

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

View file

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

View file

@ -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<UrlBuf, UrlBuf> /* from ==> to */);
@ -29,7 +29,7 @@ impl Linked {
}
}
pub fn from_file(&self, url: &UrlBuf) -> Vec<UrlBuf> {
pub fn from_file(&self, url: Url) -> Vec<UrlBuf> {
if url.scheme.is_virtual() {
vec![]
} else if let Some((parent, urn)) = url.pair() {

View file

@ -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<UrlCov, String>);
pub struct Mimetype(HashMap<UrlBufCov, String>);
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<Url<'a>>) -> Option<&str> {
self.0.get(&UrlCov::new(url)).map(|s| s.as_str())
}
#[inline]
pub fn by_url_owned(&self, url: &UrlBuf) -> Option<SStr> {
pub fn by_url_owned<'a>(&self, url: impl Into<Url<'a>>) -> Option<SStr> {
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<Url<'a>>) -> bool {
self.0.contains_key(&UrlCov::new(url))
}
#[inline]
pub fn extend(&mut self, iter: impl IntoIterator<Item = (UrlBuf, String)>) {
self.0.extend(iter.into_iter().map(|(u, m)| (UrlCov(u), m)));
self.0.extend(iter.into_iter().map(|(u, m)| (UrlBufCov(u), m)));
}
}

View file

@ -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<UrlCov>,
urls: HashSet<UrlBufCov>,
version: u64,
revision: u64,
}
impl Deref for Yanked {
type Target = HashSet<UrlCov>;
type Target = HashSet<UrlBufCov>;
fn deref(&self) -> &Self::Target { &self.urls }
}
impl Yanked {
pub fn new(cut: bool, urls: HashSet<UrlCov>) -> Self { Self { cut, urls, ..Default::default() } }
pub fn new(cut: bool, urls: HashSet<UrlBufCov>) -> 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<Url<'a>>) {
if self.urls.remove(&UrlCov::new(url)) {
self.revision += 1;
}
}
@ -39,7 +42,9 @@ impl Yanked {
}
#[inline]
pub fn contains(&self, url: impl AsRef<UrlBuf>) -> bool { self.urls.contains(UrlCov::new(&url)) }
pub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> 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;
}
}

View file

@ -1,29 +1,31 @@
use yazi_shared::url::{Url, UrlBuf};
#[derive(Default)]
pub struct Backstack<T> {
pub struct Backstack {
cursor: usize,
stack: Vec<T>,
stack: Vec<UrlBuf>,
}
impl<T: Eq + Clone> Backstack<T> {
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<T: Eq + Clone> Backstack<T> {
}
}
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<T: Eq + Clone> Backstack<T> {
}
}
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<u32> = 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"));
}
}

View file

@ -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<T: Into<UrlBuf>> From<T> for Folder {
fn from(value: T) -> Self { Self { url: value.into(), ..Default::default() } }
}
impl Folder {

View file

@ -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<Url<'a>>) -> Folder {
let url = url.into();
self.0.remove(&url).unwrap_or_else(|| Folder::from(url))
}
}

View file

@ -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<UrlCov, u64>,
parents: HashMap<UrlCov, usize>,
inner: IndexMap<UrlBufCov, u64>,
parents: HashMap<UrlBufCov, usize>,
}
impl Selected {
@ -21,27 +22,36 @@ impl Selected {
pub fn values(&self) -> impl Iterator<Item = &UrlBuf> { self.inner.keys().map(Deref::deref) }
#[inline]
pub fn contains(&self, url: impl AsRef<UrlBuf>) -> bool {
self.inner.contains_key(UrlCov::new(&url))
pub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> 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<Url<'a>>) -> bool { self.add_same([url]) == 1 }
pub fn add_many(&mut self, urls: &[impl AsRef<UrlBuf>]) -> usize {
pub fn add_many<'a, I, T>(&mut self, urls: I) -> usize
where
I: IntoIterator<Item = T>,
T: Into<Url<'a>>,
{
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<UrlBuf>]) -> usize {
fn add_same<'a, I, T>(&mut self, urls: I) -> usize
where
I: IntoIterator<Item = T>,
T: Into<Url<'a>>,
{
// 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<Url<'a>> + Clone) -> bool {
self.remove_same([url]) == 1
}
pub fn remove_many(&mut self, urls: &[impl AsRef<UrlBuf>]) -> usize {
pub fn remove_many<'a, I, T>(&mut self, urls: I) -> usize
where
I: IntoIterator<Item = T>,
T: Into<Url<'a>>,
{
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<UrlBuf>]) -> 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<Item = T>,
T: Into<Url<'a>> + 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);

View file

@ -18,7 +18,7 @@ pub struct Tab {
pub current: Folder,
pub parent: Option<Folder>,
pub backstack: Backstack<UrlBuf>,
pub backstack: Backstack,
pub history: History,
pub selected: Selected,

View file

@ -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<UrlCov>, dest: &UrlBuf, relative: bool, force: bool) {
pub fn file_link(&self, src: &HashSet<UrlBufCov>, 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<UrlCov>, dest: &UrlBuf, force: bool, follow: bool) {
pub fn file_hardlink(&self, src: &HashSet<UrlBufCov>, dest: &UrlBuf, force: bool, follow: bool) {
for u in src {
let to = dest.join(u.file_name().unwrap());
if force && *u == to {

View file

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

View file

@ -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<UrlCov>) -> Ember<'a> {
pub fn borrowed(cut: bool, urls: &'a HashSet<UrlBufCov>) -> Ember<'a> {
Self(UpdateYankedOpt { cut, urls: Cow::Borrowed(urls) }).into()
}
}
impl EmberYank<'static> {
pub fn owned(cut: bool, _: &HashSet<UrlCov>) -> Ember<'static> {
pub fn owned(cut: bool, _: &HashSet<UrlBufCov>) -> Ember<'static> {
Self(UpdateYankedOpt { cut, urls: Default::default() }).into()
}
}

View file

@ -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<UrlCov>), (cut, urls));
pub_after!(@yank(cut: bool, urls: &HashSet<UrlBufCov>), (cut, urls));
pub_after!(move(items: Vec<BodyMoveItem>), (&items), (items));

View file

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

View file

@ -36,7 +36,8 @@ impl File {
}
#[inline]
pub fn from_dummy(url: UrlBuf, ft: Option<FileType>) -> Self {
pub fn from_dummy(url: impl Into<UrlBuf>, ft: Option<FileType>) -> Self {
let url = url.into();
let cha = Cha::from_dummy(&url, ft);
Self { url, cha, link_to: None }
}

View file

@ -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<UrlBuf>) -> bool {
match provider::symlink_metadata(u).await {
pub async fn maybe_exists<'a>(url: impl Into<Url<'a>>) -> 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<UrlBuf>) -> bool {
provider::metadata(u).await.is_ok_and(|m| m.is_dir())
pub async fn must_be_dir<'a>(url: impl Into<Url<'a>>) -> 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();
}
}

View file

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

View file

@ -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>) -> UrlBuf {
clean_url(expand_url_impl(url.as_ref()))
pub fn expand_url<'a>(url: impl Into<UrlCow<'a>>) -> 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);

View file

@ -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<UrlBuf> {
Ok(url)
}
pub fn url_relative_to<'a>(from: &UrlBuf, to: &'a UrlBuf) -> Result<Cow<'a, UrlBuf>> {
pub fn url_relative_to<'a>(
from: impl Into<UrlCow<'a>>,
to: impl Into<UrlCow<'a>>,
) -> Result<UrlCow<'a>> {
url_relative_to_impl(from.into(), to.into())
}
pub fn url_relative_to_impl<'a>(from: UrlCow<'a>, to: UrlCow<'a>) -> Result<UrlCow<'a>> {
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<Cow<'a, UrlB
};
}
if from.covariant(to) {
return Ok(UrlBuf { loc: LocBuf::zeroed("."), scheme: to.scheme.clone() }.into());
if from.covariant(&to) {
return Ok(UrlBuf { loc: LocBuf::zeroed("."), scheme: to.scheme().clone() }.into());
}
let (mut f_it, mut t_it) = (from.components(), to.components());
@ -97,7 +104,7 @@ pub fn url_relative_to<'a>(from: &UrlBuf, to: &'a UrlBuf) -> Result<Cow<'a, UrlB
let rest = t_head.into_iter().chain(t_it);
let buf: PathBuf = dots.chain(rest).collect();
Ok(UrlBuf { loc: LocBuf::zeroed(buf), scheme: to.scheme.clone() }.into())
Ok(UrlBuf { loc: LocBuf::zeroed(buf), scheme: to.scheme().clone() }.into())
}
#[cfg(windows)]
@ -123,8 +130,6 @@ pub fn backslash_to_slash(p: &std::path::Path) -> 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()
);
}

View file

@ -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<UrlBuf>) -> io::Result<UrlBuf> {
if let Some(path) = url.as_ref().as_path() {
pub async fn canonicalize<'a>(url: impl Into<Url<'a>>) -> io::Result<UrlBuf> {
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<UrlBuf>) -> io::Result<UrlBuf> {
}
#[inline]
pub async fn create(url: impl AsRef<UrlBuf>) -> io::Result<RwFile> {
if let Some(path) = url.as_ref().as_path() {
pub async fn create<'a>(url: impl Into<Url<'a>>) -> io::Result<RwFile> {
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<UrlBuf>) -> io::Result<RwFile> {
}
#[inline]
pub async fn create_dir(url: impl AsRef<UrlBuf>) -> io::Result<()> {
if let Some(path) = url.as_ref().as_path() {
pub async fn create_dir<'a>(url: impl Into<Url<'a>>) -> 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<UrlBuf>) -> io::Result<()> {
}
#[inline]
pub async fn create_dir_all(url: impl AsRef<UrlBuf>) -> io::Result<()> {
if let Some(path) = url.as_ref().as_path() {
pub async fn create_dir_all<'a>(url: impl Into<Url<'a>>) -> 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<UrlBuf>) -> io::Result<()> {
}
#[inline]
pub async fn hard_link(original: impl AsRef<UrlBuf>, link: impl AsRef<UrlBuf>) -> 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<Url<'a>>,
link: impl Into<Url<'a>>,
) -> 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<UrlBuf>, link: impl AsRef<UrlBuf>) -
}
#[inline]
pub async fn metadata(url: impl AsRef<UrlBuf>) -> io::Result<std::fs::Metadata> {
if let Some(path) = url.as_ref().as_path() {
pub async fn metadata<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metadata> {
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<UrlBuf>) -> io::Result<std::fs::Metadata>
}
#[inline]
pub async fn open(url: impl AsRef<UrlBuf>) -> io::Result<RwFile> {
if let Some(path) = url.as_ref().as_path() {
pub async fn open<'a>(url: impl Into<Url<'a>>) -> io::Result<RwFile> {
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<UrlBuf>) -> io::Result<RwFile> {
}
#[inline]
pub async fn read_dir(url: impl AsRef<UrlBuf>) -> io::Result<ReadDir> {
if let Some(path) = url.as_ref().as_path() {
pub async fn read_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<ReadDir> {
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<UrlBuf>) -> io::Result<ReadDir> {
}
#[inline]
pub fn read_dir_sync(url: impl AsRef<UrlBuf>) -> io::Result<ReadDirSync> {
if let Some(path) = url.as_ref().as_path() {
pub fn read_dir_sync<'a>(url: impl Into<Url<'a>>) -> io::Result<ReadDirSync> {
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<UrlBuf>) -> io::Result<ReadDirSync> {
}
#[inline]
pub async fn read_link(url: impl AsRef<UrlBuf>) -> io::Result<UrlBuf> {
if let Some(path) = url.as_ref().as_path() {
pub async fn read_link<'a>(url: impl Into<Url<'a>>) -> io::Result<UrlBuf> {
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<UrlBuf>) -> io::Result<UrlBuf> {
}
#[inline]
pub async fn remove_dir(url: impl AsRef<UrlBuf>) -> io::Result<()> {
if let Some(path) = url.as_ref().as_path() {
pub async fn remove_dir<'a>(url: impl Into<Url<'a>>) -> 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<UrlBuf>) -> io::Result<()> {
}
#[inline]
pub async fn remove_dir_all(url: impl AsRef<UrlBuf>) -> io::Result<()> {
if let Some(path) = url.as_ref().as_path() {
pub async fn remove_dir_all<'a>(url: impl Into<Url<'a>>) -> 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<UrlBuf>) -> io::Result<()> {
}
#[inline]
pub async fn remove_file(url: impl AsRef<UrlBuf>) -> io::Result<()> {
if let Some(path) = url.as_ref().as_path() {
pub async fn remove_file<'a>(url: impl Into<Url<'a>>) -> 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<UrlBuf>) -> io::Result<()> {
}
#[inline]
pub async fn rename(from: impl AsRef<UrlBuf>, to: impl AsRef<UrlBuf>) -> 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<Url<'a>>, to: impl Into<Url<'a>>) -> 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<UrlBuf>, to: impl AsRef<UrlBuf>) -> io::Res
}
#[inline]
pub async fn symlink_dir(original: impl AsRef<UrlBuf>, link: impl AsRef<UrlBuf>) -> 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<Url<'a>>,
link: impl Into<Url<'a>>,
) -> 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<UrlBuf>, link: impl AsRef<UrlBuf>)
}
#[inline]
pub async fn symlink_file(
original: impl AsRef<UrlBuf>,
link: impl AsRef<UrlBuf>,
pub async fn symlink_file<'a>(
original: impl Into<Url<'a>>,
link: impl Into<Url<'a>>,
) -> 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<UrlBuf>) -> io::Result<std::fs::Metadata> {
if let Some(path) = url.as_ref().as_path() {
pub async fn symlink_metadata<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metadata> {
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<UrlBuf>) -> io::Result<std::fs::Metadata> {
if let Some(path) = url.as_ref().as_path() {
pub fn symlink_metadata_sync<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metadata> {
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<UrlBuf>) -> io::Result<std::fs::Met
}
#[inline]
pub async fn write(url: impl AsRef<UrlBuf>, contents: impl AsRef<[u8]>) -> io::Result<()> {
if let Some(path) = url.as_ref().as_path() {
pub async fn write<'a>(url: impl Into<Url<'a>>, 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"))

View file

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

View file

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

View file

@ -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<usize>,
pub force: bool,
pub only_if: Option<UrlBuf>,
pub only_if: Option<UrlCow<'static>>,
pub upper_bound: bool,
}

View file

@ -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<CmdCow> 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<UrlCow<'static>> 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<UrlBuf> 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 {

View file

@ -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<UrlBuf>,
pub cwd: Option<UrlCow<'static>>,
pub block: bool,
pub orphan: bool,

View file

@ -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<UrlBuf>,
pub wd: Option<UrlCow<'static>>,
}
impl From<CmdCow> for TabCreateOpt {
@ -14,10 +14,10 @@ impl From<CmdCow> 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) }
}

View file

@ -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<UrlBuf>,
pub url: Option<UrlCow<'static>>,
pub state: Option<bool>,
}

View file

@ -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<UrlBuf>,
pub urls: Vec<UrlCow<'static>>,
pub state: Option<bool>,
}

View file

@ -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<usize>,
pub only_if: Option<UrlBuf>,
pub only_if: Option<UrlCow<'static>>,
}
impl From<CmdCow> for UpdatePagedOpt {

View file

@ -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<std::collections::hash_set::IntoIter<UrlCov>, fn(UrlCov) -> yazi_binding::Url>,
std::iter::Map<hashbrown::hash_set::IntoIter<UrlBufCov>, 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<UrlCov>>,
pub urls: Cow<'a, HashSet<UrlBufCov>>,
}
impl TryFrom<CmdCow> for UpdateYankedOpt<'_> {

View file

@ -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<Function> {
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::<yazi_binding::Url>()?),
_ => Err("must be a string or a Url".into_lua_err())?,
}))

View file

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

View file

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

View file

@ -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<DataKey>) -> Option<Id> { self.get(name)?.as_id() }
#[inline]
pub fn url(&self, name: impl Into<DataKey>) -> Option<UrlCow<'_>> { 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<UrlBuf> { self.take_first()?.into_url() }
pub fn take_first_url(&mut self) -> Option<UrlCow<'static>> { self.take_first()?.into_url() }
#[inline]
pub fn take_any<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<T> {

View file

@ -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<DataKey>) -> Option<UrlBuf> {
pub fn take_url(&mut self, name: impl Into<DataKey>) -> Option<UrlCow<'static>> {
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<UrlBuf> {
pub fn take_first_url(&mut self) -> Option<UrlCow<'static>> {
match self {
Self::Owned(c) => c.take_first_url(),
Self::Borrowed(c) => c.first().and_then(Data::to_url),

View file

@ -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<UrlBuf> {
pub fn into_url(self) -> Option<UrlCow<'static>> {
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<UrlBuf> {
pub fn to_url(&self) -> Option<UrlCow<'_>> {
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<UrlBuf> {
pub fn into_url(self) -> Option<UrlCow<'static>> {
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,
}
}

View file

@ -33,8 +33,9 @@ impl PartialOrd for LocBuf {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
}
// --- Hash
impl Hash for LocBuf {
fn hash<H: Hasher>(&self, state: &mut H) { self.inner.hash(state) }
fn hash<H: Hasher>(&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() }

View file

@ -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<H: Hasher>(&self, state: &mut H) { self.inner.hash(state) }
}
impl<'a, T: ?Sized + AsRef<OsStr>> 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<Loc<'_>> 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<T>(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() }
}

View file

@ -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<PathBuf> 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<Url<'_>> 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<Self, Self::Error> {
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<Self, Self::Err> { s.as_bytes().try_into() }
fn from_str(s: &str) -> Result<Self, Self::Err> { Ok(UrlCow::try_from(s)?.into_owned()) }
}
impl TryFrom<String> for UrlBuf {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> { value.as_bytes().try_into() }
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(UrlCow::try_from(value)?.into_owned())
}
}
impl AsRef<UrlBuf> for UrlBuf {
@ -85,6 +81,15 @@ impl From<Cow<'_, UrlBuf>> for UrlBuf {
fn from(url: Cow<'_, UrlBuf>) -> Self { url.into_owned() }
}
// --- Eq
impl PartialEq<Url<'_>> for UrlBuf {
fn eq(&self, other: &Url) -> bool { self.as_url() == *other }
}
impl PartialEq<Url<'_>> for &UrlBuf {
fn eq(&self, other: &Url) -> bool { self.as_url() == *other }
}
impl UrlBuf {
pub fn join(&self, path: impl AsRef<Path>) -> 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<UrlBuf> {
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<UrlBuf> { self.as_url().parent_url() }
pub fn strip_prefix(&self, base: impl AsRef<UrlBuf>) -> 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 {

View file

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

View file

@ -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<UrlBufCov> for UrlCov<'_> {
fn eq(&self, other: &UrlBufCov) -> bool { self.0.covariant(other.0.as_url()) }
}
impl Hash for UrlCov<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.loc.hash(state);
if self.0.scheme.is_virtual() {
self.0.scheme.hash(state);
}
}
}
impl Equivalent<UrlBufCov> for UrlCov<'_> {
fn equivalent(&self, key: &UrlBufCov) -> bool { self == key }
}
impl<'a> UrlCov<'a> {
#[inline]
pub fn new(url: impl Into<Url<'a>>) -> 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<UrlBuf> for UrlCov {
fn as_ref(&self) -> &UrlBuf { &self.0 }
}
impl From<UrlBuf> for UrlCov {
impl From<UrlBuf> for UrlBufCov {
fn from(value: UrlBuf) -> Self { Self(value) }
}
impl From<UrlCov> 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<H: Hasher>(&self, state: &mut H) {
self.loc.hash(state);
if self.scheme.is_virtual() {
self.scheme.hash(state);
}
}
impl From<UrlBufCov> 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<H: Hasher>(&self, state: &mut H) { self.as_url().hash(state) }
}
impl PartialEq for UrlBufCov {
fn eq(&self, other: &Self) -> bool { self.covariant(other) }
}
impl PartialEq<UrlBuf> for UrlCov {
impl PartialEq<UrlBuf> for UrlBufCov {
fn eq(&self, other: &UrlBuf) -> bool { self.covariant(other) }
}
impl UrlCov {
impl UrlBufCov {
#[inline]
pub fn new<T: AsRef<UrlBuf>>(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<UrlCov> { self.0.parent_url().map(UrlCov) }
pub fn parent_url(&self) -> Option<UrlBufCov> { self.0.parent_url().map(UrlBufCov) }
}

159
yazi-shared/src/url/cow.rs Normal file
View file

@ -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<Url<'a>> 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<UrlBuf> 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<UrlCow<'_>> 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<Self, Self::Error> {
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<Vec<u8>> for UrlCow<'_> {
type Error = anyhow::Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
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, Self::Error> { Self::try_from(value.as_bytes()) }
}
impl TryFrom<String> for UrlCow<'_> {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(UrlCow::try_from(value.as_str())?.into_owned().into())
}
}
impl<'a> TryFrom<Cow<'a, str>> for UrlCow<'a> {
type Error = anyhow::Error;
fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
match value {
Cow::Borrowed(s) => UrlCow::try_from(s),
Cow::Owned(s) => UrlCow::try_from(s),
}
}
}
// --- Eq
impl PartialEq<UrlBuf> 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<UrlBuf> { 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) }
}

View file

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

View file

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

View file

@ -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<Url<'_>> 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<UrlBuf> for Url<'_> {
fn eq(&self, other: &UrlBuf) -> bool { *self == other.as_url() }
}
// --- Hash
impl Equivalent<UrlBuf> 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<T: AsRef<Path> + ?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<Self> {
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<UrlBuf> {
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<Self>) -> 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()) }
}