refactor: invert the dependency relationship between yazi-fs and yazi-vfs (#3215)

This commit is contained in:
三咲雅 misaki masa 2025-09-28 15:32:38 +08:00 committed by sxyazi
parent 25d5554a9a
commit 7b36b7fe14
No known key found for this signature in database
83 changed files with 662 additions and 554 deletions

213
Cargo.lock generated
View file

@ -4,9 +4,9 @@ version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
@ -244,9 +244,9 @@ dependencies = [
[[package]]
name = "backtrace"
version = "0.3.75"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
@ -254,7 +254,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
"windows-link 0.2.0",
]
[[package]]
@ -459,9 +459,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.38"
version = "1.2.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f"
dependencies = [
"find-msvc-tools",
"jobserver",
@ -1008,7 +1008,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
]
[[package]]
@ -1154,7 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
]
[[package]]
@ -1437,9 +1437,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.31.1"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "globset"
@ -1561,7 +1561,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.0",
"windows-core 0.62.1",
]
[[package]]
@ -1779,9 +1779,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.80"
version = "0.3.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
dependencies = [
"once_cell",
"wasm-bindgen",
@ -1969,9 +1969,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.7.5"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
@ -2268,9 +2268,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.7"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
@ -2396,10 +2396,11 @@ dependencies = [
[[package]]
name = "pageant"
version = "0.0.3"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd27df01428302f915ea74737fe88170dd1bab4cbd00ff9548ca85618fcd4e4"
checksum = "bb28bd89a207e5cad59072ac4b364b08459d05f90ccfbcdaa920a95857d94430"
dependencies = [
"byteorder",
"bytes",
"delegate",
"futures",
@ -2407,7 +2408,7 @@ dependencies = [
"rand 0.8.5",
"thiserror 1.0.69",
"tokio",
"windows 0.58.0",
"windows 0.59.0",
]
[[package]]
@ -2913,9 +2914,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.2"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
@ -2925,9 +2926,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
@ -2993,9 +2994,9 @@ dependencies = [
[[package]]
name = "russh"
version = "0.54.3"
version = "0.54.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00897b69ab623d39b396af89f1acbb775fb5a730f0db91833da297d5a6cd3f8d"
checksum = "54b1296a0e9d970321e59d77367b8d1511311445f3dd5d42f24e7d9a1706cf51"
dependencies = [
"aes",
"base64ct",
@ -3122,7 +3123,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
]
[[package]]
@ -3203,9 +3204,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.226"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
@ -3223,18 +3224,18 @@ dependencies = [
[[package]]
name = "serde_core"
version = "1.0.226"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.226"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@ -3502,12 +3503,11 @@ dependencies = [
[[package]]
name = "syntect"
version = "5.2.0"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
dependencies = [
"bincode",
"bitflags 1.3.2",
"flate2",
"fnv",
"once_cell",
@ -3517,7 +3517,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"thiserror 1.0.69",
"thiserror 2.0.16",
"walkdir",
]
@ -4065,9 +4065,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
dependencies = [
"cfg-if",
"once_cell",
@ -4078,9 +4078,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
@ -4092,9 +4092,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.53"
version = "0.4.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67"
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
dependencies = [
"cfg-if",
"js-sys",
@ -4105,9 +4105,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -4115,9 +4115,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
dependencies = [
"proc-macro2",
"quote",
@ -4128,18 +4128,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.80"
version = "0.3.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -4195,7 +4195,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.0",
"windows-sys 0.61.1",
]
[[package]]
@ -4216,12 +4216,12 @@ dependencies = [
[[package]]
name = "windows"
version = "0.58.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1"
dependencies = [
"windows-core 0.58.0",
"windows-targets 0.52.6",
"windows-core 0.59.0",
"windows-targets 0.53.4",
]
[[package]]
@ -4238,25 +4238,25 @@ dependencies = [
[[package]]
name = "windows-core"
version = "0.58.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce"
dependencies = [
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
"windows-implement 0.59.0",
"windows-interface 0.59.2",
"windows-result 0.3.4",
"windows-strings 0.3.1",
"windows-targets 0.53.4",
]
[[package]]
name = "windows-core"
version = "0.62.0"
version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c"
checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
dependencies = [
"windows-implement 0.60.0",
"windows-interface 0.59.1",
"windows-implement 0.60.1",
"windows-interface 0.59.2",
"windows-link 0.2.0",
"windows-result 0.4.0",
"windows-strings 0.5.0",
@ -4275,9 +4275,9 @@ dependencies = [
[[package]]
name = "windows-implement"
version = "0.58.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
dependencies = [
"proc-macro2",
"quote",
@ -4286,9 +4286,9 @@ dependencies = [
[[package]]
name = "windows-implement"
version = "0.60.0"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
dependencies = [
"proc-macro2",
"quote",
@ -4308,20 +4308,9 @@ dependencies = [
[[package]]
name = "windows-interface"
version = "0.58.0"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
dependencies = [
"proc-macro2",
"quote",
@ -4351,11 +4340,11 @@ dependencies = [
[[package]]
name = "windows-result"
version = "0.2.0"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-targets 0.52.6",
"windows-link 0.1.3",
]
[[package]]
@ -4369,12 +4358,11 @@ dependencies = [
[[package]]
name = "windows-strings"
version = "0.1.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
dependencies = [
"windows-result 0.2.0",
"windows-targets 0.52.6",
"windows-link 0.1.3",
]
[[package]]
@ -4410,14 +4398,14 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.3",
"windows-targets 0.53.4",
]
[[package]]
name = "windows-sys"
version = "0.61.0"
version = "0.61.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
dependencies = [
"windows-link 0.2.0",
]
@ -4440,11 +4428,11 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.53.3"
version = "0.53.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
dependencies = [
"windows-link 0.1.3",
"windows-link 0.2.0",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
@ -4609,6 +4597,7 @@ dependencies = [
"yazi-scheduler",
"yazi-shared",
"yazi-term",
"yazi-vfs",
"yazi-watcher",
"yazi-widgets",
]
@ -4704,7 +4693,6 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
"yazi-vfs",
]
[[package]]
@ -4736,7 +4724,6 @@ dependencies = [
"yazi-macro",
"yazi-shared",
"yazi-term",
"yazi-vfs",
]
[[package]]
@ -4764,6 +4751,7 @@ dependencies = [
"yazi-proxy",
"yazi-scheduler",
"yazi-shared",
"yazi-vfs",
"yazi-watcher",
"yazi-widgets",
]
@ -4853,16 +4841,13 @@ dependencies = [
"arc-swap",
"bitflags 2.9.4",
"core-foundation-sys",
"deadpool",
"dirs",
"foldhash 0.2.0",
"futures",
"hashbrown 0.16.0",
"libc",
"objc",
"parking_lot",
"regex",
"russh",
"scopeguard",
"serde",
"tokio",
@ -4870,12 +4855,10 @@ dependencies = [
"trash",
"twox-hash",
"uzers",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
"yazi-ffi",
"yazi-macro",
"yazi-sftp",
"yazi-shared",
"yazi-vfs",
]
[[package]]
@ -4928,7 +4911,7 @@ dependencies = [
"twox-hash",
"unicode-width 0.2.0",
"uzers",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
"yazi-adapter",
"yazi-binding",
"yazi-boot",
@ -4940,6 +4923,7 @@ dependencies = [
"yazi-prebuilt",
"yazi-proxy",
"yazi-shared",
"yazi-vfs",
"yazi-widgets",
]
@ -4987,6 +4971,7 @@ dependencies = [
"yazi-plugin",
"yazi-proxy",
"yazi-shared",
"yazi-vfs",
]
[[package]]
@ -5018,7 +5003,7 @@ dependencies = [
"serde",
"tokio",
"uzers",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
"yazi-macro",
]
@ -5030,7 +5015,7 @@ dependencies = [
"libc",
"parking_lot",
"tracing",
"windows-sys 0.61.0",
"windows-sys 0.61.1",
"yazi-macro",
"yazi-shared",
]
@ -5039,13 +5024,20 @@ dependencies = [
name = "yazi-vfs"
version = "25.9.15"
dependencies = [
"dirs",
"anyhow",
"deadpool",
"futures",
"hashbrown 0.16.0",
"parking_lot",
"russh",
"serde",
"tokio",
"toml 0.9.7",
"tracing",
"uzers",
"yazi-fs",
"yazi-macro",
"yazi-sftp",
"yazi-shared",
]
@ -5065,6 +5057,7 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
"yazi-vfs",
]
[[package]]

View file

@ -41,12 +41,12 @@ ordered-float = { version = "5.0.0", features = [ "serde" ] }
parking_lot = "0.12.4"
paste = "1.0.15"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.11.2"
russh = { version = "0.54.3", default-features = false, features = [ "ring", "rsa" ] }
regex = "1.11.3"
russh = { version = "0.54.4", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"
serde = { version = "1.0.226", features = [ "derive" ] }
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.145"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.47.1", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.16"

View file

@ -52,25 +52,25 @@ Yazi is currently in heavy development, expect breaking changes.
## Image Preview
| Platform | Protocol | Support |
| ---------------------------------------------------------------------------- | -------------------------------------- | --------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ✅ Built-in |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in |
| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required |
| Platform | Protocol | Support |
| ---------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio renders images at incorrect sizes |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in |
| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required |
See https://yazi-rs.github.io/docs/image-preview for details.

View file

@ -26,6 +26,7 @@ yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-scheduler = { path = "../yazi-scheduler", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-term = { path = "../yazi-term", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" }
yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" }

View file

@ -1,11 +1,12 @@
use std::{ffi::OsString, mem, path::MAIN_SEPARATOR_STR};
use anyhow::Result;
use yazi_fs::{CWD, path::expand_url, provider::{self, DirReader, FileHolder}};
use yazi_fs::{CWD, path::expand_url, provider::{DirReader, FileHolder}};
use yazi_macro::{act, render, succ};
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
use yazi_proxy::CmpProxy;
use yazi_shared::{OsStrSplit, event::Data, natsort, url::{UrlBuf, UrlCow, UrnBuf}};
use yazi_vfs::provider;
use crate::{Actor, Ctx};

View file

@ -7,12 +7,13 @@ 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, maybe_exists, path::skip_url, provider::{self, FileBuilder, Provider, local::{Gate, Local}}};
use yazi_fs::{File, FilesOp, 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, event::Data, terminal_clear, url::{Component, UrlBuf}};
use yazi_term::tty::TTY;
use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER;
use crate::{Actor, Ctx};

View file

@ -10,6 +10,7 @@ use yazi_macro::{act, err, render, succ};
use yazi_parser::mgr::CdOpt;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
use yazi_shared::{Debounce, errors::InputError, event::Data, url::UrlBuf};
use yazi_vfs::VfsFile;
use crate::{Actor, Ctx};

View file

@ -1,10 +1,11 @@
use anyhow::{Result, bail};
use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, provider};
use yazi_fs::{File, FilesOp, ok_or_not_found};
use yazi_macro::succ;
use yazi_parser::mgr::CreateOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
use yazi_shared::{event::Data, url::UrlBuf};
use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER;
use crate::{Actor, Ctx};

View file

@ -9,6 +9,7 @@ use yazi_macro::{act, succ};
use yazi_parser::mgr::{OpenDoOpt, OpenOpt};
use yazi_proxy::{MgrProxy, PickProxy, TasksProxy};
use yazi_shared::{MIME_DIR, event::Data, url::UrlBuf};
use yazi_vfs::VfsFile;
use crate::{Actor, Ctx, mgr::Quit};

View file

@ -7,6 +7,7 @@ use yazi_macro::{act, succ};
use yazi_parser::VoidOpt;
use yazi_shared::{event::Data, url::UrlBuf};
use yazi_term::tty::TTY;
use yazi_vfs::{VfsFiles, VfsFilesOp};
use crate::{Actor, Ctx};

View file

@ -1,11 +1,12 @@
use anyhow::Result;
use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, provider};
use yazi_fs::{File, FilesOp, ok_or_not_found};
use yazi_macro::{act, err, succ};
use yazi_parser::mgr::RenameOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
use yazi_shared::{Id, event::Data, url::UrlBuf};
use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER;
use crate::{Actor, Ctx};

View file

@ -156,9 +156,10 @@ macro_rules! impl_file_fields {
$crate::cached_field!($fields, name, |lua, me| {
me.name().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
});
$crate::cached_field!($fields, cache, |_, me| Ok(
yazi_fs::provider::cache(&me.url).map($crate::Url::new)
));
$crate::cached_field!($fields, cache, |_, me| {
use yazi_fs::FsUrl;
Ok(me.url.cache().map($crate::Url::new))
});
};
}

View file

@ -1,5 +1,5 @@
use yazi_config::YAZI;
use yazi_vfs::local::Xdg;
use yazi_fs::Xdg;
use super::Actions;

View file

@ -3,9 +3,9 @@ use std::path::PathBuf;
use futures::executor::block_on;
use hashbrown::HashSet;
use serde::Serialize;
use yazi_fs::{CWD, path::expand_url, provider};
use yazi_fs::{CWD, Xdg, path::expand_url};
use yazi_shared::url::{UrlBuf, UrlCow, UrnBuf};
use yazi_vfs::local::Xdg;
use yazi_vfs::provider;
#[derive(Debug, Default, Serialize)]
pub struct Boot {

View file

@ -24,7 +24,6 @@ yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# External dependencies
anyhow = { workspace = true }

View file

@ -1,5 +1,5 @@
use anyhow::{Context, Result};
use yazi_fs::{ok_or_not_found, provider::{DirReader, FileHolder, Provider, local::Local}, remove_dir_clean};
use yazi_fs::{ok_or_not_found, provider::{DirReader, FileHolder, Provider, local::Local}};
use yazi_macro::outln;
use super::Dependency;
@ -35,7 +35,7 @@ impl Dependency {
Err(e) => Err(e).context(format!("failed to read `{}`", assets.display()))?,
};
remove_dir_clean(&assets.into()).await;
Local.remove_dir_clean(assets).await;
Ok(())
}

View file

@ -3,9 +3,8 @@ use std::{io::BufWriter, path::{Path, PathBuf}, str::FromStr};
use anyhow::{Result, bail};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use twox_hash::XxHash3_128;
use yazi_fs::provider::{DirReader, FileHolder, Provider, local::Local};
use yazi_fs::{Xdg, provider::{DirReader, FileHolder, Provider, local::Local}};
use yazi_shared::BytesExt;
use yazi_vfs::local::Xdg;
#[derive(Clone, Default)]
pub(crate) struct Dependency {

View file

@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use yazi_fs::{provider::{DirReader, FileHolder, Provider, local::Local}, remove_dir_clean};
use yazi_fs::provider::{DirReader, FileHolder, Provider, local::Local};
use yazi_macro::outln;
use super::Dependency;
@ -30,7 +30,7 @@ impl Dependency {
self.delete_sources().await?;
}
remove_dir_clean(&to.into()).await;
Local.remove_dir_clean(to).await;
self.hash = self.hash().await?;
res2?;
res1?;

View file

@ -3,7 +3,7 @@
yazi_macro::mod_flat!(add delete dependency deploy git hash install package upgrade);
use anyhow::Context;
use yazi_vfs::local::Xdg;
use yazi_fs::Xdg;
pub(super) fn init() -> anyhow::Result<()> {
let root = Xdg::state_dir().join("packages");

View file

@ -2,9 +2,8 @@ use std::{path::PathBuf, str::FromStr};
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use yazi_fs::provider::{Provider, local::Local};
use yazi_fs::{Xdg, provider::{Provider, local::Local}};
use yazi_macro::outln;
use yazi_vfs::local::Xdg;
use super::Dependency;

View file

@ -14,7 +14,6 @@ yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-term = { path = "../yazi-term", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# External dependencies
anyhow = { workspace = true }

View file

@ -1,9 +1,8 @@
use anyhow::{Context, Result};
use serde::Deserialize;
use yazi_codegen::DeserializeOver1;
use yazi_fs::ok_or_not_found;
use yazi_fs::{Xdg, ok_or_not_found};
use yazi_shared::Layer;
use yazi_vfs::local::Xdg;
use super::{Chord, KeymapRules};

View file

@ -3,9 +3,8 @@ use std::{borrow::Cow, path::PathBuf};
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use yazi_codegen::DeserializeOver2;
use yazi_fs::path::expand_url;
use yazi_fs::{Xdg, path::expand_url};
use yazi_shared::{SStr, timestamp_us, url::Url};
use yazi_vfs::local::Xdg;
use super::PreviewWrap;

View file

@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use toml::Value;
use yazi_codegen::DeserializeOver2;
use yazi_vfs::local::Xdg;
use yazi_fs::Xdg;
#[derive(Default, Deserialize, DeserializeOver2, Serialize)]
pub struct Flavor {

View file

@ -3,9 +3,8 @@ use std::path::PathBuf;
use anyhow::{Context, Result, anyhow, bail};
use serde::Deserialize;
use yazi_codegen::{DeserializeOver1, DeserializeOver2};
use yazi_fs::{ok_or_not_found, path::expand_url};
use yazi_fs::{Xdg, ok_or_not_found, path::expand_url};
use yazi_shared::url::UrlBuf;
use yazi_vfs::local::Xdg;
use super::{Filetype, Flavor, Icon};
use crate::Style;

View file

@ -1,8 +1,7 @@
use anyhow::{Context, Result};
use serde::Deserialize;
use yazi_codegen::DeserializeOver1;
use yazi_fs::ok_or_not_found;
use yazi_vfs::local::Xdg;
use yazi_fs::{Xdg, ok_or_not_found};
use crate::{mgr, open, opener, plugin, popup, preview, tasks, which};

View file

@ -19,6 +19,7 @@ yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-scheduler = { path = "../yazi-scheduler", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" }
yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" }

View file

@ -10,6 +10,7 @@ use yazi_macro::render;
use yazi_parser::mgr::PreviewLock;
use yazi_plugin::{external::Highlighter, isolate};
use yazi_shared::{MIME_DIR, pool::{InternStr, Symbol}, url::UrlBuf};
use yazi_vfs::{VfsFiles, VfsFilesOp};
#[derive(Default)]
pub struct Preview {

View file

@ -4,8 +4,8 @@ use anyhow::Context;
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
use yazi_fs::Xdg;
use yazi_shared::{LOG_LEVEL, RoCell};
use yazi_vfs::local::Xdg;
static _GUARD: RoCell<WorkerGuard> = RoCell::new();

View file

@ -22,6 +22,8 @@ async fn main() -> anyhow::Result<()> {
yazi_config::init()?;
yazi_vfs::init();
yazi_adapter::init()?;
yazi_boot::init();

View file

@ -11,22 +11,17 @@ repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-ffi = { path = "../yazi-ffi", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-sftp = { path = "../yazi-sftp", version = "0.1.0" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# External dependencies
anyhow = { workspace = true }
arc-swap = "1.7.1"
bitflags = { workspace = true }
deadpool = { version = "0.12.3", default-features = false, features = [ "managed", "rt_tokio_1" ] }
dirs = { workspace = true }
foldhash = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
parking_lot = { workspace = true }
regex = { workspace = true }
russh = { workspace = true }
scopeguard = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true }
@ -38,7 +33,7 @@ libc = { workspace = true }
uzers = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61.0", features = [ "Win32_Storage_FileSystem" ] }
windows-sys = { version = "0.61.1", features = [ "Win32_Storage_FileSystem" ] }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation-sys = { workspace = true }

View file

@ -5,7 +5,7 @@ use yazi_macro::{unix_either, win_either};
use yazi_shared::url::Url;
use super::ChaKind;
use crate::{cha::{ChaMode, ChaType}, provider};
use crate::cha::{ChaMode, ChaType};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cha {
@ -52,27 +52,6 @@ impl Cha {
Self::from_bare(&meta).attach(ChaKind::hidden(name, &meta))
}
#[inline]
pub async fn from_url<'a>(url: impl Into<Url<'a>>) -> std::io::Result<Self> {
let url = url.into();
Ok(Self::from_follow(url, provider::symlink_metadata(url).await?).await)
}
pub async fn from_follow<'a, U>(url: U, mut cha: Self) -> Self
where
U: Into<Url<'a>>,
{
let url: Url = url.into();
let mut retain = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM);
if cha.is_link() {
retain |= ChaKind::FOLLOW;
cha = provider::metadata(url).await.unwrap_or(cha);
}
cha.attach(retain)
}
pub fn from_dummy<'a, U>(_url: U, r#type: Option<ChaType>) -> Self
where
U: Into<Url<'a>>,
@ -140,7 +119,7 @@ impl Cha {
}
#[inline]
fn attach(mut self, kind: ChaKind) -> Self {
pub fn attach(mut self, kind: ChaKind) -> Self {
self.kind |= kind;
self
}

View file

@ -3,7 +3,7 @@ use std::{env::{current_dir, set_current_dir}, ops::Deref, path::PathBuf, sync::
use arc_swap::ArcSwap;
use yazi_shared::{RoCell, url::UrlBuf};
use crate::provider;
use crate::FsUrl;
pub static CWD: RoCell<Cwd> = RoCell::new();
@ -30,7 +30,7 @@ impl Default for Cwd {
impl Cwd {
pub fn path(&self) -> PathBuf {
let url = self.0.load();
provider::cache(url.as_ref()).unwrap_or_else(|| url.loc.to_path())
url.cache().unwrap_or_else(|| url.loc.to_path())
}
pub fn set(&self, url: &UrlBuf) -> bool {
@ -71,7 +71,7 @@ impl Cwd {
fn ensure_cache(&self) -> PathBuf {
let url = self.0.load();
if let Some(p) = provider::cache(url.as_ref()) {
if let Some(p) = url.cache() {
std::fs::create_dir_all(&p).ok();
p
} else {

View file

@ -1,9 +1,8 @@
use std::{ffi::OsStr, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
use anyhow::Result;
use yazi_shared::url::{Uri, UrlBuf, UrlCow, Urn};
use yazi_shared::url::{Uri, UrlBuf, Urn};
use crate::{cha::{Cha, ChaType}, provider};
use crate::cha::{Cha, ChaType};
#[derive(Clone, Debug, Default)]
pub struct File {
@ -19,22 +18,6 @@ impl Deref for File {
}
impl File {
#[inline]
pub async fn new(url: impl Into<UrlCow<'_>>) -> Result<Self> {
let url = url.into();
let cha = provider::symlink_metadata(&url).await?;
Ok(Self::from_follow(url.into_owned(), cha).await)
}
#[inline]
pub async fn from_follow(url: UrlBuf, cha: Cha) -> Self {
let link_to = if cha.is_link() { provider::read_link(&url).await.ok() } else { None };
let cha = Cha::from_follow(&url, cha).await;
Self { url, cha, link_to }
}
#[inline]
pub fn from_dummy(url: impl Into<UrlBuf>, r#type: Option<ChaType>) -> Self {
let url = url.into();

View file

@ -1,11 +1,10 @@
use std::{mem, ops::{Deref, DerefMut, Not}};
use hashbrown::{HashMap, HashSet};
use tokio::{select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_shared::{Id, url::{UrlBuf, Urn, UrnBuf}};
use yazi_shared::{Id, url::{Urn, UrnBuf}};
use super::{FilesSorter, Filter};
use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, mounts::PARTITIONS, provider::{self, DirEntry, DirReader, FileHolder}};
use crate::{FILES_TICKET, File, SortBy};
#[derive(Default)]
pub struct Files {
@ -35,70 +34,6 @@ impl DerefMut for Files {
impl Files {
pub fn new(show_hidden: bool) -> Self { Self { show_hidden, ..Default::default() } }
pub async fn from_dir(dir: &UrlBuf) -> std::io::Result<UnboundedReceiver<File>> {
let mut it = provider::read_dir(dir).await?;
let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(async move {
while let Ok(Some(ent)) = it.next().await {
select! {
_ = tx.closed() => break,
result = ent.metadata() => {
let url = ent.url();
_ = tx.send(match result {
Ok(cha) => File::from_follow(url, cha).await,
Err(_) => File::from_dummy(url, ent.file_type().await.ok())
});
}
}
}
});
Ok(rx)
}
pub async fn from_dir_bulk(dir: &UrlBuf) -> std::io::Result<Vec<File>> {
let mut it = provider::read_dir(dir).await?;
let mut entries = Vec::with_capacity(5000);
while let Ok(Some(entry)) = it.next().await {
entries.push(entry);
}
let (first, rest) = entries.split_at(entries.len() / 3);
let (second, third) = rest.split_at(entries.len() / 3);
async fn go(entries: &[DirEntry]) -> Vec<File> {
let mut files = Vec::with_capacity(entries.len());
for ent in entries {
let url = ent.url();
files.push(match ent.metadata().await {
Ok(cha) => File::from_follow(url, cha).await,
Err(_) => File::from_dummy(url, ent.file_type().await.ok()),
});
}
files
}
Ok(
futures::future::join_all([go(first), go(second), go(third)])
.await
.into_iter()
.flatten()
.collect(),
)
}
pub async fn assert_stale(dir: &UrlBuf, cha: Cha) -> Option<Cha> {
use std::io::ErrorKind;
match Cha::from_url(dir).await {
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
Ok(c) if c.hits(cha) && PARTITIONS.read().heuristic(cha) => {}
Ok(c) => return Some(c),
Err(e) => FilesOp::issue_error(dir, e.kind()).await,
}
None
}
}
impl Files {
pub fn update_full(&mut self, files: Vec<File>) {
self.ticket = FILES_TICKET.next();

View file

@ -1,21 +1,5 @@
use anyhow::Result;
use tokio::{io, select, sync::{mpsc, oneshot}, time};
use yazi_shared::url::{Component, Url, UrlBuf};
use crate::{cha::Cha, provider::{self, DirReader, FileHolder}};
#[inline]
pub async fn maybe_exists<'a>(url: impl Into<Url<'a>>) -> bool {
match provider::symlink_metadata(url).await {
Ok(_) => true,
Err(e) => e.kind() != io::ErrorKind::NotFound,
}
}
#[inline]
pub async fn must_be_dir<'a>(url: impl Into<Url<'a>>) -> bool {
provider::metadata(url).await.is_ok_and(|m| m.is_dir())
}
use tokio::io;
use yazi_shared::url::{Component, UrlBuf};
#[inline]
pub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> {
@ -26,74 +10,6 @@ pub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> {
}
}
pub fn copy_with_progress(
from: &UrlBuf,
to: &UrlBuf,
cha: Cha,
) -> 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.clone(), to.clone());
async move {
done_tx.send(provider::copy(&from, &to, cha).await).ok();
}
});
tokio::spawn({
let (prog_tx, to) = (prog_tx.clone(), to.clone());
async move {
let mut last = 0;
let mut done = None;
loop {
select! {
res = &mut done_rx => done = Some(res.unwrap()),
_ = prog_tx.closed() => break,
_ = time::sleep(time::Duration::from_secs(3)) => {},
}
match done {
Some(Ok(len)) => {
if len > last {
prog_tx.send(Ok(len - last)).await.ok();
}
prog_tx.send(Ok(0)).await.ok();
break;
}
Some(Err(e)) => {
prog_tx.send(Err(e)).await.ok();
break;
}
None => {}
}
let len = provider::symlink_metadata(&to).await.map(|m| m.len).unwrap_or(0);
if len > last {
prog_tx.send(Ok(len - last)).await.ok();
last = len;
}
}
}
});
prog_rx
}
pub async fn remove_dir_clean(dir: &UrlBuf) {
let Ok(mut it) = provider::read_dir(dir).await else { return };
while let Ok(Some(ent)) = it.next().await {
if ent.file_type().await.is_ok_and(|t| t.is_dir()) {
let url = ent.url();
Box::pin(remove_dir_clean(&url)).await;
provider::remove_dir(&url).await.ok();
}
}
provider::remove_dir(dir).await.ok();
}
// Find the max common root in a list of urls
// e.g. /a/b/c, /a/b/d -> /a/b
// /aa/bb/cc, /aa/dd/ee -> /aa
@ -138,7 +54,8 @@ pub fn max_common_root(urls: &[UrlBuf]) -> usize {
fn test_max_common_root() {
fn assert(input: &[&str], expected: &str) {
use std::{ffi::OsStr, str::FromStr};
let urls: Vec<_> = input.iter().copied().map(UrlBuf::from_str).collect::<Result<_>>().unwrap();
let urls: Vec<_> =
input.iter().copied().map(UrlBuf::from_str).collect::<Result<_, _>>().unwrap();
let mut comp = urls[0].components();
for _ in 0..comp.clone().count() - max_common_root(&urls) {

View file

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

View file

@ -5,7 +5,7 @@ use yazi_macro::relay;
use yazi_shared::{Id, Ids, url::{UrlBuf, UrnBuf}};
use super::File;
use crate::{cha::Cha, maybe_exists};
use crate::cha::Cha;
pub static FILES_TICKET: Ids = Ids::new();
@ -121,17 +121,6 @@ impl FilesOp {
}
}
pub async fn issue_error(cwd: &UrlBuf, kind: std::io::ErrorKind) {
use std::io::ErrorKind;
if kind != ErrorKind::NotFound {
Self::IOErr(cwd.clone(), kind).emit();
} else if maybe_exists(cwd).await {
Self::IOErr(cwd.clone(), kind).emit();
} else if let Some((p, n)) = cwd.pair() {
Self::Deleting(p.into(), [n.into()].into()).emit();
}
}
pub fn diff_recoverable(&self, contains: impl Fn(&UrlBuf) -> bool) -> (Vec<UrlBuf>, Vec<UrlBuf>) {
match self {
Self::Deleting(cwd, urns) => (urns.iter().map(|u| cwd.join(u)).collect(), vec![]),

View file

@ -1,9 +1,7 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}, future::Future, io};
use std::{borrow::Cow, ffi::OsStr};
use yazi_shared::url::UrlBuf;
use crate::provider;
pub fn skip_url(url: &UrlBuf, n: usize) -> Cow<'_, OsStr> {
let mut it = url.components();
for _ in 0..n {
@ -14,56 +12,9 @@ pub fn skip_url(url: &UrlBuf, n: usize) -> Cow<'_, OsStr> {
it.os_str()
}
pub async fn unique_name<F>(u: UrlBuf, append: F) -> io::Result<UrlBuf>
where
F: Future<Output = bool>,
{
match provider::symlink_metadata(&u).await {
Ok(_) => _unique_name(u, append.await).await,
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(u),
Err(e) => Err(e),
}
}
async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result<UrlBuf> {
let Some(stem) = url.stem().map(|s| s.to_owned()) else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
};
let dot_ext = url.ext().map_or_else(OsString::new, |e| {
let mut s = OsString::with_capacity(e.len() + 1);
s.push(".");
s.push(e);
s
});
let mut i = 1u64;
let mut name = OsString::with_capacity(stem.len() + dot_ext.len() + 5);
loop {
name.clear();
name.push(&stem);
if append {
name.push(&dot_ext);
name.push(format!("_{i}"));
} else {
name.push(format!("_{i}"));
name.push(&dot_ext);
}
url.set_name(&name);
match provider::symlink_metadata(&url).await {
Ok(_) => i += 1,
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
Err(e) => return Err(e),
}
}
Ok(url)
}
#[cfg(windows)]
pub fn backslash_to_slash(p: &std::path::Path) -> Cow<'_, std::path::Path> {
use std::{ffi::OsString, path::PathBuf};
let bytes = p.as_os_str().as_encoded_bytes();
// Fast path to skip if there are no backslashes
@ -80,7 +31,7 @@ pub fn backslash_to_slash(p: &std::path::Path) -> Cow<'_, std::path::Path> {
for &b in rest {
out.push(if b == b'\\' { b'/' } else { b });
}
Cow::Owned(std::path::PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(out) }))
Cow::Owned(PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(out) }))
}
#[cfg(test)]

View file

@ -15,9 +15,9 @@ impl FileBuilder for Gate {
self
}
fn cha(&mut self, cha: Cha) -> &mut Self {
fn cha(&mut self, _cha: Cha) -> &mut Self {
#[cfg(unix)]
self.0.mode(cha.mode.bits() as _);
self.0.mode(_cha.mode.bits() as _);
self
}

View file

@ -1,4 +1,4 @@
use std::fs::{FileTimes, Permissions};
use std::fs::FileTimes;
use crate::cha::Cha;
@ -31,10 +31,10 @@ impl From<Cha> for FileTimes {
}
#[cfg(unix)]
impl From<Cha> for Permissions {
impl From<Cha> for std::fs::Permissions {
fn from(cha: Cha) -> Self {
use std::os::unix::fs::PermissionsExt;
Permissions::from_mode(cha.mode.bits() as _)
std::fs::Permissions::from_mode(cha.mode.bits() as _)
}
}

View file

@ -1,5 +1,3 @@
yazi_macro::mod_pub!(local sftp);
yazi_macro::mod_pub!(local);
yazi_macro::mod_flat!(calculator dir_entry gate provider providers read_dir rw_file traits);
pub fn init() { sftp::init(); }
yazi_macro::mod_flat!(traits);

View file

@ -142,6 +142,30 @@ pub trait Provider {
}
}
fn remove_dir_clean<P>(&self, root: P) -> impl Future<Output = ()>
where
P: AsRef<Path>,
{
let root = root.as_ref().to_path_buf();
async move {
let mut stack = vec![(root, false)];
while let Some((dir, visited)) = stack.pop() {
if visited {
self.remove_dir(&dir).await.ok();
} else if let Ok(mut it) = self.read_dir(&dir).await {
stack.push((dir, true));
while let Ok(Some(ent)) = it.next().await {
if ent.file_type().await.is_ok_and(|t| t.is_dir()) {
stack.push((ent.path(), false));
}
}
}
}
}
}
fn remove_file<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
where
P: AsRef<Path>;

32
yazi-fs/src/url.rs Normal file
View file

@ -0,0 +1,32 @@
use std::path::PathBuf;
use twox_hash::XxHash3_128;
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf}};
use crate::Xdg;
pub trait FsUrl {
fn cache(&self) -> Option<PathBuf>;
}
impl FsUrl for Url<'_> {
fn cache(&self) -> Option<PathBuf> {
match self.scheme {
SchemeRef::Regular | SchemeRef::Search(_) => None,
SchemeRef::Archive(name) => Some(
Xdg::cache_dir()
.join(format!("archive-{}", yazi_shared::url::Encode::domain(name)))
.join(format!("{:x}", XxHash3_128::oneshot(self.loc.bytes()))),
),
SchemeRef::Sftp(name) => Some(
Xdg::cache_dir()
.join(format!("sftp-{}", yazi_shared::url::Encode::domain(name)))
.join(format!("{:x}", XxHash3_128::oneshot(self.loc.bytes()))),
),
}
}
}
impl FsUrl for UrlBuf {
fn cache(&self) -> Option<PathBuf> { self.as_url().cache() }
}

View file

@ -23,6 +23,7 @@ yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-parser = { path = "../yazi-parser", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" }
# External dependencies
@ -49,7 +50,7 @@ libc = { workspace = true }
uzers = { workspace = true }
[target."cfg(windows)".dependencies]
windows-sys = { version = "0.61.0", features = [ "Win32_System_JobObjects" ] }
windows-sys = { version = "0.61.1", features = [ "Win32_System_JobObjects" ] }
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -5,7 +5,7 @@ local M = {}
local function match_mimetype(line)
for _, pat in ipairs(TYPE_PATS) do
local typ, sub = line:match(string.format("(%s/)([+-.a-z0-9]+)%%s+$", pat))
local typ, sub = line:match(string.format("(%s/)([+-.a-zA-Z0-9]+)%%s+$", pat))
if not sub then
elseif line:find(typ .. sub, 1, true) == 1 then
return typ:gsub("^x%-", "", 1) .. sub:gsub("^x%-", "", 1):gsub("^vnd%.", "", 1)

View file

@ -3,7 +3,7 @@ use yazi_binding::Error;
pub enum SizeCalculator {
Local(yazi_fs::provider::local::SizeCalculator),
Remote(yazi_fs::provider::SizeCalculator),
Remote(yazi_vfs::provider::SizeCalculator),
}
impl UserData for SizeCalculator {

View file

@ -4,6 +4,7 @@ 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_vfs::VfsFile;
pub struct FdOpt {
pub cwd: UrlBuf,

View file

@ -4,6 +4,7 @@ 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_vfs::VfsFile;
pub struct RgOpt {
pub cwd: UrlBuf,

View file

@ -4,6 +4,7 @@ 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_vfs::VfsFile;
pub struct RgaOpt {
pub cwd: UrlBuf,

View file

@ -3,8 +3,9 @@ use std::str::FromStr;
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::{self, DirReader, FileHolder}, remove_dir_clean};
use yazi_fs::{mounts::PARTITIONS, provider::{DirReader, FileHolder}};
use yazi_shared::url::UrlCow;
use yazi_vfs::{VfsFile, provider};
use crate::bindings::SizeCalculator;
@ -93,7 +94,7 @@ fn remove(lua: &Lua) -> mlua::Result<Function> {
b"file" => provider::remove_file(&*url).await,
b"dir" => provider::remove_dir(&*url).await,
b"dir_all" => provider::remove_dir_all(&*url).await,
b"dir_clean" => Ok(remove_dir_clean(&url).await),
b"dir_clean" => provider::remove_dir_clean(&*url).await,
_ => Err("Removal type must be 'file', 'dir', 'dir_all', or 'dir_clean'".into_lua_err())?,
};
@ -148,9 +149,9 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
fn calc_size(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, url: UrlRef| async move {
let it = if let Some(path) = url.as_path() {
provider::local::SizeCalculator::new(path).await.map(SizeCalculator::Local)
yazi_fs::provider::local::SizeCalculator::new(path).await.map(SizeCalculator::Local)
} else {
provider::SizeCalculator::new(&*url).await.map(SizeCalculator::Remote)
yazi_vfs::provider::SizeCalculator::new(&*url).await.map(SizeCalculator::Remote)
};
match it {
@ -173,7 +174,7 @@ fn expand_url(lua: &Lua) -> mlua::Result<Function> {
fn unique_name(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, url: UrlRef| async move {
match yazi_fs::path::unique_name(url.clone(), async { false }).await {
match yazi_vfs::unique_name(url.clone(), async { false }).await {
Ok(u) => Url::new(u).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}

View file

@ -17,6 +17,7 @@ yazi-parser = { path = "../yazi-parser", version = "25.9.15" }
yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# External dependencies
anyhow = { workspace = true }

View file

@ -4,8 +4,9 @@ use anyhow::{Result, anyhow};
use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{self, DirEntry, DirReader, FileHolder}};
use yazi_fs::{cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder}};
use yazi_shared::url::{Url, UrlBuf, UrlCow};
use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}};
use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};
use crate::{LOW, NORMAL, TaskIn, TaskOp, TaskOps, file::{FileOutDelete, FileOutDeleteDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutPaste, FileOutPasteDo, FileOutTrash}};

View file

@ -8,9 +8,10 @@ use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use tracing::error;
use yazi_config::Priority;
use yazi_fs::{FilesOp, provider};
use yazi_fs::FilesOp;
use yazi_plugin::isolate;
use yazi_shared::{event::CmdCow, url::UrlBuf};
use yazi_vfs::provider;
use super::{PreworkInFetch, PreworkInLoad, PreworkInSize};
use crate::{HIGH, NORMAL, TaskIn, TaskOp, TaskOps, prework::{PreworkOutFetch, PreworkOutLoad, PreworkOutSize}};

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsString, process::Stdio};
use anyhow::{Result, bail};
use tokio::process::{Child, Command};
use yazi_fs::provider;
use yazi_fs::FsUrl;
use yazi_shared::url::UrlBuf;
pub(crate) struct ShellOpt {
@ -30,7 +30,7 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result<Child> {
tokio::task::spawn_blocking(move || {
let cwd: Cow<_> = if let Some(path) = opt.cwd.as_path() {
path.into()
} else if let Some(cache) = provider::cache(&opt.cwd) {
} else if let Some(cache) = opt.cwd.cache() {
std::fs::create_dir_all(&cache).ok();
cache.into()
} else {

View file

@ -6,10 +6,10 @@ use parking_lot::Mutex;
use tokio::{select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump;
use yazi_fs::{must_be_dir, path::unique_name, provider, remove_dir_clean};
use yazi_parser::{app::PluginOpt, tasks::ProcessExecOpt};
use yazi_proxy::TasksProxy;
use yazi_shared::{Id, Throttle, url::UrlBuf};
use yazi_vfs::{must_be_dir, provider, unique_name};
use super::{Ongoing, TaskOp};
use crate::{HIGH, LOW, NORMAL, TaskIn, TaskOps, TaskOut, file::{File, FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileOutDelete, FileOutHardlink, FileOutPaste, FileProgDelete, FileProgHardlink, FileProgLink, FileProgPaste, FileProgTrash}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOutBg, ProcessOutBlock, ProcessOutOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
@ -89,7 +89,7 @@ impl Scheduler {
move |canceled| async move {
if !canceled {
remove_dir_clean(&from).await;
provider::remove_dir_clean(&from).await.ok();
Pump::push_move(from, to);
}
ops.out(id, FileOutPaste::Clean);

View file

@ -18,7 +18,7 @@ crossterm = { workspace = true }
foldhash = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
memchr = "2.7.5"
memchr = "2.7.6"
ordered-float = { workspace = true }
parking_lot = { workspace = true }
percent-encoding = "2.3.2"
@ -30,7 +30,7 @@ libc = { workspace = true }
uzers = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61.0", features = [ "Win32_UI_Shell" ] }
windows-sys = { version = "0.61.1", features = [ "Win32_UI_Shell" ] }
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -26,4 +26,4 @@ libc = { workspace = true }
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61.0", features = [ "Win32_Globalization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_Security" ] }
windows-sys = { version = "0.61.1", features = [ "Win32_Globalization", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Console", "Win32_System_Threading", "Win32_Security" ] }

View file

@ -9,15 +9,22 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-sftp = { path = "../yazi-sftp", version = "0.1.0" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
# External dependencies
dirs = { workspace = true }
hashbrown = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
anyhow = { workspace = true }
deadpool = { version = "0.12.3", default-features = false, features = [ "managed", "rt_tokio_1" ] }
futures = { workspace = true }
hashbrown = { workspace = true }
parking_lot = { workspace = true }
russh = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
[target."cfg(unix)".dependencies]
uzers = { workspace = true }

37
yazi-vfs/src/cha.rs Normal file
View file

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

View file

@ -3,10 +3,10 @@ use std::io;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use tokio::sync::OnceCell;
use yazi_fs::Xdg;
use yazi_macro::ok_or_not_found;
use super::Provider;
use crate::local::Xdg;
#[derive(Deserialize, Serialize)]
pub struct Vfs {

29
yazi-vfs/src/file.rs Normal file
View file

@ -0,0 +1,29 @@
use anyhow::Result;
use yazi_fs::{File, cha::Cha};
use yazi_shared::url::{UrlBuf, UrlCow};
use crate::{VfsCha, provider};
pub trait VfsFile: Sized {
fn new<'a>(url: impl Into<UrlCow<'a>>) -> impl Future<Output = Result<Self>>;
fn from_follow(url: UrlBuf, cha: Cha) -> impl Future<Output = Self>;
}
impl VfsFile for File {
#[inline]
async fn new<'a>(url: impl Into<UrlCow<'a>>) -> Result<Self> {
let url = url.into();
let cha = provider::symlink_metadata(&url).await?;
Ok(Self::from_follow(url.into_owned(), cha).await)
}
#[inline]
async fn from_follow(url: UrlBuf, cha: Cha) -> Self {
let link_to = if cha.is_link() { provider::read_link(&url).await.ok() } else { None };
let cha = Cha::from_follow(&url, cha).await;
Self { url, cha, link_to }
}
}

79
yazi-vfs/src/files.rs Normal file
View file

@ -0,0 +1,79 @@
use std::io;
use tokio::{select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_fs::{File, Files, FilesOp, cha::Cha, mounts::PARTITIONS, provider::{DirReader, FileHolder}};
use yazi_shared::url::UrlBuf;
use crate::{VfsCha, VfsFile, VfsFilesOp, provider::{self, DirEntry}};
pub trait VfsFiles {
fn from_dir(dir: &UrlBuf) -> impl Future<Output = io::Result<UnboundedReceiver<File>>>;
fn from_dir_bulk(dir: &UrlBuf) -> impl Future<Output = io::Result<Vec<File>>>;
fn assert_stale(dir: &UrlBuf, cha: Cha) -> impl Future<Output = Option<Cha>>;
}
impl VfsFiles for Files {
async fn from_dir(dir: &UrlBuf) -> std::io::Result<UnboundedReceiver<File>> {
let mut it = provider::read_dir(dir).await?;
let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(async move {
while let Ok(Some(ent)) = it.next().await {
select! {
_ = tx.closed() => break,
result = ent.metadata() => {
let url = ent.url();
_ = tx.send(match result {
Ok(cha) => File::from_follow(url, cha).await,
Err(_) => File::from_dummy(url, ent.file_type().await.ok())
});
}
}
}
});
Ok(rx)
}
async fn from_dir_bulk(dir: &UrlBuf) -> std::io::Result<Vec<File>> {
let mut it = provider::read_dir(dir).await?;
let mut entries = Vec::with_capacity(5000);
while let Ok(Some(entry)) = it.next().await {
entries.push(entry);
}
let (first, rest) = entries.split_at(entries.len() / 3);
let (second, third) = rest.split_at(entries.len() / 3);
async fn go(entries: &[DirEntry]) -> Vec<File> {
let mut files = Vec::with_capacity(entries.len());
for ent in entries {
let url = ent.url();
files.push(match ent.metadata().await {
Ok(cha) => File::from_follow(url, cha).await,
Err(_) => File::from_dummy(url, ent.file_type().await.ok()),
});
}
files
}
Ok(
futures::future::join_all([go(first), go(second), go(third)])
.await
.into_iter()
.flatten()
.collect(),
)
}
async fn assert_stale(dir: &UrlBuf, cha: Cha) -> Option<Cha> {
use std::io::ErrorKind;
match Cha::from_url(dir).await {
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
Ok(c) if c.hits(cha) && PARTITIONS.read().heuristic(cha) => {}
Ok(c) => return Some(c),
Err(e) => FilesOp::issue_error(dir, e.kind()).await,
}
None
}
}

122
yazi-vfs/src/fns.rs Normal file
View file

@ -0,0 +1,122 @@
use std::{ffi::OsString, io};
use tokio::{select, sync::{mpsc, oneshot}};
use yazi_fs::cha::Cha;
use yazi_shared::url::{Url, UrlBuf};
use crate::provider;
#[inline]
pub async fn maybe_exists<'a>(url: impl Into<Url<'a>>) -> bool {
match provider::symlink_metadata(url).await {
Ok(_) => true,
Err(e) => e.kind() != io::ErrorKind::NotFound,
}
}
#[inline]
pub async fn must_be_dir<'a>(url: impl Into<Url<'a>>) -> bool {
provider::metadata(url).await.is_ok_and(|m| m.is_dir())
}
pub async fn unique_name<F>(u: UrlBuf, append: F) -> io::Result<UrlBuf>
where
F: Future<Output = bool>,
{
match provider::symlink_metadata(&u).await {
Ok(_) => _unique_name(u, append.await).await,
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(u),
Err(e) => Err(e),
}
}
async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result<UrlBuf> {
let Some(stem) = url.stem().map(|s| s.to_owned()) else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
};
let dot_ext = url.ext().map_or_else(OsString::new, |e| {
let mut s = OsString::with_capacity(e.len() + 1);
s.push(".");
s.push(e);
s
});
let mut i = 1u64;
let mut name = OsString::with_capacity(stem.len() + dot_ext.len() + 5);
loop {
name.clear();
name.push(&stem);
if append {
name.push(&dot_ext);
name.push(format!("_{i}"));
} else {
name.push(format!("_{i}"));
name.push(&dot_ext);
}
url.set_name(&name);
match provider::symlink_metadata(&url).await {
Ok(_) => i += 1,
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
Err(e) => return Err(e),
}
}
Ok(url)
}
pub fn copy_with_progress(
from: &UrlBuf,
to: &UrlBuf,
cha: Cha,
) -> 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.clone(), to.clone());
async move {
done_tx.send(provider::copy(&from, &to, cha).await).ok();
}
});
tokio::spawn({
let (prog_tx, to) = (prog_tx.clone(), to.clone());
async move {
let mut last = 0;
let mut done = None;
loop {
select! {
res = &mut done_rx => done = Some(res.unwrap()),
_ = prog_tx.closed() => break,
_ = tokio::time::sleep(std::time::Duration::from_secs(3)) => {},
}
match done {
Some(Ok(len)) => {
if len > last {
prog_tx.send(Ok(len - last)).await.ok();
}
prog_tx.send(Ok(0)).await.ok();
break;
}
Some(Err(e)) => {
prog_tx.send(Err(e)).await.ok();
break;
}
None => {}
}
let len = provider::symlink_metadata(&to).await.map(|m| m.len).unwrap_or(0);
if len > last {
prog_tx.send(Ok(len - last)).await.ok();
last = len;
}
}
}
});
prog_rx
}

View file

@ -1 +1,5 @@
yazi_macro::mod_pub!(config local);
yazi_macro::mod_pub!(config provider);
yazi_macro::mod_flat!(cha file files fns op);
pub fn init() { provider::init(); }

View file

@ -1 +0,0 @@
yazi_macro::mod_flat!(xdg);

23
yazi-vfs/src/op.rs Normal file
View file

@ -0,0 +1,23 @@
use std::io;
use yazi_fs::FilesOp;
use yazi_shared::url::UrlBuf;
use crate::maybe_exists;
pub trait VfsFilesOp {
fn issue_error(cwd: &UrlBuf, kind: io::ErrorKind) -> impl Future<Output = ()>;
}
impl VfsFilesOp for FilesOp {
async fn issue_error(cwd: &UrlBuf, kind: std::io::ErrorKind) {
use std::io::ErrorKind;
if kind != ErrorKind::NotFound {
Self::IOErr(cwd.clone(), kind).emit();
} else if maybe_exists(cwd).await {
Self::IOErr(cwd.clone(), kind).emit();
} else if let Some((p, n)) = cwd.pair() {
Self::Deleting(p.into(), [n.into()].into()).emit();
}
}
}

View file

@ -1,8 +1,9 @@
use std::{collections::VecDeque, io, time::{Duration, Instant}};
use yazi_fs::provider::{DirReader, FileHolder};
use yazi_shared::{Either, url::{Url, UrlBuf}};
use crate::provider::{self, DirReader, FileHolder, ReadDir};
use super::ReadDir;
pub enum SizeCalculator {
File(Option<u64>),
@ -15,7 +16,7 @@ impl SizeCalculator {
U: Into<Url<'a>>,
{
let url: Url = url.into();
let cha = provider::symlink_metadata(url).await?;
let cha = super::symlink_metadata(url).await?;
Ok(if cha.is_dir() {
Self::Dir(VecDeque::from([Either::Left(url.to_owned())]))
} else {
@ -59,7 +60,7 @@ impl SizeCalculator {
let front = buf.front_mut()?;
if let Either::Left(p) = front {
*front = match provider::read_dir(p).await {
*front = match super::read_dir(p).await {
Ok(it) => Either::Right(it),
Err(_) => pop_and_continue!(),
};

View file

@ -1,12 +1,11 @@
use std::{borrow::Cow, ffi::OsStr, io, sync::Arc};
use yazi_fs::{cha::{Cha, ChaType}, provider::FileHolder};
use yazi_shared::url::UrlBuf;
use crate::{cha::{Cha, ChaType}, provider::FileHolder};
pub enum DirEntry {
Regular(super::local::DirEntry),
Search((Arc<UrlBuf>, super::local::DirEntry)),
Regular(yazi_fs::provider::local::DirEntry),
Search((Arc<UrlBuf>, yazi_fs::provider::local::DirEntry)),
Sftp((Arc<UrlBuf>, super::sftp::DirEntry)),
}

View file

@ -1,16 +1,15 @@
use std::{io, path::Path};
use yazi_fs::{cha::Cha, provider::FileBuilder};
use yazi_shared::scheme::SchemeRef;
use crate::{cha::Cha, provider::FileBuilder};
pub enum Gate {
Local(super::local::Gate),
Local(yazi_fs::provider::local::Gate),
Sftp(super::sftp::Gate),
}
impl From<super::local::Gate> for Gate {
fn from(value: super::local::Gate) -> Self { Self::Local(value) }
impl From<yazi_fs::provider::local::Gate> for Gate {
fn from(value: yazi_fs::provider::local::Gate) -> Self { Self::Local(value) }
}
impl From<super::sftp::Gate> for Gate {
@ -54,7 +53,9 @@ impl FileBuilder for Gate {
async fn new(scheme: SchemeRef<'_>) -> io::Result<Self> {
Ok(match scheme {
SchemeRef::Regular | SchemeRef::Search(_) => super::local::Gate::new(scheme).await?.into(),
SchemeRef::Regular | SchemeRef::Search(_) => {
yazi_fs::provider::local::Gate::new(scheme).await?.into()
}
SchemeRef::Archive(_) => {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))?
}

View file

@ -0,0 +1,5 @@
yazi_macro::mod_pub!(sftp);
yazi_macro::mod_flat!(calculator dir_entry gate provider providers read_dir rw_file);
pub(super) fn init() { sftp::init(); }

View file

@ -1,11 +1,10 @@
use std::{io, path::{Path, PathBuf}};
use tokio::io::{BufReader, BufWriter};
use twox_hash::XxHash3_128;
use yazi_fs::{cha::Cha, provider::{Provider, local::Local}};
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}};
use yazi_vfs::local::Xdg;
use crate::{cha::Cha, provider::{Provider, Providers, ReadDir, RwFile, local::{self, Local}}};
use super::{Providers, ReadDir, RwFile};
pub async fn absolute<'a, U>(url: U) -> io::Result<UrlCow<'a>>
where
@ -15,33 +14,13 @@ where
Providers::new(url).await?.absolute(url).await
}
pub fn cache<'a, U>(url: U) -> Option<PathBuf>
where
U: Into<Url<'a>>,
{
let url: Url = url.into();
match url.scheme {
SchemeRef::Regular | SchemeRef::Search(_) => None,
SchemeRef::Archive(name) => Some(
Xdg::cache_dir()
.join(format!("archive-{}", yazi_shared::url::Encode::domain(name)))
.join(format!("{:x}", XxHash3_128::oneshot(url.loc.bytes()))),
),
SchemeRef::Sftp(name) => Some(
Xdg::cache_dir()
.join(format!("sftp-{}", yazi_shared::url::Encode::domain(name)))
.join(format!("{:x}", XxHash3_128::oneshot(url.loc.bytes()))),
),
}
}
pub async fn calculate<'a, U>(url: U) -> io::Result<u64>
where
U: Into<Url<'a>>,
{
let url: Url = url.into();
if let Some(path) = url.as_path() {
local::SizeCalculator::total(path).await
yazi_fs::provider::local::SizeCalculator::total(path).await
} else {
super::SizeCalculator::total(url).await
}
@ -149,7 +128,7 @@ where
V: Into<Url<'a>>,
{
if let (Some(a), Some(b)) = (a.into().as_path(), b.into().as_path()) {
local::identical(a, b).await
yazi_fs::provider::local::identical(a, b).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
@ -203,6 +182,14 @@ where
Providers::new(url).await?.remove_dir_all(url.loc).await
}
pub async fn remove_dir_clean<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
let url: Url = url.into();
Ok(Providers::new(url).await?.remove_dir_clean(url.loc).await)
}
pub async fn remove_file<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,

View file

@ -1,10 +1,9 @@
use std::{io, path::{Path, PathBuf}, sync::Arc};
use yazi_fs::{cha::Cha, provider::{Provider, local::Local}};
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlCow}};
use yazi_vfs::config::{ProviderSftp, Vfs};
use super::local::Local;
use crate::{cha::Cha, provider::Provider};
use crate::config::{ProviderSftp, Vfs};
pub(super) struct Providers<'a>(Inner<'a>);

View file

@ -1,12 +1,11 @@
use std::{io, sync::Arc};
use yazi_fs::provider::DirReader;
use yazi_shared::url::UrlBuf;
use crate::provider::DirReader;
pub enum ReadDir {
Regular(super::local::ReadDir),
Search((Arc<UrlBuf>, super::local::ReadDir)),
Regular(yazi_fs::provider::local::ReadDir),
Search((Arc<UrlBuf>, yazi_fs::provider::local::ReadDir)),
Sftp((Arc<UrlBuf>, super::sftp::ReadDir)),
}

View file

@ -1,8 +1,7 @@
use std::{io, pin::Pin};
use tokio::io::{AsyncRead, AsyncWrite};
use crate::cha::Cha;
use yazi_fs::cha::Cha;
pub enum RwFile {
Tokio(tokio::fs::File),
@ -30,7 +29,7 @@ impl RwFile {
.await?;
}
Self::Sftp(f) => {
f.fsetstat(&cha.into()).await?;
f.fsetstat(&super::sftp::Cha(cha).into()).await?;
}
}

View file

@ -1,10 +1,10 @@
use std::{io, path::Path};
use yazi_fs::{cha::Cha, provider::FileBuilder};
use yazi_sftp::fs::{Attrs, Flags};
use yazi_shared::scheme::SchemeRef;
use yazi_vfs::config::{ProviderSftp, Vfs};
use crate::{cha::Cha, provider::FileBuilder};
use crate::config::{ProviderSftp, Vfs};
pub struct Gate {
sftp: super::Sftp,
@ -84,7 +84,7 @@ impl FileBuilder for Gate {
flags |= Flags::WRITE;
}
let attrs = self.cha.map_or(Attrs::default(), Attrs::from);
let attrs = self.cha.map(super::Cha).map_or(Attrs::default(), Attrs::from);
Ok(self.sftp.op().await?.open(&path, flags, &attrs).await?)
}

View file

@ -1,13 +1,29 @@
use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}};
use crate::cha::{Cha, ChaKind, ChaMode};
use yazi_fs::cha::ChaKind;
pub(crate) struct Cha(pub(crate) yazi_fs::cha::Cha);
impl From<Cha> for yazi_sftp::fs::Attrs {
fn from(cha: Cha) -> 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),
extended: Default::default(),
}
}
}
impl TryFrom<&yazi_sftp::fs::DirEntry> for Cha {
type Error = io::Error;
fn try_from(ent: &yazi_sftp::fs::DirEntry) -> Result<Self, Self::Error> {
let mut cha = Self::try_from((ent.name().as_ref(), ent.attrs()))?;
cha.nlink = ent.nlink().unwrap_or_default();
cha.0.nlink = ent.nlink().unwrap_or_default();
Ok(cha)
}
}
@ -19,9 +35,9 @@ impl TryFrom<(&OsStr, &yazi_sftp::fs::Attrs)> for Cha {
let kind =
if name.as_encoded_bytes().starts_with(b".") { ChaKind::HIDDEN } else { ChaKind::empty() };
Ok(Cha {
Ok(Cha(yazi_fs::cha::Cha {
kind,
mode: attrs.try_into()?,
mode: ChaMode::try_from(attrs)?.0,
len: attrs.size.unwrap_or(0),
atime: attrs.atime.and_then(|t| UNIX_EPOCH.checked_add(Duration::from_secs(t as u64))),
btime: None,
@ -31,29 +47,19 @@ impl TryFrom<(&OsStr, &yazi_sftp::fs::Attrs)> for Cha {
uid: attrs.uid.unwrap_or(0),
gid: attrs.gid.unwrap_or(0),
nlink: 0,
})
}))
}
}
// --- ChaMode
pub(super) struct ChaMode(pub(super) yazi_fs::cha::ChaMode);
impl TryFrom<&yazi_sftp::fs::Attrs> for ChaMode {
type Error = io::Error;
fn try_from(attrs: &yazi_sftp::fs::Attrs) -> Result<Self, Self::Error> {
ChaMode::try_from(attrs.perm.unwrap_or_default() as u16)
yazi_fs::cha::ChaMode::try_from(attrs.perm.unwrap_or_default() as u16)
.map(Self)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
}
impl From<Cha> for yazi_sftp::fs::Attrs {
fn from(cha: Cha) -> Self {
Self {
size: Some(cha.len),
uid: Some(cha.uid),
gid: Some(cha.gid),
perm: Some(cha.mode.bits() as u32),
atime: cha.atime_dur().ok().map(|d| d.as_secs() as u32),
mtime: cha.mtime_dur().ok().map(|d| d.as_secs() as u32),
extended: Default::default(),
}
}
}

View file

@ -3,7 +3,7 @@ yazi_macro::mod_flat!(gate metadata read_dir sftp);
pub(super) static CONN: yazi_shared::RoCell<
parking_lot::Mutex<
hashbrown::HashMap<
&'static yazi_vfs::config::ProviderSftp,
&'static crate::config::ProviderSftp,
&'static deadpool::managed::Pool<Sftp>,
>,
>,

View file

@ -1,6 +1,8 @@
use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf};
use crate::{cha::{Cha, ChaMode, ChaType}, provider::{DirReader, FileHolder}};
use yazi_fs::provider::{DirReader, FileHolder};
use super::{Cha, ChaMode};
pub struct ReadDir(pub(super) yazi_sftp::fs::ReadDir);
@ -20,9 +22,9 @@ impl FileHolder for DirEntry {
fn name(&self) -> Cow<'_, OsStr> { self.0.name() }
async fn metadata(&self) -> io::Result<Cha> { Cha::try_from(&self.0) }
async fn metadata(&self) -> io::Result<yazi_fs::cha::Cha> { Ok(Cha::try_from(&self.0)?.0) }
async fn file_type(&self) -> io::Result<ChaType> {
ChaMode::try_from(self.0.attrs()).map(Into::into)
async fn file_type(&self) -> io::Result<yazi_fs::cha::ChaType> {
Ok(ChaMode::try_from(self.0.attrs())?.0.into())
}
}

View file

@ -2,11 +2,12 @@ use std::{io, path::{Path, PathBuf}, sync::Arc};
use russh::keys::PrivateKeyWithHashAlg;
use tokio::io::{BufReader, BufWriter};
use yazi_fs::provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local};
use yazi_sftp::fs::{Attrs, Flags};
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}};
use yazi_vfs::config::ProviderSftp;
use crate::{cha::Cha, provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local}};
use super::Cha;
use crate::config::ProviderSftp;
#[derive(Clone, Copy)]
pub struct Sftp {
@ -76,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: Cha) -> io::Result<u64>
async fn copy<P, Q>(&self, from: P, to: Q, cha: yazi_fs::cha::Cha) -> io::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let attrs = Attrs::from(cha);
let attrs = Attrs::from(Cha(cha));
let op = self.op().await?;
let from = op.open(&from, Flags::READ, &Attrs::default()).await?;
@ -115,13 +116,13 @@ impl Provider for Sftp {
Ok(self.op().await?.hardlink(&original, &link).await?)
}
async fn metadata<P>(&self, path: P) -> io::Result<Cha>
async fn metadata<P>(&self, path: P) -> io::Result<yazi_fs::cha::Cha>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let attrs = self.op().await?.stat(path).await?;
(path.file_name().unwrap_or_default(), &attrs).try_into()
Ok(Cha::try_from((path.file_name().unwrap_or_default(), &attrs))?.0)
}
async fn read_dir<P>(&self, path: P) -> io::Result<Self::ReadDir>
@ -169,13 +170,13 @@ impl Provider for Sftp {
Ok(self.op().await?.symlink(&original, &link).await?)
}
async fn symlink_metadata<P>(&self, path: P) -> io::Result<Cha>
async fn symlink_metadata<P>(&self, path: P) -> io::Result<yazi_fs::cha::Cha>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let attrs = self.op().await?.lstat(path).await?;
(path.file_name().unwrap_or_default(), &attrs).try_into()
Ok(Cha::try_from((path.file_name().unwrap_or_default(), &attrs))?.0)
}
async fn trash<P>(&self, _path: P) -> io::Result<()>

View file

@ -14,6 +14,7 @@ yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
# Logging
tracing = { workspace = true }

View file

@ -5,6 +5,7 @@ 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_vfs::VfsFile;
use crate::{LINKED, Linked, WATCHED, WATCHER, backend::Backend};