diff --git a/Cargo.lock b/Cargo.lock index 2937f1be..3b0401b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -411,9 +411,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.39" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "jobserver", @@ -1243,9 +1243,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flate2" @@ -1874,11 +1874,10 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -2442,9 +2441,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2452,15 +2451,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 212221f6..c322f49e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ lru = "0.16.1" mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } objc = "0.2.7" ordered-float = { version = "5.1.0", features = [ "serde" ] } -parking_lot = "0.12.4" +parking_lot = "0.12.5" paste = "1.0.15" ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } regex = "1.11.3" diff --git a/cspell.json b/cspell.json index 43970748..65ac5b6a 100644 --- a/cspell.json +++ b/cspell.json @@ -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"]} \ No newline at end of file +{"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"]} \ No newline at end of file diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index 3412e42e..5e77b227 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -91,7 +91,7 @@ impl Trigger { #[cfg(test)] mod tests { - use yazi_shared::url::Urn; + use yazi_shared::url::{UrlLike, Urn}; use super::*; diff --git a/yazi-actor/src/lives/file.rs b/yazi-actor/src/lives/file.rs index 8bd20e9d..69f3052d 100644 --- a/yazi-actor/src/lives/file.rs +++ b/yazi-actor/src/lives/file.rs @@ -4,6 +4,7 @@ use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Valu use yazi_binding::Style; use yazi_config::THEME; use yazi_plugin::bindings::Range; +use yazi_shared::url::UrlLike; use super::Lives; use crate::lives::PtrCell; diff --git a/yazi-actor/src/lives/tab.rs b/yazi-actor/src/lives/tab.rs index 0e8afe81..6c97fbb3 100644 --- a/yazi-actor/src/lives/tab.rs +++ b/yazi-actor/src/lives/tab.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value}; use yazi_binding::{Id, UrlRef, cached_field}; +use yazi_shared::url::UrlLike; use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected}; diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index c63491df..71340eb2 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -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 crossterm::{execute, style::Print}; @@ -7,11 +7,11 @@ use scopeguard::defer; use tokio::io::AsyncWriteExt; use yazi_config::{YAZI, opener::OpenerRule}; 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_parser::VoidOpt; 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_vfs::{VfsFile, maybe_exists, provider}; use yazi_watcher::WATCHER; @@ -51,10 +51,13 @@ impl Actor for BulkRename { .await?; defer! { tokio::spawn(Local.remove_file(tmp.clone())); } - TasksProxy::process_exec(Cow::Borrowed(opener), cwd, vec![ - OsStr::new("").into(), - tmp.as_os_str().to_owned().into(), - ]) + TasksProxy::process_exec( + cwd, + Splatter::new(&[UrlCow::default(), Url::regular(&tmp).into()]).splat(&opener.run), + vec![UrlCow::default(), UrlBuf::from(&tmp).into()], + opener.block, + opener.orphan, + ) .await; let _permit = HIDER.acquire().await.unwrap(); diff --git a/yazi-actor/src/mgr/cd.rs b/yazi-actor/src/mgr/cd.rs index 8a4118f2..6b85e955 100644 --- a/yazi-actor/src/mgr/cd.rs +++ b/yazi-actor/src/mgr/cd.rs @@ -9,7 +9,7 @@ use yazi_fs::{File, FilesOp, path::expand_url}; use yazi_macro::{act, err, render, succ}; use yazi_parser::mgr::CdOpt; 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 crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/copy.rs b/yazi-actor/src/mgr/copy.rs index 05baac1b..152a5565 100644 --- a/yazi-actor/src/mgr/copy.rs +++ b/yazi-actor/src/mgr/copy.rs @@ -3,7 +3,7 @@ use std::ffi::OsString; use anyhow::{Result, bail}; use yazi_macro::{act, succ}; use yazi_parser::mgr::CopyOpt; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use yazi_widgets::CLIPBOARD; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/create.rs b/yazi-actor/src/mgr/create.rs index 51c57d1f..613a2dcb 100644 --- a/yazi-actor/src/mgr/create.rs +++ b/yazi-actor/src/mgr/create.rs @@ -4,7 +4,7 @@ use yazi_fs::{File, FilesOp}; use yazi_macro::{ok_or_not_found, succ}; use yazi_parser::mgr::CreateOpt; 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_watcher::WATCHER; diff --git a/yazi-actor/src/mgr/follow.rs b/yazi-actor/src/mgr/follow.rs index 36817809..00e5f4fc 100644 --- a/yazi-actor/src/mgr/follow.rs +++ b/yazi-actor/src/mgr/follow.rs @@ -2,7 +2,7 @@ use anyhow::Result; use yazi_fs::path::clean_url; use yazi_macro::{act, succ}; use yazi_parser::VoidOpt; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/hover.rs b/yazi-actor/src/mgr/hover.rs index ea6c1440..06538824 100644 --- a/yazi-actor/src/mgr/hover.rs +++ b/yazi-actor/src/mgr/hover.rs @@ -2,7 +2,7 @@ use anyhow::Result; use yazi_dds::Pubsub; use yazi_macro::{err, render, succ, tab}; use yazi_parser::mgr::HoverOpt; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/leave.rs b/yazi-actor/src/mgr/leave.rs index a7324850..8de02c97 100644 --- a/yazi-actor/src/mgr/leave.rs +++ b/yazi-actor/src/mgr/leave.rs @@ -1,7 +1,7 @@ use anyhow::Result; use yazi_macro::{act, succ}; use yazi_parser::{VoidOpt, mgr::CdSource}; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/open_do.rs b/yazi-actor/src/mgr/open_do.rs index 9ce2f0a0..151f6de2 100644 --- a/yazi-actor/src/mgr/open_do.rs +++ b/yazi-actor/src/mgr/open_do.rs @@ -1,11 +1,11 @@ -use std::borrow::Cow; - use anyhow::Result; +use hashbrown::HashMap; use yazi_config::{YAZI, popup::PickCfg}; +use yazi_fs::Splatter; use yazi_macro::succ; -use yazi_parser::mgr::OpenDoOpt; +use yazi_parser::{mgr::OpenDoOpt, tasks::ProcessOpenOpt}; use yazi_proxy::{PickProxy, TasksProxy}; -use yazi_shared::{data::Data, url::UrlCow}; +use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}}; use crate::{Actor, Ctx}; @@ -30,21 +30,53 @@ impl Actor for OpenDo { if targets.is_empty() { succ!(); } 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() { succ!(); } 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 { 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!(); } } + +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, + }); + } + } +} diff --git a/yazi-actor/src/mgr/quit.rs b/yazi-actor/src/mgr/quit.rs index cded52a0..9eb28e5a 100644 --- a/yazi-actor/src/mgr/quit.rs +++ b/yazi-actor/src/mgr/quit.rs @@ -7,7 +7,7 @@ use yazi_dds::spark::SparkKind; use yazi_macro::{emit, succ}; use yazi_parser::mgr::QuitOpt; use yazi_proxy::ConfirmProxy; -use yazi_shared::{data::Data, event::EventQuit, url::UrlCow}; +use yazi_shared::{data::Data, event::EventQuit, url::{AsUrl, UrlCow}}; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/rename.rs b/yazi-actor/src/mgr/rename.rs index 959385d1..65aadfa4 100644 --- a/yazi-actor/src/mgr/rename.rs +++ b/yazi-actor/src/mgr/rename.rs @@ -5,7 +5,7 @@ use yazi_fs::{File, FilesOp}; use yazi_macro::{act, err, ok_or_not_found, succ}; use yazi_parser::mgr::RenameOpt; 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_watcher::WATCHER; diff --git a/yazi-actor/src/mgr/reveal.rs b/yazi-actor/src/mgr/reveal.rs index 1c148b73..590d0924 100644 --- a/yazi-actor/src/mgr/reveal.rs +++ b/yazi-actor/src/mgr/reveal.rs @@ -2,7 +2,7 @@ use anyhow::Result; use yazi_fs::{File, FilesOp}; use yazi_macro::{act, render, succ}; use yazi_parser::mgr::RevealOpt; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/shell.rs b/yazi-actor/src/mgr/shell.rs index 782c7a3f..3d08ff73 100644 --- a/yazi-actor/src/mgr/shell.rs +++ b/yazi-actor/src/mgr/shell.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; 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_parser::mgr::ShellOpt; +use yazi_parser::{mgr::ShellOpt, tasks::ProcessOpenOpt}; use yazi_proxy::{InputProxy, TasksProxy}; use yazi_shared::data::Data; @@ -20,7 +21,7 @@ impl Actor for Shell { act!(mgr:escape_visual, cx)?; 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(|| { InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor)) @@ -37,18 +38,15 @@ impl Actor for Shell { return; } - TasksProxy::file_open( - Cow::Owned(OpenerRule { - run: opt.run.into_owned(), - block: opt.block, - orphan: opt.orphan, - desc: Default::default(), - r#for: None, - spread: true, - }), + TasksProxy::open_shell_compat(ProcessOpenOpt { cwd, - selected, - ); + cmd: Splatter::new(&selected).splat(opt.run.as_ref()), + args: selected, + block: opt.block, + orphan: opt.orphan, + done: None, + spread: true, + }); }); succ!(); diff --git a/yazi-actor/src/mgr/update_files.rs b/yazi-actor/src/mgr/update_files.rs index d4b7e5f6..61392b71 100644 --- a/yazi-actor/src/mgr/update_files.rs +++ b/yazi-actor/src/mgr/update_files.rs @@ -3,7 +3,7 @@ use yazi_core::tab::Folder; use yazi_fs::FilesOp; use yazi_macro::{act, render, succ}; use yazi_parser::mgr::UpdateFilesOpt; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use yazi_watcher::LINKED; use crate::{Actor, Ctx}; diff --git a/yazi-actor/src/mgr/update_mimes.rs b/yazi-actor/src/mgr/update_mimes.rs index 181f820d..aa423b1a 100644 --- a/yazi-actor/src/mgr/update_mimes.rs +++ b/yazi-actor/src/mgr/update_mimes.rs @@ -2,7 +2,7 @@ use anyhow::Result; use hashbrown::HashMap; use yazi_macro::{act, render, succ}; 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 crate::{Actor, Ctx}; diff --git a/yazi-actor/src/tasks/file_open.rs b/yazi-actor/src/tasks/file_open.rs deleted file mode 100644 index bdb635af..00000000 --- a/yazi-actor/src/tasks/file_open.rs +++ /dev/null @@ -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 { - succ!(cx.tasks.process_with_opener( - opt.cwd, - opt.opener, - opt.targets.into_iter().map(|u| u.into_os_str2()).collect(), - )); - } -} diff --git a/yazi-actor/src/tasks/mod.rs b/yazi-actor/src/tasks/mod.rs index af4b2fb9..1c71749b 100644 --- a/yazi-actor/src/tasks/mod.rs +++ b/yazi-actor/src/tasks/mod.rs @@ -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); diff --git a/yazi-actor/src/tasks/open_shell_compat.rs b/yazi-actor/src/tasks/open_shell_compat.rs new file mode 100644 index 00000000..3f287f72 --- /dev/null +++ b/yazi-actor/src/tasks/open_shell_compat.rs @@ -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 { + succ!(cx.tasks.open_shell_compat(opt)); + } +} diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index 30d200b6..163c32d6 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -1,7 +1,7 @@ use std::ops::Deref; 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}; diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index 7e738956..7f4e0f3c 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -4,7 +4,7 @@ use futures::executor::block_on; use hashbrown::HashSet; use serde::Serialize; 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; #[derive(Debug, Default, Serialize)] diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 42a431c0..6c42dde3 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -30,40 +30,37 @@ ueberzug_offset = [ 0, 0, 0, 0 ] [opener] edit = [ - { run = '${EDITOR:-vi} "$@"', desc = "$EDITOR", block = true, for = "unix" }, - { run = 'code %*', orphan = true, desc = "code", for = "windows" }, - { run = 'code -w %*', block = true, desc = "code (block)", for = "windows" }, + { run = "${EDITOR:-vi} %s", desc = "$EDITOR", for = "unix", block = true }, + { run = "code %s", desc = "code", for = "windows", orphan = true }, + { run = "code -w %s", desc = "code (block)", for = "windows", block = true }, ] play = [ - { run = 'xdg-open "$1"', desc = "Play", for = "linux" }, - { run = 'open "$@"', desc = "Play", for = "macos" }, - { run = 'start "" "%1"', orphan = true, desc = "Play", for = "windows" }, - { run = 'termux-open "$1"', desc = "Play", for = "android" }, - { run = '''mediainfo "$1"; 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 = "xdg-open %s1", desc = "Play", for = "linux" }, + { run = "open %s", desc = "Play", for = "macos" }, + { run = 'start "" %s1', orphan = true, desc = "Play", for = "windows" }, + { run = "termux-open %s1", desc = "Play", for = "android" }, + { run = "mediainfo %s1; echo 'Press enter to exit'; read _", block = true, desc = "Show media info", for = "unix" }, + { run = "mediainfo %s1 & pause", block = true, desc = "Show media info", for = "windows" }, ] open = [ - { run = 'xdg-open "$1"', desc = "Open", for = "linux" }, - { run = 'open "$@"', desc = "Open", for = "macos" }, - { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, - { run = 'termux-open "$1"', desc = "Open", for = "android" }, + { run = "xdg-open %s1", desc = "Open", for = "linux" }, + { run = "open %s", desc = "Open", for = "macos" }, + { run = 'start "" %s1', desc = "Open", for = "windows", orphan = true }, + { run = "termux-open %s1", desc = "Open", for = "android" }, ] reveal = [ - { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, - { run = 'open -R "$1"', desc = "Reveal", for = "macos" }, - { run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" }, - { run = 'termux-open "$(dirname "$1")"', desc = "Reveal", for = "android" }, - { run = '''clear; exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, + { run = "xdg-open %d1", desc = "Reveal", for = "linux" }, + { run = "open -R %s1", desc = "Reveal", for = "macos" }, + { run = "explorer /select,%s1", desc = "Reveal", for = "windows", orphan = true }, + { run = "termux-open %d1", desc = "Reveal", for = "android" }, + { run = "clear; exiftool %s1; echo 'Press enter to exit'; read _", desc = "Show EXIF", for = "unix", block = true }, ] extract = [ - { run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" }, - { run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" }, + { run = "ya pub extract --list %s", desc = "Extract here" }, ] download = [ - { run = 'ya emit download --open "$@"', desc = "Download and open", for = "unix" }, - { run = 'ya emit download --open %*', desc = "Download and open", for = "windows" }, - { run = 'ya emit download "$@"', desc = "Download", for = "unix" }, - { run = 'ya emit download %*', desc = "Download", for = "windows" }, + { run = "ya emit download --open %S", desc = "Download and open" }, + { run = "ya emit download %S", desc = "Download" }, ] [open] diff --git a/yazi-config/src/mgr/mgr.rs b/yazi-config/src/mgr/mgr.rs index 71feb9ab..2796db07 100644 --- a/yazi-config/src/mgr/mgr.rs +++ b/yazi-config/src/mgr/mgr.rs @@ -2,7 +2,7 @@ use anyhow::{Result, bail}; use serde::Deserialize; use yazi_codegen::DeserializeOver2; use yazi_fs::{CWD, SortBy}; -use yazi_shared::{SyncCell, url::UrlBuf}; +use yazi_shared::{SyncCell, url::{UrlBuf, UrlLike}}; use super::{MgrRatio, MouseEvents}; diff --git a/yazi-config/src/opener/opener.rs b/yazi-config/src/opener/opener.rs index f6e97a78..1263741d 100644 --- a/yazi-config/src/opener/opener.rs +++ b/yazi-config/src/opener/opener.rs @@ -18,21 +18,21 @@ impl Deref for 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 where I: Iterator, { - 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 - I: Iterator, + I: Iterator, { 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 I: Iterator, { diff --git a/yazi-config/src/opener/rule.rs b/yazi-config/src/opener/rule.rs index 5e3b64b2..673c44e1 100644 --- a/yazi-config/src/opener/rule.rs +++ b/yazi-config/src/opener/rule.rs @@ -1,5 +1,6 @@ use anyhow::{Result, bail}; use serde::Deserialize; +use yazi_fs::Splatter; #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct OpenerRule { @@ -35,11 +36,12 @@ impl OpenerRule { #[cfg(unix)] { - self.spread = self.run.contains("$@") || self.run.contains("$*"); + self.spread = + Splatter::<()>::spread(&self.run) || self.run.contains("$@") || self.run.contains("$*"); } #[cfg(windows)] { - self.spread = self.run.contains("%*"); + self.spread = Splatter::<()>::spread(&self.run) || self.run.contains("%*"); } Ok(self) diff --git a/yazi-config/src/theme/icon.rs b/yazi-config/src/theme/icon.rs index dd656c12..f3fd9d2b 100644 --- a/yazi-config/src/theme/icon.rs +++ b/yazi-config/src/theme/icon.rs @@ -5,7 +5,7 @@ use hashbrown::HashMap; use serde::{Deserialize, Deserializer}; use yazi_codegen::DeserializeOver2; use yazi_fs::File; -use yazi_shared::Condition; +use yazi_shared::{Condition, url::UrlLike}; use crate::{Color, Icon as I, Pattern, Style}; diff --git a/yazi-core/src/mgr/mgr.rs b/yazi-core/src/mgr/mgr.rs index ea095c53..7eae736e 100644 --- a/yazi-core/src/mgr/mgr.rs +++ b/yazi-core/src/mgr/mgr.rs @@ -1,7 +1,10 @@ +use std::iter; + use ratatui::layout::Rect; use yazi_adapter::Dimension; 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 super::{Mimetype, Tabs, Yanked}; @@ -53,3 +56,29 @@ impl Mgr { #[inline] 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) -> impl Iterator> { + 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> { + 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> { self.yanked.iter().map(|u| u.as_url()) } +} diff --git a/yazi-core/src/mgr/yanked.rs b/yazi-core/src/mgr/yanked.rs index edc3296c..b8a69af1 100644 --- a/yazi-core/src/mgr/yanked.rs +++ b/yazi-core/src/mgr/yanked.rs @@ -4,7 +4,7 @@ use hashbrown::HashSet; use yazi_dds::Pubsub; use yazi_fs::FilesOp; use yazi_macro::err; -use yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov}; +use yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlLike}; #[derive(Debug, Default)] pub struct Yanked { diff --git a/yazi-core/src/tasks/file.rs b/yazi-core/src/tasks/file.rs index 04566676..b4033d36 100644 --- a/yazi-core/src/tasks/file.rs +++ b/yazi-core/src/tasks/file.rs @@ -1,6 +1,6 @@ use hashbrown::HashSet; use tracing::debug; -use yazi_shared::url::{UrlBuf, UrlBufCov}; +use yazi_shared::url::{UrlBuf, UrlBufCov, UrlLike}; use super::Tasks; use crate::mgr::Yanked; diff --git a/yazi-core/src/tasks/process.rs b/yazi-core/src/tasks/process.rs index 6db5ee3a..b8d84df2 100644 --- a/yazi-core/src/tasks/process.rs +++ b/yazi-core/src/tasks/process.rs @@ -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_shared::url::{UrlBuf, UrlCow}; use super::Tasks; impl Tasks { - pub fn process_with_selected(&self, 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![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>, - ) { - if opener.spread { - self.scheduler.process_open(ProcessOpenOpt { cwd, opener, args, done: None }); + // TODO: remove + pub fn open_shell_compat(&self, mut opt: ProcessOpenOpt) { + if opt.spread { + self.scheduler.process_open(opt); return; } - if args.is_empty() { + if opt.args.is_empty() { return; } - if args.len() == 2 { - self.scheduler.process_open(ProcessOpenOpt { cwd, opener, args, done: None }); + if opt.args.len() == 2 { + self.scheduler.process_open(opt); return; } - let hovered = mem::take(&mut args[0]); - for target in args.into_iter().skip(1) { + let hovered = mem::take(&mut opt.args[0]); + for target in opt.args.into_iter().skip(1) { self.scheduler.process_open(ProcessOpenOpt { - cwd: cwd.clone(), - opener: opener.clone(), + cwd: opt.cwd.clone(), + cmd: opt.cmd.clone(), args: vec![hovered.clone(), target], + block: opt.block, + orphan: opt.orphan, done: None, + spread: opt.spread, }); } } diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs index b7cb6d3e..8d8853f0 100644 --- a/yazi-dds/src/spark/spark.rs +++ b/yazi-dds/src/spark/spark.rs @@ -37,7 +37,6 @@ pub enum Spark<'a> { Linemode(yazi_parser::mgr::LinemodeOpt), Link(yazi_parser::mgr::LinkOpt), Open(yazi_parser::mgr::OpenOpt), - OpenWith(yazi_parser::mgr::OpenWithOpt), OpenDo(yazi_parser::mgr::OpenDoOpt), Paste(yazi_parser::mgr::PasteOpt), Peek(yazi_parser::mgr::PeekOpt), @@ -160,7 +159,6 @@ impl<'a> IntoLua for Spark<'a> { Self::Linemode(b) => b.into_lua(lua), Self::Link(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::Paste(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::OpenDoOpt, mgr:open_do); 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::PeekOpt, mgr:peek); try_from_spark!(mgr::QuitOpt, mgr:quit); diff --git a/yazi-fm/src/app/commands/bootstrap.rs b/yazi-fm/src/app/commands/bootstrap.rs index b4733cfd..c8b3cb03 100644 --- a/yazi-fm/src/app/commands/bootstrap.rs +++ b/yazi-fm/src/app/commands/bootstrap.rs @@ -3,7 +3,7 @@ use yazi_actor::Ctx; use yazi_boot::BOOT; use yazi_macro::act; use yazi_parser::{VoidOpt, mgr::CdSource}; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, url::UrlLike}; use crate::app::App; diff --git a/yazi-fm/src/app/commands/quit.rs b/yazi-fm/src/app/commands/quit.rs index 8b0625f4..37efe25d 100644 --- a/yazi-fm/src/app/commands/quit.rs +++ b/yazi-fm/src/app/commands/quit.rs @@ -2,7 +2,7 @@ use std::ffi::OsString; use yazi_boot::ARGS; use yazi_fs::provider::{Provider, local::Local}; -use yazi_shared::event::EventQuit; +use yazi_shared::{event::EventQuit, url::UrlLike}; use crate::{Term, app::App}; diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 07b4c1ad..905f73ee 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -164,8 +164,8 @@ impl<'a> Executor<'a> { on!(arrow); on!(inspect); on!(cancel); - on!(file_open); on!(process_open); + on!(open_shell_compat); match cmd.name.as_ref() { // Help diff --git a/yazi-fs/src/cwd.rs b/yazi-fs/src/cwd.rs index f884a338..6fa64dc8 100644 --- a/yazi-fs/src/cwd.rs +++ b/yazi-fs/src/cwd.rs @@ -1,7 +1,7 @@ use std::{env::{current_dir, set_current_dir}, ops::Deref, path::PathBuf, sync::{Arc, atomic::{AtomicBool, Ordering}}}; use arc_swap::ArcSwap; -use yazi_shared::{RoCell, url::UrlBuf}; +use yazi_shared::{RoCell, url::{UrlBuf, UrlLike}}; use crate::FsUrl; diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index 9666824c..1d7fc007 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -1,6 +1,6 @@ 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}; diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index 543c0acc..4718d1f0 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -1,5 +1,5 @@ use tokio::io; -use yazi_shared::url::{Component, UrlBuf}; +use yazi_shared::url::{Component, UrlBuf, UrlLike}; #[inline] pub fn ok_or_not_found(result: io::Result) -> io::Result { diff --git a/yazi-fs/src/lib.rs b/yazi-fs/src/lib.rs index 04b7bba5..1eeb7c86 100644 --- a/yazi-fs/src/lib.rs +++ b/yazi-fs/src/lib.rs @@ -2,7 +2,7 @@ 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() { CWD.init(<_>::default()); diff --git a/yazi-fs/src/op.rs b/yazi-fs/src/op.rs index 04c4f906..f2b37d46 100644 --- a/yazi-fs/src/op.rs +++ b/yazi-fs/src/op.rs @@ -2,7 +2,7 @@ use std::path::Path; use hashbrown::{HashMap, HashSet}; 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 crate::{cha::Cha, error::Error}; diff --git a/yazi-fs/src/path/expand.rs b/yazi-fs/src/path/expand.rs index c3514278..a36a54c1 100644 --- a/yazi-fs/src/path/expand.rs +++ b/yazi-fs/src/path/expand.rs @@ -1,6 +1,6 @@ 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}; @@ -76,7 +76,7 @@ pub fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> { ) .expect("Failed to create Loc from drive letter"); 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() && home.is_absolute() { diff --git a/yazi-fs/src/path/path.rs b/yazi-fs/src/path/path.rs index fccea699..1f3803ec 100644 --- a/yazi-fs/src/path/path.rs +++ b/yazi-fs/src/path/path.rs @@ -1,6 +1,6 @@ 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> { let mut it = url.components(); @@ -36,7 +36,7 @@ pub fn backslash_to_slash(p: &std::path::Path) -> Cow<'_, std::path::Path> { #[cfg(test)] mod tests { - use yazi_shared::url::UrlCow; + use yazi_shared::url::{AsUrl, UrlCow}; use crate::path::url_relative_to; diff --git a/yazi-fs/src/path/relative.rs b/yazi-fs/src/path/relative.rs index 72eaba9b..fcf1ddb0 100644 --- a/yazi-fs/src/path/relative.rs +++ b/yazi-fs/src/path/relative.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, path::{Path, PathBuf}}; 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>( from: impl AsRef, diff --git a/yazi-fs/src/sorter.rs b/yazi-fs/src/sorter.rs index 286c8f51..109dff4e 100644 --- a/yazi-fs/src/sorter.rs +++ b/yazi-fs/src/sorter.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; 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}; diff --git a/yazi-fs/src/splatter.rs b/yazi-fs/src/splatter.rs new file mode 100644 index 00000000..9db2b6cf --- /dev/null +++ b/yazi-fs/src/splatter.rs @@ -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>>; +#[cfg(windows)] +type Iter<'a> = Peekable>; + +#[cfg(unix)] +type Buf = Vec; +#[cfg(windows)] +type Buf = Vec; + +#[derive(Clone, Copy)] +pub struct Splatter { + src: T, + tab: usize, +} + +pub trait Splatable { + fn tab(&self) -> usize; + + fn selected(&self, tab: usize, idx: Option) -> impl Iterator>; + + fn hovered(&self, tab: usize) -> Option>; + + fn yanked(&self) -> impl Iterator>; +} + +#[cfg(unix)] +fn b2c(b: u8) -> Option { Some(b as char) } +#[cfg(windows)] +fn b2c(b: u16) -> Option { char::from_u32(b as u32) } + +fn cue(buf: &mut Buf, s: impl AsRef) { + #[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 Splatter +where + T: Splatable, +{ + pub fn new(src: T) -> Self { Self { tab: src.tab() + 1, src } } + + pub fn splat(mut self, cmd: impl AsRef) -> 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 { + fn next(it: &mut Iter) -> Option { + 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 Splatter { + pub fn spread(cmd: impl AsRef) -> bool { + struct Source(Cell); + + impl Splatable for &Source { + fn tab(&self) -> usize { 0 } + + fn selected(&self, _tab: usize, idx: Option) -> impl Iterator> { + if idx.is_none() { + self.0.set(true); + } + iter::empty() + } + + fn hovered(&self, _tab: usize) -> Option> { None } + + fn yanked(&self) -> impl Iterator> { + 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) -> impl Iterator> { + 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> { + self.as_ref().first().filter(|_| tab == 1).map(|u| u.as_url()) + } + + fn yanked(&self) -> impl Iterator> { 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) -> impl Iterator> { + 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> { + if tab == 1 { + Some(Url::regular("hovered")) + } else if tab == 2 { + Some(Url::regular("hover ed")) + } else { + None + } + } + + fn yanked(&self) -> impl Iterator> { + [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}"); + } + } +} diff --git a/yazi-fs/src/url.rs b/yazi-fs/src/url.rs index 6b2e00e3..a98322de 100644 --- a/yazi-fs/src/url.rs +++ b/yazi-fs/src/url.rs @@ -1,15 +1,27 @@ -use std::path::PathBuf; +use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; 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; -pub trait FsUrl { +pub trait FsUrl<'a> { fn cache(&self) -> Option; + + 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 { match self.scheme { 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 { 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 { 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(), + } + } } diff --git a/yazi-parser/src/app/notify.rs b/yazi-parser/src/app/notify.rs index 0ae0c885..20278846 100644 --- a/yazi-parser/src/app/notify.rs +++ b/yazi-parser/src/app/notify.rs @@ -17,7 +17,7 @@ impl TryFrom for NotifyOpt { type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - c.take_any("option").ok_or_else(|| anyhow!("Invalid 'option' in NotifyOpt")) + c.take_any("opt").ok_or_else(|| anyhow!("Invalid 'opt' in NotifyOpt")) } } diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs index 18a27e70..3b10bc99 100644 --- a/yazi-parser/src/mgr/mod.rs +++ b/yazi-parser/src/mgr/mod.rs @@ -16,7 +16,6 @@ yazi_macro::mod_flat!( link open open_do - open_with paste peek quit diff --git a/yazi-parser/src/mgr/open_do.rs b/yazi-parser/src/mgr/open_do.rs index af38b273..21114c3d 100644 --- a/yazi-parser/src/mgr/open_do.rs +++ b/yazi-parser/src/mgr/open_do.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; @@ -8,8 +9,16 @@ pub struct OpenDoOpt { pub interactive: bool, } -impl From for OpenDoOpt { - fn from(mut c: CmdCow) -> Self { c.take_any("option").unwrap_or_default() } +impl TryFrom for OpenDoOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + if let Some(opt) = c.take_any2("opt") { + opt + } else { + bail!("'opt' is required for OpenDoOpt"); + } + } } impl FromLua for OpenDoOpt { diff --git a/yazi-parser/src/mgr/open_with.rs b/yazi-parser/src/mgr/open_with.rs deleted file mode 100644 index 229adc93..00000000 --- a/yazi-parser/src/mgr/open_with.rs +++ /dev/null @@ -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>, -} - -impl TryFrom for OpenWithOpt { - type Error = anyhow::Error; - - fn try_from(mut c: CmdCow) -> Result { - c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in OpenWithOpt")) - } -} - -impl FromLua for OpenWithOpt { - fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } -} - -impl IntoLua for OpenWithOpt { - fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } -} diff --git a/yazi-parser/src/tasks/process_open.rs b/yazi-parser/src/tasks/process_open.rs index 2837e555..c0a44855 100644 --- a/yazi-parser/src/tasks/process_open.rs +++ b/yazi-parser/src/tasks/process_open.rs @@ -1,25 +1,28 @@ -use std::{borrow::Cow, ffi::OsStr}; +use std::ffi::OsString; use anyhow::anyhow; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use tokio::sync::oneshot; -use yazi_config::opener::OpenerRule; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}}; // --- Exec #[derive(Debug)] pub struct ProcessOpenOpt { pub cwd: UrlBuf, - pub opener: Cow<'static, OpenerRule>, - pub args: Vec>, + pub cmd: OsString, + pub args: Vec>, + pub block: bool, + pub orphan: bool, pub done: Option>, + + pub spread: bool, // TODO: remove } impl TryFrom for ProcessOpenOpt { type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in ProcessOpenOpt")) + c.take_any("opt").ok_or_else(|| anyhow!("Missing 'opt' in ProcessOpenOpt")) } } diff --git a/yazi-plugin/src/external/fd.rs b/yazi-plugin/src/external/fd.rs index 971a02ac..7cdc38dc 100644 --- a/yazi-plugin/src/external/fd.rs +++ b/yazi-plugin/src/external/fd.rs @@ -3,7 +3,7 @@ use std::process::Stdio; use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}}; use yazi_fs::File; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlLike}; use yazi_vfs::VfsFile; pub struct FdOpt { diff --git a/yazi-plugin/src/external/rg.rs b/yazi-plugin/src/external/rg.rs index 1bdaee94..99553851 100644 --- a/yazi-plugin/src/external/rg.rs +++ b/yazi-plugin/src/external/rg.rs @@ -3,7 +3,7 @@ use std::process::Stdio; use anyhow::{Result, bail}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}}; use yazi_fs::File; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlLike}; use yazi_vfs::VfsFile; pub struct RgOpt { diff --git a/yazi-plugin/src/external/rga.rs b/yazi-plugin/src/external/rga.rs index d0409b51..db9b161a 100644 --- a/yazi-plugin/src/external/rga.rs +++ b/yazi-plugin/src/external/rga.rs @@ -3,7 +3,7 @@ use std::process::Stdio; use anyhow::{Result, bail}; use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}}; use yazi_fs::File; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlLike}; use yazi_vfs::VfsFile; pub struct RgaOpt { diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 064ec03c..76b9f06b 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -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_config::Pattern; 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 crate::bindings::SizeCalculator; diff --git a/yazi-plugin/src/utils/cache.rs b/yazi-plugin/src/utils/cache.rs index c566fdd4..ea6a3692 100644 --- a/yazi-plugin/src/utils/cache.rs +++ b/yazi-plugin/src/utils/cache.rs @@ -3,6 +3,7 @@ use std::hash::Hash; use mlua::{Function, Lua, Table}; use yazi_binding::{FileRef, Url}; use yazi_config::YAZI; +use yazi_shared::url::UrlLike; use super::Utils; use crate::Twox128; diff --git a/yazi-plugin/src/utils/image.rs b/yazi-plugin/src/utils/image.rs index ff6bae3c..2dc8c8b9 100644 --- a/yazi-plugin/src/utils/image.rs +++ b/yazi-plugin/src/utils/image.rs @@ -1,6 +1,7 @@ use mlua::{Function, IntoLuaMulti, Lua, Value}; use yazi_adapter::{ADAPTOR, Image}; use yazi_binding::{Error, UrlRef, elements::Rect}; +use yazi_shared::url::UrlLike; use super::Utils; use crate::bindings::ImageInfo; diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 51a453a4..98f4e68e 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -3,7 +3,7 @@ use yazi_binding::{Error, elements::{Area, Renderable, Text}}; use yazi_config::YAZI; use yazi_parser::mgr::{PreviewLock, UpdatePeekedOpt}; use yazi_proxy::MgrProxy; -use yazi_shared::errors::PeekError; +use yazi_shared::{errors::PeekError, url::UrlLike}; use super::Utils; use crate::external::Highlighter; diff --git a/yazi-proxy/src/app.rs b/yazi-proxy/src/app.rs index c2d6d7b1..11a41081 100644 --- a/yazi-proxy/src/app.rs +++ b/yazi-proxy/src/app.rs @@ -18,7 +18,7 @@ impl AppProxy { } 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) { diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs index 3ca21632..d29958a9 100644 --- a/yazi-proxy/src/mgr.rs +++ b/yazi-proxy/src/mgr.rs @@ -20,7 +20,7 @@ impl MgrProxy { } 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, permanently: bool) { diff --git a/yazi-proxy/src/tasks.rs b/yazi-proxy/src/tasks.rs index 9e7e37f5..cdcb8254 100644 --- a/yazi-proxy/src/tasks.rs +++ b/yazi-proxy/src/tasks.rs @@ -1,29 +1,34 @@ -use std::{borrow::Cow, ffi::OsStr}; +use std::ffi::OsString; use tokio::sync::oneshot; -use yazi_config::opener::OpenerRule; use yazi_macro::{emit, relay}; -use yazi_parser::{mgr::OpenWithOpt, tasks::ProcessOpenOpt}; +use yazi_parser::tasks::ProcessOpenOpt; use yazi_shared::url::{UrlBuf, UrlCow}; pub struct TasksProxy; impl TasksProxy { - pub fn file_open(opener: Cow<'static, OpenerRule>, cwd: UrlBuf, targets: Vec>) { - emit!(Call(relay!(tasks:file_open).with_any("option", OpenWithOpt { opener, cwd, targets }))); + // TODO: remove + pub fn open_shell_compat(opt: ProcessOpenOpt) { + emit!(Call(relay!(tasks:open_shell_compat).with_any("opt", opt))); } pub async fn process_exec( - opener: Cow<'static, OpenerRule>, cwd: UrlBuf, - args: Vec>, + cmd: OsString, + args: Vec>, + block: bool, + orphan: bool, ) { 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, - opener, + cmd, args, - done: Some(tx) + block, + orphan, + done: Some(tx), + spread: false }))); rx.await.ok(); } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 4eb3f18e..cd89463b 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -6,7 +6,7 @@ use tracing::warn; use yazi_config::YAZI; 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_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 super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; diff --git a/yazi-scheduler/src/prework/prework.rs b/yazi-scheduler/src/prework/prework.rs index 2750da77..1ba4863d 100644 --- a/yazi-scheduler/src/prework/prework.rs +++ b/yazi-scheduler/src/prework/prework.rs @@ -10,7 +10,7 @@ use tracing::error; use yazi_config::Priority; use yazi_fs::FilesOp; use yazi_plugin::isolate; -use yazi_shared::{event::CmdCow, url::UrlBuf}; +use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlLike}}; use yazi_vfs::provider; use super::{PreworkInFetch, PreworkInLoad, PreworkInSize}; diff --git a/yazi-scheduler/src/process/in.rs b/yazi-scheduler/src/process/in.rs index 7f6d25b9..41ea3b68 100644 --- a/yazi-scheduler/src/process/in.rs +++ b/yazi-scheduler/src/process/in.rs @@ -1,7 +1,7 @@ -use std::{borrow::Cow, ffi::{OsStr, OsString}}; +use std::ffi::OsString; use tokio::sync::mpsc; -use yazi_shared::{Id, url::UrlBuf}; +use yazi_shared::{Id, url::{UrlBuf, UrlCow}}; use super::ShellOpt; @@ -11,7 +11,7 @@ pub(crate) struct ProcessInBlock { pub(crate) id: Id, pub(crate) cwd: UrlBuf, pub(crate) cmd: OsString, - pub(crate) args: Vec>, + pub(crate) args: Vec>, } impl From for ShellOpt { @@ -26,7 +26,7 @@ pub(crate) struct ProcessInOrphan { pub(crate) id: Id, pub(crate) cwd: UrlBuf, pub(crate) cmd: OsString, - pub(crate) args: Vec>, + pub(crate) args: Vec>, } impl From for ShellOpt { @@ -41,7 +41,7 @@ pub(crate) struct ProcessInBg { pub(crate) id: Id, pub(crate) cwd: UrlBuf, pub(crate) cmd: OsString, - pub(crate) args: Vec>, + pub(crate) args: Vec>, pub(crate) cancel: mpsc::Receiver<()>, } diff --git a/yazi-scheduler/src/process/shell.rs b/yazi-scheduler/src/process/shell.rs index 81f2b731..f4e131ee 100644 --- a/yazi-scheduler/src/process/shell.rs +++ b/yazi-scheduler/src/process/shell.rs @@ -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 tokio::process::{Child, Command}; use yazi_fs::FsUrl; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlCow, UrlLike}; pub(crate) struct ShellOpt { pub(crate) cwd: UrlBuf, pub(crate) cmd: OsString, - pub(crate) args: Vec>, + pub(crate) args: Vec>, pub(crate) piped: bool, pub(crate) orphan: bool, } @@ -39,13 +39,17 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result { #[cfg(unix)] return Ok(unsafe { + use yazi_shared::url::AsUrl; + Command::new("sh") - .arg("-c") .stdin(opt.stdio()) .stdout(opt.stdio()) .stderr(opt.stdio()) + .arg("-c") .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) .kill_on_drop(!opt.orphan) .pre_exec(move || { @@ -60,11 +64,11 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result { #[cfg(windows)] return Ok( Command::new("cmd.exe") - .raw_arg("/C") - .raw_arg(parser::parse(&opt.cmd, &opt.args)) .stdin(opt.stdio()) .stdout(opt.stdio()) .stderr(opt.stdio()) + .raw_arg("/C") + .raw_arg(opt.cmd) .current_dir(cwd) .kill_on_drop(!opt.orphan) .spawn()?, @@ -72,176 +76,3 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result { }) .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]) -> 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, - buf: &mut Vec, - args: &[Cow], - 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::().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"#); - } - } -} diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index ab334854..e7bedd54 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -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 futures::{FutureExt, future::BoxFuture}; @@ -9,7 +9,7 @@ use yazi_dds::Pump; use yazi_fs::FsUrl; use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt}; 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 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 args = args.iter().map(|a| a.to_string_lossy()).collect::>().join(" "); + let args = opt.args.iter().map(|a| a.display().to_string()).collect::>().join(" "); if args.is_empty() { - format!("Run {:?}", opener.run) + format!("Run {:?}", opt.cmd) } else { - format!("Run {:?} with `{args}`", opener.run) + format!("Run {:?} with `{args}`", opt.cmd) } }; let mut ongoing = self.ongoing.lock(); - let (id, clean): (_, TaskOut) = if opener.block { + let (id, clean): (_, TaskOut) = if opt.block { (ongoing.add::(name), ProcessOutBlock::Clean.into()) - } else if opener.orphan { + } else if opt.orphan { (ongoing.add::(name), ProcessOutOrphan::Clean.into()) } else { (ongoing.add::(name), ProcessOutBg::Clean.into()) @@ -332,21 +332,22 @@ impl Scheduler { cancel_tx.send(()).await.ok(); cancel_tx.closed().await; } - if let Some(tx) = done { + if let Some(tx) = opt.done { tx.send(()).ok(); } ops.out(id, clean); }); - let cmd = OsString::from(&opener.run); let process = self.process.clone(); self.send_micro::<_, TaskOut>(id, NORMAL, async move { - if opener.block { - process.block(ProcessInBlock { id, cwd, cmd, args }).await?; - } else if opener.orphan { - process.orphan(ProcessInOrphan { id, cwd, cmd, args }).await?; + if opt.block { + process.block(ProcessInBlock { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }).await?; + } else if opt.orphan { + process.orphan(ProcessInOrphan { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }).await?; } 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(()) }); diff --git a/yazi-shared/src/loc/buf.rs b/yazi-shared/src/loc/buf.rs index 6b83160c..bcaa7621 100644 --- a/yazi-shared/src/loc/buf.rs +++ b/yazi-shared/src/loc/buf.rs @@ -189,7 +189,7 @@ impl LocBuf { #[cfg(test)] mod tests { use super::*; - use crate::url::UrlBuf; + use crate::url::{UrlBuf, UrlLike}; #[test] fn test_new() { diff --git a/yazi-shared/src/loc/loc.rs b/yazi-shared/src/loc/loc.rs index 94c50eab..b50db65c 100644 --- a/yazi-shared/src/loc/loc.rs +++ b/yazi-shared/src/loc/loc.rs @@ -204,6 +204,11 @@ impl<'a> Loc<'a> { } } + #[inline] + pub fn strip_prefix(self, base: impl AsRef) -> Option<&'a Path> { + self.inner.strip_prefix(base).ok() + } + #[inline] pub fn bytes(self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() } } diff --git a/yazi-shared/src/string.rs b/yazi-shared/src/string.rs index 39a5e58b..3ef681ff 100644 --- a/yazi-shared/src/string.rs +++ b/yazi-shared/src/string.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}}; -use crate::url::UrlBuf; +use crate::url::{UrlBuf, UrlLike}; pub trait IntoStringLossy { fn into_string_lossy(self) -> String; @@ -33,5 +33,6 @@ impl IntoStringLossy for Cow<'_, OsStr> { } impl IntoStringLossy for &UrlBuf { + // FIXME: remove fn into_string_lossy(self) -> String { self.os_str().into_string_lossy() } } diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index fa2d6c77..81cdc29d 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::BuildHasher, p use anyhow::Result; 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)] pub struct UrlBuf { @@ -89,67 +89,6 @@ impl UrlBuf { // }; &U } - #[inline] - pub fn join(&self, path: impl AsRef) -> 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> { 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] pub fn into_path(self) -> Option { 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() } } - #[inline] - pub fn pair(&self) -> Option<(Url<'_>, &Urn)> { self.as_url().pair() } - #[inline] 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> { self.as_url().base() } -} - impl UrlBuf { // --- Regular #[inline] @@ -225,34 +153,6 @@ impl UrlBuf { 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 { @@ -286,6 +186,7 @@ mod tests { use anyhow::Result; use super::*; + use crate::url::UrlLike; #[test] fn test_join() -> anyhow::Result<()> { diff --git a/yazi-shared/src/url/component.rs b/yazi-shared/src/url/component.rs index fcbf7470..f1de477b 100644 --- a/yazi-shared/src/url/component.rs +++ b/yazi-shared/src/url/component.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not, path::{self, PathBuf, PrefixComponent}}; -use crate::{loc::Loc, scheme::{Scheme, SchemeRef}, url::{Encode, Url, UrlBuf, UrlCow}}; +use crate::{scheme::{Scheme, SchemeRef}, url::{Encode, Url, UrlBuf}}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Component<'a> { @@ -60,8 +60,7 @@ impl<'a> FromIterator> for PathBuf { #[derive(Clone)] pub struct Components<'a> { inner: path::Components<'a>, - loc: Loc<'a>, - scheme: SchemeRef<'a>, + url: Url<'a>, scheme_yielded: bool, } @@ -69,36 +68,20 @@ impl<'a> From> for Components<'a> { fn from(value: Url<'a>) -> Self { Self { inner: value.loc.as_path().components(), - loc: value.loc, - scheme: value.scheme, + url: value, 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> { pub fn os_str(&self) -> Cow<'a, OsStr> { 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(); } - 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.push(path); s.into() @@ -107,7 +90,7 @@ impl<'a> Components<'a> { pub fn covariant(&self, other: &Self) -> bool { match (self.scheme_yielded, other.scheme_yielded) { (false, false) => {} - (true, true) if self.scheme.covariant(other.scheme) => {} + (true, true) if self.url.scheme.covariant(other.url.scheme) => {} _ => return false, } self.inner == other.inner @@ -120,7 +103,7 @@ impl<'a> Iterator for Components<'a> { fn next(&mut self) -> Option { if !self.scheme_yielded { self.scheme_yielded = true; - Some(Component::Scheme(self.scheme)) + Some(Component::Scheme(self.url.scheme)) } else { self.inner.next().map(Into::into) } @@ -140,7 +123,7 @@ impl<'a> DoubleEndedIterator for Components<'a> { Some(comp.into()) } else if !self.scheme_yielded { self.scheme_yielded = true; - Some(Component::Scheme(self.scheme)) + Some(Component::Scheme(self.url.scheme)) } else { None } @@ -151,8 +134,8 @@ impl<'a> FusedIterator for Components<'a> {} impl<'a> PartialEq for Components<'a> { fn eq(&self, other: &Self) -> bool { - Some(self.scheme).filter(|_| !self.scheme_yielded) - == Some(other.scheme).filter(|_| !other.scheme_yielded) + Some(self.url.scheme).filter(|_| !self.scheme_yielded) + == Some(other.url.scheme).filter(|_| !other.scheme_yielded) && self.inner == other.inner } } @@ -163,7 +146,7 @@ mod tests { use std::path::Path; use super::*; - use crate::pool::InternStr; + use crate::{pool::InternStr, url::UrlLike}; #[test] fn test_collect() { diff --git a/yazi-shared/src/url/cov.rs b/yazi-shared/src/url/cov.rs index 6a40a02e..d0160ede 100644 --- a/yazi-shared/src/url/cov.rs +++ b/yazi-shared/src/url/cov.rs @@ -3,7 +3,7 @@ use std::{hash::{Hash, Hasher}, ops::Deref, path::PathBuf}; use hashbrown::Equivalent; use serde::{Deserialize, Serialize}; -use crate::url::{Url, UrlBuf, UrlCow}; +use crate::url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}; #[derive(Clone, Copy)] pub struct UrlCov<'a>(Url<'a>); @@ -19,7 +19,7 @@ impl<'a> From<&'a UrlBufCov> for UrlCov<'a> { } impl PartialEq 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<'_> { @@ -79,21 +79,13 @@ impl<'a> From<&'a UrlBufCov> for Url<'a> { } impl Hash for UrlBufCov { - fn hash(&self, state: &mut H) { self.as_url().hash(state) } + fn hash(&self, state: &mut H) { UrlCov::from(self).hash(state) } } 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 for UrlBufCov { 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.0.parent().map(Into::into) } -} diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 719e8054..e624da03 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -1,9 +1,9 @@ -use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; +use std::{borrow::Cow, path::{Path, PathBuf}}; use anyhow::Result; 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)] 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] pub fn into_owned(self) -> UrlBuf { 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] pub fn to_owned(&self) -> UrlBuf { self.as_url().into() } - #[inline] - pub fn parent(&self) -> Option> { 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)>)> { let mut skip = 0; let (scheme, tilde, uri, urn) = SchemeCow::parse(bytes, &mut skip)?; @@ -179,13 +156,4 @@ impl<'a> UrlCow<'a> { impl UrlCow<'_> { #[inline] pub fn is_regular(&self) -> bool { self.as_url().is_regular() } - - #[inline] - pub fn is_absolute(&self) -> bool { self.as_url().is_absolute() } - - #[inline] - pub fn components(&self) -> Components<'_> { Components::from(self) } - - #[inline] - pub fn covariant(&self, other: &Self) -> bool { self.as_url().covariant(other) } } diff --git a/yazi-shared/src/url/display.rs b/yazi-shared/src/url/display.rs index 6b99dd9b..5935ab78 100644 --- a/yazi-shared/src/url/display.rs +++ b/yazi-shared/src/url/display.rs @@ -1,17 +1,16 @@ -use crate::url::{Encode, UrlBuf}; +use crate::url::{Encode, Url}; pub struct Display<'a> { - inner: &'a UrlBuf, + inner: Url<'a>, } -impl<'a> Display<'a> { - #[inline] - pub fn new(inner: &'a UrlBuf) -> Self { Self { inner } } +impl<'a> From> for Display<'a> { + fn from(value: Url<'a>) -> Self { Self { inner: value } } } impl<'a> std::fmt::Display for Display<'a> { 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() { Encode::from(self.inner).fmt(f)?; } diff --git a/yazi-shared/src/url/encode.rs b/yazi-shared/src/url/encode.rs index 6f404b9c..0a40c604 100644 --- a/yazi-shared/src/url/encode.rs +++ b/yazi-shared/src/url/encode.rs @@ -2,40 +2,35 @@ use std::{fmt::{self, Display}, ops::Not}; 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> { - loc: Loc<'a>, - scheme: SchemeRef<'a>, -} +#[derive(Clone, Copy)] +pub struct Encode<'a>(Url<'a>); impl<'a> From> 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> { - 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> { - #[inline] - pub(super) fn new(loc: Loc<'a>, scheme: SchemeRef<'a>) -> Self { Self { loc, scheme } } - #[inline] pub fn domain<'s>(s: &'s str) -> PercentEncode<'s> { const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':'); percent_encode(s.as_bytes(), SET) } - fn ports(&self) -> impl Display { - struct D<'a>(&'a Encode<'a>); + fn ports(self) -> impl Display { + struct D<'a>(Encode<'a>); impl Display for D<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { macro_rules! w { ($default_uri:expr, $default_urn:expr) => {{ - let uri = self.0.loc.uri().count(); - let urn = self.0.loc.urn().count(); + let uri = self.0.0.loc.uri().count(); + let urn = self.0.0.loc.urn().count(); match (uri != $default_uri, urn != $default_urn) { (true, true) => write!(f, ":{uri}:{urn}"), (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::Search(_) | SchemeRef::Archive(_) => w!(0, 0), SchemeRef::Sftp(_) => w!( - self.0.loc.as_os_str().is_empty().not() as usize, - self.0.loc.file_name().is_some() as usize + self.0.0.loc.as_os_str().is_empty().not() as usize, + self.0.0.loc.file_name().is_some() as usize ), } } @@ -63,7 +58,7 @@ impl<'a> Encode<'a> { impl Display for Encode<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use SchemeRef as S; - match self.scheme { + match self.0.scheme { S::Regular => write!(f, "regular://"), S::Search(d) => write!(f, "search://{}{}/", 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 -pub struct EncodeTilded<'a> { - loc: Loc<'a>, - scheme: SchemeRef<'a>, +#[derive(Clone, Copy)] +pub struct EncodeTilded<'a>(Url<'a>); + +impl<'a> From> for EncodeTilded<'a> { + fn from(value: Url<'a>) -> Self { Self(value) } } 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> { - fn from(value: &EncodeTilded<'a>) -> Self { Self::new(value.loc, value.scheme) } +impl<'a> From> for Encode<'a> { + fn from(value: EncodeTilded<'a>) -> Self { Self(value.0) } } impl Display for EncodeTilded<'_> { @@ -91,14 +88,14 @@ impl Display for EncodeTilded<'_> { use Encode as E; use SchemeRef as S; - let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), CONTROLS); - match self.scheme { + let loc = percent_encode(self.0.loc.as_os_str().as_encoded_bytes(), CONTROLS); + match self.0.scheme { 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) => { - 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())), } } } diff --git a/yazi-shared/src/url/traits.rs b/yazi-shared/src/url/traits.rs index 58b2e5cb..08cd2be3 100644 --- a/yazi-shared/src/url/traits.rs +++ b/yazi-shared/src/url/traits.rs @@ -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 { fn as_url(&self) -> Url<'_>; } @@ -11,7 +14,7 @@ impl AsUrl for Url<'_> { impl AsUrl for UrlBuf { #[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 { @@ -26,7 +29,12 @@ impl AsUrl for &mut UrlBuf { impl AsUrl for UrlCow<'_> { #[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<'_> { @@ -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> { 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> { 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) -> 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> { 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 UrlLike for T {} diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index 04edc737..591c2161 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -72,6 +72,39 @@ impl<'a> Url<'a> { 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] pub fn uri(self) -> &'a Uri { self.loc.uri() } diff --git a/yazi-vfs/src/fns.rs b/yazi-vfs/src/fns.rs index d6ebeede..40c518d2 100644 --- a/yazi-vfs/src/fns.rs +++ b/yazi-vfs/src/fns.rs @@ -3,7 +3,7 @@ use std::{ffi::OsString, io}; use tokio::{select, sync::{mpsc, oneshot}}; use yazi_fs::cha::Cha; use yazi_macro::ok_or_not_found; -use yazi_shared::url::{AsUrl, UrlBuf}; +use yazi_shared::url::{AsUrl, UrlBuf, UrlLike}; use crate::provider; diff --git a/yazi-vfs/src/op.rs b/yazi-vfs/src/op.rs index c8c0af7e..b0fe21d8 100644 --- a/yazi-vfs/src/op.rs +++ b/yazi-vfs/src/op.rs @@ -1,5 +1,5 @@ use yazi_fs::FilesOp; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlLike}; use crate::maybe_exists; diff --git a/yazi-vfs/src/provider/dir_entry.rs b/yazi-vfs/src/provider/dir_entry.rs index 574fa1e4..db1e0173 100644 --- a/yazi-vfs/src/provider/dir_entry.rs +++ b/yazi-vfs/src/provider/dir_entry.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, ffi::OsStr, io, sync::Arc}; use yazi_fs::{cha::{Cha, ChaType}, provider::FileHolder}; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlLike}; pub enum DirEntry { Regular(yazi_fs::provider::local::DirEntry), diff --git a/yazi-watcher/src/backend/backend.rs b/yazi-watcher/src/backend/backend.rs index d9465e93..6b3e45a6 100644 --- a/yazi-watcher/src/backend/backend.rs +++ b/yazi-watcher/src/backend/backend.rs @@ -2,7 +2,7 @@ use anyhow::Result; use hashbrown::HashSet; use tokio::sync::mpsc; use tracing::error; -use yazi_shared::url::{AsUrl, Url, UrlBuf}; +use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlLike}; use crate::{LINKED, WATCHED, backend}; diff --git a/yazi-watcher/src/watched.rs b/yazi-watcher/src/watched.rs index 69528cfb..c5a9f7af 100644 --- a/yazi-watcher/src/watched.rs +++ b/yazi-watcher/src/watched.rs @@ -1,7 +1,7 @@ use std::path::Path; use hashbrown::HashSet; -use yazi_shared::url::{AsUrl, UrlBuf}; +use yazi_shared::url::{AsUrl, UrlBuf, UrlLike}; #[derive(Default)] pub struct Watched(HashSet); diff --git a/yazi-watcher/src/watcher.rs b/yazi-watcher/src/watcher.rs index 33ca4142..7a0c0a20 100644 --- a/yazi-watcher/src/watcher.rs +++ b/yazi-watcher/src/watcher.rs @@ -4,7 +4,7 @@ use hashbrown::HashSet; use tokio::{pin, sync::{mpsc::{self, UnboundedReceiver}, watch}}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use yazi_fs::{File, FilesOp, provider::local}; -use yazi_shared::url::UrlBuf; +use yazi_shared::url::{UrlBuf, UrlLike}; use yazi_vfs::VfsFile; use crate::{LINKED, Linked, WATCHED, WATCHER, backend::Backend};