fix!: open command doesn't work under empty directories (#3226)

This commit is contained in:
三咲雅 misaki masa 2025-10-03 17:45:35 +08:00 committed by sxyazi
parent d8b0425a10
commit 149a5feffb
No known key found for this signature in database
45 changed files with 277 additions and 280 deletions

52
Cargo.lock generated
View file

@ -115,9 +115,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.11" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@ -1917,9 +1917,9 @@ dependencies = [
[[package]] [[package]]
name = "lua-src" name = "lua-src"
version = "548.1.1" version = "548.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00bc4bd1f1d5c65b30717333cbec4fa7aa378978940a1bca62f404498d423233" checksum = "bdc4e1aff422ad5f08cffb4719603dcdbc2be2307f4c1510d7aab74b7fa88ca8"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@ -2003,9 +2003,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua" name = "mlua"
version = "0.11.3" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b3dd94c3c4dea0049b22296397040840a8f6b5b5229f438434ba82df402b42d" checksum = "9be1c2bfc684b8a228fbaebf954af7a47a98ec27721986654a4cc2c40a20cc7e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bstr", "bstr",
@ -2341,9 +2341,9 @@ dependencies = [
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "5.0.0" version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"rand 0.8.5", "rand 0.8.5",
@ -2732,9 +2732,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2909,7 +2909,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
"libredox", "libredox",
"thiserror 2.0.16", "thiserror 2.0.17",
] ]
[[package]] [[package]]
@ -3517,7 +3517,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"thiserror 2.0.16", "thiserror 2.0.17",
"walkdir", "walkdir",
] ]
@ -3557,11 +3557,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.16" version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [ dependencies = [
"thiserror-impl 2.0.16", "thiserror-impl 2.0.17",
] ]
[[package]] [[package]]
@ -3577,9 +3577,9 @@ dependencies = [
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.16" version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3884,9 +3884,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.18.0" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
@ -4764,7 +4764,7 @@ dependencies = [
"anyhow", "anyhow",
"hashbrown 0.16.0", "hashbrown 0.16.0",
"mlua", "mlua",
"ordered-float 5.0.0", "ordered-float 5.1.0",
"parking_lot", "parking_lot",
"paste", "paste",
"serde", "serde",
@ -4879,7 +4879,7 @@ dependencies = [
"crossterm 0.29.0", "crossterm 0.29.0",
"hashbrown 0.16.0", "hashbrown 0.16.0",
"mlua", "mlua",
"ordered-float 5.0.0", "ordered-float 5.1.0",
"serde", "serde",
"tokio", "tokio",
"yazi-binding", "yazi-binding",
@ -4957,7 +4957,7 @@ dependencies = [
"libc", "libc",
"lru 0.16.1", "lru 0.16.1",
"mlua", "mlua",
"ordered-float 5.0.0", "ordered-float 5.1.0",
"parking_lot", "parking_lot",
"scopeguard", "scopeguard",
"serde", "serde",
@ -4998,7 +4998,7 @@ dependencies = [
"hashbrown 0.16.0", "hashbrown 0.16.0",
"libc", "libc",
"memchr", "memchr",
"ordered-float 5.0.0", "ordered-float 5.1.0",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"serde", "serde",
@ -5031,9 +5031,7 @@ dependencies = [
"hashbrown 0.16.0", "hashbrown 0.16.0",
"parking_lot", "parking_lot",
"russh", "russh",
"serde",
"tokio", "tokio",
"toml 0.9.7",
"tracing", "tracing",
"uzers", "uzers",
"yazi-config", "yazi-config",
@ -5105,9 +5103,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.1" version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]] [[package]]
name = "zune-core" name = "zune-core"

View file

@ -35,9 +35,9 @@ hashbrown = { version = "0.16.0", features = [ "serde" ] }
indexmap = { version = "2.11.4", features = [ "serde" ] } indexmap = { version = "2.11.4", features = [ "serde" ] }
libc = "0.2.176" libc = "0.2.176"
lru = "0.16.1" lru = "0.16.1"
mlua = { version = "0.11.3", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
objc = "0.2.7" objc = "0.2.7"
ordered-float = { version = "5.0.0", features = [ "serde" ] } ordered-float = { version = "5.1.0", features = [ "serde" ] }
parking_lot = "0.12.4" parking_lot = "0.12.4"
paste = "1.0.15" paste = "1.0.15"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }

View file

@ -4,7 +4,7 @@ use yazi_fs::File;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::mgr::{OpenDoOpt, OpenOpt}; use yazi_parser::mgr::{OpenDoOpt, OpenOpt};
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, url::UrlCow}; use yazi_shared::data::Data;
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
use crate::{Actor, Ctx, mgr::Quit}; use crate::{Actor, Ctx, mgr::Quit};
@ -17,27 +17,28 @@ impl Actor for Open {
const NAME: &str = "open"; const NAME: &str = "open";
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let Some(hovered) = cx.hovered().map(|h| h.url_owned()).map(UrlCow::from) else { succ!() };
if !opt.interactive && ARGS.chooser_file.is_some() { if !opt.interactive && ARGS.chooser_file.is_some() {
succ!(if !opt.targets.is_empty() { succ!(if !opt.targets.is_empty() {
Quit::with_selected(opt.targets) Quit::with_selected(opt.targets)
} else if opt.hovered { } else if opt.hovered {
Quit::with_selected([hovered]) Quit::with_selected(cx.hovered().map(|h| &h.url))
} else { } else {
act!(mgr:escape_visual, cx)?;
Quit::with_selected(cx.tab().selected_or_hovered()) Quit::with_selected(cx.tab().selected_or_hovered())
}); });
} }
if opt.targets.is_empty() { if opt.targets.is_empty() {
opt.targets = if opt.hovered { opt.targets = if opt.hovered {
vec![hovered.clone()] cx.hovered().map(|h| vec![h.url.clone().into()]).unwrap_or_default()
} else { } else {
act!(mgr:escape_visual, cx)?;
cx.tab().selected_or_hovered().cloned().map(Into::into).collect() cx.tab().selected_or_hovered().cloned().map(Into::into).collect()
}; };
} }
if opt.targets.is_empty() {
succ!();
}
let todo: Vec<_> = opt let todo: Vec<_> = opt
.targets .targets
@ -49,7 +50,7 @@ impl Actor for Open {
let cwd = cx.cwd().clone(); let cwd = cx.cwd().clone();
if todo.is_empty() { if todo.is_empty() {
return act!(mgr:open_do, cx, OpenDoOpt { cwd, hovered, targets: opt.targets, interactive: opt.interactive }); return act!(mgr:open_do, cx, OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive });
} }
let scheduler = cx.tasks.scheduler.clone(); let scheduler = cx.tasks.scheduler.clone();
@ -61,12 +62,7 @@ impl Actor for Open {
} }
} }
if scheduler.fetch_mimetype(files).await { if scheduler.fetch_mimetype(files).await {
MgrProxy::open_do(OpenDoOpt { MgrProxy::open_do(OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive });
cwd,
hovered,
targets: opt.targets,
interactive: opt.interactive,
});
} }
}); });
succ!(); succ!();

View file

@ -5,7 +5,7 @@ use yazi_config::{YAZI, popup::PickCfg};
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::OpenDoOpt; use yazi_parser::mgr::OpenDoOpt;
use yazi_proxy::{PickProxy, TasksProxy}; use yazi_proxy::{PickProxy, TasksProxy};
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlCow};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -30,7 +30,7 @@ impl Actor for OpenDo {
if targets.is_empty() { if targets.is_empty() {
succ!(); succ!();
} else if !opt.interactive { } else if !opt.interactive {
succ!(cx.tasks.process_from_files(opt.cwd, opt.hovered, targets)); succ!(cx.tasks.process_with_selected(opt.cwd, targets));
} }
let openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter()); let openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter());
@ -39,10 +39,10 @@ impl Actor for OpenDo {
} }
let pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect())); let pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect()));
let urls = [opt.hovered].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect(); let urls = [UrlCow::default()].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect();
tokio::spawn(async move { tokio::spawn(async move {
if let Ok(choice) = pick.await { if let Ok(choice) = pick.await {
TasksProxy::open_with(Cow::Borrowed(openers[choice]), opt.cwd, urls); TasksProxy::file_open(Cow::Borrowed(openers[choice]), opt.cwd, urls);
} }
}); });
succ!(); succ!();

View file

@ -77,6 +77,8 @@ impl Quit {
s s
}); });
emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() })); if !paths.is_empty() {
emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() }));
}
} }
} }

View file

@ -111,7 +111,7 @@ impl Actor for SearchStop {
succ!(); succ!();
} }
let rep = tab.history.remove_or(&tab.cwd().to_regular()); let rep = tab.history.remove_or(tab.cwd().to_regular());
drop(mem::replace(&mut tab.current, rep)); drop(mem::replace(&mut tab.current, rep));
act!(mgr:hidden, cx)?; act!(mgr:hidden, cx)?;

View file

@ -37,7 +37,7 @@ impl Actor for Shell {
return; return;
} }
TasksProxy::open_with( TasksProxy::file_open(
Cow::Owned(OpenerRule { Cow::Owned(OpenerRule {
run: opt.run.into_owned(), run: opt.run.into_owned(),
block: opt.block, block: opt.block,

View file

@ -5,15 +5,15 @@ use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
pub struct OpenWith; pub struct FileOpen;
impl Actor for OpenWith { impl Actor for FileOpen {
type Options = OpenWithOpt; type Options = OpenWithOpt;
const NAME: &str = "open_with"; const NAME: &str = "file_open";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
succ!(cx.tasks.process_from_opener( succ!(cx.tasks.process_with_opener(
opt.cwd, opt.cwd,
opt.opener, opt.opener,
opt.targets.into_iter().map(|u| u.into_os_str2()).collect(), opt.targets.into_iter().map(|u| u.into_os_str2()).collect(),

View file

@ -1 +1 @@
yazi_macro::mod_flat!(arrow cancel close inspect open_with process_exec show update_succeed); yazi_macro::mod_flat!(arrow cancel close file_open inspect process_open show update_succeed);

View file

@ -1,16 +1,16 @@
use anyhow::Result; use anyhow::Result;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::tasks::ProcessExecOpt; use yazi_parser::tasks::ProcessOpenOpt;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
pub struct ProcessExec; pub struct ProcessOpen;
impl Actor for ProcessExec { impl Actor for ProcessOpen {
type Options = ProcessExecOpt; type Options = ProcessOpenOpt;
const NAME: &str = "process_exec"; const NAME: &str = "process_open";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
succ!(cx.tasks.scheduler.process_open(opt)); succ!(cx.tasks.scheduler.process_open(opt));

View file

@ -1,7 +1,7 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_shared::{IntoOsStr, url::UrlCow}; use yazi_shared::{IntoOsStr, url::{AsUrl, UrlCow}};
use crate::{Urn, cached_field, deprecate}; use crate::{Urn, cached_field, deprecate};
@ -29,12 +29,16 @@ impl AsRef<yazi_shared::url::UrlBuf> for Url {
fn as_ref(&self) -> &yazi_shared::url::UrlBuf { &self.inner } fn as_ref(&self) -> &yazi_shared::url::UrlBuf { &self.inner }
} }
impl From<Url> for yazi_shared::url::UrlBuf { impl AsUrl for Url {
fn from(value: Url) -> Self { value.inner } fn as_url(&self) -> yazi_shared::url::Url<'_> { self.inner.as_url() }
} }
impl<'a> From<&'a Url> for yazi_shared::url::Url<'a> { impl AsUrl for &Url {
fn from(value: &'a Url) -> Self { value.as_url() } fn as_url(&self) -> yazi_shared::url::Url<'_> { self.inner.as_url() }
}
impl From<Url> for yazi_shared::url::UrlBuf {
fn from(value: Url) -> Self { value.inner }
} }
impl<'a> From<&'a Url> for UrlCow<'a> { impl<'a> From<&'a Url> for UrlCow<'a> {

View file

@ -14,4 +14,4 @@ proc-macro = true
[dependencies] [dependencies]
# External dependencies # External dependencies
syn = { version = "2.0.106", features = [ "full" ] } syn = { version = "2.0.106", features = [ "full" ] }
quote = "1.0.40" quote = "1.0.41"

View file

@ -48,7 +48,7 @@ impl Open {
M: AsRef<str>, M: AsRef<str>,
{ {
let each: Vec<IndexSet<&str>> = targets let each: Vec<IndexSet<&str>> = targets
.into_iter() .iter()
.map(|(u, m)| self.all(u, m).collect::<IndexSet<_>>()) .map(|(u, m)| self.all(u, m).collect::<IndexSet<_>>())
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.collect(); .collect();

View file

@ -3,7 +3,7 @@ use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
use globset::GlobBuilder; use globset::GlobBuilder;
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::{scheme::{SchemeCow, SchemeRef}, url::Url}; use yazi_shared::{scheme::{SchemeCow, SchemeRef}, url::AsUrl};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(try_from = "String")] #[serde(try_from = "String")]
@ -17,8 +17,8 @@ pub struct Pattern {
} }
impl Pattern { impl Pattern {
pub fn match_url<'a>(&self, url: impl Into<Url<'a>>, is_dir: bool) -> bool { pub fn match_url(&self, url: impl AsUrl, is_dir: bool) -> bool {
let url = url.into(); let url = url.as_url();
if is_dir != self.is_dir { if is_dir != self.is_dir {
return false; return false;
@ -126,7 +126,7 @@ mod tests {
use super::*; use super::*;
fn matches(glob: &str, url: &str) -> bool { fn matches(glob: &str, url: &str) -> bool {
Pattern::from_str(glob).unwrap().match_url(&UrlCow::try_from(url).unwrap(), false) Pattern::from_str(glob).unwrap().match_url(UrlCow::try_from(url).unwrap(), false)
} }
#[cfg(unix)] #[cfg(unix)]

View file

@ -1,7 +1,7 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use hashbrown::HashMap; use hashbrown::HashMap;
use yazi_shared::url::{Url, UrlBuf}; use yazi_shared::url::{AsUrl, UrlBuf};
use super::Folder; use super::Folder;
@ -19,8 +19,8 @@ impl DerefMut for History {
} }
impl History { impl History {
pub fn remove_or<'a>(&mut self, url: impl Into<Url<'a>>) -> Folder { pub fn remove_or(&mut self, url: impl AsUrl) -> Folder {
let url = url.into(); let url = url.as_url();
self.0.remove(&url).unwrap_or_else(|| Folder::from(url)) self.0.remove(&url).unwrap_or_else(|| Folder::from(url))
} }
} }

View file

@ -2,53 +2,47 @@ use std::{borrow::Cow, ffi::OsStr, mem};
use hashbrown::HashMap; use hashbrown::HashMap;
use yazi_config::{YAZI, opener::OpenerRule}; use yazi_config::{YAZI, opener::OpenerRule};
use yazi_parser::tasks::ProcessExecOpt; use yazi_parser::tasks::ProcessOpenOpt;
use yazi_shared::url::{UrlBuf, UrlCow}; use yazi_shared::url::{UrlBuf, UrlCow};
use super::Tasks; use super::Tasks;
impl Tasks { impl Tasks {
pub fn process_from_files( pub fn process_with_selected(&self, cwd: UrlBuf, targets: Vec<(UrlCow<'static>, &str)>) {
&self,
cwd: UrlBuf,
hovered: UrlCow<'static>,
targets: Vec<(UrlCow<'static>, &str)>,
) {
let mut openers = HashMap::new(); let mut openers = HashMap::new();
for (url, mime) in targets { for (url, mime) in targets {
if let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) { if let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) {
openers.entry(opener).or_insert_with(|| vec![hovered.clone()]).push(url); openers
.entry(opener)
.or_insert_with(|| vec![OsStr::new("").into()])
.push(url.into_os_str2());
} }
} }
for (opener, args) in openers { for (opener, args) in openers {
self.process_from_opener( self.process_with_opener(cwd.clone(), Cow::Borrowed(opener), args);
cwd.clone(),
Cow::Borrowed(opener),
args.into_iter().map(|u| u.into_os_str2()).collect(),
);
} }
} }
pub fn process_from_opener( pub fn process_with_opener(
&self, &self,
cwd: UrlBuf, cwd: UrlBuf,
opener: Cow<'static, OpenerRule>, opener: Cow<'static, OpenerRule>,
mut args: Vec<Cow<'static, OsStr>>, mut args: Vec<Cow<'static, OsStr>>,
) { ) {
if opener.spread { if opener.spread {
self.scheduler.process_open(ProcessExecOpt { cwd, opener, args, done: None }); self.scheduler.process_open(ProcessOpenOpt { cwd, opener, args, done: None });
return; return;
} }
if args.is_empty() { if args.is_empty() {
return; return;
} }
if args.len() == 2 { if args.len() == 2 {
self.scheduler.process_open(ProcessExecOpt { cwd, opener, args, done: None }); self.scheduler.process_open(ProcessOpenOpt { cwd, opener, args, done: None });
return; return;
} }
let hovered = mem::take(&mut args[0]); let hovered = mem::take(&mut args[0]);
for target in args.into_iter().skip(1) { for target in args.into_iter().skip(1) {
self.scheduler.process_open(ProcessExecOpt { self.scheduler.process_open(ProcessOpenOpt {
cwd: cwd.clone(), cwd: cwd.clone(),
opener: opener.clone(), opener: opener.clone(),
args: vec![hovered.clone(), target], args: vec![hovered.clone(), target],

View file

@ -108,7 +108,7 @@ pub enum Spark<'a> {
SpotCopy(yazi_parser::spot::CopyOpt), SpotCopy(yazi_parser::spot::CopyOpt),
// Tasks // Tasks
TasksProcessExec(yazi_parser::tasks::ProcessExecOpt), TasksProcessOpen(yazi_parser::tasks::ProcessOpenOpt),
TasksUpdateSucceed(yazi_parser::tasks::UpdateSucceedOpt), TasksUpdateSucceed(yazi_parser::tasks::UpdateSucceedOpt),
// Which // Which
@ -231,7 +231,7 @@ impl<'a> IntoLua for Spark<'a> {
Self::SpotCopy(b) => b.into_lua(lua), Self::SpotCopy(b) => b.into_lua(lua),
// Tasks // Tasks
Self::TasksProcessExec(b) => b.into_lua(lua), Self::TasksProcessOpen(b) => b.into_lua(lua),
Self::TasksUpdateSucceed(b) => b.into_lua(lua), Self::TasksUpdateSucceed(b) => b.into_lua(lua),
// Which // Which
@ -324,7 +324,7 @@ try_from_spark!(notify::TickOpt, notify:tick);
try_from_spark!(pick::CloseOpt, pick:close); try_from_spark!(pick::CloseOpt, pick:close);
try_from_spark!(pick::ShowOpt, pick:show); try_from_spark!(pick::ShowOpt, pick:show);
try_from_spark!(spot::CopyOpt, spot:copy); try_from_spark!(spot::CopyOpt, spot:copy);
try_from_spark!(tasks::ProcessExecOpt, tasks:process_exec); try_from_spark!(tasks::ProcessOpenOpt, tasks:process_open);
try_from_spark!(tasks::UpdateSucceedOpt, tasks:update_succeed); try_from_spark!(tasks::UpdateSucceedOpt, tasks:update_succeed);
try_from_spark!(which::CallbackOpt, which:callback); try_from_spark!(which::CallbackOpt, which:callback);
try_from_spark!(which::ShowOpt, which:show); try_from_spark!(which::ShowOpt, which:show);

View file

@ -164,8 +164,8 @@ impl<'a> Executor<'a> {
on!(arrow); on!(arrow);
on!(inspect); on!(inspect);
on!(cancel); on!(cancel);
on!(open_with); on!(file_open);
on!(process_exec); on!(process_open);
match cmd.name.as_ref() { match cmd.name.as_ref() {
// Help // Help

View file

@ -2,7 +2,7 @@ use std::{ffi::OsStr, fs::Metadata, ops::Deref, time::{Duration, SystemTime, UNI
use anyhow::bail; use anyhow::bail;
use yazi_macro::{unix_either, win_either}; use yazi_macro::{unix_either, win_either};
use yazi_shared::url::Url; use yazi_shared::url::AsUrl;
use super::ChaKind; use super::ChaKind;
use crate::cha::{ChaMode, ChaType}; use crate::cha::{ChaMode, ChaType};
@ -52,15 +52,15 @@ impl Cha {
Self::from_bare(&meta).attach(ChaKind::hidden(name, &meta)) Self::from_bare(&meta).attach(ChaKind::hidden(name, &meta))
} }
pub fn from_dummy<'a, U>(_url: U, r#type: Option<ChaType>) -> Self pub fn from_dummy<U>(_url: U, r#type: Option<ChaType>) -> Self
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let mut kind = ChaKind::DUMMY; let mut kind = ChaKind::DUMMY;
let mode = r#type.map(ChaMode::from_bare).unwrap_or_default(); let mode = r#type.map(ChaMode::from_bare).unwrap_or_default();
#[cfg(unix)] #[cfg(unix)]
if _url.into().urn().is_hidden() { if _url.as_url().urn().is_hidden() {
kind |= ChaKind::HIDDEN; kind |= ChaKind::HIDDEN;
} }

View file

@ -1,6 +1,6 @@
use std::{io, path::{Path, PathBuf}}; use std::{io, path::{Path, PathBuf}};
use yazi_shared::url::{Url, UrlCow}; use yazi_shared::url::{AsUrl, UrlCow};
use crate::{cha::Cha, path::absolute_url, provider::Provider}; use crate::{cha::Cha, path::absolute_url, provider::Provider};
@ -12,11 +12,11 @@ impl Provider for Local {
type Gate = super::Gate; type Gate = super::Gate;
type ReadDir = super::ReadDir; type ReadDir = super::ReadDir;
async fn absolute<'a, U>(&self, url: U) -> io::Result<UrlCow<'a>> async fn absolute<'a, U>(&self, url: &'a U) -> io::Result<UrlCow<'a>>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
if url.scheme.is_virtual() { if url.scheme.is_virtual() {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL")) Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL"))
} else { } else {

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, io, path::{Path, PathBuf}};
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use yazi_macro::ok_or_not_found; use yazi_macro::ok_or_not_found;
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlCow}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlCow}};
use crate::cha::{Cha, ChaType}; use crate::cha::{Cha, ChaType};
@ -11,9 +11,9 @@ pub trait Provider {
type Gate: FileBuilder<File = Self::File>; type Gate: FileBuilder<File = Self::File>;
type ReadDir: DirReader; type ReadDir: DirReader;
fn absolute<'a, U>(&self, url: U) -> impl Future<Output = io::Result<UrlCow<'a>>> fn absolute<'a, U>(&self, url: &'a U) -> impl Future<Output = io::Result<UrlCow<'a>>>
where where
U: Into<Url<'a>>; U: AsUrl;
fn canonicalize<P>(&self, path: P) -> impl Future<Output = io::Result<PathBuf>> fn canonicalize<P>(&self, path: P) -> impl Future<Output = io::Result<PathBuf>>
where where

View file

@ -4,7 +4,6 @@ use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct OpenDoOpt { pub struct OpenDoOpt {
pub cwd: UrlBuf, pub cwd: UrlBuf,
pub hovered: UrlCow<'static>,
pub targets: Vec<UrlCow<'static>>, pub targets: Vec<UrlCow<'static>>,
pub interactive: bool, pub interactive: bool,
} }

View file

@ -1 +1 @@
yazi_macro::mod_flat!(process_exec update_succeed); yazi_macro::mod_flat!(process_open update_succeed);

View file

@ -8,25 +8,25 @@ use yazi_shared::{event::CmdCow, url::UrlBuf};
// --- Exec // --- Exec
#[derive(Debug)] #[derive(Debug)]
pub struct ProcessExecOpt { pub struct ProcessOpenOpt {
pub cwd: UrlBuf, pub cwd: UrlBuf,
pub opener: Cow<'static, OpenerRule>, pub opener: Cow<'static, OpenerRule>,
pub args: Vec<Cow<'static, OsStr>>, pub args: Vec<Cow<'static, OsStr>>,
pub done: Option<oneshot::Sender<()>>, pub done: Option<oneshot::Sender<()>>,
} }
impl TryFrom<CmdCow> for ProcessExecOpt { impl TryFrom<CmdCow> for ProcessOpenOpt {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in ProcessExecOpt")) c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in ProcessOpenOpt"))
} }
} }
impl FromLua for ProcessExecOpt { impl FromLua for ProcessOpenOpt {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) } fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
} }
impl IntoLua for ProcessExecOpt { impl IntoLua for ProcessOpenOpt {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) } fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
} }

View file

@ -3,14 +3,14 @@ use std::{borrow::Cow, ffi::OsStr};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yazi_config::opener::OpenerRule; use yazi_config::opener::OpenerRule;
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
use yazi_parser::{mgr::OpenWithOpt, tasks::ProcessExecOpt}; use yazi_parser::{mgr::OpenWithOpt, tasks::ProcessOpenOpt};
use yazi_shared::url::{UrlBuf, UrlCow}; use yazi_shared::url::{UrlBuf, UrlCow};
pub struct TasksProxy; pub struct TasksProxy;
impl TasksProxy { impl TasksProxy {
pub fn open_with(opener: Cow<'static, OpenerRule>, cwd: UrlBuf, targets: Vec<UrlCow<'static>>) { pub fn file_open(opener: Cow<'static, OpenerRule>, cwd: UrlBuf, targets: Vec<UrlCow<'static>>) {
emit!(Call(relay!(tasks:open_with).with_any("option", OpenWithOpt { opener, cwd, targets }))); emit!(Call(relay!(tasks:file_open).with_any("option", OpenWithOpt { opener, cwd, targets })));
} }
pub async fn process_exec( pub async fn process_exec(
@ -19,7 +19,7 @@ impl TasksProxy {
args: Vec<Cow<'static, OsStr>>, args: Vec<Cow<'static, OsStr>>,
) { ) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
emit!(Call(relay!(tasks:process_exec).with_any("option", ProcessExecOpt { emit!(Call(relay!(tasks:process_open).with_any("option", ProcessOpenOpt {
cwd, cwd,
opener, opener,
args, args,

View file

@ -6,7 +6,7 @@ use tracing::warn;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_fs::{cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder}}; use yazi_fs::{cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder}};
use yazi_macro::ok_or_not_found; use yazi_macro::ok_or_not_found;
use yazi_shared::url::{Url, UrlBuf, UrlCow}; use yazi_shared::url::{AsUrl, UrlBuf, UrlCow};
use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}}; use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}};
use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};
@ -289,8 +289,8 @@ impl File {
} }
#[inline] #[inline]
async fn cha<'a>(url: impl Into<Url<'a>>, follow: bool) -> io::Result<Cha> { async fn cha(url: impl AsUrl, follow: bool) -> io::Result<Cha> {
let url = url.into(); let url = url.as_url();
let cha = provider::symlink_metadata(url).await?; let cha = provider::symlink_metadata(url).await?;
Ok(if follow { Cha::from_follow(url, cha).await } else { cha }) Ok(if follow { Cha::from_follow(url, cha).await } else { cha })
} }

View file

@ -7,7 +7,7 @@ use tokio::{select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::Join
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump; use yazi_dds::Pump;
use yazi_fs::FsUrl; use yazi_fs::FsUrl;
use yazi_parser::{app::PluginOpt, tasks::ProcessExecOpt}; use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt};
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_shared::{Id, Throttle, url::UrlBuf}; use yazi_shared::{Id, Throttle, url::UrlBuf};
use yazi_vfs::{must_be_dir, provider, unique_name}; use yazi_vfs::{must_be_dir, provider, unique_name};
@ -310,7 +310,7 @@ impl Scheduler {
} }
} }
pub fn process_open(&self, ProcessExecOpt { cwd, opener, args, done }: ProcessExecOpt) { pub fn process_open(&self, ProcessOpenOpt { cwd, opener, args, done }: ProcessOpenOpt) {
let name = { let name = {
let args = args.iter().map(|a| a.to_string_lossy()).collect::<Vec<_>>().join(" "); let args = args.iter().map(|a| a.to_string_lossy()).collect::<Vec<_>>().join(" ");
if args.is_empty() { if args.is_empty() {

View file

@ -33,7 +33,7 @@ impl From<Error> for std::io::Error {
Error::IO(e) => e, Error::IO(e) => e,
Error::Serde(e) => Self::new(std::io::ErrorKind::InvalidData, e), Error::Serde(e) => Self::new(std::io::ErrorKind::InvalidData, e),
Error::Status(status) => match status.code { Error::Status(status) => match status.code {
responses::StatusCode::Ok => Self::new(std::io::ErrorKind::Other, "unexpected OK"), responses::StatusCode::Ok => Self::other("unexpected OK"),
responses::StatusCode::Eof => Self::from(std::io::ErrorKind::UnexpectedEof), responses::StatusCode::Eof => Self::from(std::io::ErrorKind::UnexpectedEof),
responses::StatusCode::NoSuchFile => Self::from(std::io::ErrorKind::NotFound), responses::StatusCode::NoSuchFile => Self::from(std::io::ErrorKind::NotFound),
responses::StatusCode::PermissionDenied => Self::from(std::io::ErrorKind::PermissionDenied), responses::StatusCode::PermissionDenied => Self::from(std::io::ErrorKind::PermissionDenied),

View file

@ -78,7 +78,7 @@ impl Operator {
status.into() status.into()
} }
pub async fn fsetstat<'a>(&self, handle: &str, attrs: &'a Attrs) -> Result<(), Error> { pub async fn fsetstat(&self, handle: &str, attrs: &Attrs) -> Result<(), Error> {
let status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?; let status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?;
status.into() status.into()
} }

View file

@ -99,7 +99,7 @@ impl Cmd {
T: TryFrom<&'a Data>, T: TryFrom<&'a Data>,
T::Error: Into<anyhow::Error>, T::Error: Into<anyhow::Error>,
{ {
self.get(0).map_err(Into::into) self.get(0)
} }
pub fn second<'a, T>(&'a self) -> Result<T> pub fn second<'a, T>(&'a self) -> Result<T>
@ -107,7 +107,7 @@ impl Cmd {
T: TryFrom<&'a Data>, T: TryFrom<&'a Data>,
T::Error: Into<anyhow::Error>, T::Error: Into<anyhow::Error>,
{ {
self.get(1).map_err(Into::into) self.get(1)
} }
pub fn seq<'a, T>(&'a self) -> Vec<T> pub fn seq<'a, T>(&'a self) -> Vec<T>
@ -128,7 +128,7 @@ impl Cmd {
} }
// --- Take // --- Take
pub fn take<'a, T>(&mut self, name: impl Into<DataKey>) -> Result<T> pub fn take<T>(&mut self, name: impl Into<DataKey>) -> Result<T>
where where
T: TryFrom<Data>, T: TryFrom<Data>,
T::Error: Into<anyhow::Error>, T::Error: Into<anyhow::Error>,
@ -140,7 +140,7 @@ impl Cmd {
} }
} }
pub fn take_first<'a, T>(&mut self) -> Result<T> pub fn take_first<T>(&mut self) -> Result<T>
where where
T: TryFrom<Data>, T: TryFrom<Data>,
T::Error: Into<anyhow::Error>, T::Error: Into<anyhow::Error>,

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::BuildHasher, p
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{loc::LocBuf, pool::Pool, scheme::{Scheme, SchemeRef}, url::{Components, Display, Encode, EncodeTilded, Uri, Url, UrlCow, Urn}}; use crate::{loc::LocBuf, pool::Pool, scheme::{Scheme, SchemeRef}, url::{AsUrl, Components, Display, Encode, EncodeTilded, Uri, Url, UrlCow, Urn}};
#[derive(Clone, Default, Eq, Hash, PartialEq)] #[derive(Clone, Default, Eq, Hash, PartialEq)]
pub struct UrlBuf { pub struct UrlBuf {
@ -108,18 +108,16 @@ impl UrlBuf {
pub fn parent(&self) -> Option<Url<'_>> { self.as_url().parent() } pub fn parent(&self) -> Option<Url<'_>> { self.as_url().parent() }
#[inline] #[inline]
pub fn starts_with<'a>(&self, base: impl Into<Url<'a>>) -> bool { pub fn starts_with(&self, base: impl AsUrl) -> bool { self.as_url().starts_with(base) }
self.as_url().starts_with(base)
}
#[inline] #[inline]
pub fn ends_with<'a>(&self, child: impl Into<Url<'a>>) -> bool { self.as_url().ends_with(child) } pub fn ends_with(&self, child: impl AsUrl) -> bool { self.as_url().ends_with(child) }
pub fn strip_prefix<'a>(&self, base: impl Into<Url<'a>>) -> Option<&Urn> { pub fn strip_prefix(&self, base: impl AsUrl) -> Option<&Urn> {
use Scheme as S; use Scheme as S;
use SchemeRef as T; use SchemeRef as T;
let base = base.into(); let base = base.as_url();
let prefix = self.loc.strip_prefix(base.loc).ok()?; let prefix = self.loc.strip_prefix(base.loc).ok()?;
Some(Urn::new(match (&self.scheme, base.scheme) { Some(Urn::new(match (&self.scheme, base.scheme) {
@ -174,7 +172,7 @@ impl UrlBuf {
impl UrlBuf { impl UrlBuf {
#[inline] #[inline]
pub fn as_url(&self) -> Url<'_> { Url::from(self) } pub fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_ref() } }
#[inline] #[inline]
pub fn base(&self) -> Option<Url<'_>> { self.as_url().base() } pub fn base(&self) -> Option<Url<'_>> { self.as_url().base() }

View file

@ -33,10 +33,6 @@ impl From<UrlBuf> for UrlCow<'_> {
fn from(value: UrlBuf) -> Self { Self::Owned { loc: value.loc, scheme: value.scheme.into() } } fn from(value: UrlBuf) -> Self { Self::Owned { loc: value.loc, scheme: value.scheme.into() } }
} }
impl<'a> From<&'a UrlCow<'a>> for Url<'a> {
fn from(value: &'a UrlCow<'a>) -> Self { value.as_url() }
}
impl From<UrlCow<'_>> for UrlBuf { impl From<UrlCow<'_>> for UrlBuf {
fn from(value: UrlCow<'_>) -> Self { value.into_owned() } fn from(value: UrlCow<'_>) -> Self { value.into_owned() }
} }

View file

@ -1,4 +1,4 @@
use crate::url::{Url, UrlCow}; use crate::url::{Url, UrlBuf, UrlCow};
pub trait AsUrl { pub trait AsUrl {
fn as_url(&self) -> Url<'_>; fn as_url(&self) -> Url<'_>;
@ -9,6 +9,21 @@ impl AsUrl for Url<'_> {
fn as_url(&self) -> Url<'_> { *self } fn as_url(&self) -> Url<'_> { *self }
} }
impl AsUrl for UrlBuf {
#[inline]
fn as_url(&self) -> Url<'_> { self.as_url() }
}
impl AsUrl for &UrlBuf {
#[inline]
fn as_url(&self) -> Url<'_> { (**self).as_url() }
}
impl AsUrl for &mut UrlBuf {
#[inline]
fn as_url(&self) -> Url<'_> { (**self).as_url() }
}
impl AsUrl for UrlCow<'_> { impl AsUrl for UrlCow<'_> {
#[inline] #[inline]
fn as_url(&self) -> Url<'_> { self.as_url() } fn as_url(&self) -> Url<'_> { self.as_url() }
@ -16,5 +31,13 @@ impl AsUrl for UrlCow<'_> {
impl AsUrl for &UrlCow<'_> { impl AsUrl for &UrlCow<'_> {
#[inline] #[inline]
fn as_url(&self) -> Url<'_> { (*self).as_url() } fn as_url(&self) -> Url<'_> { (**self).as_url() }
}
impl<'a, T: AsUrl> From<&'a T> for Url<'a> {
fn from(value: &'a T) -> Self { value.as_url() }
}
impl<'a, T: AsUrl> From<&'a mut T> for Url<'a> {
fn from(value: &'a mut T) -> Self { value.as_url() }
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::Path};
use hashbrown::Equivalent; use hashbrown::Equivalent;
use crate::{loc::{Loc, LocBuf}, scheme::SchemeRef, url::{Components, Encode, Uri, UrlBuf, Urn}}; use crate::{loc::{Loc, LocBuf}, scheme::SchemeRef, url::{AsUrl, Components, Encode, Uri, UrlBuf, Urn}};
#[derive(Clone, Copy, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct Url<'a> { pub struct Url<'a> {
@ -10,16 +10,6 @@ pub struct Url<'a> {
pub scheme: SchemeRef<'a>, pub scheme: SchemeRef<'a>,
} }
impl<'a> From<&'a UrlBuf> for Url<'a> {
fn from(value: &'a UrlBuf) -> Self {
Self { loc: value.loc.as_loc(), scheme: value.scheme.as_ref() }
}
}
impl<'a> From<&'a mut UrlBuf> for Url<'a> {
fn from(value: &'a mut UrlBuf) -> Self { Self::from(&*value) }
}
// --- Eq // --- Eq
impl PartialEq<UrlBuf> for Url<'_> { impl PartialEq<UrlBuf> for Url<'_> {
fn eq(&self, other: &UrlBuf) -> bool { *self == other.as_url() } fn eq(&self, other: &UrlBuf) -> bool { *self == other.as_url() }
@ -142,14 +132,14 @@ impl<'a> Url<'a> {
} }
#[inline] #[inline]
pub fn starts_with<'b>(self, base: impl Into<Url<'b>>) -> bool { pub fn starts_with(self, base: impl AsUrl) -> bool {
let base: Url = base.into(); let base = base.as_url();
self.scheme.covariant(base.scheme) && self.loc.starts_with(base.loc) self.scheme.covariant(base.scheme) && self.loc.starts_with(base.loc)
} }
#[inline] #[inline]
pub fn ends_with<'b>(self, child: impl Into<Url<'b>>) -> bool { pub fn ends_with(self, child: impl AsUrl) -> bool {
let child: Url = child.into(); let child = child.as_url();
self.scheme.covariant(child.scheme) && self.loc.ends_with(child.loc) self.scheme.covariant(child.scheme) && self.loc.ends_with(child.loc)
} }
@ -160,8 +150,8 @@ impl<'a> Url<'a> {
pub fn os_str(self) -> Cow<'a, OsStr> { self.components().os_str() } pub fn os_str(self) -> Cow<'a, OsStr> { self.components().os_str() }
#[inline] #[inline]
pub fn covariant(self, other: impl Into<Self>) -> bool { pub fn covariant(self, other: impl AsUrl) -> bool {
let other = other.into(); let other = other.as_url();
self.scheme.covariant(other.scheme) && self.loc == other.loc self.scheme.covariant(other.scheme) && self.loc == other.loc
} }

View file

@ -22,9 +22,7 @@ futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }
russh = { workspace = true } russh = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[target."cfg(unix)".dependencies] [target."cfg(unix)".dependencies]

View file

@ -1,30 +1,30 @@
use std::io; use std::io;
use yazi_fs::cha::{Cha, ChaKind}; use yazi_fs::cha::{Cha, ChaKind};
use yazi_shared::url::Url; use yazi_shared::url::AsUrl;
use crate::provider; use crate::provider;
pub trait VfsCha: Sized { pub trait VfsCha: Sized {
fn from_url<'a>(url: impl Into<Url<'a>>) -> impl Future<Output = io::Result<Self>>; fn from_url(url: impl AsUrl) -> impl Future<Output = io::Result<Self>>;
fn from_follow<'a, U>(url: U, cha: Self) -> impl Future<Output = Self> fn from_follow<U>(url: U, cha: Self) -> impl Future<Output = Self>
where where
U: Into<Url<'a>>; U: AsUrl;
} }
impl VfsCha for Cha { impl VfsCha for Cha {
#[inline] #[inline]
async fn from_url<'a>(url: impl Into<Url<'a>>) -> io::Result<Self> { async fn from_url(url: impl AsUrl) -> io::Result<Self> {
let url = url.into(); let url = url.as_url();
Ok(Self::from_follow(url, provider::symlink_metadata(url).await?).await) Ok(Self::from_follow(url, provider::symlink_metadata(url).await?).await)
} }
async fn from_follow<'a, U>(url: U, mut cha: Self) -> Self async fn from_follow<U>(url: U, mut cha: Self) -> Self
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
let mut retain = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM); let mut retain = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM);
if cha.is_link() { if cha.is_link() {

View file

@ -3,12 +3,12 @@ use std::{ffi::OsString, io};
use tokio::{select, sync::{mpsc, oneshot}}; use tokio::{select, sync::{mpsc, oneshot}};
use yazi_fs::cha::Cha; use yazi_fs::cha::Cha;
use yazi_macro::ok_or_not_found; use yazi_macro::ok_or_not_found;
use yazi_shared::url::{Url, UrlBuf}; use yazi_shared::url::{AsUrl, UrlBuf};
use crate::provider; use crate::provider;
#[inline] #[inline]
pub async fn maybe_exists<'a>(url: impl Into<Url<'a>>) -> bool { pub async fn maybe_exists(url: impl AsUrl) -> bool {
match provider::symlink_metadata(url).await { match provider::symlink_metadata(url).await {
Ok(_) => true, Ok(_) => true,
Err(e) => e.kind() != io::ErrorKind::NotFound, Err(e) => e.kind() != io::ErrorKind::NotFound,
@ -16,7 +16,7 @@ pub async fn maybe_exists<'a>(url: impl Into<Url<'a>>) -> bool {
} }
#[inline] #[inline]
pub async fn must_be_dir<'a>(url: impl Into<Url<'a>>) -> bool { pub async fn must_be_dir(url: impl AsUrl) -> bool {
provider::metadata(url).await.is_ok_and(|m| m.is_dir()) provider::metadata(url).await.is_ok_and(|m| m.is_dir())
} }

View file

@ -1,3 +1,5 @@
#![allow(clippy::if_same_then_else, clippy::unit_arg)]
yazi_macro::mod_pub!(provider); yazi_macro::mod_pub!(provider);
yazi_macro::mod_flat!(cha file files fns op); yazi_macro::mod_flat!(cha file files fns op);

View file

@ -1,7 +1,7 @@
use std::{collections::VecDeque, io, time::{Duration, Instant}}; use std::{collections::VecDeque, io, time::{Duration, Instant}};
use yazi_fs::provider::{DirReader, FileHolder}; use yazi_fs::provider::{DirReader, FileHolder};
use yazi_shared::{Either, url::{Url, UrlBuf}}; use yazi_shared::{Either, url::{AsUrl, UrlBuf}};
use super::ReadDir; use super::ReadDir;
@ -11,11 +11,11 @@ pub enum SizeCalculator {
} }
impl SizeCalculator { impl SizeCalculator {
pub async fn new<'a, U>(url: U) -> io::Result<Self> pub async fn new<U>(url: U) -> io::Result<Self>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
let cha = super::symlink_metadata(url).await?; let cha = super::symlink_metadata(url).await?;
Ok(if cha.is_dir() { Ok(if cha.is_dir() {
Self::Dir(VecDeque::from([Either::Left(url.to_owned())])) Self::Dir(VecDeque::from([Either::Left(url.to_owned())]))
@ -24,9 +24,9 @@ impl SizeCalculator {
}) })
} }
pub async fn total<'a, U>(url: U) -> io::Result<u64> pub async fn total<U>(url: U) -> io::Result<u64>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let mut it = Self::new(url).await?; let mut it = Self::new(url).await?;
let mut total = 0; let mut total = 0;

View file

@ -2,23 +2,22 @@ use std::{io, path::{Path, PathBuf}};
use tokio::io::{BufReader, BufWriter}; use tokio::io::{BufReader, BufWriter};
use yazi_fs::{cha::Cha, provider::{Provider, local::Local}}; use yazi_fs::{cha::Cha, provider::{Provider, local::Local}};
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlBuf, UrlCow}};
use super::{Providers, ReadDir, RwFile}; use super::{Providers, ReadDir, RwFile};
pub async fn absolute<'a, U>(url: U) -> io::Result<UrlCow<'a>> pub async fn absolute<'a, U>(url: &'a U) -> io::Result<UrlCow<'a>>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); Providers::new(url.as_url()).await?.absolute(url).await
Providers::new(url).await?.absolute(url).await
} }
pub async fn calculate<'a, U>(url: U) -> io::Result<u64> pub async fn calculate<U>(url: U) -> io::Result<u64>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
if let Some(path) = url.as_path() { if let Some(path) = url.as_path() {
yazi_fs::provider::local::SizeCalculator::total(path).await yazi_fs::provider::local::SizeCalculator::total(path).await
} else { } else {
@ -26,11 +25,11 @@ where
} }
} }
pub async fn canonicalize<'a, U>(url: U) -> io::Result<UrlBuf> pub async fn canonicalize<U>(url: U) -> io::Result<UrlBuf>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
let canon = Providers::new(url).await?.canonicalize(url.loc).await?; let canon = Providers::new(url).await?.canonicalize(url.loc).await?;
Ok(match url.scheme { Ok(match url.scheme {
@ -42,11 +41,11 @@ where
}) })
} }
pub async fn casefold<'a, U>(url: U) -> io::Result<UrlBuf> pub async fn casefold<U>(url: U) -> io::Result<UrlBuf>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
let fold = Providers::new(url).await?.casefold(url.loc).await?; let fold = Providers::new(url).await?.casefold(url.loc).await?;
Ok(match url.scheme { Ok(match url.scheme {
@ -58,12 +57,12 @@ where
}) })
} }
pub async fn copy<'a, U, V>(from: U, to: V, cha: Cha) -> io::Result<u64> pub async fn copy<U, V>(from: U, to: V, cha: Cha) -> io::Result<u64>
where where
U: Into<Url<'a>>, U: AsUrl,
V: Into<Url<'a>>, V: AsUrl,
{ {
let (from, to): (Url, Url) = (from.into(), to.into()); let (from, to) = (from.as_url(), to.as_url());
match (from.as_path(), to.as_path()) { match (from.as_path(), to.as_path()) {
(Some(from), Some(to)) => Local.copy(from, to, cha).await, (Some(from), Some(to)) => Local.copy(from, to, cha).await,
@ -85,36 +84,36 @@ where
} }
} }
pub async fn create<'a, U>(url: U) -> io::Result<RwFile> pub async fn create<U>(url: U) -> io::Result<RwFile>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.create(url.loc).await Providers::new(url).await?.create(url.loc).await
} }
pub async fn create_dir<'a, U>(url: U) -> io::Result<()> pub async fn create_dir<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.create_dir(url.loc).await Providers::new(url).await?.create_dir(url.loc).await
} }
pub async fn create_dir_all<'a, U>(url: U) -> io::Result<()> pub async fn create_dir_all<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.create_dir_all(url.loc).await Providers::new(url).await?.create_dir_all(url.loc).await
} }
pub async fn hard_link<'a, U, V>(original: U, link: V) -> io::Result<()> pub async fn hard_link<U, V>(original: U, link: V) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
V: Into<Url<'a>>, V: AsUrl,
{ {
let (original, link): (Url, Url) = (original.into(), link.into()); let (original, link) = (original.as_url(), link.as_url());
if original.scheme.covariant(link.scheme) { if original.scheme.covariant(link.scheme) {
Providers::new(original).await?.hard_link(original.loc, link.loc).await Providers::new(original).await?.hard_link(original.loc, link.loc).await
} else { } else {
@ -122,88 +121,88 @@ where
} }
} }
pub async fn identical<'a, U, V>(a: U, b: V) -> io::Result<bool> pub async fn identical<U, V>(a: U, b: V) -> io::Result<bool>
where where
U: Into<Url<'a>>, U: AsUrl,
V: Into<Url<'a>>, V: AsUrl,
{ {
if let (Some(a), Some(b)) = (a.into().as_path(), b.into().as_path()) { if let (Some(a), Some(b)) = (a.as_url().as_path(), b.as_url().as_path()) {
yazi_fs::provider::local::identical(a, b).await yazi_fs::provider::local::identical(a, b).await
} else { } else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
} }
} }
pub async fn metadata<'a, U>(url: U) -> io::Result<Cha> pub async fn metadata<U>(url: U) -> io::Result<Cha>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.metadata(url.loc).await Providers::new(url).await?.metadata(url.loc).await
} }
pub async fn must_identical<'a, U, V>(a: U, b: V) -> bool pub async fn must_identical<U, V>(a: U, b: V) -> bool
where where
U: Into<Url<'a>>, U: AsUrl,
V: Into<Url<'a>>, V: AsUrl,
{ {
identical(a, b).await.unwrap_or(false) identical(a, b).await.unwrap_or(false)
} }
pub async fn read_dir<'a, U>(url: U) -> io::Result<ReadDir> pub async fn read_dir<U>(url: U) -> io::Result<ReadDir>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.read_dir(url.loc).await Providers::new(url).await?.read_dir(url.loc).await
} }
pub async fn read_link<'a, U>(url: U) -> io::Result<PathBuf> pub async fn read_link<U>(url: U) -> io::Result<PathBuf>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.read_link(url.loc).await Providers::new(url).await?.read_link(url.loc).await
} }
pub async fn remove_dir<'a, U>(url: U) -> io::Result<()> pub async fn remove_dir<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.remove_dir(url.loc).await Providers::new(url).await?.remove_dir(url.loc).await
} }
pub async fn remove_dir_all<'a, U>(url: U) -> io::Result<()> pub async fn remove_dir_all<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.remove_dir_all(url.loc).await Providers::new(url).await?.remove_dir_all(url.loc).await
} }
pub async fn remove_dir_clean<'a, U>(url: U) -> io::Result<()> pub async fn remove_dir_clean<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Ok(Providers::new(url).await?.remove_dir_clean(url.loc).await) Ok(Providers::new(url).await?.remove_dir_clean(url.loc).await)
} }
pub async fn remove_file<'a, U>(url: U) -> io::Result<()> pub async fn remove_file<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.remove_file(url.loc).await Providers::new(url).await?.remove_file(url.loc).await
} }
pub async fn rename<'a, U, V>(from: U, to: V) -> io::Result<()> pub async fn rename<U, V>(from: U, to: V) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
V: Into<Url<'a>>, V: AsUrl,
{ {
let (from, to): (Url, Url) = (from.into(), to.into()); let (from, to) = (from.as_url(), to.as_url());
if from.scheme.covariant(to.scheme) { if from.scheme.covariant(to.scheme) {
Providers::new(from).await?.rename(from.loc, to.loc).await Providers::new(from).await?.rename(from.loc, to.loc).await
} else { } else {
@ -211,52 +210,52 @@ where
} }
} }
pub async fn symlink<'a, U, F>(original: &Path, link: U, is_dir: F) -> io::Result<()> pub async fn symlink<U, F>(original: &Path, link: U, is_dir: F) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
F: AsyncFnOnce() -> io::Result<bool>, F: AsyncFnOnce() -> io::Result<bool>,
{ {
let link: Url = link.into(); let link = link.as_url();
Providers::new(link).await?.symlink(original, link.loc, is_dir).await Providers::new(link).await?.symlink(original, link.loc, is_dir).await
} }
pub async fn symlink_dir<'a, U>(original: &Path, link: U) -> io::Result<()> pub async fn symlink_dir<U>(original: &Path, link: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let link: Url = link.into(); let link = link.as_url();
Providers::new(link).await?.symlink_dir(original, link.loc).await Providers::new(link).await?.symlink_dir(original, link.loc).await
} }
pub async fn symlink_file<'a, U>(original: &Path, link: U) -> io::Result<()> pub async fn symlink_file<U>(original: &Path, link: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let link: Url = link.into(); let link = link.as_url();
Providers::new(link).await?.symlink_file(original, link.loc).await Providers::new(link).await?.symlink_file(original, link.loc).await
} }
pub async fn symlink_metadata<'a, U>(url: U) -> io::Result<Cha> pub async fn symlink_metadata<U>(url: U) -> io::Result<Cha>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.symlink_metadata(url.loc).await Providers::new(url).await?.symlink_metadata(url.loc).await
} }
pub async fn trash<'a, U>(url: U) -> io::Result<()> pub async fn trash<U>(url: U) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.trash(url.loc).await Providers::new(url).await?.trash(url.loc).await
} }
pub async fn write<'a, U, C>(url: U, contents: C) -> io::Result<()> pub async fn write<U, C>(url: U, contents: C) -> io::Result<()>
where where
U: Into<Url<'a>>, U: AsUrl,
C: AsRef<[u8]>, C: AsRef<[u8]>,
{ {
let url: Url = url.into(); let url = url.as_url();
Providers::new(url).await?.write(url.loc, contents).await Providers::new(url).await?.write(url.loc, contents).await
} }

View file

@ -2,7 +2,7 @@ use std::{io, path::{Path, PathBuf}, sync::Arc};
use yazi_config::vfs::{ProviderSftp, Vfs}; use yazi_config::vfs::{ProviderSftp, Vfs};
use yazi_fs::{cha::Cha, provider::{Provider, local::Local}}; use yazi_fs::{cha::Cha, provider::{Provider, local::Local}};
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlCow}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, Url, UrlCow}};
pub(super) struct Providers<'a>(Inner<'a>); pub(super) struct Providers<'a>(Inner<'a>);
@ -32,9 +32,9 @@ impl Provider for Providers<'_> {
type Gate = super::Gate; type Gate = super::Gate;
type ReadDir = super::ReadDir; type ReadDir = super::ReadDir;
async fn absolute<'a, U>(&self, url: U) -> io::Result<UrlCow<'a>> async fn absolute<'a, U>(&self, url: &'a U) -> io::Result<UrlCow<'a>>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
match self.0 { match self.0 {
Inner::Regular | Inner::Search(_) => Local.absolute(url).await, Inner::Regular | Inner::Search(_) => Local.absolute(url).await,

View file

@ -5,7 +5,7 @@ use tokio::io::{BufReader, BufWriter};
use yazi_config::vfs::ProviderSftp; use yazi_config::vfs::ProviderSftp;
use yazi_fs::provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local}; use yazi_fs::provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local};
use yazi_sftp::fs::{Attrs, Flags}; use yazi_sftp::fs::{Attrs, Flags};
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlBuf, UrlCow}};
use super::Cha; use super::Cha;
@ -24,11 +24,11 @@ impl Provider for Sftp {
type Gate = super::Gate; type Gate = super::Gate;
type ReadDir = super::ReadDir; type ReadDir = super::ReadDir;
async fn absolute<'a, U>(&self, url: U) -> io::Result<UrlCow<'a>> async fn absolute<'a, U>(&self, url: &'a U) -> io::Result<UrlCow<'a>>
where where
U: Into<Url<'a>>, U: AsUrl,
{ {
let url: Url = url.into(); let url = url.as_url();
Ok(if url.is_absolute() { Ok(if url.is_absolute() {
url.into() url.into()
} else if let SchemeRef::Sftp(_) = url.scheme { } else if let SchemeRef::Sftp(_) = url.scheme {

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use hashbrown::HashSet; use hashbrown::HashSet;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::error; use tracing::error;
use yazi_shared::url::{Url, UrlBuf}; use yazi_shared::url::{AsUrl, Url, UrlBuf};
use crate::{LINKED, WATCHED, backend}; use crate::{LINKED, WATCHED, backend};
@ -64,11 +64,11 @@ impl Backend {
todo.into_iter().for_each(|u| _ = out_tx.send(u)); todo.into_iter().for_each(|u| _ = out_tx.send(u));
} }
fn watch<'a>(&mut self, url: impl Into<Url<'a>>) -> Result<()> { fn watch(&mut self, url: impl AsUrl) -> Result<()> {
if let Some(path) = url.into().as_path() { self.local.watch(path) } else { Ok(()) } if let Some(path) = url.as_url().as_path() { self.local.watch(path) } else { Ok(()) }
} }
fn unwatch<'a>(&mut self, url: impl Into<Url<'a>>) -> Result<()> { fn unwatch(&mut self, url: impl AsUrl) -> Result<()> {
if let Some(path) = url.into().as_path() { self.local.unwatch(path) } else { Ok(()) } if let Some(path) = url.as_url().as_path() { self.local.unwatch(path) } else { Ok(()) }
} }
} }

View file

@ -20,10 +20,10 @@ impl DerefMut for Linked {
} }
impl Linked { impl Linked {
pub fn from_dir<'a, 'b, T>(&'a self, url: T) -> Box<dyn Iterator<Item = &'a Path> + 'b> pub fn from_dir<'a, 'b, U>(&'a self, url: U) -> Box<dyn Iterator<Item = &'a Path> + 'b>
where where
'a: 'b, 'a: 'b,
T: Into<Url<'b>>, U: Into<Url<'b>>,
{ {
let url: Url = url.into(); let url: Url = url.into();
let Some(path) = url.as_path() else { let Some(path) = url.as_path() else {

View file

@ -1,16 +1,14 @@
use std::path::Path; use std::path::Path;
use hashbrown::HashSet; use hashbrown::HashSet;
use yazi_shared::url::{Url, UrlBuf}; use yazi_shared::url::{AsUrl, UrlBuf};
#[derive(Default)] #[derive(Default)]
pub struct Watched(HashSet<UrlBuf>); pub struct Watched(HashSet<UrlBuf>);
impl Watched { impl Watched {
#[inline] #[inline]
pub(crate) fn contains<'a>(&self, url: impl Into<Url<'a>>) -> bool { pub(crate) fn contains(&self, url: impl AsUrl) -> bool { self.0.contains(&url.as_url()) }
self.0.contains(&url.into())
}
#[inline] #[inline]
pub(crate) fn diff(&self, new: &HashSet<UrlBuf>) -> (Vec<UrlBuf>, Vec<UrlBuf>) { pub(crate) fn diff(&self, new: &HashSet<UrlBuf>) -> (Vec<UrlBuf>, Vec<UrlBuf>) {
@ -26,5 +24,5 @@ impl Watched {
} }
#[inline] #[inline]
pub(crate) fn remove<'a>(&mut self, url: impl Into<Url<'a>>) { self.0.remove(&url.into()); } pub(crate) fn remove(&mut self, url: impl AsUrl) { self.0.remove(&url.as_url()); }
} }