feat: new extract builtin plugin for archive extracting (#1321)

This commit is contained in:
三咲雅 · Misaki Masa 2024-07-28 08:14:53 +08:00 committed by sxyazi
parent f024ce03e7
commit 23a56c24a4
No known key found for this signature in database
71 changed files with 1444 additions and 579 deletions

527
Cargo.lock generated
View file

@ -38,6 +38,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aligned-vec"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.18" version = "0.2.18"
@ -125,12 +131,35 @@ version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "1.7.1" version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arg_enum_proc_macro"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.71",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]] [[package]]
name = "async-priority-channel" name = "async-priority-channel"
version = "0.2.0" version = "0.2.0"
@ -146,6 +175,29 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "av1-grain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
dependencies = [
"anyhow",
"arrayvec",
"log",
"nom",
"num-rational",
"v_frame",
]
[[package]]
name = "avif-serialize"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
dependencies = [
"arrayvec",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.73" version = "0.3.73"
@ -210,6 +262,12 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bitstream-io"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -238,6 +296,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "built"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.16.0"
@ -251,10 +315,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
[[package]] [[package]]
name = "byteorder" name = "byteorder-lite"
version = "1.5.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
@ -270,18 +334,32 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "castaway" name = "castaway"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [ dependencies = [
"rustversion", "rustversion",
] ]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.2" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47de7e88bbbd467951ae7f5a6f34f70d1b4d9cfce53d5fd70f74ebe118b3db56" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
dependencies = [
"jobserver",
"libc",
]
[[package]]
name = "cfg-expr"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -303,9 +381,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.10" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -313,9 +391,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.10" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -325,18 +403,18 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.9" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faa2032320fd6f50d22af510d204b2994eef49600dfbd0e771a166213844e4cd" checksum = "c6ae69fbb0833c6fcd5a8d4b8609f108c7ad95fc11e248d853ff2c42a90df26a"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_complete_fig" name = "clap_complete_fig"
version = "4.5.1" version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4bc503cddc1cd320736fb555d6598309ad07c2ddeaa23891a10ffb759ee612" checksum = "d494102c8ff3951810c72baf96910b980fb065ca5d3101243e6a8dc19747c86b"
dependencies = [ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
@ -344,9 +422,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete_nushell" name = "clap_complete_nushell"
version = "4.5.2" version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1accf1b463dee0d3ab2be72591dccdab8bef314958340447c882c4c72acfe2a3" checksum = "5fe32110e006bccf720f8c9af3fee1ba7db290c724eab61544e1d3295be3a40e"
dependencies = [ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
@ -354,9 +432,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.8" version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -625,9 +703,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
@ -1012,20 +1090,35 @@ dependencies = [
[[package]] [[package]]
name = "image" name = "image"
version = "0.24.9" version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder", "byteorder-lite",
"color_quant", "color_quant",
"exr", "exr",
"gif", "gif",
"jpeg-decoder", "image-webp",
"num-traits", "num-traits",
"png", "png",
"qoi", "qoi",
"ravif",
"rayon",
"rgb",
"tiff", "tiff",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
dependencies = [
"byteorder-lite",
"quick-error",
] ]
[[package]] [[package]]
@ -1034,6 +1127,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "imgref"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.2.6"
@ -1046,9 +1145,9 @@ dependencies = [
[[package]] [[package]]
name = "inotify" name = "inotify"
version = "0.9.6" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"inotify-sys", "inotify-sys",
@ -1064,6 +1163,26 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.71",
]
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.0" version = "1.70.0"
@ -1094,14 +1213,20 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobserver"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "jpeg-decoder" name = "jpeg-decoder"
version = "0.3.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
dependencies = [
"rayon",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
@ -1159,6 +1284,17 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.3" version = "0.1.3"
@ -1197,6 +1333,15 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "loop9"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
dependencies = [
"imgref",
]
[[package]] [[package]]
name = "lru" name = "lru"
version = "0.12.3" version = "0.12.3"
@ -1225,6 +1370,15 @@ dependencies = [
"which", "which",
] ]
[[package]]
name = "maybe-rayon"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@ -1333,6 +1487,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -1343,11 +1503,16 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "noop_proc_macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]] [[package]]
name = "notify" name = "notify"
version = "6.1.1" version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/notify-rs/notify.git?rev=96dec74316a93bed6eec9db177b233e6e017275e#96dec74316a93bed6eec9db177b233e6e017275e"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"filetime", "filetime",
@ -1357,8 +1522,17 @@ dependencies = [
"libc", "libc",
"log", "log",
"mio 0.8.11", "mio 0.8.11",
"notify-types",
"walkdir", "walkdir",
"windows-sys 0.48.0", "windows-sys 0.52.0",
]
[[package]]
name = "notify-types"
version = "1.0.0"
source = "git+https://github.com/notify-rs/notify.git?rev=96dec74316a93bed6eec9db177b233e6e017275e#96dec74316a93bed6eec9db177b233e6e017275e"
dependencies = [
"instant",
] ]
[[package]] [[package]]
@ -1371,12 +1545,53 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.71",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -1579,6 +1794,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -1612,6 +1833,25 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "profiling"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
dependencies = [
"profiling-procmacros",
]
[[package]]
name = "profiling-procmacros"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
"syn 2.0.71",
]
[[package]] [[package]]
name = "qoi" name = "qoi"
version = "0.4.1" version = "0.4.1"
@ -1621,6 +1861,12 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.31.0" version = "0.31.0"
@ -1639,6 +1885,36 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "ratatui" name = "ratatui"
version = "0.27.0" version = "0.27.0"
@ -1660,6 +1936,55 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "rav1e"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
dependencies = [
"arbitrary",
"arg_enum_proc_macro",
"arrayvec",
"av1-grain",
"bitstream-io",
"built",
"cfg-if",
"interpolate_name",
"itertools 0.12.1",
"libc",
"libfuzzer-sys",
"log",
"maybe-rayon",
"new_debug_unreachable",
"noop_proc_macro",
"num-derive",
"num-traits",
"once_cell",
"paste",
"profiling",
"rand",
"rand_chacha",
"simd_helpers",
"system-deps",
"thiserror",
"v_frame",
"wasm-bindgen",
]
[[package]]
name = "ravif"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6"
dependencies = [
"avif-serialize",
"imgref",
"loop9",
"quick-error",
"rav1e",
"rgb",
]
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.10.0" version = "1.10.0"
@ -1738,6 +2063,15 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rgb"
version = "0.8.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@ -1822,20 +2156,21 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.120" version = "1.0.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.6" version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -1849,12 +2184,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shell-escape"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]] [[package]]
name = "shell-words" name = "shell-words"
version = "1.1.0" version = "1.1.0"
@ -1909,6 +2238,15 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simd_helpers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
dependencies = [
"quote",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -1945,9 +2283,9 @@ dependencies = [
[[package]] [[package]]
name = "stability" name = "stability"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.71", "syn 2.0.71",
@ -1967,9 +2305,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.26.2" version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [ dependencies = [
"strum_macros", "strum_macros",
] ]
@ -2031,19 +2369,38 @@ dependencies = [
] ]
[[package]] [[package]]
name = "thiserror" name = "system-deps"
version = "1.0.61" version = "6.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "target-lexicon"
version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2"
[[package]]
name = "thiserror"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.61" version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2073,9 +2430,9 @@ dependencies = [
[[package]] [[package]]
name = "tikv-jemalloc-sys" name = "tikv-jemalloc-sys"
version = "0.5.4+5.3.0-patched" version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -2083,9 +2440,9 @@ dependencies = [
[[package]] [[package]]
name = "tikv-jemallocator" name = "tikv-jemallocator"
version = "0.5.4" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865"
dependencies = [ dependencies = [
"libc", "libc",
"tikv-jemalloc-sys", "tikv-jemalloc-sys",
@ -2141,9 +2498,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.39.1" version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -2194,9 +2551,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.15" version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -2207,18 +2564,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.6" version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.16" version = "0.22.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -2354,11 +2711,12 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "unicode-truncate" name = "unicode-truncate"
version = "1.0.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [ dependencies = [
"itertools 0.12.1", "itertools 0.13.0",
"unicode-segmentation",
"unicode-width", "unicode-width",
] ]
@ -2401,6 +2759,17 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "v_frame"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
dependencies = [
"aligned-vec",
"num-traits",
"wasm-bindgen",
]
[[package]] [[package]]
name = "validator" name = "validator"
version = "0.18.1" version = "0.18.1"
@ -2476,6 +2845,12 @@ dependencies = [
"rustversion", "rustversion",
] ]
[[package]]
name = "version-compare"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@ -2962,7 +3337,6 @@ dependencies = [
"tikv-jemallocator", "tikv-jemallocator",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-util",
"tracing", "tracing",
"tracing-appender", "tracing-appender",
"tracing-subscriber", "tracing-subscriber",
@ -2986,11 +3360,11 @@ dependencies = [
"clipboard-win", "clipboard-win",
"crossterm", "crossterm",
"futures", "futures",
"globset",
"md-5", "md-5",
"mlua", "mlua",
"parking_lot", "parking_lot",
"ratatui", "ratatui",
"shell-escape",
"shell-words", "shell-words",
"syntect", "syntect",
"tokio", "tokio",
@ -3085,6 +3459,12 @@ dependencies = [
"syn 2.0.71", "syn 2.0.71",
] ]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]] [[package]]
name = "zune-inflate" name = "zune-inflate"
version = "0.2.54" version = "0.2.54"
@ -3093,3 +3473,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "zune-jpeg"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
dependencies = [
"zune-core",
]

15
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1716097317, "lastModified": 1722141560,
"narHash": "sha256-1UMrLtgzielG/Sop6gl6oTSM4pDt7rF9j9VuxhDWDlY=", "narHash": "sha256-Ul3rIdesWaiW56PS/Ak3UlJdkwBrD4UcagCmXZR9Z7Y=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8535fb92661f37ff9f0da3007fbc942f7d134b41", "rev": "038fb464fcfa79b4f08131b07f2d8c9a6bcc4160",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -43,19 +43,16 @@
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1716085073, "lastModified": 1722133294,
"narHash": "sha256-3+9gI93XxszWA2+9S2xZfws1QArPX/MC6nahOGpcMB4=", "narHash": "sha256-XKSVN+lmjVEFPjMa5Ui0VTay2Uvqa74h0MQT0HU1pqw=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "cfc8776011bd83508324115d353222475e1601c0", "rev": "9803f6e04ca37a2c072783e8297d2080f8d0e739",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -20,12 +20,12 @@ base64 = "0.22.1"
color_quant = "1.1.0" color_quant = "1.1.0"
crossterm = "0.27.0" crossterm = "0.27.0"
futures = "0.3.30" futures = "0.3.30"
image = "=0.24.9" image = "0.25.2"
imagesize = "0.13.0" imagesize = "0.13.0"
kamadak-exif = "0.5.5" kamadak-exif = "0.5.5"
ratatui = "0.27.0" ratatui = "0.27.0"
scopeguard = "1.2.0" scopeguard = "1.2.0"
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
# Logging # Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -2,7 +2,7 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use exif::{In, Tag}; use exif::{In, Tag};
use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage}; use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, DynamicImage, Limits};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS}; use yazi_config::{PREVIEW, TASKS};
@ -16,7 +16,7 @@ impl Image {
let path = path.to_owned(); let path = path.to_owned();
let mut img = tokio::task::spawn_blocking(move || { let mut img = tokio::task::spawn_blocking(move || {
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode() Self::set_limits(image::ImageReader::open(path)?.with_guessed_format()?).decode()
}) })
.await??; .await??;
@ -54,7 +54,7 @@ impl Image {
let path = path.to_owned(); let path = path.to_owned();
let mut img = tokio::task::spawn_blocking(move || { let mut img = tokio::task::spawn_blocking(move || {
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode() Self::set_limits(image::ImageReader::open(path)?.with_guessed_format()?).decode()
}) })
.await??; .await??;
@ -155,7 +155,7 @@ impl Image {
img img
} }
fn set_limits(mut r: image::io::Reader<BufReader<File>>) -> image::io::Reader<BufReader<File>> { fn set_limits(mut r: image::ImageReader<BufReader<File>>) -> image::ImageReader<BufReader<File>> {
let mut limits = Limits::no_limits(); let mut limits = Limits::no_limits();
if TASKS.image_alloc > 0 { if TASKS.image_alloc > 0 {
limits.max_alloc = Some(TASKS.image_alloc as u64); limits.max_alloc = Some(TASKS.image_alloc as u64);

View file

@ -15,12 +15,12 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" } yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
clap = { version = "4.5.10", features = [ "derive" ] } clap = { version = "4.5.11", features = [ "derive" ] }
serde = { version = "1.0.204", features = [ "derive" ] } serde = { version = "1.0.204", features = [ "derive" ] }
[build-dependencies] [build-dependencies]
clap = { version = "4.5.10", features = [ "derive" ] } clap = { version = "4.5.11", features = [ "derive" ] }
clap_complete = "4.5.9" clap_complete = "4.5.11"
clap_complete_fig = "4.5.1" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.2" clap_complete_nushell = "4.5.3"
vergen-gitcl = { version = "1.0.0", features = [ "build" ] } vergen-gitcl = { version = "1.0.0", features = [ "build" ] }

View file

@ -44,9 +44,9 @@ impl Actions {
writeln!(s, "\nVariables")?; writeln!(s, "\nVariables")?;
writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?; writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?;
writeln!(s, " EDITOR : {:?}", env::var_os("EDITOR"))?; writeln!(s, " EDITOR : {:?}", env::var_os("EDITOR"))?;
writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?;
writeln!(s, " YAZI_FILE_ONE : {:?}", env::var_os("YAZI_FILE_ONE"))?; writeln!(s, " YAZI_FILE_ONE : {:?}", env::var_os("YAZI_FILE_ONE"))?;
writeln!(s, " YAZI_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?; writeln!(s, " YAZI_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?;
writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?;
writeln!(s, "\nText Opener")?; writeln!(s, "\nText Opener")?;
writeln!( writeln!(
@ -74,7 +74,8 @@ impl Actions {
writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?; writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?;
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?; writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?; writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
writeln!(s, " unar : {}", Self::process_output("unar", "--version"))?; writeln!(s, " 7z : {}", Self::process_output("7z", "i"))?;
writeln!(s, " 7zz : {}", Self::process_output("7zz", "i"))?;
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?; writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;
writeln!(s, "\n\n--------------------------------------------------")?; writeln!(s, "\n\n--------------------------------------------------")?;

View file

@ -15,20 +15,23 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
anyhow = "1.0.86" anyhow = "1.0.86"
clap = { version = "4.5.10", features = [ "derive" ] } clap = { version = "4.5.11", features = [ "derive" ] }
crossterm = "0.27.0" crossterm = "0.27.0"
md-5 = "0.10.6" md-5 = "0.10.6"
serde_json = "1.0.120" serde_json = "1.0.121"
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
toml_edit = "0.22.16" toml_edit = "0.22.17"
[build-dependencies] [build-dependencies]
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External build dependencies
anyhow = "1.0.86" anyhow = "1.0.86"
clap = { version = "4.5.10", features = [ "derive" ] } clap = { version = "4.5.11", features = [ "derive" ] }
clap_complete = "4.5.9" clap_complete = "4.5.11"
clap_complete_fig = "4.5.1" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.2" clap_complete_nushell = "4.5.3"
serde_json = "1.0.120" serde_json = "1.0.121"
vergen-gitcl = { version = "1.0.0", features = [ "build" ] } vergen-gitcl = { version = "1.0.0", features = [ "build" ] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]

View file

@ -16,52 +16,67 @@ pub(super) struct Args {
#[derive(Subcommand)] #[derive(Subcommand)]
pub(super) enum Command { pub(super) enum Command {
/// Publish a message to remote instance(s). /// Publish a message to the current instance.
Pub(CommandPub), Pub(CommandPub),
/// Manage packages. /// Publish a message to the specified instance.
Pack(CommandPack), PubTo(CommandPubTo),
/// Subscribe to messages from all remote instances. /// Subscribe to messages from all remote instances.
Sub(CommandSub), Sub(CommandSub),
/// Manage packages.
Pack(CommandPack),
} }
#[derive(clap::Args)] #[derive(clap::Args)]
pub(super) struct CommandPub { pub(super) struct CommandPub {
/// The kind of message. /// The kind of message.
#[arg(index = 1)] #[arg(index = 1)]
pub(super) kind: String, pub(super) kind: String,
/// Send the message with a string body.
#[arg(long)]
pub(super) str: Option<String>,
/// Send the message with a JSON body.
#[arg(long)]
pub(super) json: Option<String>,
/// Send the message as string of list.
#[arg(long, num_args = 0..)]
pub(super) list: Vec<String>,
}
impl CommandPub {
#[allow(dead_code)]
pub(super) fn receiver(&self) -> Result<u64> {
if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) {
Ok(s.parse()?)
} else {
bail!("No `YAZI_ID` environment variable found.")
}
}
}
#[derive(clap::Args)]
pub(super) struct CommandPubTo {
/// The receiver ID. /// The receiver ID.
#[arg(index = 1)]
pub(super) receiver: u64,
/// The kind of message.
#[arg(index = 2)] #[arg(index = 2)]
pub(super) receiver: Option<u64>, pub(super) kind: String,
/// Send the message with a string body. /// Send the message with a string body.
#[arg(long)] #[arg(long)]
pub(super) str: Option<String>, pub(super) str: Option<String>,
/// Send the message with a JSON body. /// Send the message with a JSON body.
#[arg(long)] #[arg(long)]
pub(super) json: Option<String>, pub(super) json: Option<String>,
/// Send the message as string of list.
#[arg(long, num_args = 0..)]
pub(super) list: Vec<String>,
} }
impl CommandPub { #[derive(clap::Args)]
#[allow(dead_code)] pub(super) struct CommandSub {
pub(super) fn receiver(&self) -> Result<u64> { /// The kind of messages to subscribe to, separated by commas if multiple.
if let Some(receiver) = self.receiver { #[arg(index = 1)]
Ok(receiver) pub(super) kinds: String,
} else if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) {
Ok(s.parse()?)
} else {
bail!("No receiver ID provided, neither YAZI_ID environment variable found.")
}
}
#[allow(dead_code)]
pub(super) fn body(&self) -> Result<Cow<str>> {
if let Some(json) = &self.json {
Ok(json.into())
} else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into())
} else {
Ok("".into())
}
}
} }
#[derive(clap::Args)] #[derive(clap::Args)]
@ -81,9 +96,25 @@ pub(super) struct CommandPack {
pub(super) upgrade: bool, pub(super) upgrade: bool,
} }
#[derive(clap::Args)] // --- Macros
pub(super) struct CommandSub { macro_rules! impl_body {
/// The kind of messages to subscribe to, separated by commas if multiple. ($name:ident) => {
#[arg(index = 1)] impl $name {
pub(super) kinds: String, #[allow(dead_code)]
pub(super) fn body(&self) -> Result<Cow<str>> {
if let Some(json) = &self.json {
Ok(json.into())
} else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into())
} else if !self.list.is_empty() {
Ok(serde_json::to_string(&self.list)?.into())
} else {
Ok("".into())
}
}
}
};
} }
impl_body!(CommandPub);
impl_body!(CommandPubTo);

View file

@ -25,6 +25,24 @@ async fn main() -> anyhow::Result<()> {
std::process::exit(1); std::process::exit(1);
} }
} }
Command::PubTo(cmd) => {
yazi_boot::init_default();
yazi_dds::init();
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver, &cmd.body()?).await {
eprintln!("Cannot send message: {e}");
std::process::exit(1);
}
}
Command::Sub(cmd) => {
yazi_boot::init_default();
yazi_dds::init();
yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?;
tokio::signal::ctrl_c().await?;
}
Command::Pack(cmd) => { Command::Pack(cmd) => {
package::init(); package::init();
if cmd.install { if cmd.install {
@ -40,14 +58,6 @@ async fn main() -> anyhow::Result<()> {
package::Package::add_to_config(repo).await?; package::Package::add_to_config(repo).await?;
} }
} }
Command::Sub(cmd) => {
yazi_boot::init_default();
yazi_dds::init();
yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?;
tokio::signal::ctrl_c().await?;
}
} }
Ok(()) Ok(())

View file

@ -21,7 +21,7 @@ indexmap = "2.2.6"
ratatui = "0.27.0" ratatui = "0.27.0"
regex = "1.10.5" regex = "1.10.5"
serde = { version = "1.0.204", features = [ "derive" ] } serde = { version = "1.0.204", features = [ "derive" ] }
toml = { version = "0.8.15", features = [ "preserve_order" ] } toml = { version = "0.8.16", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] } validator = { version = "0.18.1", features = [ "derive" ] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]

View file

@ -12,44 +12,39 @@ keymap = [
{ on = "<C-c>", run = "close", desc = "Close the current tab, or quit if it is last tab" }, { on = "<C-c>", run = "close", desc = "Close the current tab, or quit if it is last tab" },
{ on = "<C-z>", run = "suspend", desc = "Suspend the process" }, { on = "<C-z>", run = "suspend", desc = "Suspend the process" },
# Navigation # Hopping
{ on = "k", run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = "K", run = "arrow -5", desc = "Move cursor up 5 lines" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "J", run = "arrow 5", desc = "Move cursor down 5 lines" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = "<S-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = "<C-u>", run = "arrow -50%", desc = "Move cursor up half page" }, { on = "<C-u>", run = "arrow -50%", desc = "Move cursor up half page" },
{ on = "<C-d>", run = "arrow 50%", desc = "Move cursor down half page" }, { on = "<C-d>", run = "arrow 50%", desc = "Move cursor down half page" },
{ on = "<C-b>", run = "arrow -100%", desc = "Move cursor up one page" }, { on = "<C-b>", run = "arrow -100%", desc = "Move cursor up one page" },
{ on = "<C-f>", run = "arrow 100%", desc = "Move cursor down one page" }, { on = "<C-f>", run = "arrow 100%", desc = "Move cursor down one page" },
{ on = "<C-PageUp>", run = "arrow -50%", desc = "Move cursor up half page" }, { on = "<S-PageUp>", run = "arrow -50%", desc = "Move cursor up half page" },
{ on = "<C-PageDown>", run = "arrow 50%", desc = "Move cursor down half page" }, { on = "<S-PageDown>", run = "arrow 50%", desc = "Move cursor down half page" },
{ on = "<PageUp>", run = "arrow -100%", desc = "Move cursor up one page" }, { on = "<PageUp>", run = "arrow -100%", desc = "Move cursor up one page" },
{ on = "<PageDown>", run = "arrow 100%", desc = "Move cursor down one page" }, { on = "<PageDown>", run = "arrow 100%", desc = "Move cursor down one page" },
{ on = [ "g", "g" ], run = "arrow -99999999", desc = "Move cursor to the top" },
{ on = "G", run = "arrow 99999999", desc = "Move cursor to the bottom" },
# Navigation
{ on = "h", run = "leave", desc = "Go back to the parent directory" }, { on = "h", run = "leave", desc = "Go back to the parent directory" },
{ on = "l", run = "enter", desc = "Enter the child directory" }, { on = "l", run = "enter", desc = "Enter the child directory" },
{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
{ on = "H", run = "back", desc = "Go back to the previous directory" }, { on = "H", run = "back", desc = "Go back to the previous directory" },
{ on = "L", run = "forward", desc = "Go forward to the next directory" }, { on = "L", run = "forward", desc = "Go forward to the next directory" },
{ on = "<A-k>", run = "seek -5", desc = "Seek up 5 units in the preview" }, # Seeking
{ on = "<A-j>", run = "seek 5", desc = "Seek down 5 units in the preview" }, { on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" },
{ on = "<A-PageUp>", run = "seek -5", desc = "Seek up 5 units in the preview" }, { on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" },
{ on = "<A-PageDown>", run = "seek 5", desc = "Seek down 5 units in the preview" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = "<Left>", run = "leave", desc = "Go back to the parent directory" },
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
{ on = [ "g", "g" ], run = "arrow -99999999", desc = "Move cursor to the top" },
{ on = "G", run = "arrow 99999999", desc = "Move cursor to the bottom" },
# Selection # Selection
{ on = "<Space>", run = [ "select --state=none", "arrow 1" ], desc = "Toggle the current selection state" }, { on = "<Space>", run = [ "select --state=none", "arrow 1" ], desc = "Toggle the current selection state" },
@ -122,6 +117,12 @@ keymap = [
{ on = [ ",", "S" ], run = [ "sort size --reverse", "linemode size" ], desc = "Sort by size (reverse)" }, { on = [ ",", "S" ], run = [ "sort size --reverse", "linemode size" ], desc = "Sort by size (reverse)" },
{ on = [ ",", "r" ], run = "sort random --reverse=no", desc = "Sort randomly" }, { on = [ ",", "r" ], run = "sort random --reverse=no", desc = "Sort randomly" },
# Goto
{ on = [ "g", "h" ], run = "cd ~", desc = "Go to the home directory" },
{ on = [ "g", "c" ], run = "cd ~/.config", desc = "Go to the config directory" },
{ on = [ "g", "d" ], run = "cd ~/Downloads", desc = "Go to the downloads directory" },
{ on = [ "g", "<Space>" ], run = "cd --interactive", desc = "Go to a directory interactively" },
# Tabs # Tabs
{ on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" }, { on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" },
@ -144,12 +145,6 @@ keymap = [
# Tasks # Tasks
{ on = "w", run = "tasks_show", desc = "Show task manager" }, { on = "w", run = "tasks_show", desc = "Show task manager" },
# Goto
{ on = [ "g", "h" ], run = "cd ~", desc = "Go to the home directory" },
{ on = [ "g", "c" ], run = "cd ~/.config", desc = "Go to the config directory" },
{ on = [ "g", "d" ], run = "cd ~/Downloads", desc = "Go to the downloads directory" },
{ on = [ "g", "<Space>" ], run = "cd --interactive", desc = "Go to a directory interactively" },
# Help # Help
{ on = "~", run = "help", desc = "Open help" }, { on = "~", run = "help", desc = "Open help" },
{ on = "<F1>", run = "help", desc = "Open help" }, { on = "<F1>", run = "help", desc = "Open help" },
@ -188,15 +183,9 @@ keymap = [
{ on = "k", run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = "K", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = "J", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = "<S-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
# Help # Help
{ on = "~", run = "help", desc = "Open help" }, { on = "~", run = "help", desc = "Open help" },
{ on = "<F1>", run = "help", desc = "Open help" }, { on = "<F1>", run = "help", desc = "Open help" },
@ -305,15 +294,9 @@ keymap = [
{ on = "k", run = "arrow -1", desc = "Move cursor up" }, { on = "k", run = "arrow -1", desc = "Move cursor up" },
{ on = "j", run = "arrow 1", desc = "Move cursor down" }, { on = "j", run = "arrow 1", desc = "Move cursor down" },
{ on = "K", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = "J", run = "arrow 5", desc = "Move cursor down 5 lines" },
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" }, { on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" }, { on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
{ on = "<S-Up>", run = "arrow -5", desc = "Move cursor up 5 lines" },
{ on = "<S-Down>", run = "arrow 5", desc = "Move cursor down 5 lines" },
# Filtering # Filtering
{ on = "/", run = "filter", desc = "Apply a filter for the help items" }, { on = "/", run = "filter", desc = "Apply a filter for the help items" },
] ]

View file

@ -38,36 +38,40 @@ open = [
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
] ]
reveal = [ reveal = [
{ run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" },
{ run = 'open -R "$1"', desc = "Reveal", for = "macos" }, { run = 'open -R "$1"', desc = "Reveal", for = "macos" },
{ run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" }, { run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" },
{ run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" },
] ]
extract = [ extract = [
{ run = 'unar "$1"', desc = "Extract here", for = "unix" }, { run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" },
{ run = 'unar "%1"', desc = "Extract here", for = "windows" }, { run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" },
] ]
play = [ play = [
{ run = 'mpv --force-window "$@"', orphan = true, for = "unix" }, { run = 'mpv --force-window "$@"', orphan = true, for = "unix" },
{ run = 'mpv --force-window "%1"', orphan = true, for = "windows" }, { run = 'mpv --force-window %*', orphan = true, for = "windows" },
{ run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" }, { run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" },
] ]
[open] [open]
rules = [ rules = [
# Folder
{ name = "*/", use = [ "edit", "open", "reveal" ] }, { name = "*/", use = [ "edit", "open", "reveal" ] },
# Text
{ mime = "text/*", use = [ "edit", "reveal" ] }, { mime = "text/*", use = [ "edit", "reveal" ] },
{ mime = "image/*", use = [ "open", "reveal" ] }, # Image
{ mime = "image/*", use = [ "open", "reveal" ] },
# Media
{ mime = "{audio,video}/*", use = [ "play", "reveal" ] }, { mime = "{audio,video}/*", use = [ "play", "reveal" ] },
{ mime = "inode/x-empty", use = [ "edit", "reveal" ] }, # Archive
{ mime = "application/{,g}zip", use = [ "extract", "reveal" ] }, { mime = "application/{,g}zip", use = [ "extract", "reveal" ] },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", use = [ "extract", "reveal" ] }, { mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", use = [ "extract", "reveal" ] },
# JSON
{ mime = "application/{json,x-ndjson}", use = [ "edit", "reveal" ] }, { mime = "application/{json,x-ndjson}", use = [ "edit", "reveal" ] },
{ mime = "*/javascript", use = [ "edit", "reveal" ] }, { mime = "*/javascript", use = [ "edit", "reveal" ] },
# Empty file
{ mime = "inode/x-empty", use = [ "edit", "reveal" ] },
# Fallback
{ name = "*", use = [ "open", "reveal" ] }, { name = "*", use = [ "open", "reveal" ] },
] ]

View file

@ -1,5 +1,7 @@
use std::path::Path;
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::{event::Cmd, Condition}; use yazi_shared::{event::Cmd, Condition, MIME_DIR};
use crate::{Pattern, Priority}; use crate::{Pattern, Priority};
@ -18,6 +20,15 @@ pub struct Fetcher {
pub prio: Priority, pub prio: Priority,
} }
impl Fetcher {
#[inline]
pub fn matches(&self, path: &Path, mime: Option<&str>, f: impl Fn(&str) -> bool + Copy) -> bool {
self.if_.as_ref().and_then(|c| c.eval(f)) != Some(false)
&& (self.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == Some(MIME_DIR))))
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FetcherProps { pub struct FetcherProps {
pub id: u8, pub id: u8,

View file

@ -1,7 +1,6 @@
use std::{path::Path, str::FromStr}; use std::{collections::HashSet, path::Path, str::FromStr};
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::MIME_DIR;
use super::{Fetcher, Preloader, Previewer}; use super::{Fetcher, Preloader, Previewer};
use crate::{plugin::MAX_PREWORKERS, Preset}; use crate::{plugin::MAX_PREWORKERS, Preset};
@ -20,39 +19,33 @@ impl Plugin {
mime: Option<&'a str>, mime: Option<&'a str>,
factor: impl Fn(&str) -> bool + Copy, factor: impl Fn(&str) -> bool + Copy,
) -> impl Iterator<Item = &'a Fetcher> { ) -> impl Iterator<Item = &'a Fetcher> {
let is_dir = mime == Some(MIME_DIR); let mut seen = HashSet::new();
self.fetchers.iter().filter(move |&f| { self.fetchers.iter().filter(move |&f| {
f.if_.as_ref().and_then(|c| c.eval(factor)) != Some(false) if seen.contains(&f.id) || !f.matches(path, mime, factor) {
&& (f.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m)) return false;
|| f.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))) }
seen.insert(&f.id);
true
}) })
} }
pub fn preloaders(&self, path: &Path, mime: Option<&str>) -> Vec<&Preloader> { pub fn preloaders<'a>(
let is_dir = mime == Some(MIME_DIR); &'a self,
let mut preloaders = Vec::with_capacity(1); path: &'a Path,
mime: Option<&'a str>,
for p in &self.preloaders { ) -> impl Iterator<Item = &'a Preloader> {
if !p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m)) let mut next = true;
&& !p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir)) self.preloaders.iter().filter(move |&p| {
{ if !next || !p.matches(path, mime) {
continue; return false;
} }
next = p.next;
preloaders.push(p); true
if !p.next { })
break;
}
}
preloaders
} }
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&Previewer> { pub fn previewer(&self, path: &Path, mime: &str) -> Option<&Previewer> {
let is_dir = mime == MIME_DIR; self.previewers.iter().find(|&p| p.matches(path, mime))
self.previewers.iter().find(|&p| {
p.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|| p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))
})
} }
} }
impl FromStr for Plugin { impl FromStr for Plugin {

View file

@ -1,5 +1,7 @@
use std::path::Path;
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::event::Cmd; use yazi_shared::{event::Cmd, MIME_DIR};
use crate::{Pattern, Priority}; use crate::{Pattern, Priority};
@ -17,6 +19,14 @@ pub struct Preloader {
pub prio: Priority, pub prio: Priority,
} }
impl Preloader {
#[inline]
pub fn matches(&self, path: &Path, mime: Option<&str>) -> bool {
self.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == Some(MIME_DIR)))
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PreloaderProps { pub struct PreloaderProps {
pub id: u8, pub id: u8,

View file

@ -1,5 +1,7 @@
use std::path::Path;
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::event::Cmd; use yazi_shared::{event::Cmd, MIME_DIR};
use crate::Pattern; use crate::Pattern;
@ -13,6 +15,12 @@ pub struct Previewer {
} }
impl Previewer { impl Previewer {
#[inline]
pub fn matches(&self, path: &Path, mime: &str) -> bool {
self.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR))
}
#[inline] #[inline]
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) } pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }

View file

@ -24,14 +24,14 @@ bitflags = "2.6.0"
crossterm = "0.27.0" crossterm = "0.27.0"
dirs = "5.0.1" dirs = "5.0.1"
futures = "0.3.30" futures = "0.3.30"
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] } notify = { git = "https://github.com/notify-rs/notify.git", rev = "96dec74316a93bed6eec9db177b233e6e017275e", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = "0.12.3" parking_lot = "0.12.3"
ratatui = "0.27.0" ratatui = "0.27.0"
regex = "1.10.5" regex = "1.10.5"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = "1.0.204" serde = "1.0.204"
shell-words = "1.1.0" shell-words = "1.1.0"
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
tokio-util = "0.7.11" tokio-util = "0.7.11"
unicode-width = "0.1.13" unicode-width = "0.1.13"

View file

@ -264,18 +264,19 @@ impl Files {
macro_rules! go { macro_rules! go {
($dist:expr, $src:expr, $inc:literal) => { ($dist:expr, $src:expr, $inc:literal) => {
let len = $src.len(); let mut b = false;
for i in 0..$dist.len() { for i in 0..$dist.len() {
if let Some(f) = $src.remove(&$dist[i].url) { if let Some(f) = $src.remove(&$dist[i].url) {
$dist[i] = f; if $dist[i] != f {
b = true;
$dist[i] = f;
}
if $src.is_empty() { if $src.is_empty() {
break; break;
} }
} }
} }
if $src.len() != len { self.revision += if b { $inc } else { 0 };
self.revision += $inc;
}
}; };
} }

View file

@ -21,5 +21,6 @@ mod unyank;
mod update_files; mod update_files;
mod update_mimetype; mod update_mimetype;
mod update_paged; mod update_paged;
mod update_task;
mod update_yanked; mod update_yanked;
mod yank; mod yank;

View file

@ -87,9 +87,9 @@ impl Manager {
let overwritten = symlink_realpath(&new).await; let overwritten = symlink_realpath(&new).await;
fs::rename(&old, &new).await?; fs::rename(&old, &new).await?;
if let Ok(p) = overwritten { if let Ok(o) = overwritten {
ok_or_not_found(fs::rename(&p, &new).await)?; ok_or_not_found(fs::rename(&o, &new).await)?;
FilesOp::Deleting(p_new.clone(), vec![Url::from(p)]).emit(); FilesOp::Deleting(p_new.clone(), vec![Url::from(o)]).emit();
} }
Pubsub::pub_from_rename(tab, &old, &new); Pubsub::pub_from_rename(tab, &old, &new);

View file

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use tracing::error;
use yazi_shared::{event::Cmd, fs::Url, render}; use yazi_shared::{event::Cmd, fs::Url, render};
use crate::{manager::{Manager, LINKED}, tasks::Tasks}; use crate::{manager::{Manager, LINKED}, tasks::Tasks};
@ -12,14 +13,14 @@ impl TryFrom<Cmd> for Opt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { updates: c.take("updates").ok_or(())?.into_table_string() }) Ok(Self { updates: c.take("updates").ok_or(())?.into_dict_string() })
} }
} }
impl Manager { impl Manager {
pub fn update_mimetype(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) { pub fn update_mimetype(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) {
let Ok(opt) = opt.try_into() else { let Ok(opt) = opt.try_into() else {
return; return error!("invalid arguments for update_mimetype");
}; };
let linked = LINKED.read(); let linked = LINKED.read();

View file

@ -0,0 +1,25 @@
use yazi_shared::{event::Cmd, fs::Url};
use crate::manager::Manager;
pub struct Opt {
url: Url,
}
impl TryFrom<Cmd> for Opt {
type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { url: c.take_any("url").ok_or(())? })
}
}
impl Manager {
pub fn update_task(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else {
return;
};
self.watcher.push_file(opt.url);
}
}

View file

@ -17,7 +17,8 @@ pub(crate) static WATCHED: RoCell<RwLock<HashSet<Url>>> = RoCell::new();
pub static LINKED: RoCell<RwLock<Linked>> = RoCell::new(); pub static LINKED: RoCell<RwLock<Linked>> = RoCell::new();
pub struct Watcher { pub struct Watcher {
tx: watch::Sender<HashSet<Url>>, in_tx: watch::Sender<HashSet<Url>>,
out_tx: mpsc::UnboundedSender<Url>,
} }
impl Watcher { impl Watcher {
@ -25,11 +26,15 @@ impl Watcher {
let (in_tx, in_rx) = watch::channel(Default::default()); let (in_tx, in_rx) = watch::channel(Default::default());
let (out_tx, out_rx) = mpsc::unbounded_channel(); let (out_tx, out_rx) = mpsc::unbounded_channel();
let out_tx_ = out_tx.clone();
let watcher = RecommendedWatcher::new( let watcher = RecommendedWatcher::new(
move |res: Result<notify::Event, notify::Error>| { move |res: Result<notify::Event, notify::Error>| {
let Ok(event) = res else { return }; let Ok(event) = res else { return };
if event.kind.is_access() {
return;
}
for path in event.paths { for path in event.paths {
out_tx.send(Url::from(path)).ok(); out_tx_.send(Url::from(path)).ok();
} }
}, },
Default::default(), Default::default(),
@ -37,12 +42,18 @@ impl Watcher {
tokio::spawn(Self::fan_in(in_rx, watcher.unwrap())); tokio::spawn(Self::fan_in(in_rx, watcher.unwrap()));
tokio::spawn(Self::fan_out(out_rx)); tokio::spawn(Self::fan_out(out_rx));
Self { tx: in_tx } Self { in_tx, out_tx }
} }
pub(super) fn watch(&mut self, mut new: HashSet<&Url>) { pub(super) fn watch(&mut self, mut new: HashSet<&Url>) {
new.retain(|&u| u.is_regular()); new.retain(|&u| u.is_regular());
self.tx.send(new.into_iter().cloned().collect()).ok(); self.in_tx.send(new.into_iter().cloned().collect()).ok();
}
pub(super) fn push_file(&self, url: Url) {
if url.parent_url().is_some_and(|p| WATCHED.read().contains(&p)) {
self.out_tx.send(url).ok();
}
} }
pub(super) fn trigger_dirs(&self, folders: &[&Folder]) { pub(super) fn trigger_dirs(&self, folders: &[&Folder]) {

View file

@ -21,8 +21,8 @@ anyhow = "1.0.86"
mlua = { version = "0.9.9", features = [ "lua54" ] } mlua = { version = "0.9.9", features = [ "lua54" ] }
parking_lot = "0.12.3" parking_lot = "0.12.3"
serde = { version = "1.0.204", features = [ "derive" ] } serde = { version = "1.0.204", features = [ "derive" ] }
serde_json = "1.0.120" serde_json = "1.0.121"
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
tokio-util = "0.7.11" tokio-util = "0.7.11"

View file

@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use mlua::{ExternalError, Lua, Table, Value, Variadic}; use mlua::{ExternalError, Lua, MultiValue, Table, Value};
use yazi_shared::{event::{Data, DataKey}, OrderedFloat}; use yazi_shared::{event::{Data, DataKey}, OrderedFloat};
pub struct Sendable; pub struct Sendable;
@ -15,12 +15,22 @@ impl Sendable {
Value::Number(n) => Data::Number(n), Value::Number(n) => Data::Number(n),
Value::String(s) => Data::String(s.to_str()?.to_owned()), Value::String(s) => Data::String(s.to_str()?.to_owned()),
Value::Table(t) => { Value::Table(t) => {
let mut map = HashMap::with_capacity(t.raw_len()); let (mut i, mut map) = (1, HashMap::with_capacity(t.raw_len()));
for result in t.pairs::<Value, Value>() { for result in t.pairs::<Value, Value>() {
let (k, v) = result?; let (k, v) = result?;
map.insert(Self::value_to_key(k)?, Self::value_to_data(v)?); let k = Self::value_to_key(k)?;
if k == DataKey::Integer(i) {
i += 1;
}
map.insert(k, Self::value_to_data(v)?);
}
if map.len() == i as usize - 1 {
Data::List(map.into_values().collect())
} else {
Data::Dict(map)
} }
Data::Table(map)
} }
Value::Function(_) => Err("function is not supported".into_lua_err())?, Value::Function(_) => Err("function is not supported".into_lua_err())?,
Value::Thread(_) => Err("thread is not supported".into_lua_err())?, Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
@ -44,8 +54,9 @@ impl Sendable {
Data::Integer(v) => Value::Integer(v), Data::Integer(v) => Value::Integer(v),
Data::Number(v) => Value::Number(v), Data::Number(v) => Value::Number(v),
Data::String(v) => Value::String(lua.create_string(v)?), Data::String(v) => Value::String(lua.create_string(v)?),
Data::Table(t) => { Data::List(v) => Value::Table(Self::list_to_table(lua, v)?),
let seq_len = t.keys().filter(|&k| !k.is_numeric()).count(); Data::Dict(t) => {
let seq_len = t.keys().filter(|&k| !k.is_integer()).count();
let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?; let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?;
for (k, v) in t { for (k, v) in t {
table.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?; table.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?;
@ -63,7 +74,7 @@ impl Sendable {
}) })
} }
pub fn vec_to_table(lua: &Lua, data: Vec<Data>) -> mlua::Result<Table> { pub fn list_to_table(lua: &Lua, data: Vec<Data>) -> mlua::Result<Table> {
let mut vec = Vec::with_capacity(data.len()); let mut vec = Vec::with_capacity(data.len());
for v in data.into_iter() { for v in data.into_iter() {
vec.push(Self::data_to_value(lua, v)?); vec.push(Self::data_to_value(lua, v)?);
@ -71,15 +82,15 @@ impl Sendable {
lua.create_sequence_from(vec) lua.create_sequence_from(vec)
} }
pub fn vec_to_variadic(lua: &Lua, data: Vec<Data>) -> mlua::Result<Variadic<Value>> { pub fn list_to_values(lua: &Lua, data: Vec<Data>) -> mlua::Result<MultiValue> {
let mut vec = Vec::with_capacity(data.len()); let mut vec = Vec::with_capacity(data.len());
for v in data { for v in data {
vec.push(Self::data_to_value(lua, v)?); vec.push(Self::data_to_value(lua, v)?);
} }
Ok(Variadic::from_iter(vec)) Ok(MultiValue::from_iter(vec))
} }
pub fn variadic_to_vec(values: Variadic<Value>) -> mlua::Result<Vec<Data>> { pub fn values_to_vec(values: MultiValue) -> mlua::Result<Vec<Data>> {
let mut vec = Vec::with_capacity(values.len()); let mut vec = Vec::with_capacity(values.len());
for value in values { for value in values {
vec.push(Self::value_to_data(value)?); vec.push(Self::value_to_data(value)?);

View file

@ -32,9 +32,8 @@ mlua = { version = "0.9.9", features = [ "lua54" ] }
ratatui = "0.27.0" ratatui = "0.27.0"
scopeguard = "1.2.0" scopeguard = "1.2.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
tokio-util = "0.7.11"
# Logging # Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
@ -49,7 +48,7 @@ signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
crossterm = { version = "0.27.0", features = [ "event-stream", "use-dev-tty" ] } crossterm = { version = "0.27.0", features = [ "event-stream", "use-dev-tty" ] }
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies] [target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
tikv-jemallocator = "0.5.4" tikv-jemallocator = "0.6.0"
[[bin]] [[bin]]
name = "yazi" name = "yazi"

View file

@ -44,12 +44,12 @@ impl App {
}; };
match LUA.named_registry_value::<RtRef>("rt") { match LUA.named_registry_value::<RtRef>("rt") {
Ok(mut r) => r.swap(&opt.id), Ok(mut r) => r.push(&opt.id),
Err(e) => return warn!("{e}"), Err(e) => return warn!("{e}"),
} }
defer! { _ = LUA.named_registry_value::<RtRef>("rt").map(|mut r| r.pop()) }
defer! { LUA.named_registry_value::<RtRef>("rt").map(|mut r| r.reset()).ok(); }; let plugin = match LOADER.load(&LUA, &opt.id) {
let plugin = match LOADER.load(&opt.id) {
Ok(plugin) => plugin, Ok(plugin) => plugin,
Err(e) => return warn!("{e}"), Err(e) => return warn!("{e}"),
}; };
@ -58,7 +58,7 @@ impl App {
if let Some(cb) = opt.cb { if let Some(cb) = opt.cb {
cb(&LUA, plugin) cb(&LUA, plugin)
} else { } else {
plugin.call_method("entry", Sendable::vec_to_table(&LUA, opt.args)?) plugin.call_method("entry", Sendable::list_to_table(&LUA, opt.args)?)
} }
}); });
} }

View file

@ -64,6 +64,7 @@ impl<'a> Executor<'a> {
}; };
} }
on!(MANAGER, update_task);
on!(MANAGER, update_files, &self.app.cx.tasks); on!(MANAGER, update_files, &self.app.cx.tasks);
on!(MANAGER, update_mimetype, &self.app.cx.tasks); on!(MANAGER, update_mimetype, &self.app.cx.tasks);
on!(MANAGER, update_paged, &self.app.cx.tasks); on!(MANAGER, update_paged, &self.app.cx.tasks);

View file

@ -36,7 +36,7 @@ impl Lives {
f: impl FnOnce(&Scope<'a, 'a>) -> mlua::Result<T>, f: impl FnOnce(&Scope<'a, 'a>) -> mlua::Result<T>,
) -> mlua::Result<T> { ) -> mlua::Result<T> {
let result = LUA.scope(|scope| { let result = LUA.scope(|scope| {
defer! { SCOPE.drop(); }; defer! { SCOPE.drop(); }
SCOPE.init(unsafe { SCOPE.init(unsafe {
mem::transmute::<&mlua::Scope<'a, 'a>, &mlua::Scope<'static, 'static>>(scope) mem::transmute::<&mlua::Scope<'a, 'a>, &mlua::Scope<'static, 'static>>(scope)
}); });

View file

@ -26,14 +26,14 @@ anyhow = "1.0.86"
base64 = "0.22.1" base64 = "0.22.1"
crossterm = "0.27.0" crossterm = "0.27.0"
futures = "0.3.30" futures = "0.3.30"
globset = "0.4.14"
md-5 = "0.10.6" md-5 = "0.10.6"
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.3" parking_lot = "0.12.3"
ratatui = "0.27.0" ratatui = "0.27.0"
shell-escape = "0.1.5"
shell-words = "1.1.0" shell-words = "1.1.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
tokio-util = "0.7.11" tokio-util = "0.7.11"
unicode-width = "0.1.13" unicode-width = "0.1.13"

View file

@ -1,62 +1,42 @@
local M = {} local M = {}
function M:peek() function M:peek()
local child
if ya.target_os() == "macos" then
child = self:try_spawn("7zz") or self:try_spawn("7z")
else
child = self:try_spawn("7z") or self:try_spawn("7zz")
end
if not child then
return ya.err("spawn `7z` and `7zz` both commands failed, error code: " .. tostring(self.last_error))
end
local limit = self.area.h local limit = self.area.h
local i, icon, names, sizes = 0, nil, {}, {} local paths, sizes = {}, {}
repeat
local next, event = child:read_line()
if event ~= 0 then
break
end
local attr, size, name = next:match("^[-%d]+%s+[:%d]+%s+([.%a]+)%s+(%d+)%s+%d+%s+(.+)[\r\n]+") local files, bound, code = self:list_files({ "-p", tostring(self.file.url) }, self.skip, limit)
if not name then if code ~= 0 then
goto continue return ya.preview_widgets(self, {
end ui.Paragraph(self.area, {
ui.Line(code == 2 and "File list in this archive is encrypted" or "Spawn `7z` and `7zz` both commands failed"),
}),
})
end
i = i + 1 for _, f in ipairs(files) do
if i <= self.skip then local icon = File({
goto continue url = Url(f.path),
end cha = Cha { kind = f.attr:sub(1, 1) == "D" and 1 or 0 },
icon = File({
url = Url(name),
cha = Cha { kind = attr:sub(1, 1) == "D" and 1 or 0 },
}):icon() }):icon()
if icon then if icon then
names[#names + 1] = ui.Line { ui.Span(" " .. icon.text .. " "):style(icon.style), ui.Span(name) } paths[#paths + 1] = ui.Line { ui.Span(" " .. icon.text .. " "):style(icon.style), ui.Span(f.path) }
else else
names[#names + 1] = ui.Line(name) paths[#paths + 1] = ui.Line(f.path)
end end
size = tonumber(size) if f.size > 0 then
if size > 0 then sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(f.size)))
sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(size)))
else else
sizes[#sizes + 1] = ui.Line("") sizes[#sizes + 1] = ui.Line("")
end end
end
::continue:: if self.skip > 0 and bound < self.skip + limit then
until i >= self.skip + limit ya.manager_emit("peek", { math.max(0, bound - limit), only_if = self.file.url, upper_bound = true })
child:start_kill()
if self.skip > 0 and i < self.skip + limit then
ya.manager_emit("peek", { math.max(0, i - limit), only_if = self.file.url, upper_bound = true })
else else
ya.preview_widgets(self, { ya.preview_widgets(self, {
ui.Paragraph(self.area, names), ui.Paragraph(self.area, paths),
ui.Paragraph(self.area, sizes):align(ui.Paragraph.RIGHT), ui.Paragraph(self.area, sizes):align(ui.Paragraph.RIGHT),
}) })
end end
@ -73,12 +53,93 @@ function M:seek(units)
end end
end end
function M:try_spawn(name) function M:spawn_7z(args)
local child, code = Command(name):args({ "l", "-ba", tostring(self.file.url) }):stdout(Command.PIPED):spawn() local last_error = nil
if not child then local try = function(name)
self.last_error = code local stdout = args[1] == "l" and Command.PIPED or Command.NULL
local child, code = Command(name):args(args):stdout(stdout):stderr(Command.PIPED):spawn()
if not child then
last_error = code
end
return child
end end
return child
local child
if ya.target_os() == "macos" then
child = try("7zz") or try("7z")
else
child = try("7z") or try("7zz")
end
if not child then
return ya.err("spawn `7z` and `7zz` both commands failed, error code: " .. tostring(last_error))
end
return child, last_error
end
---List files in an archive
---@param args table
---@param skip integer
---@param limit integer
---@return table
---@return integer
---@return integer
--- 0: success
--- 1: failed to spawn
--- 2: wrong password
--- 3: partial success
function M:list_files(args, skip, limit)
local child = self:spawn_7z { "l", "-ba", "-slt", table.unpack(args) }
if not child then
return {}, 0, 1
end
local i, files, code = 0, { { path = "", size = 0, attr = "" } }, 0
local key, value = "", ""
repeat
local next, event = child:read_line()
if event == 1 and self:is_encrypted(next) then
code = 2
break
elseif event == 1 then
code = 3
goto continue
elseif event ~= 0 then
break
end
if next == "\n" or next == "\r\n" then
i = i + 1
if files[#files].path ~= "" then
files[#files + 1] = { path = "", size = 0, attr = "" }
end
goto continue
elseif i < skip then
goto continue
end
key, value = next:match("^(%u%l+) = (.+)[\r\n]+")
if key == "Path" then
files[#files].path = value
elseif key == "Size" then
files[#files].size = tonumber(value) or 0
elseif key == "Attributes" then
files[#files].attr = value
end
::continue::
until i >= skip + limit
child:start_kill()
if files[#files].path == "" then
files[#files] = nil
end
return files, i, code
end
function M:is_encrypted(s)
return s:find("Cannot open encrypted archive. Wrong password?", 1, true)
or s:find("Data Error in encrypted file. Wrong password?", 1, true)
end end
return M return M

View file

@ -0,0 +1,117 @@
local function fail(s, ...) error(string.format(s, ...)) end
local M = {}
function M:setup()
ps.sub_remote("extract", function(args)
local noisy = #args == 1 and " --noisy" or ""
for _, arg in ipairs(args) do
ya.manager_emit("plugin", { self._id, args = ya.quote(arg, true) .. noisy })
end
end)
end
function M:entry(args)
if not args[1] then
fail("No URL provided")
end
local url, pwd = Url(args[1]), ""
while true do
if not M:try_with(url, pwd) then
break
elseif args[2] ~= "--noisy" then
fail("'%s' is password-protected, please extract it individually and enter the password", args[1])
end
local value, event = ya.input {
title = string.format('Password for "%s":', url:name()),
position = { "center", w = 50 },
}
if event == 1 then
pwd = value
else
break
end
end
end
function M:try_with(url, pwd)
local parent = url:parent()
if not parent then
fail("Invalid URL '%s'", url)
end
local tmp = fs.unique_name(parent:join(self.tmp_name(url)))
if not tmp then
fail("Failed to determine a temporary directory for %s", url)
end
local archive = require("archive")
local child, code = archive:spawn_7z { "x", "-aou", "-p" .. pwd, "-o" .. tostring(tmp), tostring(url) }
if not child then
fail("Spawn `7z` and `7zz` both commands failed, error code %s", code)
end
local output, err = child:wait_with_output()
if output and output.status.code == 2 and archive:is_encrypted(output.stderr) then
fs.remove("dir_clean", tmp)
return true -- Needs retry
end
self:tidy(url, tmp)
if not output then
fail("7zip failed to output when extracting '%s', error code %s", err, url)
elseif output.status.code ~= 0 then
fail("7zip exited when extracting '%s', error code %s", url, output.status.code)
end
end
function M:tidy(url, tmp)
local files = fs.read_dir(tmp, { limit = 2 })
if not files then
fail("Failed to read the temporary directory '%s' when extracting '%s'", tmp, url)
elseif #files == 0 then
fs.remove("dir", tmp)
fail("No files extracted from '%s'", url)
end
local target
local only_dir = #files == 1 and files[1].cha.is_dir
if only_dir then
target = url:parent():join(files[1].name)
else
target = url:parent():join(self.trim_ext(url:name()))
end
target = fs.unique_name(target)
if not target then
fail("Failed to determine a target directory for '%s'", url)
end
if only_dir and not os.rename(tostring(files[1].url), tostring(target)) then
fail('Failed to move "%s" to "%s"', files[1].url, target)
elseif not only_dir and not os.rename(tostring(tmp), tostring(target)) then
fail('Failed to move "%s" to "%s"', tmp, target)
end
fs.remove("dir", tmp)
end
function M.tmp_name(url) return ".tmp_" .. ya.md5(string.format("extract//%s//%.10f", url, ya.time())) end
function M.trim_ext(name)
-- stylua: ignore
local exts = { ["7z"] = true, apk = true, bz2 = true, bzip2 = true, exe = true, gz = true, gzip = true, iso = true, jar = true, rar = true, tar = true, tgz = true, xz = true, zip = true, zst = true }
while true do
local s = name:gsub("%.([a-zA-Z0-9]+)$", function(s) return (exts[s] or exts[s:lower()]) and "" end)
if s == name or s == "" then
break
else
name = s
end
end
return name
end
return M

View file

@ -22,7 +22,7 @@ local function entry()
local target = output.stdout:gsub("\n$", "") local target = output.stdout:gsub("\n$", "")
if target ~= "" then if target ~= "" then
ya.manager_emit(target:match("[/\\]$") and "cd" or "reveal", { target }) ya.manager_emit(target:find("[/\\]$") and "cd" or "reveal", { target })
end end
end end

View file

@ -4,7 +4,7 @@ local M = {}
local function match_mimetype(s) local function match_mimetype(s)
local type, sub = s:match("([-a-z]+/)([+-.a-zA-Z0-9]+)%s*$") local type, sub = s:match("([-a-z]+/)([+-.a-zA-Z0-9]+)%s*$")
if type and sub and string.find(SUPPORTED_TYPES, type, 1, true) then if type and sub and SUPPORTED_TYPES:find(type, 1, true) then
return type .. sub return type .. sub
end end
end end
@ -44,7 +44,7 @@ function M:fetch()
end end
valid = match_mimetype(line) valid = match_mimetype(line)
if valid and string.find(line, valid, 1, true) ~= 1 then if valid and line:find(valid, 1, true) ~= 1 then
goto continue goto continue
elseif valid then elseif valid then
j, updates[urls[i]] = j + 1, valid j, updates[urls[i]] = j + 1, valid

View file

@ -7,9 +7,7 @@ end)
local set_state = ya.sync(function(st, empty) st.empty = empty end) local set_state = ya.sync(function(st, empty) st.empty = empty end)
local function fail(s, ...) local function fail(s, ...) ya.notify { title = "Zoxide", content = s:format(...), timeout = 5, level = "error" } end
ya.notify { title = "Zoxide", content = string.format(s, ...), timeout = 5, level = "error" }
end
local function head(cwd) local function head(cwd)
local child = Command("zoxide"):args({ "query", "-l" }):stdout(Command.PIPED):spawn() local child = Command("zoxide"):args({ "query", "-l" }):stdout(Command.PIPED):spawn()

View file

@ -2,3 +2,4 @@ os.setlocale("")
package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. package.path package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. package.path
require("dds"):setup() require("dds"):setup()
require("extract"):setup()

View file

@ -21,7 +21,7 @@ function ya.list_merge(a, b)
return a return a
end end
function ya.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end function ya.basename(s) return s:gsub("(.*[/\\])(.*)", "%2") end
function ya.readable_size(size) function ya.readable_size(size)
local units = { "B", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q" } local units = { "B", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q" }
@ -37,8 +37,8 @@ function ya.readable_path(path)
local home = os.getenv("HOME") or os.getenv("USERPROFILE") local home = os.getenv("HOME") or os.getenv("USERPROFILE")
if not home then if not home then
return path return path
elseif string.sub(path, 1, #home) == home then elseif path:sub(1, #home) == home then
return "~" .. string.sub(path, #home + 1) return "~" .. path:sub(#home + 1)
else else
return path return path
end end

View file

@ -1,12 +1,10 @@
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use mlua::{AnyUserData, ExternalError, Lua, Table, UserDataFields, UserDataMethods, UserDataRef}; use mlua::{AnyUserData, ExternalError, Lua, Table, UserDataFields, UserDataMethods};
use yazi_shared::fs::ChaKind; use yazi_shared::fs::ChaKind;
use crate::bindings::Cast; use crate::bindings::Cast;
pub type UrlRef<'lua> = UserDataRef<'lua, yazi_shared::fs::Cha>;
pub struct Cha; pub struct Cha;
impl Cha { impl Cha {

View file

@ -17,8 +17,8 @@ impl File {
where where
T: AsRef<yazi_shared::fs::File>, T: AsRef<yazi_shared::fs::File>,
{ {
reg.add_field_method_get("url", |lua, me| Url::cast(lua, me.as_ref().url.clone()));
reg.add_field_method_get("cha", |lua, me| Cha::cast(lua, me.as_ref().cha)); reg.add_field_method_get("cha", |lua, me| Cha::cast(lua, me.as_ref().cha));
reg.add_field_method_get("url", |lua, me| Url::cast(lua, me.as_ref().url.clone()));
reg.add_field_method_get("link_to", |lua, me| { reg.add_field_method_get("link_to", |lua, me| {
me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose() me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose()
}); });
@ -49,8 +49,8 @@ impl File {
"File", "File",
lua.create_function(|lua, t: Table| { lua.create_function(|lua, t: Table| {
Self::cast(lua, yazi_shared::fs::File { Self::cast(lua, yazi_shared::fs::File {
url: t.raw_get::<_, AnyUserData>("url")?.take()?,
cha: t.raw_get::<_, AnyUserData>("cha")?.take()?, cha: t.raw_get::<_, AnyUserData>("cha")?.take()?,
url: t.raw_get::<_, AnyUserData>("url")?.take()?,
..Default::default() ..Default::default()
}) })
})?, })?,

View file

@ -1,12 +1,29 @@
use mlua::{IntoLuaMulti, Lua, Value}; use globset::GlobBuilder;
use mlua::{ExternalError, ExternalResult, IntoLuaMulti, Lua, Table, Value};
use tokio::fs; use tokio::fs;
use yazi_shared::fs::remove_dir_clean;
use crate::{bindings::Cast, cha::Cha, url::{Url, UrlRef}}; use crate::{bindings::Cast, cha::Cha, file::File, url::{Url, UrlRef}};
pub fn install(lua: &Lua) -> mlua::Result<()> { pub fn install(lua: &Lua) -> mlua::Result<()> {
lua.globals().raw_set( lua.globals().raw_set(
"fs", "fs",
lua.create_table_from([ lua.create_table_from([
(
"cha",
lua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move {
let meta = if follow.unwrap_or(false) {
fs::metadata(&*url).await
} else {
fs::symlink_metadata(&*url).await
};
match meta {
Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua),
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
}
})?,
),
( (
"write", "write",
lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move { lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move {
@ -17,21 +34,79 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
})?, })?,
), ),
( (
"cha", "remove",
lua.create_async_function(|lua, url: UrlRef| async move { lua.create_async_function(|lua, (type_, url): (mlua::String, UrlRef)| async move {
match fs::symlink_metadata(&*url).await { let result = match type_.to_str()? {
Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua), "file" => fs::remove_file(&*url).await,
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), "dir" => fs::remove_dir(&*url).await,
"dir_all" => fs::remove_dir_all(&*url).await,
"dir_clean" => Ok(remove_dir_clean(&url).await),
_ => {
Err("Removal type must be 'file', 'dir', 'dir_all', or 'dir_clean'".into_lua_err())?
}
};
match result {
Ok(_) => (true, Value::Nil).into_lua_multi(lua),
Err(e) => (false, e.raw_os_error()).into_lua_multi(lua),
} }
})?, })?,
), ),
( (
"cha_follow", "read_dir",
lua.create_async_function(|lua, url: UrlRef| async move { lua.create_async_function(|lua, (url, options): (UrlRef, Table)| async move {
match fs::metadata(&*url).await { let glob = if let Ok(s) = options.raw_get::<_, mlua::String>("glob") {
Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua), Some(
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), GlobBuilder::new(s.to_str()?)
.case_insensitive(true)
.literal_separator(false)
.backslash_escape(false)
.empty_alternates(true)
.build()
.into_lua_err()?
.compile_matcher(),
)
} else {
None
};
let limit = options.raw_get("limit").unwrap_or(usize::MAX);
let resolve = options.raw_get("resolve").unwrap_or(false);
let mut it = match fs::read_dir(&*url).await {
Ok(it) => it,
Err(e) => return (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
};
let mut files = vec![];
while let Ok(Some(next)) = it.next_entry().await {
if files.len() >= limit {
break;
}
let path = next.path();
if glob.as_ref().is_some_and(|g| !g.is_match(&path)) {
continue;
}
let url = yazi_shared::fs::Url::from(path);
let file = if !resolve {
yazi_shared::fs::File::from_dummy(url, next.file_type().await.ok())
} else if let Ok(meta) = next.metadata().await {
yazi_shared::fs::File::from_meta(url, meta).await
} else {
yazi_shared::fs::File::from_dummy(url, next.file_type().await.ok())
};
files.push(File::cast(lua, file)?);
} }
let tbl = lua.create_table_with_capacity(files.len(), 0)?;
for f in files {
tbl.raw_push(f)?;
}
(tbl, Value::Nil).into_lua_multi(lua)
})?, })?,
), ),
( (

View file

@ -18,7 +18,7 @@ pub async fn entry(name: String, args: Vec<Data>) -> mlua::Result<()> {
}; };
Handle::current() Handle::current()
.block_on(plugin.call_async_method("entry", Sendable::vec_to_table(&lua, args))) .block_on(plugin.call_async_method("entry", Sendable::list_to_table(&lua, args)))
}) })
.await .await
.into_lua_err()? .into_lua_err()?

View file

@ -12,6 +12,7 @@ pub fn slim_lua(name: &str) -> mlua::Result<Lua> {
crate::file::pour(&lua)?; crate::file::pour(&lua)?;
crate::url::pour(&lua)?; crate::url::pour(&lua)?;
crate::loader::install_isolate(&lua)?;
crate::fs::install(&lua)?; crate::fs::install(&lua)?;
crate::process::install(&lua)?; crate::process::install(&lua)?;
crate::utils::install_isolate(&lua)?; crate::utils::install_isolate(&lua)?;

View file

@ -1,14 +1,12 @@
use std::{borrow::Cow, collections::HashMap, ops::Deref}; use std::{borrow::Cow, collections::HashMap, ops::Deref};
use anyhow::Result; use anyhow::Result;
use mlua::{ExternalError, Table}; use mlua::{ExternalError, Lua, Table};
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::fs; use tokio::fs;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_shared::RoCell; use yazi_shared::RoCell;
use crate::LUA;
pub static LOADER: RoCell<Loader> = RoCell::new(); pub static LOADER: RoCell<Loader> = RoCell::new();
#[derive(Default)] #[derive(Default)]
@ -23,11 +21,10 @@ impl Loader {
} }
let preset = match name { let preset = match name {
"dds" => &include_bytes!("../../preset/plugins/dds.lua")[..], "archive" => &include_bytes!("../../preset/plugins/archive.lua")[..],
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
"session" => include_bytes!("../../preset/plugins/session.lua"),
"archive" => include_bytes!("../../preset/plugins/archive.lua"),
"code" => include_bytes!("../../preset/plugins/code.lua"), "code" => include_bytes!("../../preset/plugins/code.lua"),
"dds" => include_bytes!("../../preset/plugins/dds.lua"),
"extract" => include_bytes!("../../preset/plugins/extract.lua"),
"file" => include_bytes!("../../preset/plugins/file.lua"), "file" => include_bytes!("../../preset/plugins/file.lua"),
"folder" => include_bytes!("../../preset/plugins/folder.lua"), "folder" => include_bytes!("../../preset/plugins/folder.lua"),
"font" => include_bytes!("../../preset/plugins/font.lua"), "font" => include_bytes!("../../preset/plugins/font.lua"),
@ -36,7 +33,9 @@ impl Loader {
"json" => include_bytes!("../../preset/plugins/json.lua"), "json" => include_bytes!("../../preset/plugins/json.lua"),
"magick" => include_bytes!("../../preset/plugins/magick.lua"), "magick" => include_bytes!("../../preset/plugins/magick.lua"),
"mime" => include_bytes!("../../preset/plugins/mime.lua"), "mime" => include_bytes!("../../preset/plugins/mime.lua"),
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"), "pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
"session" => include_bytes!("../../preset/plugins/session.lua"),
"video" => include_bytes!("../../preset/plugins/video.lua"), "video" => include_bytes!("../../preset/plugins/video.lua"),
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"), "zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
_ => b"", _ => b"",
@ -52,19 +51,18 @@ impl Loader {
Ok(()) Ok(())
} }
pub fn load(&self, id: &str) -> mlua::Result<Table> { pub fn load<'a>(&self, lua: &'a Lua, id: &str) -> mlua::Result<Table<'a>> {
let globals = LUA.globals(); let loaded: Table = lua.globals().raw_get::<_, Table>("package")?.raw_get("loaded")?;
let loaded: Table = globals.raw_get::<_, Table>("package")?.raw_get("loaded")?;
if let Ok(t) = loaded.raw_get::<_, Table>(id) { if let Ok(t) = loaded.raw_get::<_, Table>(id) {
return Ok(t); return Ok(t);
} }
let t: Table = match self.read().get(id) { let t: Table = match self.read().get(id) {
Some(b) => LUA.load(b.as_ref()).set_name(id).call(())?, Some(b) => lua.load(b.as_ref()).set_name(id).call(())?,
None => Err(format!("plugin `{id}` not found").into_lua_err())?, None => Err(format!("plugin `{id}` not found").into_lua_err())?,
}; };
t.raw_set("_id", LUA.create_string(id)?)?; t.raw_set("_id", lua.create_string(id)?)?;
loaded.raw_set(id, t.clone())?; loaded.raw_set(id, t.clone())?;
Ok(t) Ok(t)
} }

View file

@ -8,8 +8,6 @@ use require::*;
pub(super) fn init() { LOADER.with(<_>::default); } pub(super) fn init() { LOADER.with(<_>::default); }
pub(super) fn install(lua: &'static mlua::Lua) -> mlua::Result<()> { pub(super) fn install(lua: &mlua::Lua) -> mlua::Result<()> { Require::install(lua) }
Require::install(lua)?;
Ok(()) pub(super) fn install_isolate(lua: &mlua::Lua) -> mlua::Result<()> { Require::install_isolate(lua) }
}

View file

@ -1,4 +1,6 @@
use mlua::{ExternalResult, IntoLua, Lua, MetaMethod, Table, TableExt, UserData, Value, Variadic}; use std::sync::Arc;
use mlua::{ExternalResult, Function, IntoLua, Lua, MultiValue, Table, TableExt, Value};
use super::LOADER; use super::LOADER;
use crate::RtRef; use crate::RtRef;
@ -6,41 +8,50 @@ use crate::RtRef;
pub(super) struct Require; pub(super) struct Require;
impl Require { impl Require {
pub(super) fn install(lua: &'static Lua) -> mlua::Result<()> { pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
let globals = lua.globals(); lua.globals().raw_set(
globals.raw_set(
"require", "require",
lua.create_function(|lua, name: mlua::String| { lua.create_function(|lua, id: mlua::String| {
let s = name.to_str()?; let s = id.to_str()?;
futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?; futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?;
lua.named_registry_value::<RtRef>("rt")?.swap(s); lua.named_registry_value::<RtRef>("rt")?.push(s);
let mod_ = LOADER.load(s)?; let mod_ = LOADER.load(lua, s);
lua.named_registry_value::<RtRef>("rt")?.reset(); lua.named_registry_value::<RtRef>("rt")?.pop();
Self::create_mt(lua, name, mod_) Self::create_mt(lua, s, mod_?, true)
})?, })?,
)?; )
Ok(())
} }
fn create_mt( pub(super) fn install_isolate(lua: &Lua) -> mlua::Result<()> {
lua: &'static Lua, lua.globals().raw_set(
name: mlua::String<'static>, "require",
mod_: Table<'static>, lua.create_async_function(|lua, id: mlua::String| async move {
) -> mlua::Result<Table<'static>> { let s = id.to_str()?;
let ts = LOADER.ensure(s).await.into_lua_err()?;
lua.create_table_from([("name", name.into_lua(lua)?), ("mod", mod_.into_lua(lua)?)])?;
lua.named_registry_value::<RtRef>("rt")?.push(s);
let mod_ = LOADER.load(lua, s);
lua.named_registry_value::<RtRef>("rt")?.pop();
Self::create_mt(lua, s, mod_?, false)
})?,
)
}
fn create_mt<'a>(lua: &'a Lua, id: &str, mod_: Table<'a>, sync: bool) -> mlua::Result<Table<'a>> {
let ts = lua.create_table_from([("_mod", mod_.into_lua(lua)?)])?;
let id: Arc<str> = Arc::from(id);
let mt = lua.create_table_from([( let mt = lua.create_table_from([(
"__index", "__index",
lua.create_function(|_, (_, key): (Table, mlua::String)| { lua.create_function(move |lua, (ts, key): (Table, mlua::String)| {
if key.to_str()? == "setup" { match ts.raw_get::<_, Table>("_mod")?.raw_get::<_, Value>(&key)? {
Ok(RequireSetup) Value::Function(_) => {
} else { Self::create_wrapper(lua, id.clone(), key.to_str()?, sync)?.into_lua(lua)
Err("Only `require():setup()` is supported").into_lua_err() }
v => Ok(v),
} }
})?, })?,
)])?; )])?;
@ -48,18 +59,34 @@ impl Require {
ts.set_metatable(Some(mt)); ts.set_metatable(Some(mt));
Ok(ts) Ok(ts)
} }
}
pub(super) struct RequireSetup; fn create_wrapper<'a>(
lua: &'a Lua,
id: Arc<str>,
f: &str,
sync: bool,
) -> mlua::Result<Function<'a>> {
let f: Arc<str> = Arc::from(f);
impl UserData for RequireSetup { if sync {
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { lua.create_function(move |lua, (ts, args): (Table, MultiValue)| {
methods.add_meta_method(MetaMethod::Call, |lua, _, (ts, args): (Table, Variadic<Value>)| { let mod_: Table = ts.raw_get::<_, Table>("_mod")?;
let (name, mod_): (mlua::String, Table) = (ts.raw_get("name")?, ts.raw_get("mod")?); lua.named_registry_value::<RtRef>("rt")?.push(&id);
lua.named_registry_value::<RtRef>("rt")?.swap(name.to_str()?); let result = mod_.call_method::<_, MultiValue>(&f, args);
let result = mod_.call_method::<_, Variadic<Value>>("setup", args); lua.named_registry_value::<RtRef>("rt")?.pop();
lua.named_registry_value::<RtRef>("rt")?.reset(); result
result })
}); } else {
lua.create_async_function(move |lua, (ts, args): (Table, MultiValue)| {
let (id, f) = (id.clone(), f.clone());
async move {
let mod_: Table = ts.raw_get::<_, Table>("_mod")?;
lua.named_registry_value::<RtRef>("rt")?.push(&id);
let result = mod_.call_async_method::<_, MultiValue>(&f, args).await;
lua.named_registry_value::<RtRef>("rt")?.pop();
result
}
})
}
} }
} }

View file

@ -28,7 +28,8 @@ impl Pubsub {
ps.raw_set( ps.raw_set(
"sub", "sub",
lua.create_function(|lua, (kind, f): (mlua::String, Function)| { lua.create_function(|lua, (kind, f): (mlua::String, Function)| {
let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current else { let rt = lua.named_registry_value::<RtRef>("rt")?;
let Some(cur) = rt.current() else {
return Err("`sub()` must be called in a sync plugin").into_lua_err(); return Err("`sub()` must be called in a sync plugin").into_lua_err();
}; };
if !yazi_dds::Pubsub::sub(cur, kind.to_str()?, f) { if !yazi_dds::Pubsub::sub(cur, kind.to_str()?, f) {
@ -41,7 +42,8 @@ impl Pubsub {
ps.raw_set( ps.raw_set(
"sub_remote", "sub_remote",
lua.create_function(|_, (kind, f): (mlua::String, Function)| { lua.create_function(|_, (kind, f): (mlua::String, Function)| {
let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current else { let rt = lua.named_registry_value::<RtRef>("rt")?;
let Some(cur) = rt.current() else {
return Err("`sub_remote()` must be called in a sync plugin").into_lua_err(); return Err("`sub_remote()` must be called in a sync plugin").into_lua_err();
}; };
if !yazi_dds::Pubsub::sub_remote(cur, kind.to_str()?, f) { if !yazi_dds::Pubsub::sub_remote(cur, kind.to_str()?, f) {
@ -54,7 +56,7 @@ impl Pubsub {
ps.raw_set( ps.raw_set(
"unsub", "unsub",
lua.create_function(|_, kind: mlua::String| { lua.create_function(|_, kind: mlua::String| {
if let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current { if let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current() {
Ok(yazi_dds::Pubsub::unsub(cur, kind.to_str()?)) Ok(yazi_dds::Pubsub::unsub(cur, kind.to_str()?))
} else { } else {
Err("`unsub()` must be called in a sync plugin").into_lua_err() Err("`unsub()` must be called in a sync plugin").into_lua_err()
@ -65,7 +67,7 @@ impl Pubsub {
ps.raw_set( ps.raw_set(
"unsub_remote", "unsub_remote",
lua.create_function(|_, kind: mlua::String| { lua.create_function(|_, kind: mlua::String| {
if let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current { if let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current() {
Ok(yazi_dds::Pubsub::unsub_remote(cur, kind.to_str()?)) Ok(yazi_dds::Pubsub::unsub_remote(cur, kind.to_str()?))
} else { } else {
Err("`unsub_remote()` must be called in a sync plugin").into_lua_err() Err("`unsub_remote()` must be called in a sync plugin").into_lua_err()

View file

@ -1,49 +1,53 @@
use std::collections::HashMap; use std::collections::{HashMap, VecDeque};
use mlua::{Function, UserData}; use mlua::{Function, UserData};
#[derive(Default)] #[derive(Default)]
pub struct Runtime { pub struct Runtime {
pub current: Option<String>, frames: VecDeque<RuntimeFrame>,
pub calls: usize, blocks: HashMap<String, Vec<Function<'static>>>,
pub blocks: HashMap<String, Vec<Function<'static>>>, }
struct RuntimeFrame {
id: String,
calls: usize,
} }
pub type RtRef<'lua> = mlua::UserDataRefMut<'lua, Runtime>; pub type RtRef<'lua> = mlua::UserDataRefMut<'lua, Runtime>;
impl Runtime { impl Runtime {
pub fn new(current: &str) -> Self { pub fn new(id: &str) -> Self {
Self { current: Some(current.to_owned()), ..Default::default() } Self {
frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]),
..Default::default()
}
} }
pub fn swap(&mut self, name: &str) { pub fn push(&mut self, id: &str) {
self.current = Some(name.to_owned()); self.frames.push_back(RuntimeFrame { id: id.to_owned(), calls: 0 });
self.calls = 0;
} }
pub fn reset(&mut self) { pub fn pop(&mut self) { self.frames.pop_back(); }
self.current = None;
self.calls = 0; pub fn current(&self) -> Option<&str> { self.frames.back().map(|f| f.id.as_str()) }
pub fn next_block(&mut self) -> Option<usize> {
self.frames.back_mut().map(|f| {
f.calls += 1;
f.calls - 1
})
} }
pub fn next_block(&mut self) -> usize { pub fn get_block(&self, id: &str, calls: usize) -> Option<Function<'static>> {
self.calls += 1; self.blocks.get(id).and_then(|v| v.get(calls)).cloned()
self.calls - 1
} }
pub fn get_block(&self, name: &str, calls: usize) -> Option<Function<'static>> { pub fn put_block(&mut self, f: Function<'static>) -> bool {
self.blocks.get(name).and_then(|v| v.get(calls)).cloned() let Some(cur) = self.frames.back() else { return false };
} if let Some(v) = self.blocks.get_mut(&cur.id) {
v.push(f);
pub fn push_block(&mut self, f: Function<'static>) -> bool {
let Some(ref cur) = self.current else {
return false;
};
if let Some(vec) = self.blocks.get_mut(cur) {
vec.push(f);
} else { } else {
self.blocks.insert(cur.clone(), vec![f]); self.blocks.insert(cur.id.to_owned(), vec![f]);
} }
true true
} }

View file

@ -1,4 +1,4 @@
use mlua::{AnyUserData, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef}; use mlua::{AnyUserData, ExternalError, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef, Value};
use crate::bindings::Cast; use crate::bindings::Cast;
@ -20,7 +20,16 @@ impl Url {
reg.add_method("stem", |lua, me, ()| { reg.add_method("stem", |lua, me, ()| {
me.file_stem().map(|s| lua.create_string(s.as_encoded_bytes())).transpose() me.file_stem().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
}); });
reg.add_method("join", |lua, me, other: UrlRef| Self::cast(lua, me.join(&*other))); reg.add_method("join", |lua, me, other: Value| {
Ok(match other {
Value::String(s) => Self::cast(lua, me.join(s.to_str()?)),
Value::UserData(ud) => {
let url = ud.borrow::<yazi_shared::fs::Url>()?;
Self::cast(lua, me.join(&*url))
}
_ => Err("must be a string or a Url".into_lua_err())?,
})
});
reg.add_method("parent", |lua, me, ()| { reg.add_method("parent", |lua, me, ()| {
me.parent_url().map(|u| Self::cast(lua, u)).transpose() me.parent_url().map(|u| Self::cast(lua, u)).transpose()
}); });

View file

@ -17,7 +17,7 @@ impl Utils {
let hex = { let hex = {
let mut digest = Md5::new_with_prefix(file.url.as_os_str().as_encoded_bytes()); let mut digest = Md5::new_with_prefix(file.url.as_os_str().as_encoded_bytes());
digest.update(&format!("//{:?}//{}", file.cha.mtime, t.raw_get("skip").unwrap_or(0))); digest.update(format!("//{:?}//{}", file.cha.mtime, t.raw_get("skip").unwrap_or(0)));
format!("{:x}", digest.finalize()) format!("{:x}", digest.finalize())
}; };

View file

@ -1,4 +1,4 @@
use mlua::{Lua, Table, Value, Variadic}; use mlua::{Lua, MultiValue, Table};
use tracing::{debug, error}; use tracing::{debug, error};
use super::Utils; use super::Utils;
@ -7,7 +7,7 @@ impl Utils {
pub(super) fn log(lua: &Lua, ya: &Table) -> mlua::Result<()> { pub(super) fn log(lua: &Lua, ya: &Table) -> mlua::Result<()> {
ya.raw_set( ya.raw_set(
"dbg", "dbg",
lua.create_function(|_, values: Variadic<Value>| { lua.create_function(|_, values: MultiValue| {
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" "); let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
Ok(debug!("{s}")) Ok(debug!("{s}"))
})?, })?,
@ -15,7 +15,7 @@ impl Utils {
ya.raw_set( ya.raw_set(
"err", "err",
lua.create_function(|_, values: Variadic<Value>| { lua.create_function(|_, values: MultiValue| {
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" "); let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
Ok(error!("{s}")) Ok(error!("{s}"))
})?, })?,

View file

@ -19,8 +19,8 @@ impl<'a> TryFrom<Table<'a>> for PreviewLock {
fn try_from(t: Table) -> Result<Self, Self::Error> { fn try_from(t: Table) -> Result<Self, Self::Error> {
let file: FileRef = t.raw_get("file")?; let file: FileRef = t.raw_get("file")?;
Ok(Self { Ok(Self {
url: file.url(),
cha: file.cha, cha: file.cha,
url: file.url(),
skip: t.raw_get("skip")?, skip: t.raw_get("skip")?,
window: t.raw_get("window")?, window: t.raw_get("window")?,
data: Default::default(), data: Default::default(),

View file

@ -1,4 +1,4 @@
use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, Table, Value, Variadic}; use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, MultiValue, Table, Value};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yazi_dds::Sendable; use yazi_dds::Sendable;
use yazi_shared::{emit, event::{Cmd, Data}, Layer}; use yazi_shared::{emit, event::{Cmd, Data}, Layer};
@ -12,14 +12,15 @@ impl Utils {
"sync", "sync",
lua.create_function(|lua, f: Function<'static>| { lua.create_function(|lua, f: Function<'static>| {
let mut rt = lua.named_registry_value::<RtRef>("rt")?; let mut rt = lua.named_registry_value::<RtRef>("rt")?;
if !rt.push_block(f.clone()) { if !rt.put_block(f.clone()) {
return Err("`ya.sync()` must be called in a plugin").into_lua_err(); return Err("`ya.sync()` must be called in a plugin").into_lua_err();
} }
let cur = rt.current.clone().unwrap(); let cur = rt.current().unwrap().to_owned();
lua.create_function(move |lua, mut args: Variadic<Value>| { lua.create_function(move |lua, args: MultiValue| {
args.insert(0, LOADER.load(&cur)?.into_lua(lua)?); f.call::<_, MultiValue>(MultiValue::from_iter(
f.call::<_, Variadic<Value>>(args) [LOADER.load(lua, &cur)?.into_lua(lua)?].into_iter().chain(args),
))
}) })
})?, })?,
)?; )?;
@ -31,13 +32,16 @@ impl Utils {
ya.raw_set( ya.raw_set(
"sync", "sync",
lua.create_function(|lua, ()| { lua.create_function(|lua, ()| {
let block = lua.named_registry_value::<RtRef>("rt")?.next_block(); let Some(block) = lua.named_registry_value::<RtRef>("rt")?.next_block() else {
lua.create_async_function(move |lua, args: Variadic<Value>| async move { return Err("`ya.sync()` must be called in a plugin").into_lua_err();
let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current.clone() else { };
return Err("`ya.sync()` must be called in a plugin").into_lua_err();
};
Sendable::vec_to_variadic(lua, Self::retrieve(cur, block, args).await?) lua.create_async_function(move |lua, args: MultiValue| async move {
if let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current() {
Sendable::list_to_values(lua, Self::retrieve(cur, block, args).await?)
} else {
Err("block spawned by `ya.sync()` must be called in a plugin").into_lua_err()
}
}) })
})?, })?,
)?; )?;
@ -45,34 +49,29 @@ impl Utils {
Ok(()) Ok(())
} }
async fn retrieve( async fn retrieve(name: &str, calls: usize, args: MultiValue<'_>) -> mlua::Result<Vec<Data>> {
name: String, let args = Sendable::values_to_vec(args)?;
calls: usize,
args: Variadic<Value<'_>>,
) -> mlua::Result<Vec<Data>> {
let args = Sendable::variadic_to_vec(args)?;
let (tx, rx) = oneshot::channel::<Vec<Data>>(); let (tx, rx) = oneshot::channel::<Vec<Data>>();
let callback: OptCallback = { let callback: OptCallback = {
let name = name.clone(); let name = name.to_owned();
Box::new(move |lua, plugin| { Box::new(move |lua, plugin| {
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else { let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
return Err("sync block not found".into_lua_err()); return Err("sync block not found".into_lua_err());
}; };
let mut self_args = Vec::with_capacity(args.len() + 1); let args: Vec<_> = [Ok(Value::Table(plugin))]
self_args.push(Value::Table(plugin)); .into_iter()
for arg in args { .chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d)))
self_args.push(Sendable::data_to_value(lua, arg)?); .collect::<mlua::Result<_>>()?;
}
let values = Sendable::variadic_to_vec(block.call(Variadic::from_iter(self_args))?)?; let values = Sendable::values_to_vec(block.call(MultiValue::from_vec(args))?)?;
tx.send(values).map_err(|_| "send failed".into_lua_err()) tx.send(values).map_err(|_| "send failed".into_lua_err())
}) })
}; };
emit!(Call( emit!(Call(
Cmd::args("plugin", vec![name.clone()]) Cmd::args("plugin", vec![name.to_owned()])
.with_bool("sync", true) .with_bool("sync", true)
.with_any("callback", callback), .with_any("callback", callback),
Layer::App Layer::App

View file

@ -1,5 +1,6 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use md5::{Digest, Md5};
use mlua::{Lua, Table}; use mlua::{Lua, Table};
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
@ -8,13 +9,20 @@ use crate::CLIPBOARD;
impl Utils { impl Utils {
pub(super) fn text(lua: &Lua, ya: &Table) -> mlua::Result<()> { pub(super) fn text(lua: &Lua, ya: &Table) -> mlua::Result<()> {
ya.raw_set(
"md5",
lua.create_async_function(|_, s: mlua::String| async move {
Ok(format!("{:x}", Md5::new_with_prefix(s.as_bytes()).finalize()))
})?,
)?;
ya.raw_set( ya.raw_set(
"quote", "quote",
lua.create_function(|_, (s, unix): (mlua::String, Option<bool>)| { lua.create_function(|_, (s, unix): (mlua::String, Option<bool>)| {
let s = match unix { let s = match unix {
Some(true) => yazi_shared::escape::unix(s.to_str()?), Some(true) => yazi_shared::shell::escape_unix(s.to_str()?),
Some(false) => yazi_shared::escape::windows(s.to_str()?), Some(false) => yazi_shared::shell::escape_windows(s.to_str()?),
None => yazi_shared::escape::native(s.to_str()?), None => yazi_shared::shell::escape_native(s.to_str()?),
}; };
Ok(s.into_owned()) Ok(s.into_owned())
})?, })?,

View file

@ -19,4 +19,4 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies # External dependencies
anyhow = "1.0.86" anyhow = "1.0.86"
mlua = { version = "0.9.9", features = [ "lua54" ] } mlua = { version = "0.9.9", features = [ "lua54" ] }
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }

View file

@ -36,6 +36,11 @@ impl ManagerProxy {
)); ));
} }
#[inline]
pub fn update_task(url: &Url) {
emit!(Call(Cmd::new("update_task").with_any("url", url.clone()), Layer::Manager));
}
#[inline] #[inline]
pub fn update_paged() { pub fn update_paged() {
emit!(Call(Cmd::new("update_paged"), Layer::Manager)); emit!(Call(Cmd::new("update_paged"), Layer::Manager));
@ -44,7 +49,7 @@ impl ManagerProxy {
#[inline] #[inline]
pub fn update_paged_by(page: usize, only_if: &Url) { pub fn update_paged_by(page: usize, only_if: &Url) {
emit!(Call( emit!(Call(
Cmd::args("update_paged", vec![page.to_string()]).with("only-if", only_if.to_string()), Cmd::args("update_paged", vec![page.to_string()]).with_any("only-if", only_if.clone()),
Layer::Manager Layer::Manager
)); ));
} }

View file

@ -21,7 +21,7 @@ async-priority-channel = "0.2.0"
futures = "0.3.30" futures = "0.3.30"
parking_lot = "0.12.3" parking_lot = "0.12.3"
scopeguard = "1.2.0" scopeguard = "1.2.0"
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
# Logging # Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -128,17 +128,21 @@ impl File {
self.prog.send(TaskProg::Adv(task.id, 1, task.length))? self.prog.send(TaskProg::Adv(task.id, 1, task.length))?
} }
FileOp::Trash(task) => { FileOp::Trash(task) => {
#[cfg(target_os = "macos")] tokio::task::spawn_blocking(move || {
{ #[cfg(target_os = "macos")]
use trash::{macos::{DeleteMethod, TrashContextExtMacos}, TrashContext}; {
let mut ctx = TrashContext::default(); use trash::{macos::{DeleteMethod, TrashContextExtMacos}, TrashContext};
ctx.set_delete_method(DeleteMethod::NsFileManager); let mut ctx = TrashContext::default();
ctx.delete(&task.target)?; ctx.set_delete_method(DeleteMethod::NsFileManager);
} ctx.delete(&task.target)?;
#[cfg(all(not(target_os = "macos"), not(target_os = "android")))] }
{ #[cfg(all(not(target_os = "macos"), not(target_os = "android")))]
trash::delete(&task.target)?; {
} trash::delete(&task.target)?;
}
Ok::<_, trash::Error>(())
})
.await??;
self.prog.send(TaskProg::Adv(task.id, 1, task.length))?; self.prog.send(TaskProg::Adv(task.id, 1, task.length))?;
} }
} }
@ -329,20 +333,6 @@ impl File {
let meta = fs::metadata(path).await; let meta = fs::metadata(path).await;
if meta.is_ok() { meta } else { fs::symlink_metadata(path).await } if meta.is_ok() { meta } else { fs::symlink_metadata(path).await }
} }
pub(crate) async fn remove_empty_dirs(dir: &Path) {
let Ok(mut it) = fs::read_dir(dir).await else { return };
while let Ok(Some(entry)) = it.next_entry().await {
if entry.file_type().await.is_ok_and(|t| t.is_dir()) {
let path = entry.path();
Box::pin(Self::remove_empty_dirs(&path)).await;
fs::remove_dir(path).await.ok();
}
}
fs::remove_dir(dir).await.ok();
}
} }
impl File { impl File {

View file

@ -134,7 +134,7 @@ mod parser {
if let Some(p) = pos { if let Some(p) = pos {
if let Some(arg) = args.get(p.parse::<usize>().unwrap()) { if let Some(arg) = args.get(p.parse::<usize>().unwrap()) {
if quote { if quote {
buf.extend(yazi_shared::escape::os_str(arg).encode_wide()); buf.extend(yazi_shared::shell::escape_os_str(arg).encode_wide());
} else { } else {
buf.extend(arg.encode_wide()); buf.extend(arg.encode_wide());
} }
@ -152,13 +152,13 @@ mod parser {
s.push(" "); s.push(" ");
} }
if c == '*' { if c == '*' {
s.push(yazi_shared::escape::os_str(arg)); s.push(yazi_shared::shell::escape_os_str(arg));
} else { } else {
s.push(arg); s.push(arg);
} }
} }
if quote { if quote {
buf.extend(yazi_shared::escape::os_str(&s).encode_wide()); buf.extend(yazi_shared::shell::escape_os_str(&s).encode_wide());
} else { } else {
buf.extend(s.encode_wide()); buf.extend(s.encode_wide());
} }

View file

@ -6,7 +6,8 @@ use parking_lot::Mutex;
use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle}; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
use yazi_config::{open::Opener, plugin::{Fetcher, Preloader}, TASKS}; use yazi_config::{open::Opener, plugin::{Fetcher, Preloader}, TASKS};
use yazi_dds::Pump; use yazi_dds::Pump;
use yazi_shared::{event::Data, fs::{unique_name, Url}, Throttle}; use yazi_proxy::ManagerProxy;
use yazi_shared::{event::Data, fs::{remove_dir_clean, unique_name, Url}, Throttle};
use super::{Ongoing, TaskProg, TaskStage}; use super::{Ongoing, TaskProg, TaskStage};
use crate::{file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL}; use crate::{file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL};
@ -84,7 +85,7 @@ impl Scheduler {
Box::new(move |canceled: bool| { Box::new(move |canceled: bool| {
async move { async move {
if !canceled { if !canceled {
File::remove_empty_dirs(&from).await; remove_dir_clean(&from).await;
Pump::push_move(from, to); Pump::push_move(from, to);
} }
ongoing.lock().try_remove(id, TaskStage::Hooked); ongoing.lock().try_remove(id, TaskStage::Hooked);
@ -188,6 +189,7 @@ impl Scheduler {
async move { async move {
if !canceled { if !canceled {
fs::remove_dir_all(&target).await.ok(); fs::remove_dir_all(&target).await.ok();
ManagerProxy::update_task(&target);
Pump::push_delete(target); Pump::push_delete(target);
} }
ongoing.lock().try_remove(id, TaskStage::Hooked); ongoing.lock().try_remove(id, TaskStage::Hooked);
@ -207,14 +209,29 @@ impl Scheduler {
} }
pub fn file_trash(&self, target: Url) { pub fn file_trash(&self, target: Url) {
let name = format!("Trash {:?}", target); let mut ongoing = self.ongoing.lock();
let id = self.ongoing.lock().add(TaskKind::User, name); let id = ongoing.add(TaskKind::User, format!("Trash {:?}", target));
ongoing.hooks.insert(id, {
let target = target.clone();
let ongoing = self.ongoing.clone();
Box::new(move |canceled: bool| {
async move {
if !canceled {
ManagerProxy::update_task(&target);
Pump::push_trash(target);
}
ongoing.lock().try_remove(id, TaskStage::Hooked);
}
.boxed()
})
});
let file = self.file.clone(); let file = self.file.clone();
_ = self.micro.try_send( _ = self.micro.try_send(
async move { async move {
file.trash(FileOpTrash { id, target: target.clone(), length: 0 }).await.ok(); file.trash(FileOpTrash { id, target: target.clone(), length: 0 }).await.ok();
Pump::push_trash(target);
} }
.boxed(), .boxed(),
LOW, LOW,

View file

@ -15,19 +15,17 @@ bitflags = "2.6.0"
crossterm = "0.27.0" crossterm = "0.27.0"
dirs = "5.0.1" dirs = "5.0.1"
futures = "0.3.30" futures = "0.3.30"
libc = "0.2.155"
parking_lot = "0.12.3" parking_lot = "0.12.3"
percent-encoding = "2.3.1" percent-encoding = "2.3.1"
ratatui = "0.27.0" ratatui = "0.27.0"
regex = "1.10.5" regex = "1.10.5"
serde = { version = "1.0.204", features = [ "derive" ] } serde = { version = "1.0.204", features = [ "derive" ] }
shell-words = "1.1.0" shell-words = "1.1.0"
tokio = { version = "1.39.1", features = [ "full" ] } tokio = { version = "1.39.2", features = [ "full" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.155"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52.0", features = [ "Win32_Storage_FileSystem" ] } windows-sys = { version = "0.52.0", features = [ "Win32_Storage_FileSystem", "Win32_UI_Shell" ] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
crossterm = { version = "0.27.0", features = [ "use-dev-tty" ] } crossterm = { version = "0.27.0", features = [ "use-dev-tty" ] }

View file

@ -1,41 +0,0 @@
//! Escape characters that may have special meaning in a shell, including
//! spaces. This is a modified version of the [`shell-escape`] crate and [`this
//! PR`].
//!
//! [`shell-escape`]: https://crates.io/crates/shell-escape
//! [`this PR`]: https://github.com/sfackler/shell-escape/pull/9
use std::{borrow::Cow, ffi::OsStr};
mod unix;
mod windows;
#[inline]
pub fn unix(s: &str) -> Cow<str> { unix::from_str(s) }
#[inline]
pub fn windows(s: &str) -> Cow<str> { windows::from_str(s) }
#[inline]
pub fn native(s: &str) -> Cow<str> {
#[cfg(unix)]
{
unix::from_str(s)
}
#[cfg(windows)]
{
windows::from_str(s)
}
}
#[inline]
pub fn os_str(s: &OsStr) -> Cow<OsStr> {
#[cfg(unix)]
{
unix::from_os_str(s)
}
#[cfg(windows)]
{
windows::from_os_str(s)
}
}

View file

@ -13,7 +13,8 @@ pub enum Data {
Integer(i64), Integer(i64),
Number(f64), Number(f64),
String(String), String(String),
Table(HashMap<DataKey, Data>), List(Vec<Data>),
Dict(HashMap<DataKey, Data>),
#[serde(skip_deserializing)] #[serde(skip_deserializing)]
Url(Url), Url(Url),
#[serde(skip)] #[serde(skip)]
@ -64,13 +65,13 @@ impl Data {
} }
} }
pub fn into_table_string(self) -> HashMap<String, String> { pub fn into_dict_string(self) -> HashMap<String, String> {
let Self::Table(table) = self else { let Self::Dict(dict) = self else {
return Default::default(); return Default::default();
}; };
let mut map = HashMap::with_capacity(table.len()); let mut map = HashMap::with_capacity(dict.len());
for pair in table { for pair in dict {
if let (DataKey::String(k), Self::String(v)) = pair { if let (DataKey::String(k), Self::String(v)) = pair {
map.insert(k, v); map.insert(k, v);
} }
@ -103,7 +104,7 @@ pub enum DataKey {
impl DataKey { impl DataKey {
#[inline] #[inline]
pub fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) } pub fn is_integer(&self) -> bool { matches!(self, Self::Integer(_)) }
} }
// --- Macros // --- Macros

View file

@ -15,7 +15,7 @@ bitflags! {
} }
} }
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Cha { pub struct Cha {
pub kind: ChaKind, pub kind: ChaKind,
pub len: u64, pub len: u64,

View file

@ -7,8 +7,8 @@ use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct File { pub struct File {
pub url: Url,
pub cha: Cha, pub cha: Cha,
pub url: Url,
pub link_to: Option<Url>, pub link_to: Option<Url>,
pub icon: Cell<IconCache>, pub icon: Cell<IconCache>,
} }
@ -25,6 +25,13 @@ impl AsRef<File> for File {
fn as_ref(&self) -> &File { self } fn as_ref(&self) -> &File { self }
} }
impl PartialEq for File {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cha == other.cha && self.url == other.url && self.link_to == other.link_to
}
}
impl File { impl File {
#[inline] #[inline]
pub async fn from(url: Url) -> Result<Self> { pub async fn from(url: Url) -> Result<Self> {
@ -59,13 +66,12 @@ impl File {
} }
} }
Self { url, cha: Cha::from(meta).with_kind(ck), link_to, icon: Default::default() } Self { cha: Cha::from(meta).with_kind(ck), url, link_to, icon: Default::default() }
} }
#[inline] #[inline]
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self { pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self {
// TODO: set dummy=true Self { cha: ft.map_or_else(Cha::default, Cha::from), url: url.to_owned(), ..Default::default() }
Self { url: url.to_owned(), cha: ft.map_or_else(Cha::default, Cha::from), ..Default::default() }
} }
} }

View file

@ -261,6 +261,20 @@ pub fn copy_with_progress(
rx rx
} }
pub async fn remove_dir_clean(dir: &Path) {
let Ok(mut it) = fs::read_dir(dir).await else { return };
while let Ok(Some(entry)) = it.next_entry().await {
if entry.file_type().await.is_ok_and(|t| t.is_dir()) {
let path = entry.path();
Box::pin(remove_dir_clean(&path)).await;
fs::remove_dir(path).await.ok();
}
}
fs::remove_dir(dir).await.ok();
}
// Convert a file mode to a string representation // Convert a file mode to a string representation
#[cfg(unix)] #[cfg(unix)]
#[allow(clippy::collapsible_else_if)] #[allow(clippy::collapsible_else_if)]

View file

@ -152,7 +152,7 @@ impl Url {
#[cfg(unix)] #[cfg(unix)]
#[inline] #[inline]
pub fn is_hidden(&self) -> bool { pub fn is_hidden(&self) -> bool {
self.file_name().map_or(false, |s| s.as_encoded_bytes().starts_with(&[b'.'])) self.file_name().map_or(false, |s| s.as_encoded_bytes().starts_with(b"."))
} }
} }

View file

@ -5,7 +5,6 @@ mod condition;
mod debounce; mod debounce;
mod env; mod env;
mod errors; mod errors;
pub mod escape;
pub mod event; pub mod event;
pub mod fs; pub mod fs;
mod layer; mod layer;
@ -14,6 +13,7 @@ mod number;
mod os; mod os;
mod rand; mod rand;
mod ro_cell; mod ro_cell;
pub mod shell;
mod terminal; mod terminal;
pub mod theme; pub mod theme;
mod throttle; mod throttle;

View file

@ -14,7 +14,7 @@ impl OrderedFloat {
} }
#[inline] #[inline]
pub fn get(&self) -> f64 { self.0 } pub const fn get(&self) -> f64 { self.0 }
} }
impl Hash for OrderedFloat { impl Hash for OrderedFloat {

View file

@ -0,0 +1,58 @@
//! Escape characters that may have special meaning in a shell, including
//! spaces. This is a modified version of the [`shell-escape`] crate and [`this
//! PR`].
//!
//! [`shell-escape`]: https://crates.io/crates/shell-escape
//! [`this PR`]: https://github.com/sfackler/shell-escape/pull/9
use std::{borrow::Cow, ffi::OsStr};
mod unix;
mod windows;
#[inline]
pub fn escape_unix(s: &str) -> Cow<str> { unix::escape_str(s) }
#[inline]
pub fn escape_windows(s: &str) -> Cow<str> { windows::escape_str(s) }
#[inline]
pub fn escape_native(s: &str) -> Cow<str> {
#[cfg(unix)]
{
escape_unix(s)
}
#[cfg(windows)]
{
escape_windows(s)
}
}
#[inline]
pub fn escape_os_str(s: &OsStr) -> Cow<OsStr> {
#[cfg(unix)]
{
unix::escape_os_str(s)
}
#[cfg(windows)]
{
windows::escape_os_str(s)
}
}
#[inline]
pub fn split_unix(s: &str) -> anyhow::Result<Vec<String>> { Ok(shell_words::split(s)?) }
#[cfg(windows)]
pub fn split_windows(s: &str) -> anyhow::Result<Vec<String>> { Ok(windows::split(s)?) }
pub fn split_native(s: &str) -> anyhow::Result<Vec<String>> {
#[cfg(unix)]
{
split_unix(s)
}
#[cfg(windows)]
{
split_windows(s)
}
}

View file

@ -1,23 +1,23 @@
use std::borrow::Cow; use std::borrow::Cow;
pub fn from_str(s: &str) -> Cow<str> { pub fn escape_str(s: &str) -> Cow<str> {
match from_slice(s.as_bytes()) { match escape_slice(s.as_bytes()) {
Cow::Borrowed(_) => Cow::Borrowed(s), Cow::Borrowed(_) => Cow::Borrowed(s),
Cow::Owned(v) => String::from_utf8(v).expect("Invalid bytes returned from from_slice()").into(), Cow::Owned(v) => String::from_utf8(v).expect("Invalid bytes returned by escape_slice()").into(),
} }
} }
#[cfg(unix)] #[cfg(unix)]
pub fn from_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> { pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::os::unix::ffi::{OsStrExt, OsStringExt};
match from_slice(s.as_bytes()) { match escape_slice(s.as_bytes()) {
Cow::Borrowed(_) => Cow::Borrowed(s), Cow::Borrowed(_) => Cow::Borrowed(s),
Cow::Owned(v) => std::ffi::OsString::from_vec(v).into(), Cow::Owned(v) => std::ffi::OsString::from_vec(v).into(),
} }
} }
fn from_slice(s: &[u8]) -> Cow<[u8]> { fn escape_slice(s: &[u8]) -> Cow<[u8]> {
if !s.is_empty() && s.iter().copied().all(allowed) { if !s.is_empty() && s.iter().copied().all(allowed) {
return Cow::Borrowed(s); return Cow::Borrowed(s);
} }
@ -51,31 +51,31 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_from_str() { fn test_escape_str() {
assert_eq!(from_str(""), r#"''"#); assert_eq!(escape_str(""), r#"''"#);
assert_eq!(from_str(" "), r#"' '"#); assert_eq!(escape_str(" "), r#"' '"#);
assert_eq!(from_str("*"), r#"'*'"#); assert_eq!(escape_str("*"), r#"'*'"#);
assert_eq!(from_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc"); assert_eq!(escape_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc");
assert_eq!(from_str(r#"--features="default""#), r#"'--features="default"'"#); assert_eq!(escape_str(r#"--features="default""#), r#"'--features="default"'"#);
assert_eq!(from_str("linker=gcc -L/foo -Wl,bar"), r#"'linker=gcc -L/foo -Wl,bar'"#); assert_eq!(escape_str("linker=gcc -L/foo -Wl,bar"), r#"'linker=gcc -L/foo -Wl,bar'"#);
assert_eq!( assert_eq!(
from_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"), escape_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"),
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+",
); );
assert_eq!(from_str(r#"'!\$`\\\n "#), r#"''\'''\!'\$`\\\n '"#); assert_eq!(escape_str(r#"'!\$`\\\n "#), r#"''\'''\!'\$`\\\n '"#);
} }
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn test_from_os_str() { fn test_escape_os_str() {
use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; use std::{ffi::OsStr, os::unix::ffi::OsStrExt};
fn from_str(input: &str, expected: &str) { from_bytes(input.as_bytes(), expected.as_bytes()) } fn from_str(input: &str, expected: &str) { from_bytes(input.as_bytes(), expected.as_bytes()) }
fn from_bytes(input: &[u8], expected: &[u8]) { fn from_bytes(input: &[u8], expected: &[u8]) {
assert_eq!(from_os_str(OsStr::from_bytes(input)), OsStr::from_bytes(expected)); assert_eq!(escape_os_str(OsStr::from_bytes(input)), OsStr::from_bytes(expected));
} }
from_str("", r#"''"#); from_str("", r#"''"#);

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, iter::repeat}; use std::{borrow::Cow, iter::repeat};
pub fn from_str(s: &str) -> Cow<str> { pub fn escape_str(s: &str) -> Cow<str> {
let bytes = s.as_bytes(); let bytes = s.as_bytes();
if !bytes.is_empty() && !bytes.iter().any(|&c| matches!(c, b' ' | b'"' | b'\n' | b'\t')) { if !bytes.is_empty() && !bytes.iter().any(|&c| matches!(c, b' ' | b'"' | b'\n' | b'\t')) {
return Cow::Borrowed(s); return Cow::Borrowed(s);
@ -39,7 +39,7 @@ pub fn from_str(s: &str) -> Cow<str> {
} }
#[cfg(windows)] #[cfg(windows)]
pub fn from_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> { pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::os::windows::ffi::{OsStrExt, OsStringExt};
let wide = s.encode_wide(); let wide = s.encode_wide();
@ -79,6 +79,37 @@ pub fn from_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
std::ffi::OsString::from_wide(&escaped).into() std::ffi::OsString::from_wide(&escaped).into()
} }
#[cfg(windows)]
pub fn split(s: &str) -> std::io::Result<Vec<String>> {
use std::os::windows::ffi::OsStrExt;
let s: Vec<_> = std::ffi::OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect();
split_slice(&s)
}
#[cfg(windows)]
fn split_slice(s: &[u16]) -> std::io::Result<Vec<String>> {
use std::mem::MaybeUninit;
use windows_sys::Win32::{Foundation::LocalFree, UI::Shell::CommandLineToArgvW};
let mut argc = MaybeUninit::<i32>::uninit();
let argv_p = unsafe { CommandLineToArgvW(s.as_ptr(), argc.as_mut_ptr()) };
if argv_p.is_null() {
return Err(std::io::Error::last_os_error());
}
let argv = unsafe { std::slice::from_raw_parts(argv_p, argc.assume_init() as usize) };
let mut res = vec![];
for &arg in argv {
let len = unsafe { libc::wcslen(arg) };
res.push(String::from_utf16_lossy(unsafe { std::slice::from_raw_parts(arg, len) }));
}
unsafe { LocalFree(argv_p as _) };
Ok(res)
}
#[cfg(windows)] #[cfg(windows)]
fn disallowed(b: u16) -> bool { fn disallowed(b: u16) -> bool {
match char::from_u32(b as u32) { match char::from_u32(b as u32) {
@ -92,33 +123,33 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_from_str() { fn test_escape_str() {
assert_eq!(from_str(""), r#""""#); assert_eq!(escape_str(""), r#""""#);
assert_eq!(from_str(r#""""#), r#""\"\"""#); assert_eq!(escape_str(r#""""#), r#""\"\"""#);
assert_eq!(from_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc"); assert_eq!(escape_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc");
assert_eq!(from_str(r#"\path\to\my documents\"#), r#""\path\to\my documents\\""#); assert_eq!(escape_str(r#"\path\to\my documents\"#), r#""\path\to\my documents\\""#);
assert_eq!(from_str(r#"--features="default""#), r#""--features=\"default\"""#); assert_eq!(escape_str(r#"--features="default""#), r#""--features=\"default\"""#);
assert_eq!(from_str(r#""--features=\"default\"""#), r#""\"--features=\\\"default\\\"\"""#); assert_eq!(escape_str(r#""--features=\"default\"""#), r#""\"--features=\\\"default\\\"\"""#);
assert_eq!(from_str("linker=gcc -L/foo -Wl,bar"), r#""linker=gcc -L/foo -Wl,bar""#); assert_eq!(escape_str("linker=gcc -L/foo -Wl,bar"), r#""linker=gcc -L/foo -Wl,bar""#);
} }
#[cfg(windows)] #[cfg(windows)]
#[test] #[test]
fn test_from_os_str() { fn test_escape_os_str() {
use std::{ffi::OsString, os::windows::ffi::OsStringExt}; use std::{ffi::OsString, os::windows::ffi::OsStringExt};
fn from_str(input: &str, expected: &str) { fn from_str(input: &str, expected: &str) {
let observed = OsString::from(input); let observed = OsString::from(input);
let expected = OsString::from(expected); let expected = OsString::from(expected);
assert_eq!(from_os_str(observed.as_os_str()), expected.as_os_str()); assert_eq!(escape_os_str(observed.as_os_str()), expected.as_os_str());
} }
fn from_bytes(input: &[u16], expected: &[u16]) { fn from_bytes(input: &[u16], expected: &[u16]) {
let observed = OsString::from_wide(input); let observed = OsString::from_wide(input);
let expected = OsString::from_wide(expected); let expected = OsString::from_wide(expected);
assert_eq!(from_os_str(observed.as_os_str()), expected.as_os_str()); assert_eq!(escape_os_str(observed.as_os_str()), expected.as_os_str());
} }
from_str("", r#""""#); from_str("", r#""""#);