feat!: live editing of remote files (#3264)

This commit is contained in:
三咲雅 misaki masa 2025-10-20 13:48:57 +08:00 committed by GitHub
parent 46569a1f48
commit 782ecb16b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 621 additions and 339 deletions

242
Cargo.lock generated
View file

@ -481,9 +481,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
@ -527,9 +527,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.48"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
dependencies = [
"clap_builder",
"clap_derive",
@ -537,9 +537,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.48"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
dependencies = [
"anstream",
"anstyle",
@ -549,9 +549,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.58"
version = "4.5.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a"
checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c"
dependencies = [
"clap",
]
@ -568,9 +568,9 @@ dependencies = [
[[package]]
name = "clap_complete_nushell"
version = "4.5.8"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a0c951694691e65bf9d421d597d68416c22de9632e884c28412cb8cd8b73dce"
checksum = "811159f339691baacdf7d534df2946b9d217014081099e23d31d887d99521e70"
dependencies = [
"clap",
"clap_complete",
@ -578,9 +578,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.47"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2",
@ -590,9 +590,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.5"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "clipboard-win"
@ -671,6 +671,17 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core-models"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94950e87ea550d6d68f1993f3e7bebc8cb7235157bff84337d46195c3aa0b3f0"
dependencies = [
"hax-lib",
"pastey",
"rand 0.9.2",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -1399,20 +1410,20 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.7+wasi-0.2.4",
"wasip2",
]
[[package]]
@ -1443,9 +1454,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "globset"
version = "0.4.16"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
dependencies = [
"aho-corasick",
"bstr",
@ -1467,9 +1478,9 @@ dependencies = [
[[package]]
name = "half"
version = "2.7.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
@ -1499,6 +1510,43 @@ dependencies = [
"serde",
]
[[package]]
name = "hax-lib"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d9ba66d1739c68e0219b2b2238b5c4145f491ebf181b9c6ab561a19352ae86"
dependencies = [
"hax-lib-macros",
"num-bigint",
"num-traits",
]
[[package]]
name = "hax-lib-macros"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ba777a231a58d1bce1d68313fa6b6afcc7966adef23d60f45b8a2b9b688bf1"
dependencies = [
"hax-lib-macros-types",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "hax-lib-macros-types"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "867e19177d7425140b417cd27c2e05320e727ee682e98368f88b7194e80ad515"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -1620,9 +1668,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
[[package]]
name = "indexmap"
version = "2.11.4"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
@ -1718,17 +1766,6 @@ dependencies = [
"syn",
]
[[package]]
name = "io-uring"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags 2.9.4",
"cfg-if",
"libc",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@ -1774,7 +1811,7 @@ version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.4",
"libc",
]
@ -1829,6 +1866,59 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libcrux-intrinsics"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3b41dcbc21a5fb7efbbb5af7405b2e79c4bfe443924e90b13afc0080318d31"
dependencies = [
"core-models",
"hax-lib",
]
[[package]]
name = "libcrux-ml-kem"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d368d3e8d6a74e277178d54921eca112a1e6b7837d7d8bc555091acb5d817f5"
dependencies = [
"hax-lib",
"libcrux-intrinsics",
"libcrux-platform",
"libcrux-secrets",
"libcrux-sha3",
"rand 0.9.2",
]
[[package]]
name = "libcrux-platform"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db82d058aa76ea315a3b2092f69dfbd67ddb0e462038a206e1dcd73f058c0778"
dependencies = [
"libc",
]
[[package]]
name = "libcrux-secrets"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332737e629fe6ba7547f5c0f90559eac865d5dbecf98138ffae8f16ab8cbe33f"
dependencies = [
"hax-lib",
]
[[package]]
name = "libcrux-sha3"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29d95de4257eafdfaf3bffecadb615219b0ca920c553722b3646d32dde76c797"
dependencies = [
"hax-lib",
"libcrux-intrinsics",
"libcrux-platform",
]
[[package]]
name = "libfuzzer-sys"
version = "0.4.10"
@ -1908,11 +1998,11 @@ dependencies = [
[[package]]
name = "lru"
version = "0.16.1"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8"
checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
dependencies = [
"hashbrown 0.15.5",
"hashbrown 0.16.0",
]
[[package]]
@ -1997,7 +2087,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasi",
"windows-sys 0.59.0",
]
@ -2474,6 +2564,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
[[package]]
name = "pbkdf2"
version = "0.12.2"
@ -2803,7 +2899,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
"getrandom 0.3.4",
]
[[package]]
@ -2908,9 +3004,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.1"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
@ -2920,9 +3016,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
@ -2931,9 +3027,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rfc6979"
@ -2988,9 +3084,9 @@ dependencies = [
[[package]]
name = "russh"
version = "0.54.5"
version = "0.54.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3ee9363fcf66d434d8015d9ae7d879681206981534c21bfdff8a7e34f52cca"
checksum = "23ca8e9091b72afdc9979bddcd1988ad898fa7bc0b85c8da8c154c29d44319eb"
dependencies = [
"aes",
"base64ct",
@ -3017,6 +3113,7 @@ dependencies = [
"home",
"inout",
"internal-russh-forked-ssh-key",
"libcrux-ml-kem",
"log",
"md5",
"num-bigint",
@ -3486,9 +3583,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.106"
version = "2.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
dependencies = [
"proc-macro2",
"quote",
@ -3591,9 +3688,9 @@ dependencies = [
[[package]]
name = "tikv-jemalloc-sys"
version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b"
dependencies = [
"cc",
"libc",
@ -3601,9 +3698,9 @@ dependencies = [
[[package]]
name = "tikv-jemallocator"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865"
checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a"
dependencies = [
"libc",
"tikv-jemalloc-sys",
@ -3644,29 +3741,26 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.47.1"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"tokio-macros",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
@ -3945,6 +4039,17 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
"getrandom 0.3.4",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "uzers"
version = "0.12.1"
@ -4039,15 +4144,6 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
@ -4952,7 +5048,7 @@ dependencies = [
"futures",
"hashbrown 0.16.0",
"libc",
"lru 0.16.1",
"lru 0.16.2",
"mlua",
"ordered-float 5.1.0",
"parking_lot",

View file

@ -24,17 +24,17 @@ ansi-to-tui = "7.0.0"
anyhow = "1.0.100"
base64 = "0.22.1"
bitflags = { version = "2.9.4", features = [ "serde" ] }
clap = { version = "4.5.48", features = [ "derive" ] }
clap = { version = "4.5.49", features = [ "derive" ] }
core-foundation-sys = "0.8.7"
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0"
foldhash = "0.2.0"
futures = "0.3.31"
globset = "0.4.16"
globset = "0.4.17"
hashbrown = { version = "0.16.0", features = [ "serde" ] }
indexmap = { version = "2.11.4", features = [ "serde" ] }
indexmap = { version = "2.12.0", features = [ "serde" ] }
libc = "0.2.177"
lru = "0.16.1"
lru = "0.16.2"
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" ] }
@ -42,13 +42,13 @@ parking_lot = "0.12.5"
paste = "1.0.15"
percent-encoding = "2.3.2"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.1"
russh = { version = "0.54.5", default-features = false, features = [ "ring", "rsa" ] }
regex = "1.12.2"
russh = { version = "0.54.6", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.145"
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.47.1", features = [ "full" ] }
tokio = { version = "1.48.0", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.16"
toml = { version = "0.9.8" }

View file

@ -52,7 +52,7 @@ impl Actor for BulkRename {
defer! { tokio::spawn(Local.remove_file(tmp.clone())); }
TasksProxy::process_exec(
cwd,
cwd.into(),
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
opener.block,

View file

@ -2,11 +2,13 @@ use std::{mem, time::{Duration, Instant}};
use anyhow::Result;
use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::HashSet;
use tokio::sync::oneshot;
use yazi_fs::File;
use yazi_macro::{act, succ};
use yazi_parser::mgr::DownloadOpt;
use yazi_shared::data::Data;
use yazi_fs::{File, FsUrl, provider::{Provider, local::Local}};
use yazi_macro::succ;
use yazi_parser::mgr::{DownloadOpt, OpenOpt};
use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, url::UrlCow};
use yazi_vfs::VfsFile;
use crate::{Actor, Ctx};
@ -19,18 +21,21 @@ impl Actor for Download {
const NAME: &str = "download";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let mut wg1 = FuturesUnordered::new();
for url in opt.urls {
let (tx, rx) = oneshot::channel();
cx.tasks.scheduler.file_download(url.to_owned(), Some(tx));
wg1.push(async move { (rx.await == Ok(true), url) });
}
let cwd = cx.cwd().clone();
let scheduler = cx.tasks.scheduler.clone();
tokio::spawn(async move {
Self::prepare(&opt.urls).await;
let mut wg1 = FuturesUnordered::new();
for url in opt.urls {
let (tx, rx) = oneshot::channel();
scheduler.file_download(url.to_owned(), Some(tx));
wg1.push(async move { (rx.await == Ok(false), url) });
}
let mut wg2 = vec![];
let mut urls = Vec::with_capacity(wg1.len());
let mut files = Vec::with_capacity(wg1.len());
let mut instant = Instant::now();
while let Some((success, url)) = wg1.next().await {
@ -38,7 +43,8 @@ impl Actor for Download {
continue;
}
let Ok(f) = File::new(url).await else { continue };
let Ok(f) = File::new(&url).await else { continue };
urls.push(url);
files.push(f);
if instant.elapsed() >= Duration::from_secs(1) {
@ -50,9 +56,29 @@ impl Actor for Download {
if !files.is_empty() {
wg2.push(scheduler.fetch_mimetype(files));
}
futures::future::join_all(wg2).await;
if futures::future::join_all(wg2).await.into_iter().any(|b| !b) {
return;
}
if opt.open && !urls.is_empty() {
MgrProxy::open(OpenOpt {
cwd: Some(cwd.into()),
targets: urls,
interactive: false,
hovered: false,
});
}
});
succ!();
}
}
impl Download {
async fn prepare(urls: &[UrlCow<'_>]) {
let roots: HashSet<_> = urls.iter().filter_map(|u| u.cache_root()).collect();
for mut root in roots {
root.push("%lock");
Local.create_dir_all(root).await.ok();
}
}
}

View file

@ -48,7 +48,7 @@ impl Actor for Open {
.map(|(i, _)| i)
.collect();
let cwd = cx.cwd().clone();
let cwd = opt.cwd.unwrap_or_else(|| cx.cwd().clone().into());
if todo.is_empty() {
return act!(mgr:open_do, cx, OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive });
}

View file

@ -5,7 +5,7 @@ use yazi_fs::Splatter;
use yazi_macro::succ;
use yazi_parser::{mgr::OpenDoOpt, tasks::ProcessOpenOpt};
use yazi_proxy::{PickProxy, TasksProxy};
use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}};
use yazi_shared::{data::Data, url::UrlCow};
use crate::{Actor, Ctx};
@ -60,7 +60,7 @@ impl Actor for OpenDo {
impl OpenDo {
// TODO: remove
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(UrlCow<'static>, &str)>) {
fn match_and_open(cx: &Ctx, cwd: UrlCow<'static>, 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)) {

View file

@ -39,12 +39,12 @@ impl Actor for Shell {
}
TasksProxy::open_shell_compat(ProcessOpenOpt {
cwd,
cmd: Splatter::new(&selected).splat(opt.run.as_ref()),
args: selected,
block: opt.block,
cwd: cwd.into(),
cmd: Splatter::new(&selected).splat(opt.run.as_ref()),
args: selected,
block: opt.block,
orphan: opt.orphan,
done: None,
done: None,
spread: true,
});
});

View file

@ -1,4 +1,4 @@
use std::{env, fmt::Display, path::Path};
use std::{env, fmt::Display, path::PathBuf};
use anyhow::Result;
use ratatui::layout::Rect;
@ -35,11 +35,15 @@ impl Display for Adapter {
}
impl Adapter {
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
pub async fn image_show<P>(self, path: P, max: Rect) -> Result<Rect>
where
P: Into<PathBuf>,
{
if max.is_empty() {
return Ok(Rect::default());
}
let path = path.into();
match self {
Self::Kgp => drivers::Kgp::image_show(path, max).await,
Self::KgpOld => drivers::KgpOld::image_show(path, max).await,

View file

@ -1,4 +1,4 @@
use std::{io::Write, path::Path, process::Stdio};
use std::{io::Write, path::PathBuf, process::Stdio};
use ansi_to_tui::IntoText;
use anyhow::{Result, bail};
@ -11,7 +11,7 @@ use crate::{Adapter, Emulator};
pub(crate) struct Chafa;
impl Chafa {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let child = Command::new("chafa")
.args([
"-f",

View file

@ -1,4 +1,4 @@
use std::{fmt::Write, io::Write as ioWrite, path::Path};
use std::{fmt::Write, io::Write as ioWrite, path::PathBuf};
use anyhow::Result;
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
@ -12,7 +12,7 @@ use crate::{CLOSE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct Iip;
impl Iip {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;

View file

@ -1,5 +1,5 @@
use core::str;
use std::{io::Write, path::Path};
use std::{io::Write, path::PathBuf};
use anyhow::Result;
use base64::{Engine, engine::general_purpose};
@ -313,7 +313,7 @@ static DIACRITICS: [char; 297] = [
pub(crate) struct Kgp;
impl Kgp {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max);

View file

@ -1,5 +1,5 @@
use core::str;
use std::{io::Write, path::Path};
use std::{io::Write, path::PathBuf};
use anyhow::Result;
use base64::{Engine, engine::general_purpose};
@ -12,7 +12,7 @@ use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct KgpOld;
impl KgpOld {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;

View file

@ -1,4 +1,4 @@
use std::{io::Write, path::Path};
use std::{io::Write, path::PathBuf};
use anyhow::{Result, bail};
use crossterm::{cursor::MoveTo, queue};
@ -12,7 +12,7 @@ use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct Sixel;
impl Sixel {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;

View file

@ -1,4 +1,4 @@
use std::{path::{Path, PathBuf}, process::Stdio};
use std::{path::PathBuf, process::Stdio};
use anyhow::{Result, bail};
use image::ImageReader;
@ -42,14 +42,13 @@ impl Ueberzug {
DEMON.init(Some(tx))
}
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let Some(tx) = &*DEMON else {
bail!("uninitialized ueberzugpp");
};
let p = path.to_owned();
let (w, h) = tokio::task::spawn_blocking(move || {
ImageReader::open(p)?.with_guessed_format()?.into_dimensions()
let (w, h, path) = tokio::task::spawn_blocking(move || {
ImageReader::open(&path)?.with_guessed_format()?.into_dimensions().map(|(w, h)| (w, h, path))
})
.await??;
@ -62,7 +61,7 @@ impl Ueberzug {
})
.unwrap_or(max);
tx.send(Some((path.to_owned(), area)))?;
tx.send(Some((path, area)))?;
Adapter::shown_store(area);
Ok(area)
}

View file

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::Result;
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
@ -11,7 +11,7 @@ use crate::Dimension;
pub struct Image;
impl Image {
pub async fn precache(src: &Path, cache: &Path) -> Result<()> {
pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> {
let (mut img, orientation, icc) = Self::decode_from(src).await?;
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
@ -42,7 +42,7 @@ impl Image {
Ok(Local.write(cache, buf).await?)
}
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage> {
let (mut img, orientation, _) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
@ -96,7 +96,7 @@ impl Image {
}
}
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
async fn decode_from(path: PathBuf) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
let mut limits = Limits::no_limits();
if YAZI.tasks.image_alloc > 0 {
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
@ -108,7 +108,6 @@ impl Image {
limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);
}
let path = path.to_owned();
tokio::task::spawn_blocking(move || {
let mut reader = ImageReader::open(path)?;
reader.limits(limits);

View file

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::PathBuf;
use image::{ImageDecoder, ImageError};
@ -16,8 +16,7 @@ pub struct ImageInfo {
}
impl ImageInfo {
pub async fn new(path: &Path) -> image::ImageResult<Self> {
let path = path.to_owned();
pub async fn new(path: PathBuf) -> image::ImageResult<Self> {
tokio::task::spawn_blocking(move || {
let reader = image::ImageReader::open(path)?.with_guessed_format()?;

View file

@ -4,4 +4,4 @@ mod macros;
yazi_macro::mod_pub!(elements);
yazi_macro::mod_flat!(cha color composer error file icon id iter permit runtime stage style url urn utils);
yazi_macro::mod_flat!(cha color composer error file icon id iter permit runtime scheme stage style url urn utils);

View file

@ -0,0 +1,31 @@
use std::ops::Deref;
use mlua::{UserData, UserDataFields, Value};
use crate::cached_field;
pub struct Scheme {
inner: yazi_shared::scheme::Scheme,
v_kind: Option<Value>,
}
impl Deref for Scheme {
type Target = yazi_shared::scheme::Scheme;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Scheme {
pub fn new(scheme: &yazi_shared::scheme::Scheme) -> Self {
Self { inner: scheme.clone(), v_kind: None }
}
}
impl UserData for Scheme {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, kind, |_, me| Ok(me.kind()));
fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual()));
}
}

View file

@ -3,7 +3,7 @@ use std::ops::Deref;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_shared::{IntoOsStr, url::{AsUrl, UrlCow, UrlLike}};
use crate::{Urn, cached_field, deprecate};
use crate::{Scheme, Urn, cached_field, deprecate};
pub type UrlRef = UserDataRef<Url>;
@ -16,6 +16,8 @@ pub struct Url {
v_urn: Option<Value>,
v_base: Option<Value>,
v_parent: Option<Value>,
v_scheme: Option<Value>,
v_domain: Option<Value>,
}
@ -64,6 +66,8 @@ impl Url {
v_urn: None,
v_base: None,
v_parent: None,
v_scheme: None,
v_domain: None,
}
}
@ -105,6 +109,8 @@ impl UserData for Url {
cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));
cached_field!(fields, urn, |_, me| Ok(Urn::new(me.urn())));
cached_field!(fields, base, |_, me| Ok(me.base().map(Self::new)));
cached_field!(fields, scheme, |_, me| Ok(Scheme::new(&me.scheme)));
cached_field!(fields, domain, |lua, me| {
me.scheme.domain().map(|s| lua.create_string(s)).transpose()
});

View file

@ -28,7 +28,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
# External dependencies
clap = { workspace = true }
clap_complete = "4.5.58"
clap_complete = "4.5.59"
clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.8"
clap_complete_nushell = "4.5.9"
vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] }

View file

@ -1,6 +1,6 @@
use std::path::PathBuf;
use clap::{Parser, command};
use clap::Parser;
use yazi_shared::{Id, url::UrlBuf};
#[derive(Debug, Default, Parser)]

View file

@ -41,9 +41,9 @@ yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
# External build dependencies
anyhow = { workspace = true }
clap = { workspace = true }
clap_complete = "4.5.58"
clap_complete = "4.5.59"
clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.8"
clap_complete_nushell = "4.5.9"
serde_json = { workspace = true }
vergen-gitcl = { version = "1.0.8", features = [ "build" ] }

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use anyhow::{Result, bail};
use clap::{Parser, Subcommand, command};
use clap::{Parser, Subcommand};
use yazi_shared::{Id, event::Cmd};
#[derive(Parser)]

View file

@ -13,5 +13,5 @@ proc-macro = true
[dependencies]
# External dependencies
syn = { version = "2.0.106", features = [ "full" ] }
syn = { version = "2.0.107", features = [ "full" ] }
quote = "1.0.41"

View file

@ -131,7 +131,7 @@ impl<'de> Deserialize<'de> for PatIcons {
{
#[derive(Deserialize)]
struct Shadow {
name: Pattern,
url: Pattern,
text: String,
fg: Option<Color>,
}
@ -139,7 +139,7 @@ impl<'de> Deserialize<'de> for PatIcons {
Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)?
.into_iter()
.map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
.map(|s| (s.url, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
.collect(),
))
}

View file

@ -67,7 +67,7 @@ signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
tikv-jemallocator = "0.6.0"
tikv-jemallocator = "0.6.1"
[[bin]]
name = "yazi"

View file

@ -155,14 +155,6 @@ impl Cha {
}
}
pub fn mtime_dur(self) -> anyhow::Result<Duration> {
if let Some(mtime) = self.mtime {
Ok(mtime.duration_since(UNIX_EPOCH)?)
} else {
bail!("mtime not available");
}
}
pub fn btime_dur(self) -> anyhow::Result<Duration> {
if let Some(btime) = self.btime {
Ok(btime.duration_since(UNIX_EPOCH)?)
@ -178,4 +170,12 @@ impl Cha {
bail!("ctime not available");
}
}
pub fn mtime_dur(self) -> anyhow::Result<Duration> {
if let Some(mtime) = self.mtime {
Ok(mtime.duration_since(UNIX_EPOCH)?)
} else {
bail!("mtime not available");
}
}
}

View file

@ -75,6 +75,15 @@ impl TryFrom<u16> for ChaMode {
}
}
#[cfg(unix)]
impl From<ChaMode> for std::fs::Permissions {
fn from(value: ChaMode) -> Self {
use std::os::unix::fs::PermissionsExt;
std::fs::Permissions::from_mode(value.bits() as _)
}
}
impl ChaMode {
// Convert a file mode to a string representation
#[cfg(unix)]

View file

@ -47,7 +47,7 @@ impl Cwd {
}
pub fn ensure(url: Url) -> Cow<Path> {
use std::io::ErrorKind::{AlreadyExists, NotADirectory, NotFound};
use std::{io::ErrorKind::{AlreadyExists, NotADirectory, NotFound}, path::Component as C};
let Some(cache) = url.cache() else {
return url.loc.as_path().into();
@ -58,7 +58,20 @@ impl Cwd {
return cache.into();
}
let count = cache.strip_prefix(Xdg::cache_dir()).expect("under cache dir").components().count();
let latter = cache.strip_prefix(Xdg::cache_dir()).expect("under cache dir");
let mut it = latter.components().peekable();
while it.peek() == Some(&C::CurDir) {
it.next().unwrap();
}
let mut count = 0;
for c in it {
match c {
C::Prefix(_) | C::RootDir | C::ParentDir => return cache.into(),
C::CurDir | C::Normal(_) => count += 1,
}
}
for n in (0..count).rev() {
let mut it = cache.components();
for _ in 0..n {

View file

@ -0,0 +1,53 @@
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::cha::{Cha, ChaMode};
#[derive(Clone, Copy, Debug, Default)]
pub struct Attrs {
pub mode: Option<ChaMode>,
pub atime: Option<SystemTime>,
pub btime: Option<SystemTime>,
pub mtime: Option<SystemTime>,
}
impl From<Cha> for Attrs {
fn from(value: Cha) -> Self {
Self { mode: Some(value.mode), atime: value.atime, btime: value.btime, mtime: value.mtime }
}
}
impl From<Attrs> for std::fs::FileTimes {
fn from(attrs: Attrs) -> Self {
let mut t = Self::new();
if let Some(atime) = attrs.atime {
t = t.set_accessed(atime);
}
#[cfg(target_os = "macos")]
if let Some(btime) = attrs.btime {
use std::os::macos::fs::FileTimesExt;
t = t.set_created(btime);
}
#[cfg(windows)]
if let Some(btime) = attrs.btime {
use std::os::windows::fs::FileTimesExt;
t = t.set_created(btime);
}
if let Some(mtime) = attrs.mtime {
t = t.set_modified(mtime);
}
t
}
}
impl Attrs {
pub fn atime_dur(self) -> Option<Duration> { self.atime?.duration_since(UNIX_EPOCH).ok() }
pub fn btime_dur(self) -> Option<Duration> { self.btime?.duration_since(UNIX_EPOCH).ok() }
pub fn mtime_dur(self) -> Option<Duration> { self.mtime?.duration_since(UNIX_EPOCH).ok() }
}

View file

@ -2,7 +2,7 @@ use std::{io, path::Path};
use yazi_shared::scheme::SchemeRef;
use crate::{cha::Cha, provider::FileBuilder};
use crate::provider::{Attrs, FileBuilder};
#[derive(Default)]
pub struct Gate(tokio::fs::OpenOptions);
@ -15,9 +15,11 @@ impl FileBuilder for Gate {
self
}
fn cha(&mut self, _cha: Cha) -> &mut Self {
fn attrs(&mut self, _attrs: Attrs) -> &mut Self {
#[cfg(unix)]
self.0.mode(_cha.mode.bits() as _);
if let Some(mode) = _attrs.mode {
self.0.mode(mode.bits() as _);
}
self
}

View file

@ -2,7 +2,7 @@ use std::{io, path::{Path, PathBuf}};
use yazi_shared::url::{AsUrl, UrlCow};
use crate::{cha::Cha, path::absolute_url, provider::Provider};
use crate::{cha::Cha, path::absolute_url, provider::{Attrs, Provider}};
#[derive(Clone, Copy)]
pub struct Local;
@ -40,14 +40,14 @@ impl Provider for Local {
}
#[inline]
async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
async fn copy<P, Q>(&self, from: P, to: Q, attrs: Attrs) -> io::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let from = from.as_ref().to_owned();
let to = to.as_ref().to_owned();
Self::copy_impl(from, to, cha).await
Self::copy_impl(from, to, attrs).await
}
#[inline]
@ -232,23 +232,25 @@ impl Provider for Local {
}
impl Local {
async fn copy_impl(from: PathBuf, to: PathBuf, cha: Cha) -> io::Result<u64> {
async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::Result<u64> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
use std::os::unix::fs::OpenOptionsExt;
tokio::task::spawn_blocking(move || {
let mut reader = std::fs::File::open(from)?;
let mut writer = std::fs::OpenOptions::new()
.mode(cha.mode.bits() as _)
.write(true)
.create(true)
.truncate(true)
.open(to)?;
let mut opts = std::fs::OpenOptions::new();
if let Some(mode) = attrs.mode {
opts.mode(mode.bits() as _);
}
let mut reader = std::fs::File::open(from)?;
let mut writer = opts.write(true).create(true).truncate(true).open(to)?;
let written = std::io::copy(&mut reader, &mut writer)?;
writer.set_permissions(cha.into()).ok();
writer.set_times(cha.into()).ok();
if let Some(mode) = attrs.mode {
writer.set_permissions(mode.into()).ok();
}
writer.set_times(attrs.into()).ok();
Ok(written)
})
@ -261,7 +263,7 @@ impl Local {
let written = std::fs::copy(from, &to)?;
if let Ok(file) = std::fs::File::options().write(true).open(to) {
file.set_times(cha.into()).ok();
file.set_times(attrs.into()).ok();
}
Ok(written)

View file

@ -1,40 +0,0 @@
use std::fs::FileTimes;
use crate::cha::Cha;
impl From<Cha> for FileTimes {
fn from(cha: Cha) -> Self {
let mut t = std::fs::FileTimes::new();
if let Some(atime) = cha.atime {
t = t.set_accessed(atime);
}
#[cfg(target_os = "macos")]
if let Some(btime) = cha.btime {
use std::os::macos::fs::FileTimesExt;
t = t.set_created(btime);
}
#[cfg(windows)]
if let Some(btime) = cha.btime {
use std::os::windows::fs::FileTimesExt;
t = t.set_created(btime);
}
if let Some(mtime) = cha.mtime {
t = t.set_modified(mtime);
}
t
}
}
#[cfg(unix)]
impl From<Cha> for std::fs::Permissions {
fn from(cha: Cha) -> Self {
use std::os::unix::fs::PermissionsExt;
std::fs::Permissions::from_mode(cha.mode.bits() as _)
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local metadata read_dir);
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local read_dir);

View file

@ -1,3 +1,3 @@
yazi_macro::mod_pub!(local);
yazi_macro::mod_flat!(traits);
yazi_macro::mod_flat!(attrs traits);

View file

@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use yazi_macro::ok_or_not_found;
use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlCow}};
use crate::cha::{Cha, ChaType};
use crate::{cha::{Cha, ChaType}, provider::Attrs};
pub trait Provider {
type File: AsyncRead + AsyncWrite + Unpin;
@ -23,7 +23,7 @@ pub trait Provider {
where
P: AsRef<Path>;
fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> impl Future<Output = io::Result<u64>>
fn copy<P, Q>(&self, from: P, to: Q, attrs: Attrs) -> impl Future<Output = io::Result<u64>>
where
P: AsRef<Path>,
Q: AsRef<Path>;
@ -245,7 +245,7 @@ pub trait FileBuilder {
fn append(&mut self, append: bool) -> &mut Self;
fn cha(&mut self, cha: Cha) -> &mut Self;
fn attrs(&mut self, attrs: Attrs) -> &mut Self;
fn create(&mut self, create: bool) -> &mut Self;

View file

@ -65,14 +65,14 @@ impl<'a> FsUrl<'a> for Url<'a> {
}
}
impl<'a> FsUrl<'a> for UrlBuf {
impl FsUrl<'_> for UrlBuf {
fn cache(&self) -> Option<PathBuf> { self.as_url().cache() }
fn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }
fn cache_root(&self) -> Option<PathBuf> { self.as_url().cache_root() }
fn unified_path(self) -> Cow<'a, Path> {
fn unified_path(self) -> Cow<'static, Path> {
self.cache().unwrap_or_else(|| self.loc.into_path()).into()
}
}

View file

@ -3,18 +3,26 @@ use yazi_shared::{event::CmdCow, url::UrlCow};
#[derive(Debug)]
pub struct OpenOpt {
pub cwd: Option<UrlCow<'static>>,
pub targets: Vec<UrlCow<'static>>,
pub interactive: bool,
pub hovered: bool,
}
impl From<CmdCow> for OpenOpt {
fn from(mut c: CmdCow) -> Self {
Self {
impl TryFrom<CmdCow> for OpenOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
Ok(Self {
cwd: c.take("cwd").ok(),
targets: c.take_seq(),
interactive: c.bool("interactive"),
hovered: c.bool("hovered"),
}
})
}
}

View file

@ -1,10 +1,10 @@
use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
use yazi_shared::{event::CmdCow, url::UrlCow};
#[derive(Debug, Default)]
pub struct OpenDoOpt {
pub cwd: UrlBuf,
pub cwd: UrlCow<'static>,
pub targets: Vec<UrlCow<'static>>,
pub interactive: bool,
}

View file

@ -3,12 +3,12 @@ use std::ffi::OsString;
use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::oneshot;
use yazi_shared::{event::CmdCow, url::{UrlBuf, UrlCow}};
use yazi_shared::{event::CmdCow, url::UrlCow};
// --- Exec
#[derive(Debug)]
pub struct ProcessOpenOpt {
pub cwd: UrlBuf,
pub cwd: UrlCow<'static>,
pub cmd: OsString,
pub args: Vec<UrlCow<'static>>,
pub block: bool,

View file

@ -1,7 +1,11 @@
local function fetch(_, job)
local updates = {}
for _, file in ipairs(job.files) do
updates[file.url] = "folder/local"
if file.url.scheme.is_virtual then
updates[file.url] = "folder/remote"
else
updates[file.url] = "folder/local"
end
end
ya.emit("update_mimes", { updates = updates })

View file

@ -17,7 +17,12 @@ pub struct Highlighter {
impl Highlighter {
#[inline]
pub fn new(path: &Path) -> Self { Self { path: path.to_owned() } }
pub fn new<P>(path: P) -> Self
where
P: Into<PathBuf>,
{
Self { path: path.into() }
}
pub fn init() -> (&'static Theme, &'static SyntaxSet) {
let f = || {

View file

@ -1,7 +1,8 @@
use mlua::{Function, IntoLuaMulti, Lua, Value};
use yazi_adapter::{ADAPTOR, Image};
use yazi_binding::{Error, UrlRef, elements::Rect};
use yazi_shared::url::UrlLike;
use yazi_fs::FsUrl;
use yazi_shared::url::{AsUrl, UrlLike};
use super::Utils;
use crate::bindings::ImageInfo;
@ -9,9 +10,7 @@ use crate::bindings::ImageInfo;
impl Utils {
pub(super) fn image_info(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, url: UrlRef| async move {
let Some(path) = url.as_path() else {
return (Value::Nil, Error::custom("Only local files are supported")).into_lua_multi(&lua);
};
let path = url.as_url().unified_path().into_owned();
match yazi_adapter::ImageInfo::new(path).await {
Ok(info) => ImageInfo::from(info).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua),
@ -21,9 +20,7 @@ impl Utils {
pub(super) fn image_show(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move {
let Some(path) = url.as_path() else {
return (Value::Nil, Error::custom("Only local files are supported")).into_lua_multi(&lua);
};
let path = url.as_url().unified_path();
match ADAPTOR.get().image_show(path, *rect).await {
Ok(area) => Rect::from(area).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua),
@ -33,9 +30,11 @@ impl Utils {
pub(super) fn image_precache(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (src, dist): (UrlRef, UrlRef)| async move {
let Some((src, dist)) = src.as_path().zip(dist.as_path()) else {
return (Value::Nil, Error::custom("Only local files are supported")).into_lua_multi(&lua);
let Some(dist) = dist.as_path() else {
return (Value::Nil, Error::custom("Destination must be a local path"))
.into_lua_multi(&lua);
};
let src = src.as_url().unified_path().into_owned();
match Image::precache(src, dist).await {
Ok(()) => true.into_lua_multi(&lua),
Err(e) => (false, Error::custom(e.to_string())).into_lua_multi(&lua),

View file

@ -1,9 +1,10 @@
use mlua::{AnyUserData, ExternalError, Function, IntoLuaMulti, Lua, Table, Value};
use yazi_binding::{Error, elements::{Area, Renderable, Text}};
use yazi_config::YAZI;
use yazi_fs::FsUrl;
use yazi_parser::mgr::{PreviewLock, UpdatePeekedOpt};
use yazi_proxy::MgrProxy;
use yazi_shared::{errors::PeekError, url::UrlLike};
use yazi_shared::{errors::PeekError, url::AsUrl};
use super::Utils;
use crate::external::Highlighter;
@ -14,10 +15,7 @@ impl Utils {
let area: Area = t.raw_get("area")?;
let mut lock = PreviewLock::try_from(t)?;
let Some(path) = lock.url.as_path() else {
return "Only local files are supported".into_lua_multi(&lua);
};
let path = lock.url.as_url().unified_path();
let inner = match Highlighter::new(path).highlight(lock.skip, area.size()).await {
Ok(text) => text,
Err(e @ PeekError::Exceed(max)) => return (e.to_string(), max).into_lua_multi(&lua),

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use yazi_macro::{emit, relay};
use yazi_parser::mgr::{FilterOpt, FindDoOpt, OpenDoOpt, SearchOpt, UpdatePeekedOpt, UpdateSpottedOpt};
use yazi_parser::mgr::{FilterOpt, FindDoOpt, OpenDoOpt, OpenOpt, SearchOpt, UpdatePeekedOpt, UpdateSpottedOpt};
use yazi_shared::{SStr, url::UrlBuf};
pub struct MgrProxy;
@ -19,6 +19,10 @@ impl MgrProxy {
emit!(Call(relay!(mgr:arrow, [step.into()])));
}
pub fn open(opt: OpenOpt) {
emit!(Call(relay!(mgr:open).with_any("opt", opt)));
}
pub fn open_do(opt: OpenDoOpt) {
emit!(Call(relay!(mgr:open_do).with_any("opt", opt)));
}

View file

@ -14,7 +14,7 @@ impl TasksProxy {
}
pub async fn process_exec(
cwd: UrlBuf,
cwd: UrlCow<'static>,
cmd: OsString,
args: Vec<UrlCow<'static>>,
block: bool,

View file

@ -4,7 +4,7 @@ use anyhow::{Context, Result, anyhow};
use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder, Provider, local::Local}};
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder, Provider, local::Local}};
use yazi_macro::ok_or_not_found;
use yazi_shared::{timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name};
@ -319,13 +319,9 @@ impl File {
let mut dirs = VecDeque::from([task.url.clone()]);
while let Some(src) = dirs.pop_front() {
let cache = continue_unless_ok!(src.cache().ok_or("Cannot determine cache path"));
continue_unless_ok!(match Local.create_dir(&cache).await {
Err(e) if e.kind() != AlreadyExists => Err(e),
_ => Ok(()),
});
let mut it = continue_unless_ok!(provider::read_dir(&src).await);
tokio::task::spawn_blocking(move || _ = Cwd::ensure(src.as_url())).await.ok();
while let Ok(Some(entry)) = it.next().await {
let from = entry.url();
let cha = continue_unless_ok!(Self::cha(&from, true, Some(entry)).await);
@ -351,16 +347,17 @@ impl File {
let cha = task.cha.unwrap();
let cache = task.url.cache().context("Cannot determine cache path")?;
let cache_tmp = Self::tmp(&cache).await?;
let cache_tmp = Self::tmp(&cache).await.context("Cannot determine temporary download cache")?;
let mut it = copy_with_progress(&task.url, &cache_tmp, cha);
while let Some(res) = it.recv().await {
match res {
Ok(0) => {
provider::rename(&cache_tmp, &cache).await?;
provider::rename(cache_tmp, cache).await.context("Cannot persist downloaded file")?;
let lock = task.url.cache_lock().context("Cannot determine cache lock")?;
Local.write(lock, format!("{:x}", cha.hash_u128())).await?;
let hash = format!("{:x}", cha.hash_u128());
Local.write(lock, hash).await.context("Cannot lock cache")?;
break;
}
@ -388,7 +385,7 @@ impl File {
pub(crate) async fn upload(&self, task: FileInUpload) -> Result<(), FileOutUpload> {
let cha = Self::cha(&task.url, true, None).await?;
if cha.is_orphan() {
Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?;
Err(anyhow!("Source of symlink doesn't exist"))?;
}
if !cha.is_dir() {
@ -456,19 +453,22 @@ impl File {
Err(anyhow!("Remote file has changed since last download"))?;
}
let tmp = Self::tmp(&task.url).await?;
let tmp = Self::tmp(&task.url).await.context("Cannot determine temporary upload path")?;
let mut it = copy_with_progress(&task.cache, &tmp, task.cha);
while let Some(res) = it.recv().await {
match res {
Ok(0) if hash != Self::cha(&task.url, true, None).await?.hash_u128() => {
Err(anyhow!("Remote file has changed during upload"))?
}
Ok(0) => {
provider::rename(&tmp, &task.url).await?;
let cha = Self::cha(&task.url, true, None).await.context("Cannot stat original file")?;
if hash != cha.hash_u128() {
Err(anyhow!("Remote file has changed during upload"))?;
}
let hash = format!("{:x}", Self::cha(&task.url, true, None).await?.hash_u128());
Local.write(lock, hash).await?;
provider::rename(&tmp, &task.url).await.context("Cannot persist uploaded file")?;
let cha = Self::cha(&task.url, true, None).await.context("Cannot stat uploaded file")?;
let hash = format!("{:x}", cha.hash_u128());
Local.write(lock, hash).await.context("Cannot lock cache")?;
break;
}

View file

@ -11,7 +11,7 @@ pub(crate) enum FileOutPaste {
}
impl From<std::io::Error> for FileOutPaste {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutPaste {
@ -51,7 +51,7 @@ pub(crate) enum FileOutPasteDo {
}
impl From<std::io::Error> for FileOutPasteDo {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutPasteDo {
@ -83,11 +83,11 @@ pub(crate) enum FileOutLink {
}
impl From<anyhow::Error> for FileOutLink {
fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl From<std::io::Error> for FileOutLink {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutLink {
@ -126,7 +126,7 @@ pub(crate) enum FileOutHardlink {
}
impl From<std::io::Error> for FileOutHardlink {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutHardlink {
@ -160,7 +160,7 @@ pub(crate) enum FileOutHardlinkDo {
}
impl From<std::io::Error> for FileOutHardlinkDo {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutHardlinkDo {
@ -188,7 +188,7 @@ pub(crate) enum FileOutDelete {
}
impl From<std::io::Error> for FileOutDelete {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutDelete {
@ -221,7 +221,7 @@ pub(crate) enum FileOutDeleteDo {
}
impl From<std::io::Error> for FileOutDeleteDo {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutDeleteDo {
@ -248,7 +248,7 @@ pub(crate) enum FileOutTrash {
}
impl From<std::io::Error> for FileOutTrash {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutTrash {
@ -276,7 +276,7 @@ pub(crate) enum FileOutDownload {
}
impl From<std::io::Error> for FileOutDownload {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutDownload {
@ -313,11 +313,11 @@ pub(crate) enum FileOutDownloadDo {
}
impl From<std::io::Error> for FileOutDownloadDo {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl From<anyhow::Error> for FileOutDownloadDo {
fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutDownloadDo {
@ -351,11 +351,11 @@ pub(crate) enum FileOutUpload {
}
impl From<std::io::Error> for FileOutUpload {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl From<anyhow::Error> for FileOutUpload {
fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutUpload {
@ -391,11 +391,11 @@ pub(crate) enum FileOutUploadDo {
}
impl From<std::io::Error> for FileOutUploadDo {
fn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl From<anyhow::Error> for FileOutUploadDo {
fn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }
fn from(value: anyhow::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutUploadDo {

View file

@ -1,6 +1,6 @@
use yazi_shared::Id;
use crate::{file::{FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUpload, FileInUploadDo}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}};
use crate::{file::{FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUploadDo}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}};
#[derive(Debug)]
pub(crate) enum TaskIn {
@ -11,7 +11,6 @@ pub(crate) enum TaskIn {
FileDelete(FileInDelete),
FileTrash(FileInTrash),
FileDownload(FileInDownload),
FileUpload(FileInUpload),
FileUploadDo(FileInUploadDo),
// Plugin
PluginEntry(PluginInEntry),
@ -23,7 +22,7 @@ pub(crate) enum TaskIn {
impl_from_in! {
// File
FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUpload(FileInUpload), FileUploadDo(FileInUploadDo),
FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUploadDo(FileInUploadDo),
// Plugin
PluginEntry(PluginInEntry),
// Prework
@ -40,7 +39,6 @@ impl TaskIn {
Self::FileDelete(r#in) => r#in.id,
Self::FileTrash(r#in) => r#in.id,
Self::FileDownload(r#in) => r#in.id,
Self::FileUpload(r#in) => r#in.id,
Self::FileUploadDo(r#in) => r#in.id,
// Plugin
Self::PluginEntry(r#in) => r#in.id,

View file

@ -1,7 +1,7 @@
use std::ffi::OsString;
use tokio::sync::mpsc;
use yazi_shared::{Id, url::{UrlBuf, UrlCow}};
use yazi_shared::{Id, url::UrlCow};
use super::ShellOpt;
@ -9,7 +9,7 @@ use super::ShellOpt;
#[derive(Debug)]
pub(crate) struct ProcessInBlock {
pub(crate) id: Id,
pub(crate) cwd: UrlBuf,
pub(crate) cwd: UrlCow<'static>,
pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
}
@ -24,7 +24,7 @@ impl From<ProcessInBlock> for ShellOpt {
#[derive(Debug)]
pub(crate) struct ProcessInOrphan {
pub(crate) id: Id,
pub(crate) cwd: UrlBuf,
pub(crate) cwd: UrlCow<'static>,
pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
}
@ -39,7 +39,7 @@ impl From<ProcessInOrphan> for ShellOpt {
#[derive(Debug)]
pub(crate) struct ProcessInBg {
pub(crate) id: Id,
pub(crate) cwd: UrlBuf,
pub(crate) cwd: UrlCow<'static>,
pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
pub(crate) cancel: mpsc::Receiver<()>,

View file

@ -3,10 +3,10 @@ use std::{ffi::OsString, process::Stdio};
use anyhow::Result;
use tokio::process::{Child, Command};
use yazi_fs::{Cwd, FsUrl};
use yazi_shared::url::{AsUrl, UrlBuf, UrlCow};
use yazi_shared::url::{AsUrl, UrlCow};
pub(crate) struct ShellOpt {
pub(crate) cwd: UrlBuf,
pub(crate) cwd: UrlCow<'static>,
pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
pub(crate) piped: bool,

View file

@ -282,8 +282,8 @@ impl Scheduler {
}
for rx in wg {
if rx.await != Ok(true) {
return false;
if rx.await != Ok(false) {
return false; // Canceled or error
}
}
true
@ -409,7 +409,6 @@ impl Scheduler {
TaskIn::FileDelete(r#in) => file.delete_do(r#in).await.map_err(Into::into),
TaskIn::FileTrash(r#in) => file.trash_do(r#in).await.map_err(Into::into),
TaskIn::FileDownload(r#in) => file.download_do(r#in).await.map_err(Into::into),
TaskIn::FileUpload(r#in) => file.upload(r#in).await.map_err(Into::into),
TaskIn::FileUploadDo(r#in) => file.upload_do(r#in).await.map_err(Into::into),
// Plugin
TaskIn::PluginEntry(r#in) => plugin.macro_do(r#in).await.map_err(Into::into),

View file

@ -146,6 +146,21 @@ impl Operator {
status.into()
}
pub async fn rename_posix<'a, F, T>(&self, from: F, to: T) -> Result<(), Error>
where
F: ToByteStr<'a>,
T: ToByteStr<'a>,
{
if self.extensions.lock().get("posix-rename@openssh.com").is_none_or(|s| s != "1") {
return Err(Error::Unsupported);
}
let data = requests::ExtendedRename::new(from, to)?;
let status: responses::Status =
self.send(requests::Extended::new("posix-rename@openssh.com", data)).await?;
status.into()
}
pub async fn readlink<'a, P>(&self, path: P) -> Result<PathBuf, Error>
where
P: ToByteStr<'a>,

View file

@ -25,6 +25,7 @@ pub enum Packet<'a> {
Rename(requests::Rename<'a>),
Readlink(requests::Readlink<'a>),
Symlink(requests::Symlink<'a>),
ExtendedRename(requests::Extended<'a, requests::ExtendedRename<'a>>),
ExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>),
ExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>),
ExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>),
@ -59,6 +60,7 @@ impl_from_packet! {
Rename(requests::Rename<'a>),
Readlink(requests::Readlink<'a>),
Symlink(requests::Symlink<'a>),
ExtendedRename(requests::Extended<'a, requests::ExtendedRename<'a>>),
ExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>),
ExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>),
ExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>),
@ -105,6 +107,7 @@ impl Packet<'_> {
Self::Rename(_) => 18,
Self::Readlink(_) => 19,
Self::Symlink(_) => 20,
Self::ExtendedRename(_) => 200,
Self::ExtendedFsync(_) => 200,
Self::ExtendedHardlink(_) => 200,
Self::ExtendedLimits(_) => 200,
@ -141,6 +144,7 @@ impl Packet<'_> {
Self::Rename(v) => v.id,
Self::Readlink(v) => v.id,
Self::Symlink(v) => v.id,
Self::ExtendedRename(v) => v.id,
Self::ExtendedFsync(v) => v.id,
Self::ExtendedHardlink(v) => v.id,
Self::ExtendedLimits(v) => v.id,
@ -177,6 +181,7 @@ impl Packet<'_> {
Self::Rename(v) => v.id = id,
Self::Readlink(v) => v.id = id,
Self::Symlink(v) => v.id = id,
Self::ExtendedRename(v) => v.id = id,
Self::ExtendedFsync(v) => v.id = id,
Self::ExtendedHardlink(v) => v.id = id,
Self::ExtendedLimits(v) => v.id = id,
@ -215,6 +220,7 @@ impl Packet<'_> {
Self::Rename(v) => type_len + v.len(),
Self::Readlink(v) => type_len + v.len(),
Self::Symlink(v) => type_len + v.len(),
Self::ExtendedRename(v) => type_len + v.len(),
Self::ExtendedFsync(v) => type_len + v.len(),
Self::ExtendedHardlink(v) => type_len + v.len(),
Self::ExtendedLimits(v) => type_len + v.len(),

View file

@ -27,6 +27,27 @@ pub trait ExtendedData: Debug + Serialize + for<'de> Deserialize<'de> {
fn len(&self) -> usize;
}
// --- POSIX Rename
#[derive(Debug, Deserialize, Serialize)]
pub struct ExtendedRename<'a> {
pub from: ByteStr<'a>,
pub to: ByteStr<'a>,
}
impl<'a> ExtendedRename<'a> {
pub fn new<F, T>(from: F, to: T) -> Result<Self, Error>
where
F: ToByteStr<'a>,
T: ToByteStr<'a>,
{
Ok(Self { from: from.to_byte_str()?, to: to.to_byte_str()? })
}
}
impl ExtendedData for ExtendedRename<'_> {
fn len(&self) -> usize { 4 + self.from.len() + 4 + self.to.len() }
}
// --- Fsync
#[derive(Debug, Deserialize, Serialize)]
pub struct ExtendedFsync<'a> {

View file

@ -1,7 +1,7 @@
use std::{ffi::OsString, io};
use tokio::{select, sync::{mpsc, oneshot}};
use yazi_fs::cha::Cha;
use yazi_fs::provider::Attrs;
use yazi_macro::ok_or_not_found;
use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlLike};
@ -63,22 +63,27 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result<UrlBuf> {
Ok(url)
}
pub fn copy_with_progress<U, V>(from: U, to: V, cha: Cha) -> mpsc::Receiver<Result<u64, io::Error>>
pub fn copy_with_progress<U, V, A>(
from: U,
to: V,
attrs: A,
) -> mpsc::Receiver<Result<u64, io::Error>>
where
U: AsUrl,
V: AsUrl,
A: Into<Attrs>,
{
_copy_with_progress(from.as_url(), to.as_url(), cha)
_copy_with_progress(from.as_url(), to.as_url(), attrs.into())
}
fn _copy_with_progress(from: Url, to: Url, cha: Cha) -> mpsc::Receiver<Result<u64, io::Error>> {
fn _copy_with_progress(from: Url, to: Url, attrs: Attrs) -> mpsc::Receiver<Result<u64, io::Error>> {
let (prog_tx, prog_rx) = mpsc::channel(1);
let (done_tx, mut done_rx) = oneshot::channel();
tokio::spawn({
let (from, to) = (from.to_owned(), to.to_owned());
async move {
done_tx.send(provider::copy(from, to, cha).await).ok();
done_tx.send(provider::copy(from, to, attrs).await).ok();
}
});

View file

@ -1,6 +1,6 @@
use std::{io, path::Path};
use yazi_fs::{cha::Cha, provider::FileBuilder};
use yazi_fs::provider::{Attrs, FileBuilder};
use yazi_shared::scheme::SchemeRef;
pub enum Gate {
@ -27,10 +27,10 @@ impl FileBuilder for Gate {
self
}
fn cha(&mut self, cha: Cha) -> &mut Self {
fn attrs(&mut self, attrs: Attrs) -> &mut Self {
match self {
Self::Local(g) => _ = g.cha(cha),
Self::Sftp(g) => _ = g.cha(cha),
Self::Local(g) => _ = g.attrs(attrs),
Self::Sftp(g) => _ = g.attrs(attrs),
};
self
}

View file

@ -1,7 +1,7 @@
use std::{io, path::{Path, PathBuf}};
use tokio::io::{BufReader, BufWriter};
use yazi_fs::{cha::Cha, provider::{Provider, local::Local}};
use tokio::io::{AsyncWriteExt, BufReader, BufWriter};
use yazi_fs::{cha::Cha, provider::{Attrs, Provider, local::Local}};
use yazi_shared::{scheme::SchemeRef, url::{AsUrl, UrlBuf, UrlCow}};
use super::{Providers, ReadDir, RwFile};
@ -57,7 +57,7 @@ where
})
}
pub async fn copy<U, V>(from: U, to: V, cha: Cha) -> io::Result<u64>
pub async fn copy<U, V>(from: U, to: V, attrs: Attrs) -> io::Result<u64>
where
U: AsUrl,
V: AsUrl,
@ -65,9 +65,9 @@ where
let (from, to) = (from.as_url(), to.as_url());
match (from.as_path(), to.as_path()) {
(Some(from), Some(to)) => Local.copy(from, to, cha).await,
(Some(from), Some(to)) => Local.copy(from, to, attrs).await,
(None, None) if from.scheme.covariant(to.scheme) => {
Providers::new(from).await?.copy(from.loc, to.loc, cha).await
Providers::new(from).await?.copy(from.loc, to.loc, attrs).await
}
(Some(_), None) | (None, Some(_)) | (None, None) => {
let src = Providers::new(from).await?.open(from.loc).await?;
@ -75,10 +75,11 @@ where
let mut reader = BufReader::with_capacity(524288, src);
let mut writer = BufWriter::with_capacity(524288, dist);
let written = tokio::io::copy(&mut reader, &mut writer).await?;
writer.into_inner().set_cha(cha).await.ok();
writer.flush().await?;
writer.get_ref().set_attrs(attrs).await.ok();
writer.shutdown().await.ok();
Ok(written)
}
}

View file

@ -1,7 +1,7 @@
use std::{io, path::{Path, PathBuf}, sync::Arc};
use yazi_config::vfs::{ProviderSftp, Vfs};
use yazi_fs::{cha::Cha, provider::{Provider, local::Local}};
use yazi_fs::{cha::Cha, provider::{Attrs, Provider, local::Local}};
use yazi_shared::{scheme::SchemeRef, url::{AsUrl, Url, UrlCow}};
pub(super) struct Providers<'a>(Inner<'a>);
@ -62,14 +62,14 @@ impl Provider for Providers<'_> {
}
}
async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
async fn copy<P, Q>(&self, from: P, to: Q, attrs: Attrs) -> io::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
match self.0 {
Inner::Regular | Inner::Search(_) => Local.copy(from, to, cha).await,
Inner::Sftp((p, _)) => p.copy(from, to, cha).await,
Inner::Regular | Inner::Search(_) => Local.copy(from, to, attrs).await,
Inner::Sftp((p, _)) => p.copy(from, to, attrs).await,
}
}

View file

@ -1,7 +1,7 @@
use std::{io, pin::Pin};
use tokio::io::{AsyncRead, AsyncWrite};
use yazi_fs::cha::Cha;
use yazi_fs::provider::Attrs;
pub enum RwFile {
Tokio(tokio::fs::File),
@ -17,19 +17,21 @@ impl From<yazi_sftp::fs::File> for RwFile {
}
impl RwFile {
pub async fn set_cha(&self, cha: Cha) -> io::Result<()> {
pub async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> {
match self {
Self::Tokio(f) => {
let std = f.try_clone().await?.into_std().await;
tokio::task::spawn_blocking(move || {
#[cfg(unix)]
std.set_permissions(cha.into()).ok();
std.set_times(cha.into()).ok();
if let Some(mode) = attrs.mode {
std.set_permissions(mode.into()).ok();
}
std.set_times(attrs.into()).ok();
})
.await?;
}
Self::Sftp(f) => {
f.fsetstat(&super::sftp::Cha(cha).into()).await?;
f.fsetstat(&super::sftp::Attrs(attrs).into()).await?;
}
}

View file

@ -1,15 +1,15 @@
use std::{io, path::Path};
use yazi_config::vfs::{ProviderSftp, Vfs};
use yazi_fs::{cha::Cha, provider::FileBuilder};
use yazi_sftp::fs::{Attrs, Flags};
use yazi_fs::provider::{Attrs, FileBuilder};
use yazi_sftp::fs::Flags;
use yazi_shared::scheme::SchemeRef;
pub struct Gate {
sftp: super::Sftp,
append: bool,
cha: Option<Cha>,
attrs: Attrs,
create: bool,
create_new: bool,
read: bool,
@ -25,8 +25,8 @@ impl FileBuilder for Gate {
self
}
fn cha(&mut self, cha: Cha) -> &mut Self {
self.cha = Some(cha);
fn attrs(&mut self, attrs: Attrs) -> &mut Self {
self.attrs = attrs;
self
}
@ -50,7 +50,7 @@ impl FileBuilder for Gate {
sftp,
append: false,
cha: None,
attrs: Attrs::default(),
create: false,
create_new: false,
read: false,
@ -83,7 +83,7 @@ impl FileBuilder for Gate {
flags |= Flags::WRITE;
}
let attrs = self.cha.map(super::Cha).map_or(Attrs::default(), Attrs::from);
let attrs = super::Attrs(self.attrs).into();
Ok(self.sftp.op().await?.open(&path, flags, &attrs).await?)
}

View file

@ -2,22 +2,26 @@ use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}};
use yazi_fs::cha::ChaKind;
pub(crate) struct Cha(pub(crate) yazi_fs::cha::Cha);
// --- Attrs
pub(crate) struct Attrs(pub(crate) yazi_fs::provider::Attrs);
impl From<Cha> for yazi_sftp::fs::Attrs {
fn from(cha: Cha) -> Self {
impl From<Attrs> for yazi_sftp::fs::Attrs {
fn from(attrs: Attrs) -> Self {
Self {
size: Some(cha.0.len),
uid: Some(cha.0.uid),
gid: Some(cha.0.gid),
perm: Some(cha.0.mode.bits() as u32),
atime: cha.0.atime_dur().ok().map(|d| d.as_secs() as u32),
mtime: cha.0.mtime_dur().ok().map(|d| d.as_secs() as u32),
size: None,
uid: None,
gid: None,
perm: attrs.0.mode.map(|m| m.bits() as u32),
atime: attrs.0.atime_dur().map(|d| d.as_secs() as u32),
mtime: attrs.0.mtime_dur().map(|d| d.as_secs() as u32),
extended: Default::default(),
}
}
}
// --- Cha
pub(crate) struct Cha(pub(crate) yazi_fs::cha::Cha);
impl TryFrom<&yazi_sftp::fs::DirEntry> for Cha {
type Error = io::Error;

View file

@ -1,7 +1,7 @@
use std::{io, path::{Path, PathBuf}, sync::Arc};
use russh::keys::PrivateKeyWithHashAlg;
use tokio::io::{BufReader, BufWriter};
use tokio::io::{AsyncWriteExt, BufReader, BufWriter};
use yazi_config::vfs::ProviderSftp;
use yazi_fs::provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local};
use yazi_sftp::fs::{Attrs, Flags};
@ -77,12 +77,12 @@ impl Provider for Sftp {
similar.map(|n| parent.join(n)).ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))
}
async fn copy<P, Q>(&self, from: P, to: Q, cha: yazi_fs::cha::Cha) -> io::Result<u64>
async fn copy<P, Q>(&self, from: P, to: Q, attrs: yazi_fs::provider::Attrs) -> io::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let attrs = Attrs::from(Cha(cha));
let attrs = Attrs::from(super::Attrs(attrs));
let op = self.op().await?;
let from = op.open(&from, Flags::READ, &Attrs::default()).await?;
@ -90,10 +90,11 @@ impl Provider for Sftp {
let mut reader = BufReader::with_capacity(524288, from);
let mut writer = BufWriter::with_capacity(524288, to);
let written = tokio::io::copy(&mut reader, &mut writer).await?;
writer.into_inner().fsetstat(&attrs).await.ok();
writer.flush().await?;
writer.get_ref().fsetstat(&attrs).await.ok();
writer.shutdown().await.ok();
Ok(written)
}
@ -158,7 +159,16 @@ impl Provider for Sftp {
P: AsRef<Path>,
Q: AsRef<Path>,
{
Ok(self.op().await?.rename(&from, &to).await?)
let op = self.op().await?;
match op.rename_posix(&from, &to).await {
Ok(()) => {}
Err(yazi_sftp::Error::Unsupported) => {
op.remove(&to).await?;
op.rename(&from, &to).await?;
}
Err(e) => Err(e)?,
}
Ok(())
}
async fn symlink<P, Q, F>(&self, original: P, link: Q, _is_dir: F) -> io::Result<()>

View file

@ -2,7 +2,7 @@ use std::{path::Path, time::Duration};
use anyhow::Result;
use hashbrown::HashSet;
use notify::{ErrorKind::WatchNotFound, PollWatcher, RecommendedWatcher, RecursiveMode, Watcher};
use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Watcher};
use tokio::{pin, sync::mpsc::{self, UnboundedReceiver}};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_fs::{File, FilesOp, provider};
@ -34,13 +34,17 @@ impl Local {
}
pub(crate) fn watch(&mut self, path: &Path) -> Result<()> {
Ok(self.0.watch(path, RecursiveMode::NonRecursive)?)
match self.0.watch(path, RecursiveMode::NonRecursive) {
Ok(()) => Ok(()),
Err(e) if matches!(e.kind, notify::ErrorKind::PathNotFound) => Ok(()),
Err(e) => Err(e)?,
}
}
pub(crate) fn unwatch(&mut self, path: &Path) -> Result<()> {
match self.0.unwatch(path) {
Ok(()) => Ok(()),
Err(e) if matches!(e.kind, WatchNotFound) => Ok(()),
Err(e) if matches!(e.kind, notify::ErrorKind::WatchNotFound) => Ok(()),
Err(e) => Err(e)?,
}
}