feat: shell formatting (#3232)

This commit is contained in:
三咲雅 misaki masa 2025-10-06 23:44:32 +08:00 committed by GitHub
parent cd6881c9fe
commit 554cb52cc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
86 changed files with 832 additions and 656 deletions

31
Cargo.lock generated
View file

@ -100,9 +100,9 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.20" version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@ -411,9 +411,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.23.2" version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -459,9 +459,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.39" version = "1.2.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -1243,9 +1243,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -1874,11 +1874,10 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.13" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [ dependencies = [
"autocfg",
"scopeguard", "scopeguard",
] ]
@ -2442,9 +2441,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.4" version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -2452,15 +2451,15 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.11" version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets 0.52.6", "windows-link 0.2.0",
] ]
[[package]] [[package]]

View file

@ -38,7 +38,7 @@ lru = "0.16.1"
mlua = { version = "0.11.4", 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.1.0", features = [ "serde" ] } ordered-float = { version = "5.1.0", features = [ "serde" ] }
parking_lot = "0.12.4" parking_lot = "0.12.5"
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" ] }
regex = "1.11.3" regex = "1.11.3"

View file

@ -1 +1 @@
{"version":"0.2","language":"en","flagWords":[],"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","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes"]} {"version":"0.2","flagWords":[],"language":"en","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","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable"]}

View file

@ -91,7 +91,7 @@ impl Trigger {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use yazi_shared::url::Urn; use yazi_shared::url::{UrlLike, Urn};
use super::*; use super::*;

View file

@ -4,6 +4,7 @@ use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Valu
use yazi_binding::Style; use yazi_binding::Style;
use yazi_config::THEME; use yazi_config::THEME;
use yazi_plugin::bindings::Range; use yazi_plugin::bindings::Range;
use yazi_shared::url::UrlLike;
use super::Lives; use super::Lives;
use crate::lives::PtrCell; use crate::lives::PtrCell;

View file

@ -2,6 +2,7 @@ use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Id, UrlRef, cached_field}; use yazi_binding::{Id, UrlRef, cached_field};
use yazi_shared::url::UrlLike;
use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected}; use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}, hash::Hash, io::{Read, Write}, ops::Deref}; use std::{ffi::{OsStr, OsString}, hash::Hash, io::{Read, Write}, ops::Deref};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use crossterm::{execute, style::Print}; use crossterm::{execute, style::Print};
@ -7,11 +7,11 @@ use scopeguard::defer;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use yazi_config::{YAZI, opener::OpenerRule}; use yazi_config::{YAZI, opener::OpenerRule};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}}; use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
use yazi_macro::{err, succ}; use yazi_macro::{err, succ};
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER, TasksProxy}; use yazi_proxy::{AppProxy, HIDER, TasksProxy};
use yazi_shared::{OsStrJoin, data::Data, terminal_clear, url::{Component, Url, UrlBuf}}; use yazi_shared::{OsStrJoin, data::Data, terminal_clear, url::{Component, Url, UrlBuf, UrlCow, UrlLike}};
use yazi_term::tty::TTY; use yazi_term::tty::TTY;
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -51,10 +51,13 @@ impl Actor for BulkRename {
.await?; .await?;
defer! { tokio::spawn(Local.remove_file(tmp.clone())); } defer! { tokio::spawn(Local.remove_file(tmp.clone())); }
TasksProxy::process_exec(Cow::Borrowed(opener), cwd, vec![ TasksProxy::process_exec(
OsStr::new("").into(), cwd,
tmp.as_os_str().to_owned().into(), Splatter::new(&[UrlCow::default(), Url::regular(&tmp).into()]).splat(&opener.run),
]) vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
opener.block,
opener.orphan,
)
.await; .await;
let _permit = HIDER.acquire().await.unwrap(); let _permit = HIDER.acquire().await.unwrap();

View file

@ -9,7 +9,7 @@ use yazi_fs::{File, FilesOp, path::expand_url};
use yazi_macro::{act, err, render, succ}; use yazi_macro::{act, err, render, succ};
use yazi_parser::mgr::CdOpt; use yazi_parser::mgr::CdOpt;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy}; use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
use yazi_shared::{Debounce, data::Data, errors::InputError, url::UrlBuf}; use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -3,7 +3,7 @@ use std::ffi::OsString;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::mgr::CopyOpt; use yazi_parser::mgr::CopyOpt;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use yazi_widgets::CLIPBOARD; use yazi_widgets::CLIPBOARD;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -4,7 +4,7 @@ use yazi_fs::{File, FilesOp};
use yazi_macro::{ok_or_not_found, succ}; use yazi_macro::{ok_or_not_found, succ};
use yazi_parser::mgr::CreateOpt; use yazi_parser::mgr::CreateOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
use yazi_shared::{data::Data, url::UrlBuf}; use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use yazi_fs::path::clean_url; use yazi_fs::path::clean_url;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_macro::{err, render, succ, tab}; use yazi_macro::{err, render, succ, tab};
use yazi_parser::mgr::HoverOpt; use yazi_parser::mgr::HoverOpt;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidOpt, mgr::CdSource}; use yazi_parser::{VoidOpt, mgr::CdSource};
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,11 +1,11 @@
use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use hashbrown::HashMap;
use yazi_config::{YAZI, popup::PickCfg}; use yazi_config::{YAZI, popup::PickCfg};
use yazi_fs::Splatter;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::OpenDoOpt; use yazi_parser::{mgr::OpenDoOpt, tasks::ProcessOpenOpt};
use yazi_proxy::{PickProxy, TasksProxy}; use yazi_proxy::{PickProxy, TasksProxy};
use yazi_shared::{data::Data, url::UrlCow}; use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -30,21 +30,53 @@ 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_with_selected(opt.cwd, targets)); succ!(Self::match_and_open(cx, 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()).collect();
if openers.is_empty() { if openers.is_empty() {
succ!(); succ!();
} }
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 = [UrlCow::default()].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect(); let urls: Vec<_> =
[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::file_open(Cow::Borrowed(openers[choice]), opt.cwd, urls); TasksProxy::open_shell_compat(ProcessOpenOpt {
cwd: opt.cwd,
cmd: Splatter::new(&urls).splat(&openers[choice].run),
args: urls,
block: openers[choice].block,
orphan: openers[choice].orphan,
done: None,
spread: openers[choice].spread,
});
} }
}); });
succ!(); succ!();
} }
} }
impl OpenDo {
// TODO: remove
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(UrlCow<'static>, &str)>) {
let mut openers = HashMap::new();
for (url, mime) in targets {
if let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) {
openers.entry(opener).or_insert_with(|| vec![UrlCow::default()]).push(url);
}
}
for (opener, args) in openers {
cx.tasks.open_shell_compat(ProcessOpenOpt {
cwd: cwd.clone(),
cmd: Splatter::new(&args).splat(&opener.run),
args,
block: opener.block,
orphan: opener.orphan,
done: None,
spread: opener.spread,
});
}
}
}

View file

@ -7,7 +7,7 @@ use yazi_dds::spark::SparkKind;
use yazi_macro::{emit, succ}; use yazi_macro::{emit, succ};
use yazi_parser::mgr::QuitOpt; use yazi_parser::mgr::QuitOpt;
use yazi_proxy::ConfirmProxy; use yazi_proxy::ConfirmProxy;
use yazi_shared::{data::Data, event::EventQuit, url::UrlCow}; use yazi_shared::{data::Data, event::EventQuit, url::{AsUrl, UrlCow}};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -5,7 +5,7 @@ use yazi_fs::{File, FilesOp};
use yazi_macro::{act, err, ok_or_not_found, succ}; use yazi_macro::{act, err, ok_or_not_found, succ};
use yazi_parser::mgr::RenameOpt; use yazi_parser::mgr::RenameOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
use yazi_shared::{Id, data::Data, url::UrlBuf}; use yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use yazi_fs::{File, FilesOp}; use yazi_fs::{File, FilesOp};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::RevealOpt; use yazi_parser::mgr::RevealOpt;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -1,9 +1,10 @@
use std::borrow::Cow; use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use yazi_config::{opener::OpenerRule, popup::InputCfg}; use yazi_config::popup::InputCfg;
use yazi_fs::Splatter;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::mgr::ShellOpt; use yazi_parser::{mgr::ShellOpt, tasks::ProcessOpenOpt};
use yazi_proxy::{InputProxy, TasksProxy}; use yazi_proxy::{InputProxy, TasksProxy};
use yazi_shared::data::Data; use yazi_shared::data::Data;
@ -20,7 +21,7 @@ impl Actor for Shell {
act!(mgr:escape_visual, cx)?; act!(mgr:escape_visual, cx)?;
let cwd = opt.cwd.take().unwrap_or(cx.cwd().into()).into_owned(); let cwd = opt.cwd.take().unwrap_or(cx.cwd().into()).into_owned();
let selected = cx.tab().hovered_and_selected().cloned().map(Into::into).collect(); let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect();
let input = opt.interactive.then(|| { let input = opt.interactive.then(|| {
InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor)) InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor))
@ -37,18 +38,15 @@ impl Actor for Shell {
return; return;
} }
TasksProxy::file_open( TasksProxy::open_shell_compat(ProcessOpenOpt {
Cow::Owned(OpenerRule {
run: opt.run.into_owned(),
block: opt.block,
orphan: opt.orphan,
desc: Default::default(),
r#for: None,
spread: true,
}),
cwd, cwd,
selected, cmd: Splatter::new(&selected).splat(opt.run.as_ref()),
); args: selected,
block: opt.block,
orphan: opt.orphan,
done: None,
spread: true,
});
}); });
succ!(); succ!();

View file

@ -3,7 +3,7 @@ use yazi_core::tab::Folder;
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::UpdateFilesOpt; use yazi_parser::mgr::UpdateFilesOpt;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use yazi_watcher::LINKED; use yazi_watcher::LINKED;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use hashbrown::HashMap; use hashbrown::HashMap;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::UpdateMimesOpt; use yazi_parser::mgr::UpdateMimesOpt;
use yazi_shared::{data::Data, pool::InternStr, url::UrlCov}; use yazi_shared::{data::Data, pool::InternStr, url::{AsUrl, UrlCov}};
use yazi_watcher::LINKED; use yazi_watcher::LINKED;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

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

View file

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

View file

@ -0,0 +1,19 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::tasks::ProcessOpenOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct OpenShellCompat;
// TODO: remove
impl Actor for OpenShellCompat {
type Options = ProcessOpenOpt;
const NAME: &str = "open_shell_compat";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
succ!(cx.tasks.open_shell_compat(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::{AsUrl, UrlCow}}; use yazi_shared::{IntoOsStr, url::{AsUrl, UrlCow, UrlLike}};
use crate::{Urn, cached_field, deprecate}; use crate::{Urn, cached_field, deprecate};

View file

@ -4,7 +4,7 @@ use futures::executor::block_on;
use hashbrown::HashSet; use hashbrown::HashSet;
use serde::Serialize; use serde::Serialize;
use yazi_fs::{CWD, Xdg, path::expand_url}; use yazi_fs::{CWD, Xdg, path::expand_url};
use yazi_shared::url::{UrlBuf, UrlCow, UrnBuf}; use yazi_shared::url::{UrlBuf, UrlCow, UrlLike, UrnBuf};
use yazi_vfs::provider; use yazi_vfs::provider;
#[derive(Debug, Default, Serialize)] #[derive(Debug, Default, Serialize)]

View file

@ -30,40 +30,37 @@ ueberzug_offset = [ 0, 0, 0, 0 ]
[opener] [opener]
edit = [ edit = [
{ run = '${EDITOR:-vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, { run = "${EDITOR:-vi} %s", desc = "$EDITOR", for = "unix", block = true },
{ run = 'code %*', orphan = true, desc = "code", for = "windows" }, { run = "code %s", desc = "code", for = "windows", orphan = true },
{ run = 'code -w %*', block = true, desc = "code (block)", for = "windows" }, { run = "code -w %s", desc = "code (block)", for = "windows", block = true },
] ]
play = [ play = [
{ run = 'xdg-open "$1"', desc = "Play", for = "linux" }, { run = "xdg-open %s1", desc = "Play", for = "linux" },
{ run = 'open "$@"', desc = "Play", for = "macos" }, { run = "open %s", desc = "Play", for = "macos" },
{ run = 'start "" "%1"', orphan = true, desc = "Play", for = "windows" }, { run = 'start "" %s1', orphan = true, desc = "Play", for = "windows" },
{ run = 'termux-open "$1"', desc = "Play", for = "android" }, { run = "termux-open %s1", desc = "Play", for = "android" },
{ run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" }, { run = "mediainfo %s1; echo 'Press enter to exit'; read _", block = true, desc = "Show media info", for = "unix" },
{ run = 'mediainfo "%1" & pause', block = true, desc = "Show media info", for = "windows" }, { run = "mediainfo %s1 & pause", block = true, desc = "Show media info", for = "windows" },
] ]
open = [ open = [
{ run = 'xdg-open "$1"', desc = "Open", for = "linux" }, { run = "xdg-open %s1", desc = "Open", for = "linux" },
{ run = 'open "$@"', desc = "Open", for = "macos" }, { run = "open %s", desc = "Open", for = "macos" },
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, { run = 'start "" %s1', desc = "Open", for = "windows", orphan = true },
{ run = 'termux-open "$1"', desc = "Open", for = "android" }, { run = "termux-open %s1", desc = "Open", for = "android" },
] ]
reveal = [ reveal = [
{ run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, { run = "xdg-open %d1", desc = "Reveal", for = "linux" },
{ run = 'open -R "$1"', desc = "Reveal", for = "macos" }, { run = "open -R %s1", desc = "Reveal", for = "macos" },
{ run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" }, { run = "explorer /select,%s1", desc = "Reveal", for = "windows", orphan = true },
{ run = 'termux-open "$(dirname "$1")"', desc = "Reveal", for = "android" }, { run = "termux-open %d1", desc = "Reveal", for = "android" },
{ run = '''clear; exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, { run = "clear; exiftool %s1; echo 'Press enter to exit'; read _", desc = "Show EXIF", for = "unix", block = true },
] ]
extract = [ extract = [
{ run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" }, { run = "ya pub extract --list %s", desc = "Extract here" },
{ run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" },
] ]
download = [ download = [
{ run = 'ya emit download --open "$@"', desc = "Download and open", for = "unix" }, { run = "ya emit download --open %S", desc = "Download and open" },
{ run = 'ya emit download --open %*', desc = "Download and open", for = "windows" }, { run = "ya emit download %S", desc = "Download" },
{ run = 'ya emit download "$@"', desc = "Download", for = "unix" },
{ run = 'ya emit download %*', desc = "Download", for = "windows" },
] ]
[open] [open]

View file

@ -2,7 +2,7 @@ use anyhow::{Result, bail};
use serde::Deserialize; use serde::Deserialize;
use yazi_codegen::DeserializeOver2; use yazi_codegen::DeserializeOver2;
use yazi_fs::{CWD, SortBy}; use yazi_fs::{CWD, SortBy};
use yazi_shared::{SyncCell, url::UrlBuf}; use yazi_shared::{SyncCell, url::{UrlBuf, UrlLike}};
use super::{MgrRatio, MouseEvents}; use super::{MgrRatio, MouseEvents};

View file

@ -18,21 +18,21 @@ impl Deref for Opener {
} }
impl Opener { impl Opener {
pub fn all<'a, I>(&'a self, uses: I) -> Vec<&'a OpenerRule> pub fn all<'a, I>(&self, uses: I) -> impl Iterator<Item = &OpenerRule>
where where
I: Iterator<Item = &'a str>, I: Iterator<Item = &'a str>,
{ {
uses.flat_map(|use_| self.get(use_)).flatten().collect() uses.flat_map(|use_| self.get(use_)).flatten()
} }
pub fn first<'a, 'b, I>(&'a self, uses: I) -> Option<&'a OpenerRule> pub fn first<'a, I>(&self, uses: I) -> Option<&OpenerRule>
where where
I: Iterator<Item = &'b str>, I: Iterator<Item = &'a str>,
{ {
uses.flat_map(|use_| self.get(use_)).flatten().next() uses.flat_map(|use_| self.get(use_)).flatten().next()
} }
pub fn block<'a, I>(&'a self, uses: I) -> Option<&'a OpenerRule> pub fn block<'a, I>(&self, uses: I) -> Option<&OpenerRule>
where where
I: Iterator<Item = &'a str>, I: Iterator<Item = &'a str>,
{ {

View file

@ -1,5 +1,6 @@
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use serde::Deserialize; use serde::Deserialize;
use yazi_fs::Splatter;
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct OpenerRule { pub struct OpenerRule {
@ -35,11 +36,12 @@ impl OpenerRule {
#[cfg(unix)] #[cfg(unix)]
{ {
self.spread = self.run.contains("$@") || self.run.contains("$*"); self.spread =
Splatter::<()>::spread(&self.run) || self.run.contains("$@") || self.run.contains("$*");
} }
#[cfg(windows)] #[cfg(windows)]
{ {
self.spread = self.run.contains("%*"); self.spread = Splatter::<()>::spread(&self.run) || self.run.contains("%*");
} }
Ok(self) Ok(self)

View file

@ -5,7 +5,7 @@ use hashbrown::HashMap;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use yazi_codegen::DeserializeOver2; use yazi_codegen::DeserializeOver2;
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::Condition; use yazi_shared::{Condition, url::UrlLike};
use crate::{Color, Icon as I, Pattern, Style}; use crate::{Color, Icon as I, Pattern, Style};

View file

@ -1,7 +1,10 @@
use std::iter;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_adapter::Dimension; use yazi_adapter::Dimension;
use yazi_config::popup::{Origin, Position}; use yazi_config::popup::{Origin, Position};
use yazi_shared::url::UrlBuf; use yazi_fs::Splatable;
use yazi_shared::url::{AsUrl, Url, UrlBuf};
use yazi_watcher::Watcher; use yazi_watcher::Watcher;
use super::{Mimetype, Tabs, Yanked}; use super::{Mimetype, Tabs, Yanked};
@ -53,3 +56,29 @@ impl Mgr {
#[inline] #[inline]
pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.active_mut().parent.as_mut() } pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.active_mut().parent.as_mut() }
} }
impl Splatable for Mgr {
fn tab(&self) -> usize { self.tabs.cursor }
fn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
idx = idx.and_then(|i| i.checked_sub(1));
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.map(|tab| tab.selected_or_hovered())
.unwrap_or_else(|| Box::new(iter::empty()))
.skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX })
.map(|u| u.as_url())
}
fn hovered(&self, tab: usize) -> Option<Url<'_>> {
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.and_then(|tab| tab.hovered())
.map(|h| h.url.as_url())
}
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { self.yanked.iter().map(|u| u.as_url()) }
}

View file

@ -4,7 +4,7 @@ use hashbrown::HashSet;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_macro::err; use yazi_macro::err;
use yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov}; use yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlLike};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Yanked { pub struct Yanked {

View file

@ -1,6 +1,6 @@
use hashbrown::HashSet; use hashbrown::HashSet;
use tracing::debug; use tracing::debug;
use yazi_shared::url::{UrlBuf, UrlBufCov}; use yazi_shared::url::{UrlBuf, UrlBufCov, UrlLike};
use super::Tasks; use super::Tasks;
use crate::mgr::Yanked; use crate::mgr::Yanked;

View file

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

View file

@ -37,7 +37,6 @@ pub enum Spark<'a> {
Linemode(yazi_parser::mgr::LinemodeOpt), Linemode(yazi_parser::mgr::LinemodeOpt),
Link(yazi_parser::mgr::LinkOpt), Link(yazi_parser::mgr::LinkOpt),
Open(yazi_parser::mgr::OpenOpt), Open(yazi_parser::mgr::OpenOpt),
OpenWith(yazi_parser::mgr::OpenWithOpt),
OpenDo(yazi_parser::mgr::OpenDoOpt), OpenDo(yazi_parser::mgr::OpenDoOpt),
Paste(yazi_parser::mgr::PasteOpt), Paste(yazi_parser::mgr::PasteOpt),
Peek(yazi_parser::mgr::PeekOpt), Peek(yazi_parser::mgr::PeekOpt),
@ -160,7 +159,6 @@ impl<'a> IntoLua for Spark<'a> {
Self::Linemode(b) => b.into_lua(lua), Self::Linemode(b) => b.into_lua(lua),
Self::Link(b) => b.into_lua(lua), Self::Link(b) => b.into_lua(lua),
Self::Open(b) => b.into_lua(lua), Self::Open(b) => b.into_lua(lua),
Self::OpenWith(b) => b.into_lua(lua),
Self::OpenDo(b) => b.into_lua(lua), Self::OpenDo(b) => b.into_lua(lua),
Self::Paste(b) => b.into_lua(lua), Self::Paste(b) => b.into_lua(lua),
Self::Peek(b) => b.into_lua(lua), Self::Peek(b) => b.into_lua(lua),
@ -295,7 +293,6 @@ try_from_spark!(mgr::LinemodeOpt, mgr:linemode);
try_from_spark!(mgr::LinkOpt, mgr:link); try_from_spark!(mgr::LinkOpt, mgr:link);
try_from_spark!(mgr::OpenDoOpt, mgr:open_do); try_from_spark!(mgr::OpenDoOpt, mgr:open_do);
try_from_spark!(mgr::OpenOpt, mgr:open); try_from_spark!(mgr::OpenOpt, mgr:open);
try_from_spark!(mgr::OpenWithOpt, mgr:open_with);
try_from_spark!(mgr::PasteOpt, mgr:paste); try_from_spark!(mgr::PasteOpt, mgr:paste);
try_from_spark!(mgr::PeekOpt, mgr:peek); try_from_spark!(mgr::PeekOpt, mgr:peek);
try_from_spark!(mgr::QuitOpt, mgr:quit); try_from_spark!(mgr::QuitOpt, mgr:quit);

View file

@ -3,7 +3,7 @@ use yazi_actor::Ctx;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_macro::act; use yazi_macro::act;
use yazi_parser::{VoidOpt, mgr::CdSource}; use yazi_parser::{VoidOpt, mgr::CdSource};
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::app::App; use crate::app::App;

View file

@ -2,7 +2,7 @@ use std::ffi::OsString;
use yazi_boot::ARGS; use yazi_boot::ARGS;
use yazi_fs::provider::{Provider, local::Local}; use yazi_fs::provider::{Provider, local::Local};
use yazi_shared::event::EventQuit; use yazi_shared::{event::EventQuit, url::UrlLike};
use crate::{Term, app::App}; use crate::{Term, app::App};

View file

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

View file

@ -1,7 +1,7 @@
use std::{env::{current_dir, set_current_dir}, ops::Deref, path::PathBuf, sync::{Arc, atomic::{AtomicBool, Ordering}}}; use std::{env::{current_dir, set_current_dir}, ops::Deref, path::PathBuf, sync::{Arc, atomic::{AtomicBool, Ordering}}};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use yazi_shared::{RoCell, url::UrlBuf}; use yazi_shared::{RoCell, url::{UrlBuf, UrlLike}};
use crate::FsUrl; use crate::FsUrl;

View file

@ -1,6 +1,6 @@
use std::{ffi::OsStr, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}}; use std::{ffi::OsStr, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
use yazi_shared::url::{Uri, UrlBuf, Urn}; use yazi_shared::url::{Uri, UrlBuf, UrlLike, Urn};
use crate::cha::{Cha, ChaType}; use crate::cha::{Cha, ChaType};

View file

@ -1,5 +1,5 @@
use tokio::io; use tokio::io;
use yazi_shared::url::{Component, UrlBuf}; use yazi_shared::url::{Component, UrlBuf, UrlLike};
#[inline] #[inline]
pub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> { pub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> {

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(cha error mounts path provider); yazi_macro::mod_pub!(cha error mounts path provider);
yazi_macro::mod_flat!(cwd file files filter fns op sorter sorting stage url xdg); yazi_macro::mod_flat!(cwd file files filter fns op sorter sorting splatter stage url xdg);
pub fn init() { pub fn init() {
CWD.init(<_>::default()); CWD.init(<_>::default());

View file

@ -2,7 +2,7 @@ use std::path::Path;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use yazi_macro::relay; use yazi_macro::relay;
use yazi_shared::{Id, Ids, url::{UrlBuf, UrnBuf}}; use yazi_shared::{Id, Ids, url::{UrlBuf, UrlLike, UrnBuf}};
use super::File; use super::File;
use crate::{cha::Cha, error::Error}; use crate::{cha::Cha, error::Error};

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}, path::{Path, PathBuf}}; use std::{borrow::Cow, ffi::{OsStr, OsString}, path::{Path, PathBuf}};
use yazi_shared::{loc::LocBuf, url::{Url, UrlBuf, UrlCow}}; use yazi_shared::{loc::LocBuf, url::{AsUrl, Url, UrlBuf, UrlCow}};
use crate::{CWD, path::clean_url}; use crate::{CWD, path::clean_url};
@ -76,7 +76,7 @@ pub fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> {
) )
.expect("Failed to create Loc from drive letter"); .expect("Failed to create Loc from drive letter");
UrlBuf { loc, scheme: url.scheme.into() }.into() UrlBuf { loc, scheme: url.scheme.into() }.into()
} else if let Ok(rest) = url.loc.strip_prefix("~/") } else if let Some(rest) = url.loc.strip_prefix("~/")
&& let Some(home) = dirs::home_dir() && let Some(home) = dirs::home_dir()
&& home.is_absolute() && home.is_absolute()
{ {

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::OsStr}; use std::{borrow::Cow, ffi::OsStr};
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
pub fn skip_url(url: &UrlBuf, n: usize) -> Cow<'_, OsStr> { pub fn skip_url(url: &UrlBuf, n: usize) -> Cow<'_, OsStr> {
let mut it = url.components(); let mut it = url.components();
@ -36,7 +36,7 @@ pub fn backslash_to_slash(p: &std::path::Path) -> Cow<'_, std::path::Path> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use yazi_shared::url::UrlCow; use yazi_shared::url::{AsUrl, UrlCow};
use crate::path::url_relative_to; use crate::path::url_relative_to;

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, path::{Path, PathBuf}}; use std::{borrow::Cow, path::{Path, PathBuf}};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_shared::{loc::LocBuf, url::{Url, UrlBuf, UrlCow}}; use yazi_shared::{loc::LocBuf, url::{Url, UrlBuf, UrlCow, UrlLike}};
pub fn path_relative_to<'a>( pub fn path_relative_to<'a>(
from: impl AsRef<Path>, from: impl AsRef<Path>,

View file

@ -1,7 +1,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use hashbrown::HashMap; use hashbrown::HashMap;
use yazi_shared::{LcgRng, natsort, translit::Transliterator, url::UrnBuf}; use yazi_shared::{LcgRng, natsort, translit::Transliterator, url::{UrlLike, UrnBuf}};
use crate::{File, SortBy}; use crate::{File, SortBy};

355
yazi-fs/src/splatter.rs Normal file
View file

@ -0,0 +1,355 @@
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(windows)]
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::{cell::Cell, ffi::{OsStr, OsString}, iter::{self, Peekable}, mem};
use yazi_shared::url::{AsUrl, Url, UrlCow};
use crate::FsUrl;
#[cfg(unix)]
type Iter<'a> = Peekable<std::iter::Copied<std::slice::Iter<'a, u8>>>;
#[cfg(windows)]
type Iter<'a> = Peekable<std::os::windows::ffi::EncodeWide<'a>>;
#[cfg(unix)]
type Buf = Vec<u8>;
#[cfg(windows)]
type Buf = Vec<u16>;
#[derive(Clone, Copy)]
pub struct Splatter<T> {
src: T,
tab: usize,
}
pub trait Splatable {
fn tab(&self) -> usize;
fn selected(&self, tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>>;
fn hovered(&self, tab: usize) -> Option<Url<'_>>;
fn yanked(&self) -> impl Iterator<Item = Url<'_>>;
}
#[cfg(unix)]
fn b2c(b: u8) -> Option<char> { Some(b as char) }
#[cfg(windows)]
fn b2c(b: u16) -> Option<char> { char::from_u32(b as u32) }
fn cue(buf: &mut Buf, s: impl AsRef<OsStr>) {
#[cfg(unix)]
buf.extend(yazi_shared::shell::escape_os_str(s.as_ref()).as_bytes());
#[cfg(windows)]
buf.extend(yazi_shared::shell::escape_os_str(s.as_ref()).encode_wide());
}
impl<T> Splatter<T>
where
T: Splatable,
{
pub fn new(src: T) -> Self { Self { tab: src.tab() + 1, src } }
pub fn splat(mut self, cmd: impl AsRef<OsStr>) -> OsString {
#[cfg(unix)]
let mut it = cmd.as_ref().as_bytes().iter().copied().peekable();
#[cfg(windows)]
let mut it = cmd.as_ref().encode_wide().peekable();
let mut buf = vec![];
while let Some(cur) = it.next() {
if b2c(cur) == Some('%') && it.peek().is_some() {
self.visit(&mut it, &mut buf);
} else {
buf.push(cur);
}
}
#[cfg(unix)]
return OsString::from_vec(buf);
#[cfg(windows)]
return OsString::from_wide(&buf);
}
fn visit(&mut self, it: &mut Iter, buf: &mut Buf) {
let c = it.peek().copied().and_then(b2c);
match c {
Some('s') | Some('S') => self.visit_selected(it, buf),
Some('h') | Some('H') => self.visit_hovered(it, buf),
Some('d') | Some('D') => self.visit_dirname(it, buf),
Some('t') | Some('T') => self.visit_tab(it, buf),
Some('y') | Some('Y') => self.visit_yanked(it, buf),
Some('%') => self.visit_escape(it, buf),
Some('*') => self.visit_selected(it, buf), // TODO: remove this
Some(c) if c.is_ascii_digit() => self.visit_digit(it, buf),
_ => self.visit_unknown(it, buf),
}
}
fn visit_selected(&mut self, it: &mut Iter, buf: &mut Buf) {
let c = it.next().and_then(b2c);
let idx = self.consume_digit(it);
let mut first = true;
for url in self.src.selected(self.tab, idx) {
if !mem::replace(&mut first, false) {
buf.push(b' ' as _);
}
if c == Some('S') {
cue(buf, url.os_str());
} else {
cue(buf, url.unified_path_str());
}
}
if first && idx.is_some() {
cue(buf, "");
}
}
fn visit_hovered(&mut self, it: &mut Iter, buf: &mut Buf) {
match it.next().and_then(b2c) {
Some('h') => {
cue(buf, self.src.hovered(self.tab).map(|u| u.unified_path_str()).unwrap_or_default());
}
Some('H') => {
cue(buf, self.src.hovered(self.tab).map(|u| u.os_str()).unwrap_or_default());
}
_ => unreachable!(),
}
}
fn visit_dirname(&mut self, it: &mut Iter, buf: &mut Buf) {
let c = it.next().and_then(b2c);
let idx = self.consume_digit(it);
let mut first = true;
for url in self.src.selected(self.tab, idx) {
if !mem::replace(&mut first, false) {
buf.push(b' ' as _);
}
if c == Some('D') {
cue(buf, url.parent().map(|p| p.os_str()).unwrap_or_default());
} else {
cue(buf, url.parent().map(|p| p.unified_path_str()).unwrap_or_default());
}
}
if first && idx.is_some() {
cue(buf, "");
}
}
fn visit_tab(&mut self, it: &mut Iter, buf: &mut Buf) {
let old = self.tab;
match it.next().and_then(b2c) {
Some('t') => self.tab = self.tab.saturating_add(1),
Some('T') => self.tab = self.tab.saturating_sub(1),
_ => unreachable!(),
}
self.visit(it, buf);
self.tab = old;
}
fn visit_digit(&mut self, it: &mut Iter, buf: &mut Buf) {
// TODO: remove
match self.consume_digit(it) {
Some(0) => {
cue(buf, self.src.hovered(self.tab).map(|u| u.unified_path_str()).unwrap_or_default());
}
Some(n) => {
cue(
buf,
self
.src
.selected(self.tab, Some(n))
.next()
.map(|u| u.unified_path_str())
.unwrap_or_default(),
);
}
None => unreachable!(),
}
}
fn visit_yanked(&mut self, it: &mut Iter, buf: &mut Buf) {
let c = it.next().and_then(b2c);
let mut first = true;
for url in self.src.yanked() {
if !mem::replace(&mut first, false) {
buf.push(b' ' as _);
}
if c == Some('Y') {
cue(buf, url.os_str());
} else {
cue(buf, url.unified_path_str());
}
}
}
fn visit_escape(&mut self, it: &mut Iter, buf: &mut Buf) { buf.push(it.next().unwrap()); }
fn visit_unknown(&mut self, it: &mut Iter, buf: &mut Buf) {
buf.push(b'%' as _);
if let Some(b) = it.next() {
buf.push(b);
}
}
fn consume_digit(&mut self, it: &mut Iter) -> Option<usize> {
fn next(it: &mut Iter) -> Option<usize> {
let n = b2c(*it.peek()?)?.to_digit(10)? as usize;
it.next();
Some(n)
}
let mut sum = next(it)?;
while let Some(n) = next(it) {
sum = sum.checked_mul(10)?.checked_add(n)?;
}
Some(sum)
}
}
impl<T> Splatter<T> {
pub fn spread(cmd: impl AsRef<OsStr>) -> bool {
struct Source(Cell<bool>);
impl Splatable for &Source {
fn tab(&self) -> usize { 0 }
fn selected(&self, _tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
if idx.is_none() {
self.0.set(true);
}
iter::empty()
}
fn hovered(&self, _tab: usize) -> Option<Url<'_>> { None }
fn yanked(&self) -> impl Iterator<Item = Url<'_>> {
self.0.set(true);
iter::empty()
}
}
let src = Source(Cell::new(false));
Splatter { src: &src, tab: 1 }.splat(cmd.as_ref());
src.0.get()
}
}
// TODO: remove
impl<'a, T> Splatable for &'a T
where
T: AsRef<[UrlCow<'a>]>,
{
fn tab(&self) -> usize { 0 }
fn selected(&self, tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
self
.as_ref()
.iter()
.filter(move |_| tab == 1)
.map(|u| u.as_url())
.skip(idx.unwrap_or(1))
.take(if idx.is_some() { 1 } else { usize::MAX })
}
fn hovered(&self, tab: usize) -> Option<Url<'_>> {
self.as_ref().first().filter(|_| tab == 1).map(|u| u.as_url())
}
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { iter::empty() }
}
#[cfg(test)]
mod tests {
use super::*;
struct Source(usize);
impl Splatable for Source {
fn tab(&self) -> usize { self.0 }
fn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
let urls = if tab == 1 {
vec![Url::regular("t1/s1"), Url::regular("t1/s2")]
} else if tab == 2 {
vec![Url::regular("t 2/s 1"), Url::regular("t 2/s 2")]
} else {
vec![]
};
idx = idx.and_then(|i| i.checked_sub(1));
urls.into_iter().skip(idx.unwrap_or(0)).take(if idx.is_some() { 1 } else { usize::MAX })
}
fn hovered(&self, tab: usize) -> Option<Url<'_>> {
if tab == 1 {
Some(Url::regular("hovered"))
} else if tab == 2 {
Some(Url::regular("hover ed"))
} else {
None
}
}
fn yanked(&self) -> impl Iterator<Item = Url<'_>> {
[Url::regular("y1"), Url::regular("y 2"), Url::regular("y3")].into_iter()
}
}
#[test]
#[cfg(unix)]
fn test_unix() {
let cases = [
// Selected
(Source(0), r#"ls %s"#, r#"ls t1/s1 t1/s2"#),
(Source(0), r#"ls %s1 %s2 %s3"#, r#"ls t1/s1 t1/s2 ''"#),
(Source(0), r#"ls %s %s2 %s"#, r#"ls t1/s1 t1/s2 t1/s2 t1/s1 t1/s2"#),
(Source(1), r#"ls %s"#, r#"ls 't 2/s 1' 't 2/s 2'"#),
(Source(1), r#"ls %s1 %s3 %s2"#, r#"ls 't 2/s 1' '' 't 2/s 2'"#),
(Source(2), r#"ls %s"#, r#"ls "#),
(Source(2), r#"ls %s1 %s %s2"#, r#"ls '' ''"#),
// Hovered
(Source(0), r#"ls %h"#, r#"ls hovered"#),
(Source(1), r#"ls %h"#, r#"ls 'hover ed'"#),
(Source(2), r#"ls %h"#, r#"ls ''"#),
// Dirname
(Source(0), r#"cd %d"#, r#"cd t1 t1"#),
(Source(1), r#"cd %d"#, r#"cd 't 2' 't 2'"#),
(Source(1), r#"cd %d1 %d3 %d2"#, r#"cd 't 2' '' 't 2'"#),
(Source(2), r#"cd %d %d1"#, r#"cd ''"#),
// Yanked
(Source(0), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
(Source(1), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
(Source(2), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
// Tab
(Source(0), r#"ls %s %ts %s"#, r#"ls t1/s1 t1/s2 't 2/s 1' 't 2/s 2' t1/s1 t1/s2"#),
(Source(1), r#"ls %s1 %ts %s2"#, r#"ls 't 2/s 1' 't 2/s 2'"#),
(Source(1), r#"ls %s1 %Ts1 %s2 %Ts2"#, r#"ls 't 2/s 1' t1/s1 't 2/s 2' t1/s2"#),
(Source(0), r#"ls %s1 %Ts1 %s2 %Ts2"#, r#"ls t1/s1 '' t1/s2 ''"#),
(Source(0), r#"ls %ty"#, r#"ls y1 'y 2' y3"#),
(Source(0), r#"ls %Ty"#, r#"ls y1 'y 2' y3"#),
// Escape
(
Source(0),
r#"echo % %% %s2 %%h %d %%%y %%%%ts %%%%%ts1"#,
r#"echo % % t1/s2 %h t1 t1 %y1 'y 2' y3 %%ts %%'t 2/s 1'"#,
),
// TODO: remove
(Source(0), r#"ls %1 %* %2 %0 %3"#, r#"ls t1/s1 t1/s1 t1/s2 t1/s2 hovered ''"#),
];
for (src, cmd, expected) in cases {
let s = Splatter::new(src).splat(OsStr::new(cmd));
assert_eq!(s, OsStr::new(expected), "{cmd}");
}
}
}

View file

@ -1,15 +1,27 @@
use std::path::PathBuf; use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
use twox_hash::XxHash3_128; use twox_hash::XxHash3_128;
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}}; use yazi_shared::{scheme::SchemeRef, url::{AsUrl, Url, UrlBuf, UrlCow}};
use crate::Xdg; use crate::Xdg;
pub trait FsUrl { pub trait FsUrl<'a> {
fn cache(&self) -> Option<PathBuf>; fn cache(&self) -> Option<PathBuf>;
fn unified_path(self) -> Cow<'a, Path>;
fn unified_path_str(self) -> Cow<'a, OsStr>
where
Self: Sized,
{
match self.unified_path() {
Cow::Borrowed(p) => p.as_os_str().into(),
Cow::Owned(p) => p.into_os_string().into(),
}
}
} }
impl FsUrl for Url<'_> { impl<'a> FsUrl<'a> for Url<'a> {
fn cache(&self) -> Option<PathBuf> { fn cache(&self) -> Option<PathBuf> {
match self.scheme { match self.scheme {
SchemeRef::Regular | SchemeRef::Search(_) => None, SchemeRef::Regular | SchemeRef::Search(_) => None,
@ -25,12 +37,28 @@ impl FsUrl for Url<'_> {
), ),
} }
} }
fn unified_path(self) -> Cow<'a, Path> {
self.cache().map(Cow::Owned).unwrap_or_else(|| Cow::Borrowed(self.loc.as_path()))
}
} }
impl FsUrl for UrlBuf { impl<'a> FsUrl<'a> for UrlBuf {
fn cache(&self) -> Option<PathBuf> { self.as_url().cache() } fn cache(&self) -> Option<PathBuf> { self.as_url().cache() }
fn unified_path(self) -> Cow<'a, Path> {
self.cache().unwrap_or_else(|| self.loc.into_path()).into()
}
} }
impl FsUrl for UrlCow<'_> { impl<'a> FsUrl<'a> for UrlCow<'a> {
fn cache(&self) -> Option<PathBuf> { self.as_url().cache() } fn cache(&self) -> Option<PathBuf> { self.as_url().cache() }
fn unified_path(self) -> Cow<'a, Path> {
match (self.cache(), self) {
(None, UrlCow::Borrowed { loc, .. }) => loc.as_path().into(),
(None, UrlCow::Owned { loc, .. }) => loc.into_path().into(),
(Some(cache), _) => cache.into(),
}
}
} }

View file

@ -17,7 +17,7 @@ impl TryFrom<CmdCow> for NotifyOpt {
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!("Invalid 'option' in NotifyOpt")) c.take_any("opt").ok_or_else(|| anyhow!("Invalid 'opt' in NotifyOpt"))
} }
} }

View file

@ -16,7 +16,6 @@ yazi_macro::mod_flat!(
link link
open open
open_do open_do
open_with
paste paste
peek peek
quit quit

View file

@ -1,3 +1,4 @@
use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
@ -8,8 +9,16 @@ pub struct OpenDoOpt {
pub interactive: bool, pub interactive: bool,
} }
impl From<CmdCow> for OpenDoOpt { impl TryFrom<CmdCow> for OpenDoOpt {
fn from(mut c: CmdCow) -> Self { c.take_any("option").unwrap_or_default() } type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
opt
} else {
bail!("'opt' is required for OpenDoOpt");
}
}
} }
impl FromLua for OpenDoOpt { impl FromLua for OpenDoOpt {

View file

@ -1,29 +0,0 @@
use std::borrow::Cow;
use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_config::opener::OpenerRule;
use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
#[derive(Debug)]
pub struct OpenWithOpt {
pub opener: Cow<'static, OpenerRule>,
pub cwd: UrlBuf,
pub targets: Vec<UrlCow<'static>>,
}
impl TryFrom<CmdCow> for OpenWithOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in OpenWithOpt"))
}
}
impl FromLua for OpenWithOpt {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
}
impl IntoLua for OpenWithOpt {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
}

View file

@ -1,25 +1,28 @@
use std::{borrow::Cow, ffi::OsStr}; use std::ffi::OsString;
use anyhow::anyhow; use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yazi_config::opener::OpenerRule; use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
use yazi_shared::{event::CmdCow, url::UrlBuf};
// --- Exec // --- Exec
#[derive(Debug)] #[derive(Debug)]
pub struct ProcessOpenOpt { pub struct ProcessOpenOpt {
pub cwd: UrlBuf, pub cwd: UrlBuf,
pub opener: Cow<'static, OpenerRule>, pub cmd: OsString,
pub args: Vec<Cow<'static, OsStr>>, pub args: Vec<UrlCow<'static>>,
pub block: bool,
pub orphan: bool,
pub done: Option<oneshot::Sender<()>>, pub done: Option<oneshot::Sender<()>>,
pub spread: bool, // TODO: remove
} }
impl TryFrom<CmdCow> for ProcessOpenOpt { 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 ProcessOpenOpt")) c.take_any("opt").ok_or_else(|| anyhow!("Missing 'opt' in ProcessOpenOpt"))
} }
} }

View file

@ -3,7 +3,7 @@ use std::process::Stdio;
use anyhow::Result; use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
pub struct FdOpt { pub struct FdOpt {

View file

@ -3,7 +3,7 @@ use std::process::Stdio;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
pub struct RgOpt { pub struct RgOpt {

View file

@ -3,7 +3,7 @@ use std::process::Stdio;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
pub struct RgaOpt { pub struct RgaOpt {

View file

@ -4,7 +4,7 @@ use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};
use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef}; use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef};
use yazi_config::Pattern; use yazi_config::Pattern;
use yazi_fs::{mounts::PARTITIONS, provider::{DirReader, FileHolder}}; use yazi_fs::{mounts::PARTITIONS, provider::{DirReader, FileHolder}};
use yazi_shared::url::UrlCow; use yazi_shared::url::{UrlCow, UrlLike};
use yazi_vfs::{VfsFile, provider}; use yazi_vfs::{VfsFile, provider};
use crate::bindings::SizeCalculator; use crate::bindings::SizeCalculator;

View file

@ -3,6 +3,7 @@ use std::hash::Hash;
use mlua::{Function, Lua, Table}; use mlua::{Function, Lua, Table};
use yazi_binding::{FileRef, Url}; use yazi_binding::{FileRef, Url};
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_shared::url::UrlLike;
use super::Utils; use super::Utils;
use crate::Twox128; use crate::Twox128;

View file

@ -1,6 +1,7 @@
use mlua::{Function, IntoLuaMulti, Lua, Value}; use mlua::{Function, IntoLuaMulti, Lua, Value};
use yazi_adapter::{ADAPTOR, Image}; use yazi_adapter::{ADAPTOR, Image};
use yazi_binding::{Error, UrlRef, elements::Rect}; use yazi_binding::{Error, UrlRef, elements::Rect};
use yazi_shared::url::UrlLike;
use super::Utils; use super::Utils;
use crate::bindings::ImageInfo; use crate::bindings::ImageInfo;

View file

@ -3,7 +3,7 @@ use yazi_binding::{Error, elements::{Area, Renderable, Text}};
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_parser::mgr::{PreviewLock, UpdatePeekedOpt}; use yazi_parser::mgr::{PreviewLock, UpdatePeekedOpt};
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
use yazi_shared::errors::PeekError; use yazi_shared::{errors::PeekError, url::UrlLike};
use super::Utils; use super::Utils;
use crate::external::Highlighter; use crate::external::Highlighter;

View file

@ -18,7 +18,7 @@ impl AppProxy {
} }
pub fn notify(opt: NotifyOpt) { pub fn notify(opt: NotifyOpt) {
emit!(Call(relay!(app:notify).with_any("option", opt))); emit!(Call(relay!(app:notify).with_any("opt", opt)));
} }
pub fn update_notify(dur: Duration) { pub fn update_notify(dur: Duration) {

View file

@ -20,7 +20,7 @@ impl MgrProxy {
} }
pub fn open_do(opt: OpenDoOpt) { pub fn open_do(opt: OpenDoOpt) {
emit!(Call(relay!(mgr:open_do).with_any("option", opt))); emit!(Call(relay!(mgr:open_do).with_any("opt", opt)));
} }
pub fn remove_do(targets: Vec<UrlBuf>, permanently: bool) { pub fn remove_do(targets: Vec<UrlBuf>, permanently: bool) {

View file

@ -1,29 +1,34 @@
use std::{borrow::Cow, ffi::OsStr}; use std::ffi::OsString;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yazi_config::opener::OpenerRule;
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
use yazi_parser::{mgr::OpenWithOpt, tasks::ProcessOpenOpt}; use yazi_parser::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 file_open(opener: Cow<'static, OpenerRule>, cwd: UrlBuf, targets: Vec<UrlCow<'static>>) { // TODO: remove
emit!(Call(relay!(tasks:file_open).with_any("option", OpenWithOpt { opener, cwd, targets }))); pub fn open_shell_compat(opt: ProcessOpenOpt) {
emit!(Call(relay!(tasks:open_shell_compat).with_any("opt", opt)));
} }
pub async fn process_exec( pub async fn process_exec(
opener: Cow<'static, OpenerRule>,
cwd: UrlBuf, cwd: UrlBuf,
args: Vec<Cow<'static, OsStr>>, cmd: OsString,
args: Vec<UrlCow<'static>>,
block: bool,
orphan: bool,
) { ) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
emit!(Call(relay!(tasks:process_open).with_any("option", ProcessOpenOpt { emit!(Call(relay!(tasks:process_open).with_any("opt", ProcessOpenOpt {
cwd, cwd,
opener, cmd,
args, args,
done: Some(tx) block,
orphan,
done: Some(tx),
spread: false
}))); })));
rx.await.ok(); rx.await.ok();
} }

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::{AsUrl, UrlBuf, UrlCow}; use yazi_shared::url::{AsUrl, UrlBuf, UrlCow, UrlLike};
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};

View file

@ -10,7 +10,7 @@ use tracing::error;
use yazi_config::Priority; use yazi_config::Priority;
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_plugin::isolate; use yazi_plugin::isolate;
use yazi_shared::{event::CmdCow, url::UrlBuf}; use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlLike}};
use yazi_vfs::provider; use yazi_vfs::provider;
use super::{PreworkInFetch, PreworkInLoad, PreworkInSize}; use super::{PreworkInFetch, PreworkInLoad, PreworkInSize};

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}}; use std::ffi::OsString;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_shared::{Id, url::UrlBuf}; use yazi_shared::{Id, url::{UrlBuf, UrlCow}};
use super::ShellOpt; use super::ShellOpt;
@ -11,7 +11,7 @@ pub(crate) struct ProcessInBlock {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<Cow<'static, OsStr>>, pub(crate) args: Vec<UrlCow<'static>>,
} }
impl From<ProcessInBlock> for ShellOpt { impl From<ProcessInBlock> for ShellOpt {
@ -26,7 +26,7 @@ pub(crate) struct ProcessInOrphan {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<Cow<'static, OsStr>>, pub(crate) args: Vec<UrlCow<'static>>,
} }
impl From<ProcessInOrphan> for ShellOpt { impl From<ProcessInOrphan> for ShellOpt {
@ -41,7 +41,7 @@ pub(crate) struct ProcessInBg {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<Cow<'static, OsStr>>, pub(crate) args: Vec<UrlCow<'static>>,
pub(crate) cancel: mpsc::Receiver<()>, pub(crate) cancel: mpsc::Receiver<()>,
} }

View file

@ -1,14 +1,14 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}, process::Stdio}; use std::{borrow::Cow, ffi::OsString, process::Stdio};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use tokio::process::{Child, Command}; use tokio::process::{Child, Command};
use yazi_fs::FsUrl; use yazi_fs::FsUrl;
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlCow, UrlLike};
pub(crate) struct ShellOpt { pub(crate) struct ShellOpt {
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<Cow<'static, OsStr>>, pub(crate) args: Vec<UrlCow<'static>>,
pub(crate) piped: bool, pub(crate) piped: bool,
pub(crate) orphan: bool, pub(crate) orphan: bool,
} }
@ -39,13 +39,17 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result<Child> {
#[cfg(unix)] #[cfg(unix)]
return Ok(unsafe { return Ok(unsafe {
use yazi_shared::url::AsUrl;
Command::new("sh") Command::new("sh")
.arg("-c")
.stdin(opt.stdio()) .stdin(opt.stdio())
.stdout(opt.stdio()) .stdout(opt.stdio())
.stderr(opt.stdio()) .stderr(opt.stdio())
.arg("-c")
.arg(opt.cmd) .arg(opt.cmd)
.args(opt.args) .arg("--")
// TODO: remove
.args(opt.args.iter().skip(1).map(|u| u.as_url().unified_path_str()))
.current_dir(cwd) .current_dir(cwd)
.kill_on_drop(!opt.orphan) .kill_on_drop(!opt.orphan)
.pre_exec(move || { .pre_exec(move || {
@ -60,11 +64,11 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result<Child> {
#[cfg(windows)] #[cfg(windows)]
return Ok( return Ok(
Command::new("cmd.exe") Command::new("cmd.exe")
.raw_arg("/C")
.raw_arg(parser::parse(&opt.cmd, &opt.args))
.stdin(opt.stdio()) .stdin(opt.stdio())
.stdout(opt.stdio()) .stdout(opt.stdio())
.stderr(opt.stdio()) .stderr(opt.stdio())
.raw_arg("/C")
.raw_arg(opt.cmd)
.current_dir(cwd) .current_dir(cwd)
.kill_on_drop(!opt.orphan) .kill_on_drop(!opt.orphan)
.spawn()?, .spawn()?,
@ -72,176 +76,3 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result<Child> {
}) })
.await? .await?
} }
#[cfg(windows)]
mod parser {
use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::Peekable, os::windows::ffi::{EncodeWide, OsStrExt, OsStringExt}};
macro_rules! w {
($c:literal) => {
$c as u16
};
}
pub(super) fn parse(cmd: &OsStr, args: &[Cow<OsStr>]) -> OsString {
if cmd.len() < 2 {
return cmd.to_owned();
}
let mut buf = Vec::with_capacity(cmd.len());
let mut it = cmd.encode_wide().peekable();
while let Some(c) = it.next() {
if c == w!('%') {
let n = visit_percent(it.clone(), &mut buf, &args, false);
if n == 0 {
buf.push(c);
} else {
it.by_ref().take(n).for_each(drop);
}
} else if c == w!('"') && it.peek().is_some_and(|&c| c == w!('%')) {
it.next();
let n = visit_percent(it.clone(), &mut buf, &args, true);
if n == 0 {
buf.push(c);
buf.push(w!('%'));
} else {
it.by_ref().take(n).for_each(drop);
}
} else {
buf.push(c);
}
}
OsString::from_wide(&buf)
}
fn visit_percent(
mut it: Peekable<EncodeWide>,
buf: &mut Vec<u16>,
args: &[Cow<OsStr>],
quote: bool,
) -> usize {
let Some(c) = it.next().and_then(|c| char::from_u32(c as _)) else {
return 0;
};
let mut pos = None;
if c.is_ascii_digit() {
let mut p = c.to_string();
while let Some(n) = it.peek().and_then(|&c| char::from_u32(c as _)) {
if n.is_ascii_digit() {
it.next();
p.push(n);
} else {
break;
}
}
pos = Some(p);
}
if quote && !it.next().is_some_and(|e| e == w!('"')) {
return 0;
}
if let Some(p) = pos {
if let Some(arg) = args.get(p.parse::<usize>().unwrap()) {
if quote {
buf.extend(yazi_shared::shell::escape_os_str(arg).encode_wide());
} else {
buf.extend(arg.encode_wide());
}
}
return p.len() + quote as usize;
}
if c != '*' && c != '@' {
return 0;
}
let mut s = OsString::new();
for (i, arg) in args.iter().skip(1).enumerate() {
if i > 0 {
s.push(" ");
}
if c == '*' {
s.push(yazi_shared::shell::escape_os_str(arg));
} else {
s.push(arg);
}
}
if quote {
buf.extend(yazi_shared::shell::escape_os_str(&s).encode_wide());
} else {
buf.extend(s.encode_wide());
}
1 + quote as usize
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
fn parse(cmd: &str, args: &[&str]) -> String {
let cmd = OsString::from(cmd);
let args: Vec<_> = args.iter().map(|&s| OsString::from(s).into()).collect();
super::parse(&cmd, &args).to_str().unwrap().to_owned()
}
#[test]
fn test_no_quote() {
let s = parse("echo abc xyz %0 %2", &["000", "111", "222"]);
assert_eq!(s, "echo abc xyz 000 222");
let s = parse(" echo abc xyz %1 %2 ", &["", "111", "222"]);
assert_eq!(s, " echo abc xyz 111 222 ");
}
#[test]
fn test_single_quote() {
let s = parse("echo 'abc xyz' '%1' %2", &["000", "111", "222"]);
assert_eq!(s, "echo 'abc xyz' '111' 222");
let s = parse(r#"echo 'abc ""xyz' '%1' %2"#, &["", "111", "222"]);
assert_eq!(s, r#"echo 'abc ""xyz' '111' 222"#);
}
#[test]
fn test_double_quote() {
let s = parse(r#"echo "abc ' 'xyz" "%1" %2 %3"#, &["", "111", "222"]);
assert_eq!(s, r#"echo "abc ' 'xyz" 111 222 "#);
}
#[test]
fn test_escaped() {
let s = parse(r#"echo "a bc ' 'x\nyz" "\%1" "\"%2"" %3"#, &["", "111", "22 2"]);
assert_eq!(s, r#"echo "a bc ' 'x\nyz" "\111" "\"22 2"" "#);
}
#[test]
fn test_percent_star() {
let s = parse("echo %* xyz", &[]);
assert_eq!(s, "echo xyz");
let s = parse("echo %* xyz", &["000", "111", "222"]);
assert_eq!(s, "echo 111 222 xyz");
let s = parse("echo '%*' xyz", &["000", "111", "22 2"]);
assert_eq!(s, r#"echo '111 "22 2"' xyz"#);
let s = parse("echo -C%* xyz", &[]);
assert_eq!(s, "echo -C xyz");
let s = parse("echo -C%* xyz", &["000", " 111", "222"]);
assert_eq!(s, r#"echo -C" 111" 222 xyz"#);
}
#[test]
fn test_env_var() {
let s = parse(r#"%EDITOR% %@ "%@" %* "%*" xyz"#, &["000", "1 11", "222"]);
assert_eq!(s, r#"%EDITOR% 1 11 222 "1 11 222" "1 11" 222 "\"1 11\" 222" xyz"#);
}
}
}

View file

@ -1,4 +1,4 @@
use std::{ffi::OsString, future::Future, sync::Arc, time::Duration}; use std::{future::Future, sync::Arc, time::Duration};
use anyhow::Result; use anyhow::Result;
use futures::{FutureExt, future::BoxFuture}; use futures::{FutureExt, future::BoxFuture};
@ -9,7 +9,7 @@ use yazi_dds::Pump;
use yazi_fs::FsUrl; use yazi_fs::FsUrl;
use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt}; 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, UrlLike}};
use yazi_vfs::{must_be_dir, provider, unique_name}; use yazi_vfs::{must_be_dir, provider, unique_name};
use super::{Ongoing, TaskOp}; use super::{Ongoing, TaskOp};
@ -306,20 +306,20 @@ impl Scheduler {
} }
} }
pub fn process_open(&self, ProcessOpenOpt { cwd, opener, args, done }: ProcessOpenOpt) { pub fn process_open(&self, opt: ProcessOpenOpt) {
let name = { let name = {
let args = args.iter().map(|a| a.to_string_lossy()).collect::<Vec<_>>().join(" "); let args = opt.args.iter().map(|a| a.display().to_string()).collect::<Vec<_>>().join(" ");
if args.is_empty() { if args.is_empty() {
format!("Run {:?}", opener.run) format!("Run {:?}", opt.cmd)
} else { } else {
format!("Run {:?} with `{args}`", opener.run) format!("Run {:?} with `{args}`", opt.cmd)
} }
}; };
let mut ongoing = self.ongoing.lock(); let mut ongoing = self.ongoing.lock();
let (id, clean): (_, TaskOut) = if opener.block { let (id, clean): (_, TaskOut) = if opt.block {
(ongoing.add::<ProcessProgBlock>(name), ProcessOutBlock::Clean.into()) (ongoing.add::<ProcessProgBlock>(name), ProcessOutBlock::Clean.into())
} else if opener.orphan { } else if opt.orphan {
(ongoing.add::<ProcessProgOrphan>(name), ProcessOutOrphan::Clean.into()) (ongoing.add::<ProcessProgOrphan>(name), ProcessOutOrphan::Clean.into())
} else { } else {
(ongoing.add::<ProcessProgBg>(name), ProcessOutBg::Clean.into()) (ongoing.add::<ProcessProgBg>(name), ProcessOutBg::Clean.into())
@ -332,21 +332,22 @@ impl Scheduler {
cancel_tx.send(()).await.ok(); cancel_tx.send(()).await.ok();
cancel_tx.closed().await; cancel_tx.closed().await;
} }
if let Some(tx) = done { if let Some(tx) = opt.done {
tx.send(()).ok(); tx.send(()).ok();
} }
ops.out(id, clean); ops.out(id, clean);
}); });
let cmd = OsString::from(&opener.run);
let process = self.process.clone(); let process = self.process.clone();
self.send_micro::<_, TaskOut>(id, NORMAL, async move { self.send_micro::<_, TaskOut>(id, NORMAL, async move {
if opener.block { if opt.block {
process.block(ProcessInBlock { id, cwd, cmd, args }).await?; process.block(ProcessInBlock { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }).await?;
} else if opener.orphan { } else if opt.orphan {
process.orphan(ProcessInOrphan { id, cwd, cmd, args }).await?; process.orphan(ProcessInOrphan { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }).await?;
} else { } else {
process.bg(ProcessInBg { id, cwd, cmd, args, cancel: cancel_rx }).await?; process
.bg(ProcessInBg { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args, cancel: cancel_rx })
.await?;
} }
Ok(()) Ok(())
}); });

View file

@ -189,7 +189,7 @@ impl LocBuf {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::url::UrlBuf; use crate::url::{UrlBuf, UrlLike};
#[test] #[test]
fn test_new() { fn test_new() {

View file

@ -204,6 +204,11 @@ impl<'a> Loc<'a> {
} }
} }
#[inline]
pub fn strip_prefix(self, base: impl AsRef<Path>) -> Option<&'a Path> {
self.inner.strip_prefix(base).ok()
}
#[inline] #[inline]
pub fn bytes(self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() } pub fn bytes(self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() }
} }

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}}; use std::{borrow::Cow, ffi::{OsStr, OsString}};
use crate::url::UrlBuf; use crate::url::{UrlBuf, UrlLike};
pub trait IntoStringLossy { pub trait IntoStringLossy {
fn into_string_lossy(self) -> String; fn into_string_lossy(self) -> String;
@ -33,5 +33,6 @@ impl IntoStringLossy for Cow<'_, OsStr> {
} }
impl IntoStringLossy for &UrlBuf { impl IntoStringLossy for &UrlBuf {
// FIXME: remove
fn into_string_lossy(self) -> String { self.os_str().into_string_lossy() } fn into_string_lossy(self) -> String { self.os_str().into_string_lossy() }
} }

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::{AsUrl, Components, Display, Encode, EncodeTilded, Uri, Url, UrlCow, Urn}}; use crate::{loc::LocBuf, pool::Pool, scheme::Scheme, url::{AsUrl, Encode, EncodeTilded, Url, UrlCow}};
#[derive(Clone, Default, Eq, Hash, PartialEq)] #[derive(Clone, Default, Eq, Hash, PartialEq)]
pub struct UrlBuf { pub struct UrlBuf {
@ -89,67 +89,6 @@ impl UrlBuf {
// }; &U // }; &U
} }
#[inline]
pub fn join(&self, path: impl AsRef<Path>) -> Self { self.as_url().join(path) }
#[inline]
pub fn components(&self) -> Components<'_> { Components::from(self) }
#[inline]
pub fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() }
#[inline]
pub fn display(&self) -> Display<'_> { Display::new(self) }
#[inline]
pub fn covariant(&self, other: &Self) -> bool { self.as_url().covariant(other) }
#[inline]
pub fn parent(&self) -> Option<Url<'_>> { self.as_url().parent() }
#[inline]
pub fn starts_with(&self, base: impl AsUrl) -> bool { self.as_url().starts_with(base) }
#[inline]
pub fn ends_with(&self, child: impl AsUrl) -> bool { self.as_url().ends_with(child) }
pub fn strip_prefix(&self, base: impl AsUrl) -> Option<&Urn> {
use Scheme as S;
use SchemeRef as T;
let base = base.as_url();
let prefix = self.loc.strip_prefix(base.loc).ok()?;
Some(Urn::new(match (&self.scheme, base.scheme) {
// Same scheme
(S::Regular, T::Regular) => Some(prefix),
(S::Search(_), T::Search(_)) => Some(prefix),
(S::Archive(a), T::Archive(b)) => Some(prefix).filter(|_| a == b),
(S::Sftp(a), T::Sftp(b)) => Some(prefix).filter(|_| a == b),
// Both are local files
(S::Regular, T::Search(_)) => Some(prefix),
(S::Search(_), T::Regular) => Some(prefix),
// Only the entry of archives is a local file
(S::Regular, T::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()),
(S::Search(_), T::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()),
(S::Archive(_), T::Regular) => Some(prefix).filter(|_| self.uri().is_empty()),
(S::Archive(_), T::Search(_)) => Some(prefix).filter(|_| self.uri().is_empty()),
// Independent virtual file space
(S::Regular, T::Sftp(_)) => None,
(S::Search(_), T::Sftp(_)) => None,
(S::Archive(_), T::Sftp(_)) => None,
(S::Sftp(_), T::Regular) => None,
(S::Sftp(_), T::Search(_)) => None,
(S::Sftp(_), T::Archive(_)) => None,
}?))
}
#[inline]
pub fn as_path(&self) -> Option<&Path> { self.as_url().as_path() }
#[inline] #[inline]
pub fn into_path(self) -> Option<PathBuf> { pub fn into_path(self) -> Option<PathBuf> {
Some(self.loc.into_path()).filter(|_| !self.scheme.is_virtual()) Some(self.loc.into_path()).filter(|_| !self.scheme.is_virtual())
@ -163,21 +102,10 @@ impl UrlBuf {
Self { loc: self.loc.rebase(base), scheme: self.scheme.clone() } Self { loc: self.loc.rebase(base), scheme: self.scheme.clone() }
} }
#[inline]
pub fn pair(&self) -> Option<(Url<'_>, &Urn)> { self.as_url().pair() }
#[inline] #[inline]
pub fn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self) } pub fn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self) }
} }
impl UrlBuf {
#[inline]
pub fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_ref() } }
#[inline]
pub fn base(&self) -> Option<Url<'_>> { self.as_url().base() }
}
impl UrlBuf { impl UrlBuf {
// --- Regular // --- Regular
#[inline] #[inline]
@ -225,34 +153,6 @@ impl UrlBuf {
Scheme::Archive(_) => false, Scheme::Archive(_) => false,
} }
} }
// FIXME: remove
#[inline]
pub fn into_path2(self) -> PathBuf { self.loc.into_path() }
#[inline]
pub fn name(&self) -> Option<&OsStr> { self.as_url().name() }
#[inline]
pub fn stem(&self) -> Option<&OsStr> { self.as_url().stem() }
#[inline]
pub fn ext(&self) -> Option<&OsStr> { self.as_url().ext() }
#[inline]
pub fn uri(&self) -> &Uri { self.as_url().uri() }
#[inline]
pub fn urn(&self) -> &Urn { self.as_url().urn() }
#[inline]
pub fn is_absolute(&self) -> bool { self.as_url().is_absolute() }
#[inline]
pub fn has_root(&self) -> bool { self.as_url().has_root() }
#[inline]
pub fn has_trail(&self) -> bool { self.as_url().has_trail() }
} }
impl Debug for UrlBuf { impl Debug for UrlBuf {
@ -286,6 +186,7 @@ mod tests {
use anyhow::Result; use anyhow::Result;
use super::*; use super::*;
use crate::url::UrlLike;
#[test] #[test]
fn test_join() -> anyhow::Result<()> { fn test_join() -> anyhow::Result<()> {

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not, path::{self, PathBuf, PrefixComponent}}; use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not, path::{self, PathBuf, PrefixComponent}};
use crate::{loc::Loc, scheme::{Scheme, SchemeRef}, url::{Encode, Url, UrlBuf, UrlCow}}; use crate::{scheme::{Scheme, SchemeRef}, url::{Encode, Url, UrlBuf}};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Component<'a> { pub enum Component<'a> {
@ -60,8 +60,7 @@ impl<'a> FromIterator<Component<'a>> for PathBuf {
#[derive(Clone)] #[derive(Clone)]
pub struct Components<'a> { pub struct Components<'a> {
inner: path::Components<'a>, inner: path::Components<'a>,
loc: Loc<'a>, url: Url<'a>,
scheme: SchemeRef<'a>,
scheme_yielded: bool, scheme_yielded: bool,
} }
@ -69,36 +68,20 @@ impl<'a> From<Url<'a>> for Components<'a> {
fn from(value: Url<'a>) -> Self { fn from(value: Url<'a>) -> Self {
Self { Self {
inner: value.loc.as_path().components(), inner: value.loc.as_path().components(),
loc: value.loc, url: value,
scheme: value.scheme,
scheme_yielded: false, 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.as_ref(),
scheme_yielded: false,
}
}
}
impl<'a> From<&'a UrlCow<'a>> for Components<'a> {
fn from(value: &'a UrlCow<'a>) -> Self { Self::from(value.as_url()) }
}
impl<'a> Components<'a> { impl<'a> Components<'a> {
pub fn os_str(&self) -> Cow<'a, OsStr> { pub fn os_str(&self) -> Cow<'a, OsStr> {
let path = self.inner.as_path(); let path = self.inner.as_path();
if !self.scheme.is_virtual() || self.scheme_yielded { if !self.url.scheme.is_virtual() || self.scheme_yielded {
return path.as_os_str().into(); return path.as_os_str().into();
} }
let mut s = OsString::from(Encode::new(self.loc, self.scheme).to_string()); let mut s = OsString::from(Encode::from(self.url).to_string());
s.reserve_exact(path.as_os_str().len()); s.reserve_exact(path.as_os_str().len());
s.push(path); s.push(path);
s.into() s.into()
@ -107,7 +90,7 @@ impl<'a> Components<'a> {
pub fn covariant(&self, other: &Self) -> bool { pub fn covariant(&self, other: &Self) -> bool {
match (self.scheme_yielded, other.scheme_yielded) { match (self.scheme_yielded, other.scheme_yielded) {
(false, false) => {} (false, false) => {}
(true, true) if self.scheme.covariant(other.scheme) => {} (true, true) if self.url.scheme.covariant(other.url.scheme) => {}
_ => return false, _ => return false,
} }
self.inner == other.inner self.inner == other.inner
@ -120,7 +103,7 @@ impl<'a> Iterator for Components<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if !self.scheme_yielded { if !self.scheme_yielded {
self.scheme_yielded = true; self.scheme_yielded = true;
Some(Component::Scheme(self.scheme)) Some(Component::Scheme(self.url.scheme))
} else { } else {
self.inner.next().map(Into::into) self.inner.next().map(Into::into)
} }
@ -140,7 +123,7 @@ impl<'a> DoubleEndedIterator for Components<'a> {
Some(comp.into()) Some(comp.into())
} else if !self.scheme_yielded { } else if !self.scheme_yielded {
self.scheme_yielded = true; self.scheme_yielded = true;
Some(Component::Scheme(self.scheme)) Some(Component::Scheme(self.url.scheme))
} else { } else {
None None
} }
@ -151,8 +134,8 @@ impl<'a> FusedIterator for Components<'a> {}
impl<'a> PartialEq for Components<'a> { impl<'a> PartialEq for Components<'a> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
Some(self.scheme).filter(|_| !self.scheme_yielded) Some(self.url.scheme).filter(|_| !self.scheme_yielded)
== Some(other.scheme).filter(|_| !other.scheme_yielded) == Some(other.url.scheme).filter(|_| !other.scheme_yielded)
&& self.inner == other.inner && self.inner == other.inner
} }
} }
@ -163,7 +146,7 @@ mod tests {
use std::path::Path; use std::path::Path;
use super::*; use super::*;
use crate::pool::InternStr; use crate::{pool::InternStr, url::UrlLike};
#[test] #[test]
fn test_collect() { fn test_collect() {

View file

@ -3,7 +3,7 @@ use std::{hash::{Hash, Hasher}, ops::Deref, path::PathBuf};
use hashbrown::Equivalent; use hashbrown::Equivalent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::url::{Url, UrlBuf, UrlCow}; use crate::url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct UrlCov<'a>(Url<'a>); pub struct UrlCov<'a>(Url<'a>);
@ -19,7 +19,7 @@ impl<'a> From<&'a UrlBufCov> for UrlCov<'a> {
} }
impl PartialEq<UrlBufCov> for UrlCov<'_> { impl PartialEq<UrlBufCov> for UrlCov<'_> {
fn eq(&self, other: &UrlBufCov) -> bool { self.0.covariant(other.0.as_url()) } fn eq(&self, other: &UrlBufCov) -> bool { self.0.covariant(&other.0) }
} }
impl Hash for UrlCov<'_> { impl Hash for UrlCov<'_> {
@ -79,21 +79,13 @@ impl<'a> From<&'a UrlBufCov> for Url<'a> {
} }
impl Hash for UrlBufCov { impl Hash for UrlBufCov {
fn hash<H: Hasher>(&self, state: &mut H) { self.as_url().hash(state) } fn hash<H: Hasher>(&self, state: &mut H) { UrlCov::from(self).hash(state) }
} }
impl PartialEq for UrlBufCov { impl PartialEq for UrlBufCov {
fn eq(&self, other: &Self) -> bool { self.covariant(other) } fn eq(&self, other: &Self) -> bool { self.covariant(&other.0) }
} }
impl PartialEq<UrlBuf> for UrlBufCov { impl PartialEq<UrlBuf> for UrlBufCov {
fn eq(&self, other: &UrlBuf) -> bool { self.covariant(other) } fn eq(&self, other: &UrlBuf) -> bool { self.covariant(other) }
} }
impl UrlBufCov {
#[inline]
pub fn as_url(&self) -> UrlCov<'_> { UrlCov::from(self) }
#[inline]
pub fn parent(&self) -> Option<Self> { self.0.parent().map(Into::into) }
}

View file

@ -1,9 +1,9 @@
use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; use std::{borrow::Cow, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use percent_encoding::percent_decode; use percent_encoding::percent_decode;
use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{SchemeCow, SchemeRef}, url::{Components, Url, UrlBuf, Urn}}; use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{SchemeCow, SchemeRef}, url::{AsUrl, Url, UrlBuf}};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum UrlCow<'a> { pub enum UrlCow<'a> {
@ -113,14 +113,6 @@ impl<'a> UrlCow<'a> {
} }
} }
#[inline]
pub fn as_url(&self) -> Url<'_> {
match self {
UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_ref() },
UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_ref() },
}
}
#[inline] #[inline]
pub fn into_owned(self) -> UrlBuf { pub fn into_owned(self) -> UrlBuf {
match self { match self {
@ -137,24 +129,9 @@ impl<'a> UrlCow<'a> {
} }
} }
// FIXME: remove
#[inline]
pub fn into_os_str2(self) -> Cow<'a, OsStr> {
match self {
UrlCow::Borrowed { loc, .. } => loc.as_path().as_os_str().into(),
UrlCow::Owned { loc, .. } => loc.into_path().into_os_string().into(),
}
}
#[inline] #[inline]
pub fn to_owned(&self) -> UrlBuf { self.as_url().into() } pub fn to_owned(&self) -> UrlBuf { self.as_url().into() }
#[inline]
pub fn parent(&self) -> Option<Url<'_>> { self.as_url().parent() }
#[inline]
pub fn pair(&self) -> Option<(Url<'_>, &Urn)> { self.as_url().pair() }
pub fn parse(bytes: &[u8]) -> Result<(SchemeCow<'_>, Cow<'_, Path>, Option<(usize, usize)>)> { pub fn parse(bytes: &[u8]) -> Result<(SchemeCow<'_>, Cow<'_, Path>, Option<(usize, usize)>)> {
let mut skip = 0; let mut skip = 0;
let (scheme, tilde, uri, urn) = SchemeCow::parse(bytes, &mut skip)?; let (scheme, tilde, uri, urn) = SchemeCow::parse(bytes, &mut skip)?;
@ -179,13 +156,4 @@ impl<'a> UrlCow<'a> {
impl UrlCow<'_> { impl UrlCow<'_> {
#[inline] #[inline]
pub fn is_regular(&self) -> bool { self.as_url().is_regular() } 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

@ -1,17 +1,16 @@
use crate::url::{Encode, UrlBuf}; use crate::url::{Encode, Url};
pub struct Display<'a> { pub struct Display<'a> {
inner: &'a UrlBuf, inner: Url<'a>,
} }
impl<'a> Display<'a> { impl<'a> From<Url<'a>> for Display<'a> {
#[inline] fn from(value: Url<'a>) -> Self { Self { inner: value } }
pub fn new(inner: &'a UrlBuf) -> Self { Self { inner } }
} }
impl<'a> std::fmt::Display for Display<'a> { impl<'a> std::fmt::Display for Display<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let UrlBuf { loc, scheme } = self.inner; let Url { loc, scheme } = self.inner;
if scheme.is_virtual() { if scheme.is_virtual() {
Encode::from(self.inner).fmt(f)?; Encode::from(self.inner).fmt(f)?;
} }

View file

@ -2,40 +2,35 @@ use std::{fmt::{self, Display}, ops::Not};
use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode}; use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode};
use crate::{loc::Loc, scheme::SchemeRef, url::{Url, UrlBuf}}; use crate::{scheme::SchemeRef, url::{AsUrl, Url, UrlBuf}};
pub struct Encode<'a> { #[derive(Clone, Copy)]
loc: Loc<'a>, pub struct Encode<'a>(Url<'a>);
scheme: SchemeRef<'a>,
}
impl<'a> From<Url<'a>> for Encode<'a> { impl<'a> From<Url<'a>> for Encode<'a> {
fn from(url: Url<'a>) -> Self { Self::new(url.loc, url.scheme) } fn from(value: Url<'a>) -> Self { Self(value) }
} }
impl<'a> From<&'a UrlBuf> for Encode<'a> { impl<'a> From<&'a UrlBuf> for Encode<'a> {
fn from(url: &'a UrlBuf) -> Self { Self::new(url.loc.as_loc(), url.scheme.as_ref()) } fn from(value: &'a UrlBuf) -> Self { Self(value.as_url()) }
} }
impl<'a> Encode<'a> { impl<'a> Encode<'a> {
#[inline]
pub(super) fn new(loc: Loc<'a>, scheme: SchemeRef<'a>) -> Self { Self { loc, scheme } }
#[inline] #[inline]
pub fn domain<'s>(s: &'s str) -> PercentEncode<'s> { pub fn domain<'s>(s: &'s str) -> PercentEncode<'s> {
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':'); const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':');
percent_encode(s.as_bytes(), SET) percent_encode(s.as_bytes(), SET)
} }
fn ports(&self) -> impl Display { fn ports(self) -> impl Display {
struct D<'a>(&'a Encode<'a>); struct D<'a>(Encode<'a>);
impl Display for D<'_> { impl Display for D<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
macro_rules! w { macro_rules! w {
($default_uri:expr, $default_urn:expr) => {{ ($default_uri:expr, $default_urn:expr) => {{
let uri = self.0.loc.uri().count(); let uri = self.0.0.loc.uri().count();
let urn = self.0.loc.urn().count(); let urn = self.0.0.loc.urn().count();
match (uri != $default_uri, urn != $default_urn) { match (uri != $default_uri, urn != $default_urn) {
(true, true) => write!(f, ":{uri}:{urn}"), (true, true) => write!(f, ":{uri}:{urn}"),
(true, false) => write!(f, ":{uri}"), (true, false) => write!(f, ":{uri}"),
@ -45,12 +40,12 @@ impl<'a> Encode<'a> {
}}; }};
} }
match self.0.scheme { match self.0.0.scheme {
SchemeRef::Regular => Ok(()), SchemeRef::Regular => Ok(()),
SchemeRef::Search(_) | SchemeRef::Archive(_) => w!(0, 0), SchemeRef::Search(_) | SchemeRef::Archive(_) => w!(0, 0),
SchemeRef::Sftp(_) => w!( SchemeRef::Sftp(_) => w!(
self.0.loc.as_os_str().is_empty().not() as usize, self.0.0.loc.as_os_str().is_empty().not() as usize,
self.0.loc.file_name().is_some() as usize self.0.0.loc.file_name().is_some() as usize
), ),
} }
} }
@ -63,7 +58,7 @@ impl<'a> Encode<'a> {
impl Display for Encode<'_> { impl Display for Encode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use SchemeRef as S; use SchemeRef as S;
match self.scheme { match self.0.scheme {
S::Regular => write!(f, "regular://"), S::Regular => write!(f, "regular://"),
S::Search(d) => write!(f, "search://{}{}/", Self::domain(d), self.ports()), S::Search(d) => write!(f, "search://{}{}/", Self::domain(d), self.ports()),
S::Archive(d) => write!(f, "archive://{}{}/", Self::domain(d), self.ports()), S::Archive(d) => write!(f, "archive://{}{}/", Self::domain(d), self.ports()),
@ -73,17 +68,19 @@ impl Display for Encode<'_> {
} }
// --- Tilded // --- Tilded
pub struct EncodeTilded<'a> { #[derive(Clone, Copy)]
loc: Loc<'a>, pub struct EncodeTilded<'a>(Url<'a>);
scheme: SchemeRef<'a>,
impl<'a> From<Url<'a>> for EncodeTilded<'a> {
fn from(value: Url<'a>) -> Self { Self(value) }
} }
impl<'a> From<&'a UrlBuf> for EncodeTilded<'a> { impl<'a> From<&'a UrlBuf> for EncodeTilded<'a> {
fn from(url: &'a UrlBuf) -> Self { Self { loc: url.loc.as_loc(), scheme: url.scheme.as_ref() } } fn from(value: &'a UrlBuf) -> Self { Self(value.as_url()) }
} }
impl<'a> From<&EncodeTilded<'a>> for Encode<'a> { impl<'a> From<EncodeTilded<'a>> for Encode<'a> {
fn from(value: &EncodeTilded<'a>) -> Self { Self::new(value.loc, value.scheme) } fn from(value: EncodeTilded<'a>) -> Self { Self(value.0) }
} }
impl Display for EncodeTilded<'_> { impl Display for EncodeTilded<'_> {
@ -91,14 +88,14 @@ impl Display for EncodeTilded<'_> {
use Encode as E; use Encode as E;
use SchemeRef as S; use SchemeRef as S;
let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), CONTROLS); let loc = percent_encode(self.0.loc.as_os_str().as_encoded_bytes(), CONTROLS);
match self.scheme { match self.0.scheme {
S::Regular => write!(f, "regular~://{loc}"), S::Regular => write!(f, "regular~://{loc}"),
S::Search(d) => write!(f, "search~://{}{}/{loc}", E::domain(d), E::ports(&self.into())), S::Search(d) => write!(f, "search~://{}{}/{loc}", E::domain(d), E::ports((*self).into())),
S::Archive(d) => { S::Archive(d) => {
write!(f, "archive~://{}{}/{loc}", E::domain(d), E::ports(&self.into())) write!(f, "archive~://{}{}/{loc}", E::domain(d), E::ports((*self).into()))
} }
S::Sftp(d) => write!(f, "sftp~://{}{}/{loc}", E::domain(d), E::ports(&self.into())), S::Sftp(d) => write!(f, "sftp~://{}{}/{loc}", E::domain(d), E::ports((*self).into())),
} }
} }
} }

View file

@ -1,5 +1,8 @@
use crate::url::{Url, UrlBuf, UrlCow}; use std::{borrow::Cow, ffi::OsStr, path::Path};
use crate::url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn};
// --- AsUrl
pub trait AsUrl { pub trait AsUrl {
fn as_url(&self) -> Url<'_>; fn as_url(&self) -> Url<'_>;
} }
@ -11,7 +14,7 @@ impl AsUrl for Url<'_> {
impl AsUrl for UrlBuf { impl AsUrl for UrlBuf {
#[inline] #[inline]
fn as_url(&self) -> Url<'_> { self.as_url() } fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_ref() } }
} }
impl AsUrl for &UrlBuf { impl AsUrl for &UrlBuf {
@ -26,7 +29,12 @@ impl AsUrl for &mut UrlBuf {
impl AsUrl for UrlCow<'_> { impl AsUrl for UrlCow<'_> {
#[inline] #[inline]
fn as_url(&self) -> Url<'_> { self.as_url() } fn as_url(&self) -> Url<'_> {
match self {
UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_ref() },
UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_ref() },
}
}
} }
impl AsUrl for &UrlCow<'_> { impl AsUrl for &UrlCow<'_> {
@ -41,3 +49,51 @@ impl<'a, T: AsUrl> From<&'a T> for Url<'a> {
impl<'a, T: AsUrl> From<&'a mut T> for Url<'a> { impl<'a, T: AsUrl> From<&'a mut T> for Url<'a> {
fn from(value: &'a mut T) -> Self { value.as_url() } fn from(value: &'a mut T) -> Self { value.as_url() }
} }
// UrlLike
pub trait UrlLike
where
Self: AsUrl + Sized,
{
fn as_path(&self) -> Option<&Path> { self.as_url().as_path() }
fn base(&self) -> Option<Url<'_>> { self.as_url().base() }
fn components(&self) -> Components<'_> { self.as_url().into() }
fn covariant(&self, other: impl AsUrl) -> bool { self.as_url().covariant(other) }
fn display(&self) -> Display<'_> { self.as_url().into() }
fn ends_with(&self, child: impl AsUrl) -> bool { self.as_url().ends_with(child) }
fn ext(&self) -> Option<&OsStr> { self.as_url().ext() }
fn has_root(&self) -> bool { self.as_url().has_root() }
fn has_trail(&self) -> bool { self.as_url().has_trail() }
fn is_absolute(&self) -> bool { self.as_url().is_absolute() }
fn join(&self, path: impl AsRef<Path>) -> UrlBuf { self.as_url().join(path) }
fn name(&self) -> Option<&OsStr> { self.as_url().name() }
fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() }
fn pair(&self) -> Option<(Url<'_>, &Urn)> { self.as_url().pair() }
fn parent(&self) -> Option<Url<'_>> { self.as_url().parent() }
fn starts_with(&self, base: impl AsUrl) -> bool { self.as_url().starts_with(base) }
fn stem(&self) -> Option<&OsStr> { self.as_url().stem() }
fn strip_prefix(&self, base: impl AsUrl) -> Option<&Urn> { self.as_url().strip_prefix(base) }
fn uri(&self) -> &Uri { self.as_url().uri() }
fn urn(&self) -> &Urn { self.as_url().urn() }
}
impl<T: AsUrl> UrlLike for T {}

View file

@ -72,6 +72,39 @@ impl<'a> Url<'a> {
UrlBuf { loc, scheme: self.scheme.into() } UrlBuf { loc, scheme: self.scheme.into() }
} }
pub fn strip_prefix(self, base: impl AsUrl) -> Option<&'a Urn> {
use SchemeRef as S;
let base = base.as_url();
let prefix = self.loc.strip_prefix(base.loc)?;
Some(Urn::new(match (self.scheme, base.scheme) {
// Same scheme
(S::Regular, S::Regular) => Some(prefix),
(S::Search(_), S::Search(_)) => Some(prefix),
(S::Archive(a), S::Archive(b)) => Some(prefix).filter(|_| a == b),
(S::Sftp(a), S::Sftp(b)) => Some(prefix).filter(|_| a == b),
// Both are local files
(S::Regular, S::Search(_)) => Some(prefix),
(S::Search(_), S::Regular) => Some(prefix),
// Only the entry of archives is a local file
(S::Regular, S::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()),
(S::Search(_), S::Archive(_)) => Some(prefix).filter(|_| base.uri().is_empty()),
(S::Archive(_), S::Regular) => Some(prefix).filter(|_| self.uri().is_empty()),
(S::Archive(_), S::Search(_)) => Some(prefix).filter(|_| self.uri().is_empty()),
// Independent virtual file space
(S::Regular, S::Sftp(_)) => None,
(S::Search(_), S::Sftp(_)) => None,
(S::Archive(_), S::Sftp(_)) => None,
(S::Sftp(_), S::Regular) => None,
(S::Sftp(_), S::Search(_)) => None,
(S::Sftp(_), S::Archive(_)) => None,
}?))
}
#[inline] #[inline]
pub fn uri(self) -> &'a Uri { self.loc.uri() } pub fn uri(self) -> &'a Uri { self.loc.uri() }

View file

@ -3,7 +3,7 @@ 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::{AsUrl, UrlBuf}; use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
use crate::provider; use crate::provider;

View file

@ -1,5 +1,5 @@
use yazi_fs::FilesOp; use yazi_fs::FilesOp;
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
use crate::maybe_exists; use crate::maybe_exists;

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, ffi::OsStr, io, sync::Arc}; use std::{borrow::Cow, ffi::OsStr, io, sync::Arc};
use yazi_fs::{cha::{Cha, ChaType}, provider::FileHolder}; use yazi_fs::{cha::{Cha, ChaType}, provider::FileHolder};
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
pub enum DirEntry { pub enum DirEntry {
Regular(yazi_fs::provider::local::DirEntry), Regular(yazi_fs::provider::local::DirEntry),

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::{AsUrl, Url, UrlBuf}; use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlLike};
use crate::{LINKED, WATCHED, backend}; use crate::{LINKED, WATCHED, backend};

View file

@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use hashbrown::HashSet; use hashbrown::HashSet;
use yazi_shared::url::{AsUrl, UrlBuf}; use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
#[derive(Default)] #[derive(Default)]
pub struct Watched(HashSet<UrlBuf>); pub struct Watched(HashSet<UrlBuf>);

View file

@ -4,7 +4,7 @@ use hashbrown::HashSet;
use tokio::{pin, sync::{mpsc::{self, UnboundedReceiver}, watch}}; use tokio::{pin, sync::{mpsc::{self, UnboundedReceiver}, watch}};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_fs::{File, FilesOp, provider::local}; use yazi_fs::{File, FilesOp, provider::local};
use yazi_shared::url::UrlBuf; use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
use crate::{LINKED, Linked, WATCHED, WATCHER, backend::Backend}; use crate::{LINKED, Linked, WATCHED, WATCHER, backend::Backend};