From 23a56c24a4c15807bc74dc904da10ed4cb5c3e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sun, 28 Jul 2024 08:14:53 +0800 Subject: [PATCH] feat: new `extract` builtin plugin for archive extracting (#1321) --- Cargo.lock | 525 +++++++++++++++--- flake.lock | 15 +- yazi-adapter/Cargo.toml | 4 +- yazi-adapter/src/image.rs | 8 +- yazi-boot/Cargo.toml | 10 +- yazi-boot/src/actions/debug.rs | 5 +- yazi-cli/Cargo.toml | 21 +- yazi-cli/src/args.rs | 87 ++- yazi-cli/src/main.rs | 26 +- yazi-config/Cargo.toml | 2 +- yazi-config/preset/keymap.toml | 59 +- yazi-config/preset/yazi.toml | 30 +- yazi-config/src/plugin/fetcher.rs | 13 +- yazi-config/src/plugin/plugin.rs | 47 +- yazi-config/src/plugin/preloader.rs | 12 +- yazi-config/src/plugin/previewer.rs | 10 +- yazi-core/Cargo.toml | 4 +- yazi-core/src/folder/files.rs | 11 +- yazi-core/src/manager/commands/mod.rs | 1 + yazi-core/src/manager/commands/rename.rs | 6 +- .../src/manager/commands/update_mimetype.rs | 5 +- yazi-core/src/manager/commands/update_task.rs | 25 + yazi-core/src/manager/watcher.rs | 19 +- yazi-dds/Cargo.toml | 4 +- yazi-dds/src/sendable.rs | 31 +- yazi-fm/Cargo.toml | 5 +- yazi-fm/src/app/commands/plugin.rs | 8 +- yazi-fm/src/executor.rs | 1 + yazi-fm/src/lives/lives.rs | 2 +- yazi-plugin/Cargo.toml | 4 +- yazi-plugin/preset/plugins/archive.lua | 151 +++-- yazi-plugin/preset/plugins/extract.lua | 117 ++++ yazi-plugin/preset/plugins/fzf.lua | 2 +- yazi-plugin/preset/plugins/mime.lua | 4 +- yazi-plugin/preset/plugins/zoxide.lua | 4 +- yazi-plugin/preset/setup.lua | 1 + yazi-plugin/preset/ya.lua | 6 +- yazi-plugin/src/cha/cha.rs | 4 +- yazi-plugin/src/file/file.rs | 4 +- yazi-plugin/src/fs/fs.rs | 99 +++- yazi-plugin/src/isolate/entry.rs | 2 +- yazi-plugin/src/isolate/isolate.rs | 1 + yazi-plugin/src/loader/loader.rs | 22 +- yazi-plugin/src/loader/mod.rs | 6 +- yazi-plugin/src/loader/require.rs | 99 ++-- yazi-plugin/src/pubsub/pubsub.rs | 10 +- yazi-plugin/src/runtime.rs | 56 +- yazi-plugin/src/url/url.rs | 13 +- yazi-plugin/src/utils/cache.rs | 2 +- yazi-plugin/src/utils/log.rs | 6 +- yazi-plugin/src/utils/preview.rs | 2 +- yazi-plugin/src/utils/sync.rs | 51 +- yazi-plugin/src/utils/text.rs | 14 +- yazi-proxy/Cargo.toml | 2 +- yazi-proxy/src/manager.rs | 7 +- yazi-scheduler/Cargo.toml | 2 +- yazi-scheduler/src/file/file.rs | 40 +- yazi-scheduler/src/process/shell.rs | 6 +- yazi-scheduler/src/scheduler.rs | 27 +- yazi-shared/Cargo.toml | 8 +- yazi-shared/src/escape/mod.rs | 41 -- yazi-shared/src/event/data.rs | 13 +- yazi-shared/src/fs/cha.rs | 2 +- yazi-shared/src/fs/file.rs | 14 +- yazi-shared/src/fs/fns.rs | 14 + yazi-shared/src/fs/url.rs | 2 +- yazi-shared/src/lib.rs | 2 +- yazi-shared/src/number.rs | 2 +- yazi-shared/src/shell/mod.rs | 58 ++ yazi-shared/src/{escape => shell}/unix.rs | 34 +- yazi-shared/src/{escape => shell}/windows.rs | 57 +- 71 files changed, 1436 insertions(+), 571 deletions(-) create mode 100644 yazi-core/src/manager/commands/update_task.rs create mode 100644 yazi-plugin/preset/plugins/extract.lua delete mode 100644 yazi-shared/src/escape/mod.rs create mode 100644 yazi-shared/src/shell/mod.rs rename yazi-shared/src/{escape => shell}/unix.rs (66%) rename yazi-shared/src/{escape => shell}/windows.rs (64%) diff --git a/Cargo.lock b/Cargo.lock index c5eca7741..7db79e403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.18" @@ -125,12 +131,35 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "async-priority-channel" version = "0.2.0" @@ -146,6 +175,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "backtrace" version = "0.3.73" @@ -210,6 +262,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitstream-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" + [[package]] name = "block-buffer" version = "0.10.4" @@ -238,6 +296,12 @@ dependencies = [ "serde", ] +[[package]] +name = "built" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" + [[package]] name = "bumpalo" version = "3.16.0" @@ -251,10 +315,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] -name = "byteorder" -version = "1.5.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" @@ -270,18 +334,32 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.1.2" +version = "1.1.6" 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]] name = "cfg-if" @@ -303,9 +381,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.10" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ "clap_builder", "clap_derive", @@ -313,9 +391,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.10" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" dependencies = [ "anstream", "anstyle", @@ -325,18 +403,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.9" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa2032320fd6f50d22af510d204b2994eef49600dfbd0e771a166213844e4cd" +checksum = "c6ae69fbb0833c6fcd5a8d4b8609f108c7ad95fc11e248d853ff2c42a90df26a" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4bc503cddc1cd320736fb555d6598309ad07c2ddeaa23891a10ffb759ee612" +checksum = "d494102c8ff3951810c72baf96910b980fb065ca5d3101243e6a8dc19747c86b" dependencies = [ "clap", "clap_complete", @@ -344,9 +422,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.2" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1accf1b463dee0d3ab2be72591dccdab8bef314958340447c882c4c72acfe2a3" +checksum = "5fe32110e006bccf720f8c9af3fee1ba7db290c724eab61544e1d3295be3a40e" dependencies = [ "clap", "clap_complete", @@ -354,9 +432,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ "heck", "proc-macro2", @@ -625,9 +703,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" @@ -1012,20 +1090,35 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "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]] @@ -1034,6 +1127,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "2.2.6" @@ -1046,9 +1145,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.9.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" dependencies = [ "bitflags 1.3.2", "inotify-sys", @@ -1064,6 +1163,26 @@ dependencies = [ "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]] name = "is_terminal_polyfill" version = "1.70.0" @@ -1094,14 +1213,20 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -1159,6 +1284,17 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "libredox" version = "0.1.3" @@ -1197,6 +1333,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru" version = "0.12.3" @@ -1225,6 +1370,15 @@ dependencies = [ "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]] name = "md-5" version = "0.10.6" @@ -1333,6 +1487,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nom" version = "7.1.3" @@ -1343,11 +1503,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "notify" version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +source = "git+https://github.com/notify-rs/notify.git?rev=96dec74316a93bed6eec9db177b233e6e017275e#96dec74316a93bed6eec9db177b233e6e017275e" dependencies = [ "bitflags 2.6.0", "filetime", @@ -1357,8 +1522,17 @@ dependencies = [ "libc", "log", "mio 0.8.11", + "notify-types", "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]] @@ -1371,12 +1545,53 @@ dependencies = [ "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]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "num-traits" version = "0.2.19" @@ -1579,6 +1794,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1612,6 +1833,25 @@ dependencies = [ "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]] name = "qoi" version = "0.4.1" @@ -1621,6 +1861,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.31.0" @@ -1639,6 +1885,36 @@ dependencies = [ "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]] name = "ratatui" version = "0.27.0" @@ -1660,6 +1936,55 @@ dependencies = [ "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]] name = "rayon" version = "1.10.0" @@ -1738,6 +2063,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "rgb" +version = "0.8.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" +dependencies = [ + "bytemuck", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1822,20 +2156,21 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -1849,12 +2184,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-escape" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" - [[package]] name = "shell-words" version = "1.1.0" @@ -1909,6 +2238,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "slab" version = "0.4.9" @@ -1945,9 +2283,9 @@ dependencies = [ [[package]] name = "stability" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", "syn 2.0.71", @@ -1967,9 +2305,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] @@ -2030,20 +2368,39 @@ dependencies = [ "walkdir", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", @@ -2073,9 +2430,9 @@ dependencies = [ [[package]] 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" -checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" dependencies = [ "cc", "libc", @@ -2083,9 +2440,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -2141,9 +2498,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.1" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", @@ -2194,9 +2551,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "indexmap", "serde", @@ -2207,18 +2564,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.16" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap", "serde", @@ -2354,11 +2711,12 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-truncate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools 0.12.1", + "itertools 0.13.0", + "unicode-segmentation", "unicode-width", ] @@ -2401,6 +2759,17 @@ dependencies = [ "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]] name = "validator" version = "0.18.1" @@ -2476,6 +2845,12 @@ dependencies = [ "rustversion", ] +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -2962,7 +3337,6 @@ dependencies = [ "tikv-jemallocator", "tokio", "tokio-stream", - "tokio-util", "tracing", "tracing-appender", "tracing-subscriber", @@ -2986,11 +3360,11 @@ dependencies = [ "clipboard-win", "crossterm", "futures", + "globset", "md-5", "mlua", "parking_lot", "ratatui", - "shell-escape", "shell-words", "syntect", "tokio", @@ -3085,6 +3459,12 @@ dependencies = [ "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]] name = "zune-inflate" version = "0.2.54" @@ -3093,3 +3473,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "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", +] diff --git a/flake.lock b/flake.lock index f0a731ee8..215a6a931 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716097317, - "narHash": "sha256-1UMrLtgzielG/Sop6gl6oTSM4pDt7rF9j9VuxhDWDlY=", + "lastModified": 1722141560, + "narHash": "sha256-Ul3rIdesWaiW56PS/Ak3UlJdkwBrD4UcagCmXZR9Z7Y=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8535fb92661f37ff9f0da3007fbc942f7d134b41", + "rev": "038fb464fcfa79b4f08131b07f2d8c9a6bcc4160", "type": "github" }, "original": { @@ -43,19 +43,16 @@ }, "rust-overlay": { "inputs": { - "flake-utils": [ - "flake-utils" - ], "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1716085073, - "narHash": "sha256-3+9gI93XxszWA2+9S2xZfws1QArPX/MC6nahOGpcMB4=", + "lastModified": 1722133294, + "narHash": "sha256-XKSVN+lmjVEFPjMa5Ui0VTay2Uvqa74h0MQT0HU1pqw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "cfc8776011bd83508324115d353222475e1601c0", + "rev": "9803f6e04ca37a2c072783e8297d2080f8d0e739", "type": "github" }, "original": { diff --git a/yazi-adapter/Cargo.toml b/yazi-adapter/Cargo.toml index f310115fc..9541686da 100644 --- a/yazi-adapter/Cargo.toml +++ b/yazi-adapter/Cargo.toml @@ -20,12 +20,12 @@ base64 = "0.22.1" color_quant = "1.1.0" crossterm = "0.27.0" futures = "0.3.30" -image = "=0.24.9" +image = "0.25.2" imagesize = "0.13.0" kamadak-exif = "0.5.5" ratatui = "0.27.0" scopeguard = "1.2.0" -tokio = { version = "1.39.1", features = [ "full" ] } +tokio = { version = "1.39.2", features = [ "full" ] } # Logging tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } diff --git a/yazi-adapter/src/image.rs b/yazi-adapter/src/image.rs index d5d39ca39..3979cc464 100644 --- a/yazi-adapter/src/image.rs +++ b/yazi-adapter/src/image.rs @@ -2,7 +2,7 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}}; use anyhow::Result; 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 yazi_config::{PREVIEW, TASKS}; @@ -16,7 +16,7 @@ impl Image { let path = path.to_owned(); 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??; @@ -54,7 +54,7 @@ impl Image { let path = path.to_owned(); 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??; @@ -155,7 +155,7 @@ impl Image { img } - fn set_limits(mut r: image::io::Reader>) -> image::io::Reader> { + fn set_limits(mut r: image::ImageReader>) -> image::ImageReader> { let mut limits = Limits::no_limits(); if TASKS.image_alloc > 0 { limits.max_alloc = Some(TASKS.image_alloc as u64); diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index bfadc0df6..ea2c81085 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -15,12 +15,12 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" } yazi-shared = { path = "../yazi-shared", version = "0.2.5" } # External dependencies -clap = { version = "4.5.10", features = [ "derive" ] } +clap = { version = "4.5.11", features = [ "derive" ] } serde = { version = "1.0.204", features = [ "derive" ] } [build-dependencies] -clap = { version = "4.5.10", features = [ "derive" ] } -clap_complete = "4.5.9" -clap_complete_fig = "4.5.1" -clap_complete_nushell = "4.5.2" +clap = { version = "4.5.11", features = [ "derive" ] } +clap_complete = "4.5.11" +clap_complete_fig = "4.5.2" +clap_complete_nushell = "4.5.3" vergen-gitcl = { version = "1.0.0", features = [ "build" ] } diff --git a/yazi-boot/src/actions/debug.rs b/yazi-boot/src/actions/debug.rs index 70ad74d61..e245a0dd4 100644 --- a/yazi-boot/src/actions/debug.rs +++ b/yazi-boot/src/actions/debug.rs @@ -44,9 +44,9 @@ impl Actions { writeln!(s, "\nVariables")?; writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?; 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_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?; + writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?; writeln!(s, "\nText Opener")?; writeln!( @@ -74,7 +74,8 @@ impl Actions { writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?; writeln!(s, " chafa : {}", Self::process_output("chafa", "--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, "\n\n--------------------------------------------------")?; diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 8eab9c150..a6cd1afa3 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -15,20 +15,23 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" } # External dependencies anyhow = "1.0.86" -clap = { version = "4.5.10", features = [ "derive" ] } +clap = { version = "4.5.11", features = [ "derive" ] } crossterm = "0.27.0" md-5 = "0.10.6" -serde_json = "1.0.120" -tokio = { version = "1.39.1", features = [ "full" ] } -toml_edit = "0.22.16" +serde_json = "1.0.121" +tokio = { version = "1.39.2", features = [ "full" ] } +toml_edit = "0.22.17" [build-dependencies] +yazi-shared = { path = "../yazi-shared", version = "0.2.5" } + +# External build dependencies anyhow = "1.0.86" -clap = { version = "4.5.10", features = [ "derive" ] } -clap_complete = "4.5.9" -clap_complete_fig = "4.5.1" -clap_complete_nushell = "4.5.2" -serde_json = "1.0.120" +clap = { version = "4.5.11", features = [ "derive" ] } +clap_complete = "4.5.11" +clap_complete_fig = "4.5.2" +clap_complete_nushell = "4.5.3" +serde_json = "1.0.121" vergen-gitcl = { version = "1.0.0", features = [ "build" ] } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/yazi-cli/src/args.rs b/yazi-cli/src/args.rs index 479716fdc..2fd287adf 100644 --- a/yazi-cli/src/args.rs +++ b/yazi-cli/src/args.rs @@ -16,52 +16,67 @@ pub(super) struct Args { #[derive(Subcommand)] pub(super) enum Command { - /// Publish a message to remote instance(s). + /// Publish a message to the current instance. Pub(CommandPub), - /// Manage packages. - Pack(CommandPack), + /// Publish a message to the specified instance. + PubTo(CommandPubTo), /// Subscribe to messages from all remote instances. Sub(CommandSub), + /// Manage packages. + Pack(CommandPack), } #[derive(clap::Args)] pub(super) struct CommandPub { /// The kind of message. #[arg(index = 1)] - pub(super) kind: String, - /// The receiver ID. - #[arg(index = 2)] - pub(super) receiver: Option, + pub(super) kind: String, /// Send the message with a string body. #[arg(long)] - pub(super) str: Option, + pub(super) str: Option, /// Send the message with a JSON body. #[arg(long)] - pub(super) json: Option, + pub(super) json: Option, + /// Send the message as string of list. + #[arg(long, num_args = 0..)] + pub(super) list: Vec, } impl CommandPub { #[allow(dead_code)] pub(super) fn receiver(&self) -> Result { - if let Some(receiver) = self.receiver { - Ok(receiver) - } else if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) { + 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.") + bail!("No `YAZI_ID` environment variable found.") } } +} - #[allow(dead_code)] - pub(super) fn body(&self) -> Result> { - 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)] +pub(super) struct CommandPubTo { + /// The receiver ID. + #[arg(index = 1)] + pub(super) receiver: u64, + /// The kind of message. + #[arg(index = 2)] + pub(super) kind: String, + /// Send the message with a string body. + #[arg(long)] + pub(super) str: Option, + /// Send the message with a JSON body. + #[arg(long)] + pub(super) json: Option, + /// Send the message as string of list. + #[arg(long, num_args = 0..)] + pub(super) list: Vec, +} + +#[derive(clap::Args)] +pub(super) struct CommandSub { + /// The kind of messages to subscribe to, separated by commas if multiple. + #[arg(index = 1)] + pub(super) kinds: String, } #[derive(clap::Args)] @@ -81,9 +96,25 @@ pub(super) struct CommandPack { pub(super) upgrade: bool, } -#[derive(clap::Args)] -pub(super) struct CommandSub { - /// The kind of messages to subscribe to, separated by commas if multiple. - #[arg(index = 1)] - pub(super) kinds: String, +// --- Macros +macro_rules! impl_body { + ($name:ident) => { + impl $name { + #[allow(dead_code)] + pub(super) fn body(&self) -> Result> { + 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); diff --git a/yazi-cli/src/main.rs b/yazi-cli/src/main.rs index 680c1b32f..8b0bce070 100644 --- a/yazi-cli/src/main.rs +++ b/yazi-cli/src/main.rs @@ -25,6 +25,24 @@ async fn main() -> anyhow::Result<()> { 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) => { package::init(); if cmd.install { @@ -40,14 +58,6 @@ async fn main() -> anyhow::Result<()> { 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(()) diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index fe08e5664..e63ac5103 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -21,7 +21,7 @@ indexmap = "2.2.6" ratatui = "0.27.0" regex = "1.10.5" 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" ] } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 9e3163ffc..2398f28db 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -12,44 +12,39 @@ keymap = [ { on = "", run = "close", desc = "Close the current tab, or quit if it is last tab" }, { on = "", run = "suspend", desc = "Suspend the process" }, - # Navigation + # Hopping { on = "k", run = "arrow -1", desc = "Move cursor up" }, { 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 = "", run = "arrow -5", desc = "Move cursor up 5 lines" }, - { on = "", run = "arrow 5", desc = "Move cursor down 5 lines" }, + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, { on = "", run = "arrow -100%", desc = "Move cursor up one page" }, { on = "", run = "arrow 100%", desc = "Move cursor down one page" }, - { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, - { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, + { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, + { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, { on = "", run = "arrow -100%", desc = "Move cursor up one page" }, { on = "", 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 = "l", run = "enter", desc = "Enter the child directory" }, - { on = "H", run = "back", desc = "Go back to the previous directory" }, - { on = "L", run = "forward", desc = "Go forward to the next directory" }, - - { on = "", run = "seek -5", desc = "Seek up 5 units in the preview" }, - { on = "", run = "seek 5", desc = "Seek down 5 units in the preview" }, - { on = "", run = "seek -5", desc = "Seek up 5 units in the preview" }, - { on = "", run = "seek 5", desc = "Seek down 5 units in the preview" }, - - { on = "", run = "arrow -1", desc = "Move cursor up" }, - { on = "", run = "arrow 1", desc = "Move cursor down" }, { on = "", run = "leave", desc = "Go back to the parent directory" }, { on = "", 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" }, + { on = "H", run = "back", desc = "Go back to the previous directory" }, + { on = "L", run = "forward", desc = "Go forward to the next directory" }, + + # Seeking + { on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" }, + { on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" }, # Selection { on = "", 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 = [ ",", "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", "" ], run = "cd --interactive", desc = "Go to a directory interactively" }, + # Tabs { on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" }, @@ -144,12 +145,6 @@ keymap = [ # Tasks { 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", "" ], run = "cd --interactive", desc = "Go to a directory interactively" }, - # Help { on = "~", run = "help", desc = "Open help" }, { on = "", run = "help", desc = "Open help" }, @@ -188,15 +183,9 @@ keymap = [ { on = "k", run = "arrow -1", desc = "Move cursor up" }, { 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 = "", run = "arrow -1", desc = "Move cursor up" }, { on = "", run = "arrow 1", desc = "Move cursor down" }, - { on = "", run = "arrow -5", desc = "Move cursor up 5 lines" }, - { on = "", run = "arrow 5", desc = "Move cursor down 5 lines" }, - # Help { on = "~", run = "help", desc = "Open help" }, { on = "", run = "help", desc = "Open help" }, @@ -305,15 +294,9 @@ keymap = [ { on = "k", run = "arrow -1", desc = "Move cursor up" }, { 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 = "", run = "arrow -1", desc = "Move cursor up" }, { on = "", run = "arrow 1", desc = "Move cursor down" }, - { on = "", run = "arrow -5", desc = "Move cursor up 5 lines" }, - { on = "", run = "arrow 5", desc = "Move cursor down 5 lines" }, - # Filtering { on = "/", run = "filter", desc = "Apply a filter for the help items" }, ] diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index cdbe1ebca..0df4b4dd2 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -38,36 +38,40 @@ open = [ { run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" }, ] reveal = [ - { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, - { run = 'open -R "$1"', desc = "Reveal", for = "macos" }, - { run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" }, + { run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" }, + { run = 'open -R "$1"', desc = "Reveal", for = "macos" }, + { run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" }, { run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" }, ] extract = [ - { run = 'unar "$1"', desc = "Extract here", for = "unix" }, - { run = 'unar "%1"', desc = "Extract here", for = "windows" }, + { run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" }, + { run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" }, ] play = [ { 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" }, ] [open] rules = [ + # Folder { name = "*/", use = [ "edit", "open", "reveal" ] }, - - { mime = "text/*", use = [ "edit", "reveal" ] }, - { mime = "image/*", use = [ "open", "reveal" ] }, + # Text + { mime = "text/*", use = [ "edit", "reveal" ] }, + # Image + { mime = "image/*", use = [ "open", "reveal" ] }, + # Media { mime = "{audio,video}/*", use = [ "play", "reveal" ] }, - { mime = "inode/x-empty", use = [ "edit", "reveal" ] }, - + # Archive { mime = "application/{,g}zip", 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 = "*/javascript", use = [ "edit", "reveal" ] }, - + # Empty file + { mime = "inode/x-empty", use = [ "edit", "reveal" ] }, + # Fallback { name = "*", use = [ "open", "reveal" ] }, ] diff --git a/yazi-config/src/plugin/fetcher.rs b/yazi-config/src/plugin/fetcher.rs index c08a710be..3890a5ca1 100644 --- a/yazi-config/src/plugin/fetcher.rs +++ b/yazi-config/src/plugin/fetcher.rs @@ -1,5 +1,7 @@ +use std::path::Path; + use serde::Deserialize; -use yazi_shared::{event::Cmd, Condition}; +use yazi_shared::{event::Cmd, Condition, MIME_DIR}; use crate::{Pattern, Priority}; @@ -18,6 +20,15 @@ pub struct Fetcher { 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)] pub struct FetcherProps { pub id: u8, diff --git a/yazi-config/src/plugin/plugin.rs b/yazi-config/src/plugin/plugin.rs index 7a9b96ffe..0c1d97d34 100644 --- a/yazi-config/src/plugin/plugin.rs +++ b/yazi-config/src/plugin/plugin.rs @@ -1,7 +1,6 @@ -use std::{path::Path, str::FromStr}; +use std::{collections::HashSet, path::Path, str::FromStr}; use serde::Deserialize; -use yazi_shared::MIME_DIR; use super::{Fetcher, Preloader, Previewer}; use crate::{plugin::MAX_PREWORKERS, Preset}; @@ -20,39 +19,33 @@ impl Plugin { mime: Option<&'a str>, factor: impl Fn(&str) -> bool + Copy, ) -> impl Iterator { - let is_dir = mime == Some(MIME_DIR); + let mut seen = HashSet::new(); self.fetchers.iter().filter(move |&f| { - f.if_.as_ref().and_then(|c| c.eval(factor)) != Some(false) - && (f.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m)) - || f.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))) + if seen.contains(&f.id) || !f.matches(path, mime, factor) { + return false; + } + seen.insert(&f.id); + true }) } - pub fn preloaders(&self, path: &Path, mime: Option<&str>) -> Vec<&Preloader> { - let is_dir = mime == Some(MIME_DIR); - let mut preloaders = Vec::with_capacity(1); - - for p in &self.preloaders { - if !p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m)) - && !p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir)) - { - continue; - } - - preloaders.push(p); - if !p.next { - break; + pub fn preloaders<'a>( + &'a self, + path: &'a Path, + mime: Option<&'a str>, + ) -> impl Iterator { + let mut next = true; + self.preloaders.iter().filter(move |&p| { + if !next || !p.matches(path, mime) { + return false; } - } - preloaders + next = p.next; + true + }) } pub fn previewer(&self, path: &Path, mime: &str) -> Option<&Previewer> { - let is_dir = mime == MIME_DIR; - 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)) - }) + self.previewers.iter().find(|&p| p.matches(path, mime)) } } impl FromStr for Plugin { diff --git a/yazi-config/src/plugin/preloader.rs b/yazi-config/src/plugin/preloader.rs index 03a4e513b..dcfe4d326 100644 --- a/yazi-config/src/plugin/preloader.rs +++ b/yazi-config/src/plugin/preloader.rs @@ -1,5 +1,7 @@ +use std::path::Path; + use serde::Deserialize; -use yazi_shared::event::Cmd; +use yazi_shared::{event::Cmd, MIME_DIR}; use crate::{Pattern, Priority}; @@ -17,6 +19,14 @@ pub struct Preloader { 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)] pub struct PreloaderProps { pub id: u8, diff --git a/yazi-config/src/plugin/previewer.rs b/yazi-config/src/plugin/previewer.rs index a960201fa..40ace4461 100644 --- a/yazi-config/src/plugin/previewer.rs +++ b/yazi-config/src/plugin/previewer.rs @@ -1,5 +1,7 @@ +use std::path::Path; + use serde::Deserialize; -use yazi_shared::event::Cmd; +use yazi_shared::{event::Cmd, MIME_DIR}; use crate::Pattern; @@ -13,6 +15,12 @@ pub struct 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] pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) } diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index 6c9a87092..8ac5454b1 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -24,14 +24,14 @@ bitflags = "2.6.0" crossterm = "0.27.0" dirs = "5.0.1" 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" ratatui = "0.27.0" regex = "1.10.5" scopeguard = "1.2.0" serde = "1.0.204" 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-util = "0.7.11" unicode-width = "0.1.13" diff --git a/yazi-core/src/folder/files.rs b/yazi-core/src/folder/files.rs index a9bb0e2f3..e55535fef 100644 --- a/yazi-core/src/folder/files.rs +++ b/yazi-core/src/folder/files.rs @@ -264,18 +264,19 @@ impl Files { macro_rules! go { ($dist:expr, $src:expr, $inc:literal) => { - let len = $src.len(); + let mut b = false; for i in 0..$dist.len() { 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() { break; } } } - if $src.len() != len { - self.revision += $inc; - } + self.revision += if b { $inc } else { 0 }; }; } diff --git a/yazi-core/src/manager/commands/mod.rs b/yazi-core/src/manager/commands/mod.rs index d690ce0cb..8829cccd3 100644 --- a/yazi-core/src/manager/commands/mod.rs +++ b/yazi-core/src/manager/commands/mod.rs @@ -21,5 +21,6 @@ mod unyank; mod update_files; mod update_mimetype; mod update_paged; +mod update_task; mod update_yanked; mod yank; diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index adebe7d43..3fd5efc9a 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -87,9 +87,9 @@ impl Manager { let overwritten = symlink_realpath(&new).await; fs::rename(&old, &new).await?; - if let Ok(p) = overwritten { - ok_or_not_found(fs::rename(&p, &new).await)?; - FilesOp::Deleting(p_new.clone(), vec![Url::from(p)]).emit(); + if let Ok(o) = overwritten { + ok_or_not_found(fs::rename(&o, &new).await)?; + FilesOp::Deleting(p_new.clone(), vec![Url::from(o)]).emit(); } Pubsub::pub_from_rename(tab, &old, &new); diff --git a/yazi-core/src/manager/commands/update_mimetype.rs b/yazi-core/src/manager/commands/update_mimetype.rs index 15f24e0ba..c3720a01a 100644 --- a/yazi-core/src/manager/commands/update_mimetype.rs +++ b/yazi-core/src/manager/commands/update_mimetype.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use tracing::error; use yazi_shared::{event::Cmd, fs::Url, render}; use crate::{manager::{Manager, LINKED}, tasks::Tasks}; @@ -12,14 +13,14 @@ impl TryFrom for Opt { type Error = (); fn try_from(mut c: Cmd) -> Result { - Ok(Self { updates: c.take("updates").ok_or(())?.into_table_string() }) + Ok(Self { updates: c.take("updates").ok_or(())?.into_dict_string() }) } } impl Manager { pub fn update_mimetype(&mut self, opt: impl TryInto, tasks: &Tasks) { let Ok(opt) = opt.try_into() else { - return; + return error!("invalid arguments for update_mimetype"); }; let linked = LINKED.read(); diff --git a/yazi-core/src/manager/commands/update_task.rs b/yazi-core/src/manager/commands/update_task.rs new file mode 100644 index 000000000..fa2a8df48 --- /dev/null +++ b/yazi-core/src/manager/commands/update_task.rs @@ -0,0 +1,25 @@ +use yazi_shared::{event::Cmd, fs::Url}; + +use crate::manager::Manager; + +pub struct Opt { + url: Url, +} + +impl TryFrom for Opt { + type Error = (); + + fn try_from(mut c: Cmd) -> Result { + Ok(Self { url: c.take_any("url").ok_or(())? }) + } +} + +impl Manager { + pub fn update_task(&mut self, opt: impl TryInto) { + let Ok(opt) = opt.try_into() else { + return; + }; + + self.watcher.push_file(opt.url); + } +} diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 5e6048c16..a2c14d922 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -17,7 +17,8 @@ pub(crate) static WATCHED: RoCell>> = RoCell::new(); pub static LINKED: RoCell> = RoCell::new(); pub struct Watcher { - tx: watch::Sender>, + in_tx: watch::Sender>, + out_tx: mpsc::UnboundedSender, } impl Watcher { @@ -25,11 +26,15 @@ impl Watcher { let (in_tx, in_rx) = watch::channel(Default::default()); let (out_tx, out_rx) = mpsc::unbounded_channel(); + let out_tx_ = out_tx.clone(); let watcher = RecommendedWatcher::new( move |res: Result| { let Ok(event) = res else { return }; + if event.kind.is_access() { + return; + } for path in event.paths { - out_tx.send(Url::from(path)).ok(); + out_tx_.send(Url::from(path)).ok(); } }, Default::default(), @@ -37,12 +42,18 @@ impl Watcher { tokio::spawn(Self::fan_in(in_rx, watcher.unwrap())); 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>) { 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]) { diff --git a/yazi-dds/Cargo.toml b/yazi-dds/Cargo.toml index 2d5546271..dca5e4fdc 100644 --- a/yazi-dds/Cargo.toml +++ b/yazi-dds/Cargo.toml @@ -21,8 +21,8 @@ anyhow = "1.0.86" mlua = { version = "0.9.9", features = [ "lua54" ] } parking_lot = "0.12.3" serde = { version = "1.0.204", features = [ "derive" ] } -serde_json = "1.0.120" -tokio = { version = "1.39.1", features = [ "full" ] } +serde_json = "1.0.121" +tokio = { version = "1.39.2", features = [ "full" ] } tokio-stream = "0.1.15" tokio-util = "0.7.11" diff --git a/yazi-dds/src/sendable.rs b/yazi-dds/src/sendable.rs index d9957420a..d1b895aee 100644 --- a/yazi-dds/src/sendable.rs +++ b/yazi-dds/src/sendable.rs @@ -1,6 +1,6 @@ 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}; pub struct Sendable; @@ -15,12 +15,22 @@ impl Sendable { Value::Number(n) => Data::Number(n), Value::String(s) => Data::String(s.to_str()?.to_owned()), 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::() { 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::Thread(_) => Err("thread is not supported".into_lua_err())?, @@ -44,8 +54,9 @@ impl Sendable { Data::Integer(v) => Value::Integer(v), Data::Number(v) => Value::Number(v), Data::String(v) => Value::String(lua.create_string(v)?), - Data::Table(t) => { - let seq_len = t.keys().filter(|&k| !k.is_numeric()).count(); + Data::List(v) => Value::Table(Self::list_to_table(lua, v)?), + 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)?; for (k, v) in t { 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) -> mlua::Result { + pub fn list_to_table(lua: &Lua, data: Vec) -> mlua::Result
{ let mut vec = Vec::with_capacity(data.len()); for v in data.into_iter() { vec.push(Self::data_to_value(lua, v)?); @@ -71,15 +82,15 @@ impl Sendable { lua.create_sequence_from(vec) } - pub fn vec_to_variadic(lua: &Lua, data: Vec) -> mlua::Result> { + pub fn list_to_values(lua: &Lua, data: Vec) -> mlua::Result { let mut vec = Vec::with_capacity(data.len()); for v in data { 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) -> mlua::Result> { + pub fn values_to_vec(values: MultiValue) -> mlua::Result> { let mut vec = Vec::with_capacity(values.len()); for value in values { vec.push(Self::value_to_data(value)?); diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index d9fabdcba..2ffb2176f 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -32,9 +32,8 @@ mlua = { version = "0.9.9", features = [ "lua54" ] } ratatui = "0.27.0" scopeguard = "1.2.0" 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-util = "0.7.11" # Logging 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" ] } [target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies] -tikv-jemallocator = "0.5.4" +tikv-jemallocator = "0.6.0" [[bin]] name = "yazi" diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index 7c981dbfe..95ee0fb9d 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -44,12 +44,12 @@ impl App { }; match LUA.named_registry_value::("rt") { - Ok(mut r) => r.swap(&opt.id), + Ok(mut r) => r.push(&opt.id), Err(e) => return warn!("{e}"), } + defer! { _ = LUA.named_registry_value::("rt").map(|mut r| r.pop()) } - defer! { LUA.named_registry_value::("rt").map(|mut r| r.reset()).ok(); }; - let plugin = match LOADER.load(&opt.id) { + let plugin = match LOADER.load(&LUA, &opt.id) { Ok(plugin) => plugin, Err(e) => return warn!("{e}"), }; @@ -58,7 +58,7 @@ impl App { if let Some(cb) = opt.cb { cb(&LUA, plugin) } else { - plugin.call_method("entry", Sendable::vec_to_table(&LUA, opt.args)?) + plugin.call_method("entry", Sendable::list_to_table(&LUA, opt.args)?) } }); } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index dcc9f89b2..ffa0222b1 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -64,6 +64,7 @@ impl<'a> Executor<'a> { }; } + on!(MANAGER, update_task); on!(MANAGER, update_files, &self.app.cx.tasks); on!(MANAGER, update_mimetype, &self.app.cx.tasks); on!(MANAGER, update_paged, &self.app.cx.tasks); diff --git a/yazi-fm/src/lives/lives.rs b/yazi-fm/src/lives/lives.rs index fefb68882..bd991132e 100644 --- a/yazi-fm/src/lives/lives.rs +++ b/yazi-fm/src/lives/lives.rs @@ -36,7 +36,7 @@ impl Lives { f: impl FnOnce(&Scope<'a, 'a>) -> mlua::Result, ) -> mlua::Result { let result = LUA.scope(|scope| { - defer! { SCOPE.drop(); }; + defer! { SCOPE.drop(); } SCOPE.init(unsafe { mem::transmute::<&mlua::Scope<'a, 'a>, &mlua::Scope<'static, 'static>>(scope) }); diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 6616ee5ac..941f86f3c 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -26,14 +26,14 @@ anyhow = "1.0.86" base64 = "0.22.1" crossterm = "0.27.0" futures = "0.3.30" +globset = "0.4.14" md-5 = "0.10.6" mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } parking_lot = "0.12.3" ratatui = "0.27.0" -shell-escape = "0.1.5" shell-words = "1.1.0" 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-util = "0.7.11" unicode-width = "0.1.13" diff --git a/yazi-plugin/preset/plugins/archive.lua b/yazi-plugin/preset/plugins/archive.lua index 3827d7544..4fcc57614 100644 --- a/yazi-plugin/preset/plugins/archive.lua +++ b/yazi-plugin/preset/plugins/archive.lua @@ -1,62 +1,42 @@ local M = {} 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 i, icon, names, sizes = 0, nil, {}, {} - 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]+") - if not name then - goto continue - end + local paths, sizes = {}, {} - i = i + 1 - if i <= self.skip then - goto continue - end + local files, bound, code = self:list_files({ "-p", tostring(self.file.url) }, self.skip, limit) + if code ~= 0 then + return ya.preview_widgets(self, { + 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 - icon = File({ - url = Url(name), - cha = Cha { kind = attr:sub(1, 1) == "D" and 1 or 0 }, + for _, f in ipairs(files) do + local icon = File({ + url = Url(f.path), + cha = Cha { kind = f.attr:sub(1, 1) == "D" and 1 or 0 }, }):icon() 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 - names[#names + 1] = ui.Line(name) + paths[#paths + 1] = ui.Line(f.path) end - size = tonumber(size) - if size > 0 then - sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(size))) + if f.size > 0 then + sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(f.size))) else sizes[#sizes + 1] = ui.Line("") end + end - ::continue:: - until i >= self.skip + limit - - 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 }) + if self.skip > 0 and bound < self.skip + limit then + ya.manager_emit("peek", { math.max(0, bound - limit), only_if = self.file.url, upper_bound = true }) else ya.preview_widgets(self, { - ui.Paragraph(self.area, names), + ui.Paragraph(self.area, paths), ui.Paragraph(self.area, sizes):align(ui.Paragraph.RIGHT), }) end @@ -73,12 +53,93 @@ function M:seek(units) end end -function M:try_spawn(name) - local child, code = Command(name):args({ "l", "-ba", tostring(self.file.url) }):stdout(Command.PIPED):spawn() +function M:spawn_7z(args) + local last_error = nil + local try = function(name) + 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 + + 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 - self.last_error = code + return {}, 0, 1 end - return child + + 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 return M diff --git a/yazi-plugin/preset/plugins/extract.lua b/yazi-plugin/preset/plugins/extract.lua new file mode 100644 index 000000000..11bb98d09 --- /dev/null +++ b/yazi-plugin/preset/plugins/extract.lua @@ -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 diff --git a/yazi-plugin/preset/plugins/fzf.lua b/yazi-plugin/preset/plugins/fzf.lua index 70033f168..4de71612a 100644 --- a/yazi-plugin/preset/plugins/fzf.lua +++ b/yazi-plugin/preset/plugins/fzf.lua @@ -22,7 +22,7 @@ local function entry() local target = output.stdout:gsub("\n$", "") 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 diff --git a/yazi-plugin/preset/plugins/mime.lua b/yazi-plugin/preset/plugins/mime.lua index 548b07e4a..c74be89dd 100644 --- a/yazi-plugin/preset/plugins/mime.lua +++ b/yazi-plugin/preset/plugins/mime.lua @@ -4,7 +4,7 @@ local M = {} local function match_mimetype(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 end end @@ -44,7 +44,7 @@ function M:fetch() end 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 elseif valid then j, updates[urls[i]] = j + 1, valid diff --git a/yazi-plugin/preset/plugins/zoxide.lua b/yazi-plugin/preset/plugins/zoxide.lua index 83ee601ba..0e2c21d58 100644 --- a/yazi-plugin/preset/plugins/zoxide.lua +++ b/yazi-plugin/preset/plugins/zoxide.lua @@ -7,9 +7,7 @@ end) local set_state = ya.sync(function(st, empty) st.empty = empty end) -local function fail(s, ...) - ya.notify { title = "Zoxide", content = string.format(s, ...), timeout = 5, level = "error" } -end +local function fail(s, ...) ya.notify { title = "Zoxide", content = s:format(...), timeout = 5, level = "error" } end local function head(cwd) local child = Command("zoxide"):args({ "query", "-l" }):stdout(Command.PIPED):spawn() diff --git a/yazi-plugin/preset/setup.lua b/yazi-plugin/preset/setup.lua index bba7d57e3..0c00ec166 100644 --- a/yazi-plugin/preset/setup.lua +++ b/yazi-plugin/preset/setup.lua @@ -2,3 +2,4 @@ os.setlocale("") package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. package.path require("dds"):setup() +require("extract"):setup() diff --git a/yazi-plugin/preset/ya.lua b/yazi-plugin/preset/ya.lua index 85dad18ed..23ebe0164 100644 --- a/yazi-plugin/preset/ya.lua +++ b/yazi-plugin/preset/ya.lua @@ -21,7 +21,7 @@ function ya.list_merge(a, b) return a 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) 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") if not home then return path - elseif string.sub(path, 1, #home) == home then - return "~" .. string.sub(path, #home + 1) + elseif path:sub(1, #home) == home then + return "~" .. path:sub(#home + 1) else return path end diff --git a/yazi-plugin/src/cha/cha.rs b/yazi-plugin/src/cha/cha.rs index c2de1b04c..cf62b3893 100644 --- a/yazi-plugin/src/cha/cha.rs +++ b/yazi-plugin/src/cha/cha.rs @@ -1,12 +1,10 @@ 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 crate::bindings::Cast; -pub type UrlRef<'lua> = UserDataRef<'lua, yazi_shared::fs::Cha>; - pub struct Cha; impl Cha { diff --git a/yazi-plugin/src/file/file.rs b/yazi-plugin/src/file/file.rs index 5b7266ae5..5334645b9 100644 --- a/yazi-plugin/src/file/file.rs +++ b/yazi-plugin/src/file/file.rs @@ -17,8 +17,8 @@ impl File { where T: AsRef, { - 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("url", |lua, me| Url::cast(lua, me.as_ref().url.clone())); reg.add_field_method_get("link_to", |lua, me| { me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose() }); @@ -49,8 +49,8 @@ impl File { "File", lua.create_function(|lua, t: Table| { Self::cast(lua, yazi_shared::fs::File { - url: t.raw_get::<_, AnyUserData>("url")?.take()?, cha: t.raw_get::<_, AnyUserData>("cha")?.take()?, + url: t.raw_get::<_, AnyUserData>("url")?.take()?, ..Default::default() }) })?, diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index fca87fe0b..23919cc01 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -1,12 +1,29 @@ -use mlua::{IntoLuaMulti, Lua, Value}; +use globset::GlobBuilder; +use mlua::{ExternalError, ExternalResult, IntoLuaMulti, Lua, Table, Value}; 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<()> { lua.globals().raw_set( "fs", lua.create_table_from([ + ( + "cha", + lua.create_async_function(|lua, (url, follow): (UrlRef, Option)| 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", lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move { @@ -17,21 +34,79 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { })?, ), ( - "cha", - lua.create_async_function(|lua, url: UrlRef| async move { - match fs::symlink_metadata(&*url).await { - Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua), - Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), + "remove", + lua.create_async_function(|lua, (type_, url): (mlua::String, UrlRef)| async move { + let result = match type_.to_str()? { + "file" => fs::remove_file(&*url).await, + "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", - lua.create_async_function(|lua, url: UrlRef| async move { - match fs::metadata(&*url).await { - Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua), - Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), + "read_dir", + lua.create_async_function(|lua, (url, options): (UrlRef, Table)| async move { + let glob = if let Ok(s) = options.raw_get::<_, mlua::String>("glob") { + Some( + 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) })?, ), ( diff --git a/yazi-plugin/src/isolate/entry.rs b/yazi-plugin/src/isolate/entry.rs index 425e96016..9fea86040 100644 --- a/yazi-plugin/src/isolate/entry.rs +++ b/yazi-plugin/src/isolate/entry.rs @@ -18,7 +18,7 @@ pub async fn entry(name: String, args: Vec) -> mlua::Result<()> { }; 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 .into_lua_err()? diff --git a/yazi-plugin/src/isolate/isolate.rs b/yazi-plugin/src/isolate/isolate.rs index 8dcc57180..321d0c9cc 100644 --- a/yazi-plugin/src/isolate/isolate.rs +++ b/yazi-plugin/src/isolate/isolate.rs @@ -12,6 +12,7 @@ pub fn slim_lua(name: &str) -> mlua::Result { crate::file::pour(&lua)?; crate::url::pour(&lua)?; + crate::loader::install_isolate(&lua)?; crate::fs::install(&lua)?; crate::process::install(&lua)?; crate::utils::install_isolate(&lua)?; diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index 00a05336f..9a551253e 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -1,14 +1,12 @@ use std::{borrow::Cow, collections::HashMap, ops::Deref}; use anyhow::Result; -use mlua::{ExternalError, Table}; +use mlua::{ExternalError, Lua, Table}; use parking_lot::RwLock; use tokio::fs; use yazi_boot::BOOT; use yazi_shared::RoCell; -use crate::LUA; - pub static LOADER: RoCell = RoCell::new(); #[derive(Default)] @@ -23,11 +21,10 @@ impl Loader { } let preset = match name { - "dds" => &include_bytes!("../../preset/plugins/dds.lua")[..], - "noop" => include_bytes!("../../preset/plugins/noop.lua"), - "session" => include_bytes!("../../preset/plugins/session.lua"), - "archive" => include_bytes!("../../preset/plugins/archive.lua"), + "archive" => &include_bytes!("../../preset/plugins/archive.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"), "folder" => include_bytes!("../../preset/plugins/folder.lua"), "font" => include_bytes!("../../preset/plugins/font.lua"), @@ -36,7 +33,9 @@ impl Loader { "json" => include_bytes!("../../preset/plugins/json.lua"), "magick" => include_bytes!("../../preset/plugins/magick.lua"), "mime" => include_bytes!("../../preset/plugins/mime.lua"), + "noop" => include_bytes!("../../preset/plugins/noop.lua"), "pdf" => include_bytes!("../../preset/plugins/pdf.lua"), + "session" => include_bytes!("../../preset/plugins/session.lua"), "video" => include_bytes!("../../preset/plugins/video.lua"), "zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"), _ => b"", @@ -52,19 +51,18 @@ impl Loader { Ok(()) } - pub fn load(&self, id: &str) -> mlua::Result
{ - let globals = LUA.globals(); - let loaded: Table = globals.raw_get::<_, Table>("package")?.raw_get("loaded")?; + pub fn load<'a>(&self, lua: &'a Lua, id: &str) -> mlua::Result> { + let loaded: Table = lua.globals().raw_get::<_, Table>("package")?.raw_get("loaded")?; if let Ok(t) = loaded.raw_get::<_, Table>(id) { return Ok(t); } 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())?, }; - t.raw_set("_id", LUA.create_string(id)?)?; + t.raw_set("_id", lua.create_string(id)?)?; loaded.raw_set(id, t.clone())?; Ok(t) } diff --git a/yazi-plugin/src/loader/mod.rs b/yazi-plugin/src/loader/mod.rs index 6c59670a2..639a57e61 100644 --- a/yazi-plugin/src/loader/mod.rs +++ b/yazi-plugin/src/loader/mod.rs @@ -8,8 +8,6 @@ use require::*; pub(super) fn init() { LOADER.with(<_>::default); } -pub(super) fn install(lua: &'static mlua::Lua) -> mlua::Result<()> { - Require::install(lua)?; +pub(super) fn install(lua: &mlua::Lua) -> mlua::Result<()> { Require::install(lua) } - Ok(()) -} +pub(super) fn install_isolate(lua: &mlua::Lua) -> mlua::Result<()> { Require::install_isolate(lua) } diff --git a/yazi-plugin/src/loader/require.rs b/yazi-plugin/src/loader/require.rs index cde162e64..d9cbd4e08 100644 --- a/yazi-plugin/src/loader/require.rs +++ b/yazi-plugin/src/loader/require.rs @@ -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 crate::RtRef; @@ -6,41 +8,50 @@ use crate::RtRef; pub(super) struct Require; impl Require { - pub(super) fn install(lua: &'static Lua) -> mlua::Result<()> { - let globals = lua.globals(); - - globals.raw_set( + pub(super) fn install(lua: &Lua) -> mlua::Result<()> { + lua.globals().raw_set( "require", - lua.create_function(|lua, name: mlua::String| { - let s = name.to_str()?; + lua.create_function(|lua, id: mlua::String| { + let s = id.to_str()?; futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?; - lua.named_registry_value::("rt")?.swap(s); - let mod_ = LOADER.load(s)?; - lua.named_registry_value::("rt")?.reset(); + lua.named_registry_value::("rt")?.push(s); + let mod_ = LOADER.load(lua, s); + lua.named_registry_value::("rt")?.pop(); - Self::create_mt(lua, name, mod_) + Self::create_mt(lua, s, mod_?, true) })?, - )?; + ) + } + + pub(super) fn install_isolate(lua: &Lua) -> mlua::Result<()> { + lua.globals().raw_set( + "require", + lua.create_async_function(|lua, id: mlua::String| async move { + let s = id.to_str()?; + LOADER.ensure(s).await.into_lua_err()?; - Ok(()) + lua.named_registry_value::("rt")?.push(s); + let mod_ = LOADER.load(lua, s); + lua.named_registry_value::("rt")?.pop(); + + Self::create_mt(lua, s, mod_?, false) + })?, + ) } - fn create_mt( - lua: &'static Lua, - name: mlua::String<'static>, - mod_: Table<'static>, - ) -> mlua::Result> { - let ts = - lua.create_table_from([("name", name.into_lua(lua)?), ("mod", mod_.into_lua(lua)?)])?; + fn create_mt<'a>(lua: &'a Lua, id: &str, mod_: Table<'a>, sync: bool) -> mlua::Result> { + let ts = lua.create_table_from([("_mod", mod_.into_lua(lua)?)])?; + let id: Arc = Arc::from(id); let mt = lua.create_table_from([( "__index", - lua.create_function(|_, (_, key): (Table, mlua::String)| { - if key.to_str()? == "setup" { - Ok(RequireSetup) - } else { - Err("Only `require():setup()` is supported").into_lua_err() + lua.create_function(move |lua, (ts, key): (Table, mlua::String)| { + match ts.raw_get::<_, Table>("_mod")?.raw_get::<_, Value>(&key)? { + Value::Function(_) => { + Self::create_wrapper(lua, id.clone(), key.to_str()?, sync)?.into_lua(lua) + } + v => Ok(v), } })?, )])?; @@ -48,18 +59,34 @@ impl Require { ts.set_metatable(Some(mt)); Ok(ts) } -} -pub(super) struct RequireSetup; + fn create_wrapper<'a>( + lua: &'a Lua, + id: Arc, + f: &str, + sync: bool, + ) -> mlua::Result> { + let f: Arc = Arc::from(f); -impl UserData for RequireSetup { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_meta_method(MetaMethod::Call, |lua, _, (ts, args): (Table, Variadic)| { - let (name, mod_): (mlua::String, Table) = (ts.raw_get("name")?, ts.raw_get("mod")?); - lua.named_registry_value::("rt")?.swap(name.to_str()?); - let result = mod_.call_method::<_, Variadic>("setup", args); - lua.named_registry_value::("rt")?.reset(); - result - }); + if sync { + lua.create_function(move |lua, (ts, args): (Table, MultiValue)| { + let mod_: Table = ts.raw_get::<_, Table>("_mod")?; + lua.named_registry_value::("rt")?.push(&id); + let result = mod_.call_method::<_, MultiValue>(&f, args); + lua.named_registry_value::("rt")?.pop(); + 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::("rt")?.push(&id); + let result = mod_.call_async_method::<_, MultiValue>(&f, args).await; + lua.named_registry_value::("rt")?.pop(); + result + } + }) + } } } diff --git a/yazi-plugin/src/pubsub/pubsub.rs b/yazi-plugin/src/pubsub/pubsub.rs index acc81d9e2..220bd8b9e 100644 --- a/yazi-plugin/src/pubsub/pubsub.rs +++ b/yazi-plugin/src/pubsub/pubsub.rs @@ -28,7 +28,8 @@ impl Pubsub { ps.raw_set( "sub", lua.create_function(|lua, (kind, f): (mlua::String, Function)| { - let Some(ref cur) = lua.named_registry_value::("rt")?.current else { + let rt = lua.named_registry_value::("rt")?; + let Some(cur) = rt.current() else { return Err("`sub()` must be called in a sync plugin").into_lua_err(); }; if !yazi_dds::Pubsub::sub(cur, kind.to_str()?, f) { @@ -41,7 +42,8 @@ impl Pubsub { ps.raw_set( "sub_remote", lua.create_function(|_, (kind, f): (mlua::String, Function)| { - let Some(ref cur) = lua.named_registry_value::("rt")?.current else { + let rt = lua.named_registry_value::("rt")?; + let Some(cur) = rt.current() else { 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) { @@ -54,7 +56,7 @@ impl Pubsub { ps.raw_set( "unsub", lua.create_function(|_, kind: mlua::String| { - if let Some(ref cur) = lua.named_registry_value::("rt")?.current { + if let Some(cur) = lua.named_registry_value::("rt")?.current() { Ok(yazi_dds::Pubsub::unsub(cur, kind.to_str()?)) } else { Err("`unsub()` must be called in a sync plugin").into_lua_err() @@ -65,7 +67,7 @@ impl Pubsub { ps.raw_set( "unsub_remote", lua.create_function(|_, kind: mlua::String| { - if let Some(ref cur) = lua.named_registry_value::("rt")?.current { + if let Some(cur) = lua.named_registry_value::("rt")?.current() { Ok(yazi_dds::Pubsub::unsub_remote(cur, kind.to_str()?)) } else { Err("`unsub_remote()` must be called in a sync plugin").into_lua_err() diff --git a/yazi-plugin/src/runtime.rs b/yazi-plugin/src/runtime.rs index 757416103..7a33be183 100644 --- a/yazi-plugin/src/runtime.rs +++ b/yazi-plugin/src/runtime.rs @@ -1,49 +1,53 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use mlua::{Function, UserData}; #[derive(Default)] pub struct Runtime { - pub current: Option, - pub calls: usize, - pub blocks: HashMap>>, + frames: VecDeque, + blocks: HashMap>>, +} + +struct RuntimeFrame { + id: String, + calls: usize, } pub type RtRef<'lua> = mlua::UserDataRefMut<'lua, Runtime>; impl Runtime { - pub fn new(current: &str) -> Self { - Self { current: Some(current.to_owned()), ..Default::default() } + pub fn new(id: &str) -> Self { + Self { + frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]), + ..Default::default() + } } - pub fn swap(&mut self, name: &str) { - self.current = Some(name.to_owned()); - self.calls = 0; + pub fn push(&mut self, id: &str) { + self.frames.push_back(RuntimeFrame { id: id.to_owned(), calls: 0 }); } - pub fn reset(&mut self) { - self.current = None; - self.calls = 0; - } + pub fn pop(&mut self) { self.frames.pop_back(); } - pub fn next_block(&mut self) -> usize { - self.calls += 1; - self.calls - 1 - } + pub fn current(&self) -> Option<&str> { self.frames.back().map(|f| f.id.as_str()) } - pub fn get_block(&self, name: &str, calls: usize) -> Option> { - self.blocks.get(name).and_then(|v| v.get(calls)).cloned() + pub fn next_block(&mut self) -> Option { + self.frames.back_mut().map(|f| { + f.calls += 1; + f.calls - 1 + }) } - pub fn push_block(&mut self, f: Function<'static>) -> bool { - let Some(ref cur) = self.current else { - return false; - }; + pub fn get_block(&self, id: &str, calls: usize) -> Option> { + self.blocks.get(id).and_then(|v| v.get(calls)).cloned() + } - if let Some(vec) = self.blocks.get_mut(cur) { - vec.push(f); + pub fn put_block(&mut self, f: Function<'static>) -> bool { + let Some(cur) = self.frames.back() else { return false }; + if let Some(v) = self.blocks.get_mut(&cur.id) { + v.push(f); } else { - self.blocks.insert(cur.clone(), vec![f]); + self.blocks.insert(cur.id.to_owned(), vec![f]); } true } diff --git a/yazi-plugin/src/url/url.rs b/yazi-plugin/src/url/url.rs index 30c5b7c6a..b0749c1e4 100644 --- a/yazi-plugin/src/url/url.rs +++ b/yazi-plugin/src/url/url.rs @@ -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; @@ -20,7 +20,16 @@ impl Url { reg.add_method("stem", |lua, me, ()| { 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::()?; + Self::cast(lua, me.join(&*url)) + } + _ => Err("must be a string or a Url".into_lua_err())?, + }) + }); reg.add_method("parent", |lua, me, ()| { me.parent_url().map(|u| Self::cast(lua, u)).transpose() }); diff --git a/yazi-plugin/src/utils/cache.rs b/yazi-plugin/src/utils/cache.rs index 828927973..c139d36ac 100644 --- a/yazi-plugin/src/utils/cache.rs +++ b/yazi-plugin/src/utils/cache.rs @@ -17,7 +17,7 @@ impl Utils { let hex = { 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()) }; diff --git a/yazi-plugin/src/utils/log.rs b/yazi-plugin/src/utils/log.rs index de2a59ac0..a5daa1f98 100644 --- a/yazi-plugin/src/utils/log.rs +++ b/yazi-plugin/src/utils/log.rs @@ -1,4 +1,4 @@ -use mlua::{Lua, Table, Value, Variadic}; +use mlua::{Lua, MultiValue, Table}; use tracing::{debug, error}; use super::Utils; @@ -7,7 +7,7 @@ impl Utils { pub(super) fn log(lua: &Lua, ya: &Table) -> mlua::Result<()> { ya.raw_set( "dbg", - lua.create_function(|_, values: Variadic| { + lua.create_function(|_, values: MultiValue| { let s = values.into_iter().map(|v| format!("{v:#?}")).collect::>().join(" "); Ok(debug!("{s}")) })?, @@ -15,7 +15,7 @@ impl Utils { ya.raw_set( "err", - lua.create_function(|_, values: Variadic| { + lua.create_function(|_, values: MultiValue| { let s = values.into_iter().map(|v| format!("{v:#?}")).collect::>().join(" "); Ok(error!("{s}")) })?, diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 9c9408233..7143e2aae 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -19,8 +19,8 @@ impl<'a> TryFrom> for PreviewLock { fn try_from(t: Table) -> Result { let file: FileRef = t.raw_get("file")?; Ok(Self { - url: file.url(), cha: file.cha, + url: file.url(), skip: t.raw_get("skip")?, window: t.raw_get("window")?, data: Default::default(), diff --git a/yazi-plugin/src/utils/sync.rs b/yazi-plugin/src/utils/sync.rs index 667c3f44f..1e185b3b6 100644 --- a/yazi-plugin/src/utils/sync.rs +++ b/yazi-plugin/src/utils/sync.rs @@ -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 yazi_dds::Sendable; use yazi_shared::{emit, event::{Cmd, Data}, Layer}; @@ -12,14 +12,15 @@ impl Utils { "sync", lua.create_function(|lua, f: Function<'static>| { let mut rt = lua.named_registry_value::("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(); } - let cur = rt.current.clone().unwrap(); - lua.create_function(move |lua, mut args: Variadic| { - args.insert(0, LOADER.load(&cur)?.into_lua(lua)?); - f.call::<_, Variadic>(args) + let cur = rt.current().unwrap().to_owned(); + lua.create_function(move |lua, args: MultiValue| { + f.call::<_, MultiValue>(MultiValue::from_iter( + [LOADER.load(lua, &cur)?.into_lua(lua)?].into_iter().chain(args), + )) }) })?, )?; @@ -31,13 +32,16 @@ impl Utils { ya.raw_set( "sync", lua.create_function(|lua, ()| { - let block = lua.named_registry_value::("rt")?.next_block(); - lua.create_async_function(move |lua, args: Variadic| async move { - let Some(cur) = lua.named_registry_value::("rt")?.current.clone() else { - return Err("`ya.sync()` must be called in a plugin").into_lua_err(); - }; + let Some(block) = lua.named_registry_value::("rt")?.next_block() 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::("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(()) } - async fn retrieve( - name: String, - calls: usize, - args: Variadic>, - ) -> mlua::Result> { - let args = Sendable::variadic_to_vec(args)?; + async fn retrieve(name: &str, calls: usize, args: MultiValue<'_>) -> mlua::Result> { + let args = Sendable::values_to_vec(args)?; let (tx, rx) = oneshot::channel::>(); let callback: OptCallback = { - let name = name.clone(); + let name = name.to_owned(); Box::new(move |lua, plugin| { let Some(block) = lua.named_registry_value::("rt")?.get_block(&name, calls) else { return Err("sync block not found".into_lua_err()); }; - let mut self_args = Vec::with_capacity(args.len() + 1); - self_args.push(Value::Table(plugin)); - for arg in args { - self_args.push(Sendable::data_to_value(lua, arg)?); - } + let args: Vec<_> = [Ok(Value::Table(plugin))] + .into_iter() + .chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d))) + .collect::>()?; - 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()) }) }; emit!(Call( - Cmd::args("plugin", vec![name.clone()]) + Cmd::args("plugin", vec![name.to_owned()]) .with_bool("sync", true) .with_any("callback", callback), Layer::App diff --git a/yazi-plugin/src/utils/text.rs b/yazi-plugin/src/utils/text.rs index 9df2fd513..9ac04f74a 100644 --- a/yazi-plugin/src/utils/text.rs +++ b/yazi-plugin/src/utils/text.rs @@ -1,5 +1,6 @@ use std::ops::ControlFlow; +use md5::{Digest, Md5}; use mlua::{Lua, Table}; use unicode_width::UnicodeWidthChar; @@ -8,13 +9,20 @@ use crate::CLIPBOARD; impl Utils { 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( "quote", lua.create_function(|_, (s, unix): (mlua::String, Option)| { let s = match unix { - Some(true) => yazi_shared::escape::unix(s.to_str()?), - Some(false) => yazi_shared::escape::windows(s.to_str()?), - None => yazi_shared::escape::native(s.to_str()?), + Some(true) => yazi_shared::shell::escape_unix(s.to_str()?), + Some(false) => yazi_shared::shell::escape_windows(s.to_str()?), + None => yazi_shared::shell::escape_native(s.to_str()?), }; Ok(s.into_owned()) })?, diff --git a/yazi-proxy/Cargo.toml b/yazi-proxy/Cargo.toml index 39767a7a6..f09ffb815 100644 --- a/yazi-proxy/Cargo.toml +++ b/yazi-proxy/Cargo.toml @@ -19,4 +19,4 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" } # External dependencies anyhow = "1.0.86" mlua = { version = "0.9.9", features = [ "lua54" ] } -tokio = { version = "1.39.1", features = [ "full" ] } +tokio = { version = "1.39.2", features = [ "full" ] } diff --git a/yazi-proxy/src/manager.rs b/yazi-proxy/src/manager.rs index 60088df3f..2bfb5e0f5 100644 --- a/yazi-proxy/src/manager.rs +++ b/yazi-proxy/src/manager.rs @@ -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] pub fn update_paged() { emit!(Call(Cmd::new("update_paged"), Layer::Manager)); @@ -44,7 +49,7 @@ impl ManagerProxy { #[inline] pub fn update_paged_by(page: usize, only_if: &Url) { 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 )); } diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 84fc09284..2a8ab6721 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -21,7 +21,7 @@ async-priority-channel = "0.2.0" futures = "0.3.30" parking_lot = "0.12.3" scopeguard = "1.2.0" -tokio = { version = "1.39.1", features = [ "full" ] } +tokio = { version = "1.39.2", features = [ "full" ] } # Logging tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 0373a59c5..5b0638522 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -128,17 +128,21 @@ impl File { self.prog.send(TaskProg::Adv(task.id, 1, task.length))? } FileOp::Trash(task) => { - #[cfg(target_os = "macos")] - { - use trash::{macos::{DeleteMethod, TrashContextExtMacos}, TrashContext}; - let mut ctx = TrashContext::default(); - ctx.set_delete_method(DeleteMethod::NsFileManager); - ctx.delete(&task.target)?; - } - #[cfg(all(not(target_os = "macos"), not(target_os = "android")))] - { - trash::delete(&task.target)?; - } + tokio::task::spawn_blocking(move || { + #[cfg(target_os = "macos")] + { + use trash::{macos::{DeleteMethod, TrashContextExtMacos}, TrashContext}; + let mut ctx = TrashContext::default(); + ctx.set_delete_method(DeleteMethod::NsFileManager); + ctx.delete(&task.target)?; + } + #[cfg(all(not(target_os = "macos"), not(target_os = "android")))] + { + trash::delete(&task.target)?; + } + Ok::<_, trash::Error>(()) + }) + .await??; self.prog.send(TaskProg::Adv(task.id, 1, task.length))?; } } @@ -329,20 +333,6 @@ impl File { let meta = fs::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 { diff --git a/yazi-scheduler/src/process/shell.rs b/yazi-scheduler/src/process/shell.rs index 84978b058..22e699b2f 100644 --- a/yazi-scheduler/src/process/shell.rs +++ b/yazi-scheduler/src/process/shell.rs @@ -134,7 +134,7 @@ mod parser { if let Some(p) = pos { if let Some(arg) = args.get(p.parse::().unwrap()) { if quote { - buf.extend(yazi_shared::escape::os_str(arg).encode_wide()); + buf.extend(yazi_shared::shell::escape_os_str(arg).encode_wide()); } else { buf.extend(arg.encode_wide()); } @@ -152,13 +152,13 @@ mod parser { s.push(" "); } if c == '*' { - s.push(yazi_shared::escape::os_str(arg)); + s.push(yazi_shared::shell::escape_os_str(arg)); } else { s.push(arg); } } if quote { - buf.extend(yazi_shared::escape::os_str(&s).encode_wide()); + buf.extend(yazi_shared::shell::escape_os_str(&s).encode_wide()); } else { buf.extend(s.encode_wide()); } diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index 0daa9cef3..769b1431f 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -6,7 +6,8 @@ use parking_lot::Mutex; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle}; use yazi_config::{open::Opener, plugin::{Fetcher, Preloader}, TASKS}; 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 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| { async move { if !canceled { - File::remove_empty_dirs(&from).await; + remove_dir_clean(&from).await; Pump::push_move(from, to); } ongoing.lock().try_remove(id, TaskStage::Hooked); @@ -188,6 +189,7 @@ impl Scheduler { async move { if !canceled { fs::remove_dir_all(&target).await.ok(); + ManagerProxy::update_task(&target); Pump::push_delete(target); } ongoing.lock().try_remove(id, TaskStage::Hooked); @@ -207,14 +209,29 @@ impl Scheduler { } pub fn file_trash(&self, target: Url) { - let name = format!("Trash {:?}", target); - let id = self.ongoing.lock().add(TaskKind::User, name); + let mut ongoing = self.ongoing.lock(); + 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(); _ = self.micro.try_send( async move { file.trash(FileOpTrash { id, target: target.clone(), length: 0 }).await.ok(); - Pump::push_trash(target); } .boxed(), LOW, diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index ed84c2092..96c00bebe 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -15,19 +15,17 @@ bitflags = "2.6.0" crossterm = "0.27.0" dirs = "5.0.1" futures = "0.3.30" +libc = "0.2.155" parking_lot = "0.12.3" percent-encoding = "2.3.1" ratatui = "0.27.0" regex = "1.10.5" serde = { version = "1.0.204", features = [ "derive" ] } shell-words = "1.1.0" -tokio = { version = "1.39.1", features = [ "full" ] } - -[target."cfg(unix)".dependencies] -libc = "0.2.155" +tokio = { version = "1.39.2", features = [ "full" ] } [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] crossterm = { version = "0.27.0", features = [ "use-dev-tty" ] } diff --git a/yazi-shared/src/escape/mod.rs b/yazi-shared/src/escape/mod.rs deleted file mode 100644 index 297f05e36..000000000 --- a/yazi-shared/src/escape/mod.rs +++ /dev/null @@ -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 { unix::from_str(s) } - -#[inline] -pub fn windows(s: &str) -> Cow { windows::from_str(s) } - -#[inline] -pub fn native(s: &str) -> Cow { - #[cfg(unix)] - { - unix::from_str(s) - } - #[cfg(windows)] - { - windows::from_str(s) - } -} - -#[inline] -pub fn os_str(s: &OsStr) -> Cow { - #[cfg(unix)] - { - unix::from_os_str(s) - } - #[cfg(windows)] - { - windows::from_os_str(s) - } -} diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 2b833e7e4..1cd618332 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -13,7 +13,8 @@ pub enum Data { Integer(i64), Number(f64), String(String), - Table(HashMap), + List(Vec), + Dict(HashMap), #[serde(skip_deserializing)] Url(Url), #[serde(skip)] @@ -64,13 +65,13 @@ impl Data { } } - pub fn into_table_string(self) -> HashMap { - let Self::Table(table) = self else { + pub fn into_dict_string(self) -> HashMap { + let Self::Dict(dict) = self else { return Default::default(); }; - let mut map = HashMap::with_capacity(table.len()); - for pair in table { + let mut map = HashMap::with_capacity(dict.len()); + for pair in dict { if let (DataKey::String(k), Self::String(v)) = pair { map.insert(k, v); } @@ -103,7 +104,7 @@ pub enum DataKey { impl DataKey { #[inline] - pub fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) } + pub fn is_integer(&self) -> bool { matches!(self, Self::Integer(_)) } } // --- Macros diff --git a/yazi-shared/src/fs/cha.rs b/yazi-shared/src/fs/cha.rs index f50fa5f17..10f09edb9 100644 --- a/yazi-shared/src/fs/cha.rs +++ b/yazi-shared/src/fs/cha.rs @@ -15,7 +15,7 @@ bitflags! { } } -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Cha { pub kind: ChaKind, pub len: u64, diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index bd7fdca28..5ab7456d0 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -7,8 +7,8 @@ use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; #[derive(Clone, Debug, Default)] pub struct File { - pub url: Url, pub cha: Cha, + pub url: Url, pub link_to: Option, pub icon: Cell, } @@ -25,6 +25,13 @@ impl AsRef for File { 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 { #[inline] pub async fn from(url: Url) -> Result { @@ -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] pub fn from_dummy(url: Url, ft: Option) -> Self { - // TODO: set dummy=true - Self { url: url.to_owned(), cha: ft.map_or_else(Cha::default, Cha::from), ..Default::default() } + Self { cha: ft.map_or_else(Cha::default, Cha::from), url: url.to_owned(), ..Default::default() } } } diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index 051c4b124..dae881559 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -261,6 +261,20 @@ pub fn copy_with_progress( 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 #[cfg(unix)] #[allow(clippy::collapsible_else_if)] diff --git a/yazi-shared/src/fs/url.rs b/yazi-shared/src/fs/url.rs index c417ce047..01db98d3c 100644 --- a/yazi-shared/src/fs/url.rs +++ b/yazi-shared/src/fs/url.rs @@ -152,7 +152,7 @@ impl Url { #[cfg(unix)] #[inline] 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".")) } } diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 0fef293d7..fbbf0230b 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -5,7 +5,6 @@ mod condition; mod debounce; mod env; mod errors; -pub mod escape; pub mod event; pub mod fs; mod layer; @@ -14,6 +13,7 @@ mod number; mod os; mod rand; mod ro_cell; +pub mod shell; mod terminal; pub mod theme; mod throttle; diff --git a/yazi-shared/src/number.rs b/yazi-shared/src/number.rs index 3ef13f559..0633a8c78 100644 --- a/yazi-shared/src/number.rs +++ b/yazi-shared/src/number.rs @@ -14,7 +14,7 @@ impl OrderedFloat { } #[inline] - pub fn get(&self) -> f64 { self.0 } + pub const fn get(&self) -> f64 { self.0 } } impl Hash for OrderedFloat { diff --git a/yazi-shared/src/shell/mod.rs b/yazi-shared/src/shell/mod.rs new file mode 100644 index 000000000..02a3699d6 --- /dev/null +++ b/yazi-shared/src/shell/mod.rs @@ -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 { unix::escape_str(s) } + +#[inline] +pub fn escape_windows(s: &str) -> Cow { windows::escape_str(s) } + +#[inline] +pub fn escape_native(s: &str) -> Cow { + #[cfg(unix)] + { + escape_unix(s) + } + #[cfg(windows)] + { + escape_windows(s) + } +} + +#[inline] +pub fn escape_os_str(s: &OsStr) -> Cow { + #[cfg(unix)] + { + unix::escape_os_str(s) + } + #[cfg(windows)] + { + windows::escape_os_str(s) + } +} + +#[inline] +pub fn split_unix(s: &str) -> anyhow::Result> { Ok(shell_words::split(s)?) } + +#[cfg(windows)] +pub fn split_windows(s: &str) -> anyhow::Result> { Ok(windows::split(s)?) } + +pub fn split_native(s: &str) -> anyhow::Result> { + #[cfg(unix)] + { + split_unix(s) + } + #[cfg(windows)] + { + split_windows(s) + } +} diff --git a/yazi-shared/src/escape/unix.rs b/yazi-shared/src/shell/unix.rs similarity index 66% rename from yazi-shared/src/escape/unix.rs rename to yazi-shared/src/shell/unix.rs index 8bad15747..e578bf0e7 100644 --- a/yazi-shared/src/escape/unix.rs +++ b/yazi-shared/src/shell/unix.rs @@ -1,23 +1,23 @@ use std::borrow::Cow; -pub fn from_str(s: &str) -> Cow { - match from_slice(s.as_bytes()) { +pub fn escape_str(s: &str) -> Cow { + match escape_slice(s.as_bytes()) { 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)] -pub fn from_os_str(s: &std::ffi::OsStr) -> Cow { +pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow { 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::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) { return Cow::Borrowed(s); } @@ -51,31 +51,31 @@ mod tests { use super::*; #[test] - fn test_from_str() { - assert_eq!(from_str(""), r#"''"#); - assert_eq!(from_str(" "), r#"' '"#); - assert_eq!(from_str("*"), r#"'*'"#); + fn test_escape_str() { + assert_eq!(escape_str(""), r#"''"#); + assert_eq!(escape_str(" "), r#"' '"#); + assert_eq!(escape_str("*"), r#"'*'"#); - assert_eq!(from_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc"); - assert_eq!(from_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("--aaa=bbb-ccc"), "--aaa=bbb-ccc"); + assert_eq!(escape_str(r#"--features="default""#), r#"'--features="default"'"#); + assert_eq!(escape_str("linker=gcc -L/foo -Wl,bar"), r#"'linker=gcc -L/foo -Wl,bar'"#); assert_eq!( - from_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"), + escape_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+", ); - assert_eq!(from_str(r#"'!\$`\\\n "#), r#"''\'''\!'\$`\\\n '"#); + assert_eq!(escape_str(r#"'!\$`\\\n "#), r#"''\'''\!'\$`\\\n '"#); } #[cfg(unix)] #[test] - fn test_from_os_str() { + fn test_escape_os_str() { 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_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#"''"#); diff --git a/yazi-shared/src/escape/windows.rs b/yazi-shared/src/shell/windows.rs similarity index 64% rename from yazi-shared/src/escape/windows.rs rename to yazi-shared/src/shell/windows.rs index bd08631bb..bed10dc55 100644 --- a/yazi-shared/src/escape/windows.rs +++ b/yazi-shared/src/shell/windows.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, iter::repeat}; -pub fn from_str(s: &str) -> Cow { +pub fn escape_str(s: &str) -> Cow { let bytes = s.as_bytes(); if !bytes.is_empty() && !bytes.iter().any(|&c| matches!(c, b' ' | b'"' | b'\n' | b'\t')) { return Cow::Borrowed(s); @@ -39,7 +39,7 @@ pub fn from_str(s: &str) -> Cow { } #[cfg(windows)] -pub fn from_os_str(s: &std::ffi::OsStr) -> Cow { +pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow { use std::os::windows::ffi::{OsStrExt, OsStringExt}; let wide = s.encode_wide(); @@ -79,6 +79,37 @@ pub fn from_os_str(s: &std::ffi::OsStr) -> Cow { std::ffi::OsString::from_wide(&escaped).into() } +#[cfg(windows)] +pub fn split(s: &str) -> std::io::Result> { + 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> { + use std::mem::MaybeUninit; + + use windows_sys::Win32::{Foundation::LocalFree, UI::Shell::CommandLineToArgvW}; + + let mut argc = MaybeUninit::::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)] fn disallowed(b: u16) -> bool { match char::from_u32(b as u32) { @@ -92,33 +123,33 @@ mod tests { use super::*; #[test] - fn test_from_str() { - assert_eq!(from_str(""), r#""""#); - assert_eq!(from_str(r#""""#), r#""\"\"""#); + fn test_escape_str() { + assert_eq!(escape_str(""), r#""""#); + assert_eq!(escape_str(r#""""#), r#""\"\"""#); - assert_eq!(from_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("--aaa=bbb-ccc"), "--aaa=bbb-ccc"); + 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!(from_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(r#"--features="default""#), r#""--features=\"default\"""#); + assert_eq!(escape_str(r#""--features=\"default\"""#), r#""\"--features=\\\"default\\\"\"""#); + assert_eq!(escape_str("linker=gcc -L/foo -Wl,bar"), r#""linker=gcc -L/foo -Wl,bar""#); } #[cfg(windows)] #[test] - fn test_from_os_str() { + fn test_escape_os_str() { use std::{ffi::OsString, os::windows::ffi::OsStringExt}; fn from_str(input: &str, expected: &str) { let observed = OsString::from(input); 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]) { let observed = OsString::from_wide(input); 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#""""#);