diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6ad65887..8c7716aa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -15,3 +15,7 @@ A clear and concise description of the rationale of the changes, to help our rev If it has already been detailed in the associated issue, please skip this section. --> + +## Checklist + +- [ ] I have read [CONTRIBUTING.md](https://github.com/sxyazi/yazi/blob/main/CONTRIBUTING.md) diff --git a/CHANGELOG.md b/CHANGELOG.md index af2c8f23..9f8db42d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): - Drag and drop ([#4005]) - Bulk create ([#3793]) - Image preview with Überzug++ on Niri ([#3990]) +- New gait for input `backward` and `forward` actions ([#4012]) ### Changed diff --git a/Cargo.lock b/Cargo.lock index a8e9faab..2ad2822a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" dependencies = [ "crypto-common 0.2.2", - "inout 0.2.2", + "inout", ] [[package]] @@ -165,6 +165,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.4.2" @@ -378,9 +387,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -434,15 +443,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "block-padding" version = "0.4.2" @@ -594,9 +594,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -614,7 +614,7 @@ checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", "crypto-common 0.2.2", - "inout 0.2.2", + "inout", "zeroize", ] @@ -804,6 +804,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -844,7 +850,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crossterm_winapi", "mio", "parking_lot", @@ -861,7 +867,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crossterm_winapi", "derive_more", "document-features", @@ -969,12 +975,12 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-pre.6" +version = "5.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" +checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "curve25519-dalek-derive", "digest 0.11.3", "fiat-crypto", @@ -1274,9 +1280,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.7" +version = "3.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20449acd54b660981ae5caa2bcb56d1fe7f25f2e37a38ec507400fab034d4bb6" +checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" dependencies = [ "curve25519-dalek", "ed25519", @@ -1296,22 +1302,22 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" -version = "0.14.0-rc.32" +version = "0.14.0-rc.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda94f31325c4275e9706adecbb6f0650dee2f904c915a98e3d81adaaaa757aa" +checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" dependencies = [ "base16ct", "crypto-bigint", "crypto-common 0.2.2", "digest 0.11.3", + "ff", + "group", "hkdf", "hybrid-array", "once_cell", "pem-rfc7468", "pkcs8", "rand_core 0.10.1", - "rustcrypto-ff", - "rustcrypto-group", "sec1", "subtle", "zeroize", @@ -1469,6 +1475,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.3.0" @@ -1663,10 +1679,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1688,11 +1702,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -1733,6 +1749,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "half" version = "2.7.1" @@ -1946,11 +1973,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "inotify-sys", "libc", ] @@ -1964,23 +1991,13 @@ dependencies = [ "libc", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding 0.3.3", - "generic-array 0.14.7", -] - [[package]] name = "inout" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ - "block-padding 0.4.2", + "block-padding", "hybrid-array", ] @@ -2020,6 +2037,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2110,7 +2136,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", ] @@ -2146,9 +2172,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libfuzzer-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [ "arbitrary", "cc", @@ -2175,7 +2201,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -2207,9 +2233,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "loop9" @@ -2220,15 +2246,6 @@ dependencies = [ "imgref", ] -[[package]] -name = "lru" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" -dependencies = [ - "hashbrown 0.16.1", -] - [[package]] name = "lru" version = "0.18.0" @@ -2287,9 +2304,9 @@ dependencies = [ [[package]] name = "md5" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" @@ -2438,7 +2455,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -2451,7 +2468,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -2497,7 +2514,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "fsevent-sys", "inotify", "kqueue", @@ -2515,7 +2532,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -2630,7 +2647,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", ] @@ -2661,7 +2678,7 @@ version = "6.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "libc", "once_cell", "onig_sys", @@ -2714,9 +2731,9 @@ dependencies = [ [[package]] name = "p256" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a" +checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e" dependencies = [ "ecdsa", "elliptic-curve", @@ -2727,9 +2744,9 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03" +checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02" dependencies = [ "ecdsa", "elliptic-curve", @@ -2741,9 +2758,9 @@ dependencies = [ [[package]] name = "p521" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7" +checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae" dependencies = [ "base16ct", "ecdsa", @@ -2779,6 +2796,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ + "approx", "bytemuck", "fast-srgb8", "libm", @@ -3046,7 +3064,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -3108,23 +3126,23 @@ dependencies = [ [[package]] name = "primefield" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6" +checksum = "f845ec3240cd5ed5e1e31cf3ff633a5bf47c698dc4092ba9e767415b3d393406" dependencies = [ "crypto-bigint", "crypto-common 0.2.2", + "ff", "rand_core 0.10.1", - "rustcrypto-ff", "subtle", "zeroize", ] [[package]] name = "primeorder" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a" +checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf" dependencies = [ "elliptic-curve", ] @@ -3334,19 +3352,21 @@ dependencies = [ [[package]] name = "ratatui-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "compact_str", - "hashbrown 0.16.1", + "critical-section", + "hashbrown 0.17.1", "indoc", "itertools", "kasuari", - "lru 0.16.4", + "lru", + "palette", "serde", - "strum 0.27.2", + "strum 0.28.0", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", @@ -3355,9 +3375,9 @@ dependencies = [ [[package]] name = "ratatui-crossterm" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +checksum = "2b2867bedcbd6a690ca4f8672a687b730ec07660c79844517b084311b529980c" dependencies = [ "cfg-if", "crossterm 0.28.1", @@ -3368,9 +3388,9 @@ dependencies = [ [[package]] name = "ratatui-termion" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cade85a8591fbc911e147951422f0d6fd40f4948b271b6216c7dc01838996f8" +checksum = "5c16cc35a9d9114e0b2bb4b22018b96ae7f5fe60e2595dc73e622b4e78624835" dependencies = [ "instability", "ratatui-core", @@ -3379,9 +3399,9 @@ dependencies = [ [[package]] name = "ratatui-termwiz" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +checksum = "386b8ff8f74ed749509391c56d549761a2fcdb408e1f42e467286bcb7dac8967" dependencies = [ "ratatui-core", "termwiz", @@ -3393,7 +3413,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "hashbrown 0.16.1", "indoc", "instability", @@ -3482,7 +3502,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -3596,13 +3616,13 @@ dependencies = [ [[package]] name = "russh" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67013f080c226e5a34db1c71f2567f44d95a6300005bb6cd4e2c8fe3c326d1b" +checksum = "bbf893f64684e58da8a68d56a5e84d1cf0440226274c515770fe267707a7d0b0" dependencies = [ "aes", - "bitflags 2.11.1", - "block-padding 0.4.2", + "bitflags 2.13.0", + "block-padding", "byteorder", "bytes", "cbc", @@ -3620,12 +3640,11 @@ dependencies = [ "enum_dispatch", "futures", "generic-array 1.4.3", - "getrandom 0.2.17", + "getrandom 0.4.2", "ghash", "hex-literal", - "hkdf", "hmac", - "inout 0.1.4", + "inout", "internal-russh-num-bigint", "keccak", "log", @@ -3711,34 +3730,13 @@ dependencies = [ "semver", ] -[[package]] -name = "rustcrypto-ff" -version = "0.14.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd2a8adb347447693cd2ba0d218c4b66c62da9b0a5672b17b981e4291ec65ff6" -dependencies = [ - "rand_core 0.10.1", - "subtle", -] - -[[package]] -name = "rustcrypto-group" -version = "0.14.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369f9b61aa45933c062c9f6b5c3c50ab710687eca83dd3802653b140b43f85ed" -dependencies = [ - "rand_core 0.10.1", - "rustcrypto-ff", - "subtle", -] - [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3751,7 +3749,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -3924,9 +3922,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64", "bs58", @@ -3944,9 +3942,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -4378,7 +4376,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.11.1", + "bitflags 2.13.0", "fancy-regex", "filedescriptor", "finl_unicode", @@ -4779,9 +4777,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" @@ -5045,7 +5043,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "hashbrown 0.15.5", "indexmap 2.14.0", "semver", @@ -5142,9 +5140,9 @@ dependencies = [ [[package]] name = "wide" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7714cd0430a663154667c74da5d09325c2387695bee18b3f7f72825aa3693a" +checksum = "dfdfe6a32973f2d1b268b8895845a8a96cac2f0191e72c27cc929036060dbf89" dependencies = [ "bytemuck", "safe_arch", @@ -5579,7 +5577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.13.0", "indexmap 2.14.0", "log", "serde", @@ -5790,10 +5788,11 @@ version = "26.5.6" dependencies = [ "anyhow", "arc-swap", - "bitflags 2.11.1", + "bitflags 2.13.0", "globset", "hashbrown 0.17.1", "indexmap 2.14.0", + "mlua", "ratatui", "regex", "serde", @@ -5855,6 +5854,7 @@ dependencies = [ "anyhow", "hashbrown 0.17.1", "indexmap 2.14.0", + "inventory", "mlua", "ordered-float 5.3.0", "parking_lot", @@ -5951,13 +5951,14 @@ version = "26.5.6" dependencies = [ "anyhow", "arc-swap", - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation-sys", "dirs", "either", "foldhash 0.2.0", "hashbrown 0.17.1", "libc", + "mlua", "objc2", "parking_lot", "percent-encoding", @@ -5991,7 +5992,7 @@ name = "yazi-parser" version = "26.5.6" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.13.0", "hashbrown 0.17.1", "mlua", "paste", @@ -6021,9 +6022,9 @@ dependencies = [ "ansi-to-tui", "anyhow", "futures", + "inventory", "libc", "mlua", - "paste", "percent-encoding", "ratatui", "serde_json", @@ -6036,6 +6037,7 @@ dependencies = [ "yazi-adapter", "yazi-binding", "yazi-boot", + "yazi-codegen", "yazi-config", "yazi-core", "yazi-dds", @@ -6104,7 +6106,7 @@ dependencies = [ "foldhash 0.2.0", "hashbrown 0.17.1", "libc", - "lru 0.18.0", + "lru", "mlua", "ordered-float 5.3.0", "parking_lot", @@ -6127,7 +6129,7 @@ dependencies = [ name = "yazi-sftp" version = "26.5.6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "parking_lot", "russh", "serde", @@ -6144,8 +6146,10 @@ dependencies = [ "foldhash 0.2.0", "futures", "hashbrown 0.17.1", + "inventory", "libc", "memchr", + "mlua", "ordered-float 5.3.0", "parking_lot", "paste", @@ -6188,11 +6192,12 @@ version = "26.5.6" dependencies = [ "anyhow", "base64", - "bitflags 2.11.1", + "bitflags 2.13.0", "futures", "parking_lot", "ratatui", "rustix 1.1.4", + "serde", "signal-hook 0.4.4", "strum 0.28.0", "thiserror 2.0.18", diff --git a/Cargo.toml b/Cargo.toml index 516ac590..1056c8b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,8 +39,8 @@ ansi-to-tui = "8.0.1" anyhow = "1.0.102" arc-swap = { version = "1.9.1", features = [ "serde" ] } base64 = "0.22.1" -bitflags = { version = "2.11.1", features = [ "serde" ] } -chrono = "0.4.44" +bitflags = { version = "2.13.0", features = [ "serde" ] } +chrono = "0.4.45" clap = { version = "4.6.1", features = [ "derive" ] } core-foundation-sys = "0.8.7" dirs = "6.0.0" @@ -51,6 +51,7 @@ futures = "0.3.32" globset = "0.4.18" hashbrown = { version = "0.17.1", features = [ "serde" ] } indexmap = { version = "2.14.0", features = [ "serde" ] } +inventory = "0.3.24" libc = "0.2.186" lru = "0.18.0" mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] } @@ -60,13 +61,13 @@ parking_lot = "0.12.5" paste = "1.0.15" percent-encoding = "2.3.2" rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] } -ratatui = { version = "0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] } +ratatui = { version = "=0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] } regex = "1.12.3" -russh = { version = "0.61.1", default-features = false, features = [ "ring", "rsa" ] } +russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] } scopeguard = "1.2.0" serde = { version = "1.0.228", features = [ "derive" ] } serde_json = "1.0.150" -serde_with = "3.20.0" +serde_with = "3.21.0" strum = { version = "0.28.0", features = [ "derive" ] } syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } thiserror = "2.0.18" diff --git a/yazi-actor/src/lives/which.rs b/yazi-actor/src/lives/which.rs index 66016cfd..e44281aa 100644 --- a/yazi-actor/src/lives/which.rs +++ b/yazi-actor/src/lives/which.rs @@ -29,7 +29,7 @@ impl UserData for Which { cached_field!(fields, tx, |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx))); fields.add_field_method_get("times", |_, me| Ok(me.inner.times)); cached_field!(fields, cands, |lua, me| { - lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::ChordCow)) + lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::keymap::ChordCow)) }); fields.add_field_method_get("active", |_, me| Ok(me.inner.active)); diff --git a/yazi-binding/src/data/any.rs b/yazi-binding/src/data/any.rs new file mode 100644 index 00000000..eb5e353d --- /dev/null +++ b/yazi-binding/src/data/any.rs @@ -0,0 +1,16 @@ +use mlua::UserData; +use yazi_codegen::FromLuaOwned; + +#[derive(FromLuaOwned)] +pub struct DataAny(Box); + +impl DataAny { + pub fn new(value: T) -> Self + where + T: yazi_shared::data::DataAny, + { + Self(Box::new(value)) + } +} + +impl UserData for DataAny {} diff --git a/yazi-binding/src/data/mod.rs b/yazi-binding/src/data/mod.rs new file mode 100644 index 00000000..83b99df1 --- /dev/null +++ b/yazi-binding/src/data/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(any); diff --git a/yazi-binding/src/event/action.rs b/yazi-binding/src/event/action.rs new file mode 100644 index 00000000..d108be34 --- /dev/null +++ b/yazi-binding/src/event/action.rs @@ -0,0 +1,19 @@ +use std::ops::Deref; + +use mlua::UserData; + +pub struct Action { + inner: yazi_shared::event::Action, +} + +impl Deref for Action { + type Target = yazi_shared::event::Action; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl Action { + pub fn new(inner: impl Into) -> Self { Self { inner: inner.into() } } +} + +impl UserData for Action {} diff --git a/yazi-binding/src/event/cmd.rs b/yazi-binding/src/event/cmd.rs new file mode 100644 index 00000000..bd46f44e --- /dev/null +++ b/yazi-binding/src/event/cmd.rs @@ -0,0 +1,19 @@ +use std::ops::Deref; + +use mlua::UserData; + +pub struct Cmd { + inner: yazi_shared::event::Cmd, +} + +impl Deref for Cmd { + type Target = yazi_shared::event::Cmd; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl Cmd { + pub fn new(inner: impl Into) -> Self { Self { inner: inner.into() } } +} + +impl UserData for Cmd {} diff --git a/yazi-binding/src/event/mod.rs b/yazi-binding/src/event/mod.rs new file mode 100644 index 00000000..62b86ac6 --- /dev/null +++ b/yazi-binding/src/event/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(action cmd); diff --git a/yazi-binding/src/keymap/chord.rs b/yazi-binding/src/keymap/chord.rs new file mode 100644 index 00000000..dc0a6b4f --- /dev/null +++ b/yazi-binding/src/keymap/chord.rs @@ -0,0 +1,80 @@ +use std::{ops::Deref, sync::Arc}; + +use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value}; + +use crate::{Id, Iter, cached_field, event::Action, keymap::Key}; + +pub struct Chord { + inner: Arc, + + v_on: Option, + v_run: Option, +} + +impl Deref for Chord { + type Target = Arc; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl From for Arc { + fn from(value: Chord) -> Self { value.inner } +} + +impl Chord { + pub fn new(inner: impl Into>) -> Self { + Self { inner: inner.into(), v_on: None, v_run: None } + } +} + +impl FromLua for Chord { + fn from_lua(value: Value, lua: &Lua) -> mlua::Result { + Ok(Self::new(lua.from_value::(value)?)) + } +} + +impl UserData for Chord { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("id", |_, me| Ok(Id(me.inner.id))); + + cached_field!(fields, on, |lua, me| lua.create_sequence_from(me.on.iter().copied().map(Key))); + + cached_field!(fields, run, |lua, me| lua + .create_sequence_from(me.run.iter().cloned().map(Action::new))); + } +} + +// --- Matcher +pub struct ChordMatcher(pub(super) yazi_config::keymap::ChordMatcher); + +impl TryFrom for ChordMatcher { + type Error = mlua::Error; + + fn try_from(value: Table) -> Result { + let id: Id = value.raw_get("id").unwrap_or_default(); + + Ok(Self(yazi_config::keymap::ChordMatcher { id: id.0, ..Default::default() })) + } +} + +impl FromLua for ChordMatcher { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + match value { + Value::Table(t) => t.try_into(), + _ => Err("expected a table of ChordMatcher".into_lua_err()), + } + } +} + +// --- ChordIter +pub struct ChordIter(pub(super) yazi_config::keymap::ChordIter); + +impl ChordIter { + pub fn new(inner: impl Into) -> Self { Self(inner.into()) } +} + +impl IntoLua for ChordIter { + fn into_lua(self, lua: &Lua) -> mlua::Result { + Iter::new(self.0.map(Chord::new), None).into_lua(lua) + } +} diff --git a/yazi-binding/src/chord_cow.rs b/yazi-binding/src/keymap/chord_cow.rs similarity index 100% rename from yazi-binding/src/chord_cow.rs rename to yazi-binding/src/keymap/chord_cow.rs diff --git a/yazi-binding/src/keymap/chords.rs b/yazi-binding/src/keymap/chords.rs new file mode 100644 index 00000000..07c160bc --- /dev/null +++ b/yazi-binding/src/keymap/chords.rs @@ -0,0 +1,53 @@ +use std::ops::Deref; + +use mlua::{ExternalError, ExternalResult, MetaMethod, UserData, UserDataMethods}; + +use super::{Chord, ChordMatcher}; +use crate::keymap::ChordIter; + +pub struct Chords { + inner: &'static yazi_config::keymap::Chords, +} + +impl Deref for Chords { + type Target = yazi_config::keymap::Chords; + + fn deref(&self) -> &Self::Target { self.inner } +} + +impl Chords { + pub fn new(inner: &'static yazi_config::keymap::Chords) -> Self { Self { inner } } +} + +impl UserData for Chords { + fn add_methods>(methods: &mut M) { + methods.add_method("match", |_, me, matcher: Option| { + Ok(match matcher { + Some(matcher) => ChordIter::new(yazi_config::keymap::ChordIter { + chords: me.as_erased(), + matcher: matcher.0, + ..Default::default() + }), + None => ChordIter::new(&*me.inner), + }) + }); + + methods.add_method("insert", |_, me, (index, chord): (isize, Chord)| { + let index = match index { + 1.. => index - 1, + 0 => return Err("index must be 1-based or negative".into_lua_err()), + _ => index, + }; + + me.insert(index, chord.clone()).into_lua_err()?; + Ok(chord) + }); + + methods.add_method("remove", |_, me, matcher: ChordMatcher| { + me.remove(matcher.0); + Ok(()) + }); + + methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.load().len())); + } +} diff --git a/yazi-binding/src/keymap/key.rs b/yazi-binding/src/keymap/key.rs new file mode 100644 index 00000000..2e35cad7 --- /dev/null +++ b/yazi-binding/src/keymap/key.rs @@ -0,0 +1,18 @@ +use std::ops::Deref; + +use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; + +use crate::SER_OPT; + +#[derive(Clone, Copy, Default, FromLua)] +pub struct Key(pub yazi_config::keymap::Key); + +impl Deref for Key { + type Target = yazi_config::keymap::Key; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl IntoLua for Key { + fn into_lua(self, lua: &Lua) -> mlua::Result { lua.to_value_with(&self.0, SER_OPT) } +} diff --git a/yazi-binding/src/keymap/mod.rs b/yazi-binding/src/keymap/mod.rs new file mode 100644 index 00000000..80857a4a --- /dev/null +++ b/yazi-binding/src/keymap/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(chord chord_cow chords key section); diff --git a/yazi-binding/src/keymap/section.rs b/yazi-binding/src/keymap/section.rs new file mode 100644 index 00000000..7f8ee4de --- /dev/null +++ b/yazi-binding/src/keymap/section.rs @@ -0,0 +1,48 @@ +use std::ops::Deref; + +use anyhow::bail; +use mlua::{UserData, UserDataFields, Value}; +use yazi_config::KEYMAP; +use yazi_shared::Layer; + +use crate::{cached_field, keymap::Chords}; + +pub struct KeymapSection { + inner: &'static yazi_config::keymap::KeymapSection, + + v_rules: Option, +} + +impl Deref for KeymapSection { + type Target = yazi_config::keymap::KeymapSection; + + fn deref(&self) -> &Self::Target { self.inner } +} + +impl TryFrom for KeymapSection { + type Error = anyhow::Error; + + fn try_from(value: Layer) -> Result { + let inner = match value { + Layer::Null | Layer::App => bail!("invalid layer"), + Layer::Mgr => KEYMAP.mgr.as_erased(), + Layer::Tasks => KEYMAP.tasks.as_erased(), + Layer::Spot => KEYMAP.spot.as_erased(), + Layer::Pick => KEYMAP.pick.as_erased(), + Layer::Input => KEYMAP.input.as_erased(), + Layer::Confirm => KEYMAP.confirm.as_erased(), + Layer::Help => KEYMAP.help.as_erased(), + Layer::Cmp => KEYMAP.cmp.as_erased(), + Layer::Which => bail!("invalid layer"), + Layer::Notify => bail!("invalid layer"), + }; + + Ok(Self { inner, v_rules: None }) + } +} + +impl UserData for KeymapSection { + fn add_fields>(fields: &mut F) { + cached_field!(fields, rules, |_, me| Ok(Chords::new(me.inner))); + } +} diff --git a/yazi-binding/src/lib.rs b/yazi-binding/src/lib.rs index 548e58aa..b6f1f48b 100644 --- a/yazi-binding/src/lib.rs +++ b/yazi-binding/src/lib.rs @@ -1,5 +1,5 @@ mod macros; -yazi_macro::mod_pub!(config elements process theme); +yazi_macro::mod_pub!(config data elements event keymap process theme); -yazi_macro::mod_flat!(access calculator cha chan chord_cow composer dnd error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector stage style tty url utils); +yazi_macro::mod_flat!(access calculator cha chan composer dnd error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector stage style tty url utils); diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index ea98433b..9b01c7c9 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -28,6 +28,7 @@ bitflags = { workspace = true } globset = { workspace = true } hashbrown = { workspace = true } indexmap = { workspace = true } +mlua = { workspace = true } ratatui = { workspace = true } regex = { workspace = true } serde = { workspace = true } diff --git a/yazi-config/src/keymap/chord.rs b/yazi-config/src/keymap/chord.rs index d99ee567..a69b18be 100644 --- a/yazi-config/src/keymap/chord.rs +++ b/yazi-config/src/keymap/chord.rs @@ -1,17 +1,19 @@ -use std::{borrow::Cow, hash::{Hash, Hasher}, sync::OnceLock}; +use std::{borrow::Cow, hash::{Hash, Hasher}, sync::{Arc, OnceLock}}; use regex::Regex; use serde::{Deserialize, Deserializer, de}; use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany}; -use yazi_shared::{Layer, Source, event::Action}; +use yazi_shared::{Id, Layer, Source, event::Action}; -use super::Key; -use crate::{Mixable, Platform}; +use super::{Key, ids::chord_id}; +use crate::{Mixable, Platform, keymap::Chords}; static RE: OnceLock = OnceLock::new(); -#[derive(Clone, Debug, Default, Deserialize)] -pub struct Chord { +#[derive(Debug, Default, Deserialize)] +pub struct Chord { + #[serde(skip, default = "chord_id")] + pub id: Id, #[serde(deserialize_with = "deserialize_on")] pub on: Vec, #[serde(deserialize_with = "deserialize_run::")] @@ -22,6 +24,18 @@ pub struct Chord { pub r#for: Platform, } +impl Clone for Chord { + fn clone(&self) -> Self { + Self { + id: chord_id(), + on: self.on.clone(), + run: self.run.clone(), + desc: self.desc.clone(), + r#for: self.r#for, + } + } +} + impl PartialEq for Chord { fn eq(&self, other: &Self) -> bool { self.on == other.on } } @@ -33,6 +47,10 @@ impl Hash for Chord { } impl Chord { + pub fn as_erased(self: &Arc) -> &Arc> { + unsafe { &*(self as *const Arc> as *const Arc>) } + } + pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() } pub fn run(&self) -> String { @@ -81,12 +99,12 @@ fn deserialize_run<'de, const L: u8, D>(deserializer: D) -> Result, where D: Deserializer<'de>, { - let mut actions: Vec = OneOrMany::::deserialize_as(deserializer)?; - let Some(layer) = Layer::from_repr(L) else { return Err(de::Error::custom(format!("invalid keymap layer const: {L}"))); }; + let mut actions: Vec = OneOrMany::::deserialize_as(deserializer)?; + for action in &mut actions { action.source = Source::Key; if action.layer == Layer::Null { @@ -96,3 +114,54 @@ where Ok(actions) } + +// --- Matcher +#[derive(Default)] +pub struct ChordMatcher { + pub id: Id, + pub all: bool, +} + +impl ChordMatcher { + pub fn matches(&self, chord: &Chord) -> bool { + if self.all { + true + } else if self.id != Id::ZERO { + chord.id == self.id + } else { + false + } + } +} + +// --- Iter +#[derive(Default)] +pub struct ChordIter { + pub chords: Arc>>, + pub matcher: ChordMatcher, + pub offset: usize, +} + +impl From<&Chords> for ChordIter { + fn from(chords: &Chords) -> Self { + Self { + chords: chords.load_full(), + matcher: ChordMatcher { all: true, ..Default::default() }, + ..Default::default() + } + } +} + +impl Iterator for ChordIter { + type Item = Arc; + + fn next(&mut self) -> Option { + while let Some(chord) = self.chords.get(self.offset) { + self.offset += 1; + if self.matcher.matches(chord) { + return Some(chord.clone()); + } + } + None + } +} diff --git a/yazi-config/src/keymap/chords.rs b/yazi-config/src/keymap/chords.rs new file mode 100644 index 00000000..7f6f0807 --- /dev/null +++ b/yazi-config/src/keymap/chords.rs @@ -0,0 +1,57 @@ +use std::{ops::Deref, sync::Arc}; + +use arc_swap::ArcSwap; +use serde::Deserialize; +use yazi_shared::Layer; +use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, vec::{IndexAtError, VecExt}}; + +use super::Chord; +use crate::keymap::ChordMatcher; + +#[derive(Debug, Default, Deserialize)] +pub struct Chords(ArcSwap>>>); + +impl Deref for Chords { + type Target = ArcSwap>>>; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl From>>> for Chords { + fn from(inner: Vec>>) -> Self { Self(inner.into_pointee()) } +} + +impl Chords { + pub fn as_erased(&self) -> Arc>>> { + let chords = self.0.load_full(); + unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec>>) } + } + + pub fn insert(&self, index: isize, rule: Arc) -> Result<(), IndexAtError> { + self.0.try_rcu(|rules| { + let (before, after) = rules.split_at(rules.index_at(index)?); + Ok( + before + .iter() + .cloned() + .chain([rule.as_erased().clone()]) + .chain(after.iter().cloned()) + .collect::>(), + ) + })?; + + Ok(()) + } + + pub fn remove(&self, matcher: ChordMatcher) { + self.0.rcu(|chords| { + let mut next = Vec::clone(chords); + next.retain(|chord| !matcher.matches(chord.as_erased())); + next + }); + } + + pub(crate) fn unwrap_unchecked(self) -> Vec>> { + Arc::try_unwrap(self.0.into_inner()).expect("unique chords arc") + } +} diff --git a/yazi-config/src/keymap/cow.rs b/yazi-config/src/keymap/cow.rs index 472405d7..cf9a2d1c 100644 --- a/yazi-config/src/keymap/cow.rs +++ b/yazi-config/src/keymap/cow.rs @@ -1,21 +1,25 @@ -use std::ops::Deref; +use std::{ops::Deref, sync::Arc}; use yazi_shared::{Layer, event::ActionCow}; use super::Chord; #[derive(Clone, Debug)] -pub enum ChordCow { +pub enum ChordCow { Owned(Chord), - Borrowed(&'static Chord), + Borrowed(Arc>), } impl From> for ChordCow { fn from(c: Chord) -> Self { Self::Owned(c) } } -impl From<&'static Chord> for ChordCow { - fn from(c: &'static Chord) -> Self { Self::Borrowed(c) } +impl From>> for ChordCow { + fn from(c: Arc>) -> Self { Self::Borrowed(c) } +} + +impl From<&Arc>> for ChordCow { + fn from(c: &Arc>) -> Self { Self::Borrowed(c.clone()) } } impl Deref for ChordCow { @@ -37,7 +41,7 @@ impl ChordCow { pub fn into_seq(self) -> Vec { match self { Self::Owned(c) => c.run.into_iter().rev().map(Into::into).collect(), - Self::Borrowed(c) => c.run.iter().rev().map(Into::into).collect(), + Self::Borrowed(c) => c.run.iter().rev().cloned().map(Into::into).collect(), } } } diff --git a/yazi-config/src/keymap/ids.rs b/yazi-config/src/keymap/ids.rs new file mode 100644 index 00000000..82149e34 --- /dev/null +++ b/yazi-config/src/keymap/ids.rs @@ -0,0 +1,6 @@ +use yazi_shared::{Id, Ids}; + +pub fn chord_id() -> Id { + static IDS: Ids = Ids::new(); + IDS.next() +} diff --git a/yazi-config/src/keymap/key.rs b/yazi-config/src/keymap/key.rs index 4cd39b47..f36f2a2c 100644 --- a/yazi-config/src/keymap/key.rs +++ b/yazi-config/src/keymap/key.rs @@ -1,14 +1,17 @@ use std::{fmt::{Display, Write}, str::FromStr}; use anyhow::bail; +use serde::{Deserialize, Serialize}; use yazi_term::event::{KeyCode, KeyEvent, Modifiers}; -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct Key { + #[serde(flatten)] pub code: KeyCode, pub shift: bool, pub ctrl: bool, pub alt: bool, + #[serde(rename = "super")] pub super_: bool, } diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index 98bdca94..36712efe 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -1,37 +1,39 @@ +use std::{ops::Deref, sync::Arc}; + use anyhow::{Context, Result}; use serde::Deserialize; use yazi_codegen::{DeserializeOver, DeserializeOver1}; use yazi_fs::{Xdg, ok_or_not_found}; use yazi_shared::Layer; -use super::{Chord, KeymapRules}; +use super::{Chord, KeymapSection}; #[derive(Deserialize, DeserializeOver, DeserializeOver1)] pub struct Keymap { - pub mgr: KeymapRules<{ Layer::Mgr as u8 }>, - pub tasks: KeymapRules<{ Layer::Tasks as u8 }>, - pub spot: KeymapRules<{ Layer::Spot as u8 }>, - pub pick: KeymapRules<{ Layer::Pick as u8 }>, - pub input: KeymapRules<{ Layer::Input as u8 }>, - pub confirm: KeymapRules<{ Layer::Confirm as u8 }>, - pub help: KeymapRules<{ Layer::Help as u8 }>, - pub cmp: KeymapRules<{ Layer::Cmp as u8 }>, + pub mgr: KeymapSection<{ Layer::Mgr as u8 }>, + pub tasks: KeymapSection<{ Layer::Tasks as u8 }>, + pub spot: KeymapSection<{ Layer::Spot as u8 }>, + pub pick: KeymapSection<{ Layer::Pick as u8 }>, + pub input: KeymapSection<{ Layer::Input as u8 }>, + pub confirm: KeymapSection<{ Layer::Confirm as u8 }>, + pub help: KeymapSection<{ Layer::Help as u8 }>, + pub cmp: KeymapSection<{ Layer::Cmp as u8 }>, } impl Keymap { - pub fn get(&self, layer: Layer) -> &[Chord] { + pub fn get(&self, layer: Layer) -> Arc>> { match layer { - Layer::Null | Layer::App => &[], - Layer::Mgr => self.mgr.as_erased_slice(), - Layer::Tasks => self.tasks.as_erased_slice(), - Layer::Spot => self.spot.as_erased_slice(), - Layer::Pick => self.pick.as_erased_slice(), - Layer::Input => self.input.as_erased_slice(), - Layer::Confirm => self.confirm.as_erased_slice(), - Layer::Help => self.help.as_erased_slice(), - Layer::Cmp => self.cmp.as_erased_slice(), - Layer::Which => &[], - Layer::Notify => &[], + Layer::Null | Layer::App => Arc::new(Vec::new()), + Layer::Mgr => self.mgr.deref().as_erased(), + Layer::Tasks => self.tasks.deref().as_erased(), + Layer::Spot => self.spot.deref().as_erased(), + Layer::Pick => self.pick.deref().as_erased(), + Layer::Input => self.input.deref().as_erased(), + Layer::Confirm => self.confirm.deref().as_erased(), + Layer::Help => self.help.deref().as_erased(), + Layer::Cmp => self.cmp.deref().as_erased(), + Layer::Which => Arc::new(Vec::new()), + Layer::Notify => Arc::new(Vec::new()), } } } diff --git a/yazi-config/src/keymap/mod.rs b/yazi-config/src/keymap/mod.rs index ddc3039a..b608ba6a 100644 --- a/yazi-config/src/keymap/mod.rs +++ b/yazi-config/src/keymap/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(chord cow key keymap rules); +yazi_macro::mod_flat!(chord chords cow ids key keymap section); diff --git a/yazi-config/src/keymap/rules.rs b/yazi-config/src/keymap/rules.rs deleted file mode 100644 index b3276bf1..00000000 --- a/yazi-config/src/keymap/rules.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::ops::Deref; - -use hashbrown::HashSet; -use serde::Deserialize; -use yazi_codegen::DeserializeOver2; -use yazi_shim::toml::DeserializeOverHook; - -use super::{Chord, Key}; -use crate::mix; - -#[derive(Default, Deserialize, DeserializeOver2)] -pub struct KeymapRules { - keymap: Vec>, - #[serde(default)] - prepend_keymap: Vec>, - #[serde(default)] - append_keymap: Vec>, -} - -impl Deref for KeymapRules { - type Target = [Chord]; - - fn deref(&self) -> &Self::Target { self.as_erased_slice() } -} - -impl KeymapRules { - pub(super) fn as_erased_slice(&self) -> &[Chord] { - // Safety: `Chord` only changes deserialization behavior; the const parameter - // does not participate in layout, so a shared slice can be reinterpreted as - // the default `Chord` view. - unsafe { &*(self.keymap.as_slice() as *const [Chord] as *const [Chord]) } - } -} - -impl DeserializeOverHook for KeymapRules { - fn deserialize_over_hook(self) -> Result { - #[inline] - fn on(Chord { on, .. }: &Chord) -> [Key; 2] { - [on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()] - } - - let a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect(); - let b_seen: HashSet<_> = self.keymap.iter().map(on).collect(); - - let keymap = mix( - self.prepend_keymap, - self.keymap.into_iter().filter(|v| !a_seen.contains(&on(v))), - self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))), - ); - - Ok(Self { keymap, ..Default::default() }) - } -} diff --git a/yazi-config/src/keymap/section.rs b/yazi-config/src/keymap/section.rs new file mode 100644 index 00000000..42df0036 --- /dev/null +++ b/yazi-config/src/keymap/section.rs @@ -0,0 +1,52 @@ +use std::{ops::Deref, sync::Arc}; + +use hashbrown::HashSet; +use serde::Deserialize; +use yazi_codegen::DeserializeOver2; +use yazi_shared::Layer; +use yazi_shim::toml::DeserializeOverHook; + +use super::{Chord, Chords, Key}; +use crate::mix; + +#[derive(Default, Deserialize, DeserializeOver2)] +pub struct KeymapSection { + keymap: Chords, + #[serde(default)] + prepend_keymap: Vec>, + #[serde(default)] + append_keymap: Vec>, +} + +impl Deref for KeymapSection { + type Target = Chords; + + fn deref(&self) -> &Self::Target { &self.keymap } +} + +impl KeymapSection { + pub fn as_erased(&self) -> &KeymapSection { + unsafe { &*(self as *const Self as *const KeymapSection) } + } +} + +impl DeserializeOverHook for KeymapSection { + fn deserialize_over_hook(self) -> Result { + #[inline] + fn on(Chord { on, .. }: &Chord) -> [Key; 2] { + [on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()] + } + + let keymap = self.keymap.unwrap_unchecked(); + let a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect(); + let b_seen: HashSet<_> = keymap.iter().map(|c| on(c)).collect(); + + let keymap: Vec>> = mix( + self.prepend_keymap, + keymap.into_iter().filter(|v| !a_seen.contains(&on(v))), + self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))), + ); + + Ok(Self { keymap: keymap.into(), ..Default::default() }) + } +} diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 0ee60811..dfa642d6 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -1,4 +1,4 @@ -yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which vfs); +yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme vfs which); yazi_macro::mod_flat!(icon layout mixing pattern platform preset priority selectable selector style utils yazi); diff --git a/yazi-config/src/popup/options.rs b/yazi-config/src/popup/options.rs index 6170bfd9..9dcfb7ee 100644 --- a/yazi-config/src/popup/options.rs +++ b/yazi-config/src/popup/options.rs @@ -1,9 +1,11 @@ use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}}; +use yazi_macro::impl_data_any; use yazi_shared::{scheme::Encode as EncodeScheme, strand::ToStrand, url::{Url, UrlBuf}}; use super::{Offset, Position}; use crate::{YAZI, popup::Origin}; +// --- InputCfg #[derive(Clone, Debug, Default)] pub struct InputCfg { pub title: String, @@ -15,20 +17,7 @@ pub struct InputCfg { pub completion: bool, } -#[derive(Clone, Debug, Default)] -pub struct PickCfg { - pub title: String, - pub items: Vec, - pub position: Position, -} - -#[derive(Clone, Debug, Default)] -pub struct ConfirmCfg { - pub position: Position, - pub title: Line<'static>, - pub body: Paragraph<'static>, - pub list: Paragraph<'static>, -} +impl_data_any!(InputCfg); impl InputCfg { pub fn cd(cwd: Url) -> Self { @@ -117,6 +106,45 @@ impl InputCfg { } } +// --- PickCfg +#[derive(Clone, Debug, Default)] +pub struct PickCfg { + pub title: String, + pub items: Vec, + pub position: Position, +} + +impl_data_any!(PickCfg); + +impl PickCfg { + fn max_height(len: usize) -> u16 { + YAZI.pick.open_offset.height.min(YAZI.pick.border().saturating_add(len as u16)) + } + + pub fn open(items: Vec) -> Self { + let max_height = Self::max_height(items.len()); + Self { + title: YAZI.pick.open_title.clone(), + items, + position: Position::new(YAZI.pick.open_origin, Offset { + height: max_height, + ..YAZI.pick.open_offset + }), + } + } +} + +// --- ConfirmCfg +#[derive(Clone, Debug, Default)] +pub struct ConfirmCfg { + pub position: Position, + pub title: Line<'static>, + pub body: Paragraph<'static>, + pub list: Paragraph<'static>, +} + +impl_data_any!(ConfirmCfg); + impl ConfirmCfg { fn new( title: String, @@ -188,21 +216,3 @@ impl ConfirmCfg { Some(Text::from_iter(lines)) } } - -impl PickCfg { - fn max_height(len: usize) -> u16 { - YAZI.pick.open_offset.height.min(YAZI.pick.border().saturating_add(len as u16)) - } - - pub fn open(items: Vec) -> Self { - let max_height = Self::max_height(items.len()); - Self { - title: YAZI.pick.open_title.clone(), - items, - position: Position::new(YAZI.pick.open_origin, Offset { - height: max_height, - ..YAZI.pick.open_offset - }), - } - } -} diff --git a/yazi-core/src/app/plugin.rs b/yazi-core/src/app/plugin.rs index 3677cec4..c943f0b1 100644 --- a/yazi-core/src/app/plugin.rs +++ b/yazi-core/src/app/plugin.rs @@ -6,9 +6,9 @@ use hashbrown::HashMap; use mlua::{Lua, Table}; use serde::Deserialize; use strum::EnumString; +use yazi_macro::impl_data_any; use yazi_scheduler::plugin::PluginInEntry; use yazi_shared::{SStr, data::{Data, DataKey}, event::{ActionCow, Cmd}}; - #[derive(Clone, Debug, Default)] pub struct PluginOpt { pub id: SStr, @@ -17,6 +17,8 @@ pub struct PluginOpt { pub callback: Option>, } +impl_data_any!(PluginOpt); + impl TryFrom for PluginOpt { type Error = anyhow::Error; diff --git a/yazi-core/src/app/quit.rs b/yazi-core/src/app/quit.rs index 3d79209b..c977f3c1 100644 --- a/yazi-core/src/app/quit.rs +++ b/yazi-core/src/app/quit.rs @@ -1,6 +1,7 @@ use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use yazi_binding::SER_OPT; +use yazi_macro::impl_data_any; use yazi_shared::{event::ActionCow, strand::StrandBuf}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] @@ -13,6 +14,8 @@ pub struct QuitOpt { pub no_cwd_file: bool, } +impl_data_any!(QuitOpt); + impl TryFrom for QuitOpt { type Error = anyhow::Error; diff --git a/yazi-core/src/cmp/option.rs b/yazi-core/src/cmp/option.rs index 9e1543c2..401c7ab5 100644 --- a/yazi-core/src/cmp/option.rs +++ b/yazi-core/src/cmp/option.rs @@ -1,3 +1,4 @@ +use yazi_macro::impl_data_any; use yazi_shared::{Id, path::PathBufDyn, url::UrlBuf}; use crate::cmp::CmpItem; @@ -9,3 +10,5 @@ pub struct CmpOpt { pub word: PathBufDyn, pub ticket: Id, } + +impl_data_any!(CmpOpt); diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index a6ef0c9a..a59aa23d 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use anyhow::Result; use unicode_width::UnicodeWidthStr; use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}}; @@ -12,7 +14,7 @@ use crate::help::HELP_MARGIN; pub struct Help { pub visible: bool, pub layer: Layer, - pub(super) bindings: Vec<&'static Chord>, + pub(super) bindings: Vec>, // Filter pub keyword: String, @@ -51,10 +53,10 @@ impl Help { if kw.is_empty() { self.keyword = String::new(); - self.bindings = KEYMAP.get(self.layer).iter().collect(); + self.bindings = KEYMAP.get(self.layer).iter().cloned().collect(); } else if self.keyword != kw { self.keyword = kw.to_owned(); - self.bindings = KEYMAP.get(self.layer).iter().filter(|&c| c.contains(kw)).collect(); + self.bindings = KEYMAP.get(self.layer).iter().filter(|&c| c.contains(kw)).cloned().collect(); } render!(self.scroll(0)); @@ -73,7 +75,7 @@ impl Help { } // --- Bindings - pub fn window(&self) -> &[&Chord] { + pub fn window(&self) -> &[Arc] { let end = (self.offset + self.limit()).min(self.bindings.len()); &self.bindings[self.offset..end] } diff --git a/yazi-core/src/mgr/displace.rs b/yazi-core/src/mgr/displace.rs index 11d63448..bdc38148 100644 --- a/yazi-core/src/mgr/displace.rs +++ b/yazi-core/src/mgr/displace.rs @@ -1,3 +1,4 @@ +use yazi_macro::impl_data_any; use yazi_shared::url::UrlBuf; #[derive(Clone, Debug)] @@ -5,3 +6,5 @@ pub struct DisplaceOpt { pub to: Result, pub from: UrlBuf, } + +impl_data_any!(DisplaceOpt); diff --git a/yazi-core/src/mgr/filter.rs b/yazi-core/src/mgr/filter.rs index f7fdc413..448b9f07 100644 --- a/yazi-core/src/mgr/filter.rs +++ b/yazi-core/src/mgr/filter.rs @@ -1,4 +1,5 @@ use yazi_fs::FilterCase; +use yazi_macro::impl_data_any; use yazi_shared::{SStr, event::ActionCow}; #[derive(Clone, Debug, Default)] @@ -8,6 +9,8 @@ pub struct FilterOpt { pub done: bool, } +impl_data_any!(FilterOpt); + impl TryFrom for FilterOpt { type Error = anyhow::Error; diff --git a/yazi-core/src/mgr/find.rs b/yazi-core/src/mgr/find.rs index 18caf255..f9d0561a 100644 --- a/yazi-core/src/mgr/find.rs +++ b/yazi-core/src/mgr/find.rs @@ -1,5 +1,6 @@ use anyhow::bail; use yazi_fs::FilterCase; +use yazi_macro::impl_data_any; use yazi_shared::{SStr, event::ActionCow}; #[derive(Clone, Debug)] @@ -9,6 +10,8 @@ pub struct FindDoOpt { pub case: FilterCase, } +impl_data_any!(FindDoOpt); + impl TryFrom for FindDoOpt { type Error = anyhow::Error; diff --git a/yazi-core/src/mgr/open.rs b/yazi-core/src/mgr/open.rs index ca6c5a89..f0b1e1f1 100644 --- a/yazi-core/src/mgr/open.rs +++ b/yazi-core/src/mgr/open.rs @@ -1,6 +1,8 @@ use yazi_fs::File; +use yazi_macro::impl_data_any; use yazi_shared::{event::ActionCow, url::UrlBuf}; +// --- OpenOpt #[derive(Clone, Debug)] pub struct OpenOpt { pub cwd: Option, @@ -9,6 +11,8 @@ pub struct OpenOpt { pub hovered: bool, } +impl_data_any!(OpenOpt); + impl TryFrom for OpenOpt { type Error = anyhow::Error; @@ -22,10 +26,12 @@ impl TryFrom for OpenOpt { } } -// OpenDoOpt +// --- OpenDoOpt #[derive(Clone, Debug, Default)] pub struct OpenDoOpt { pub cwd: UrlBuf, pub targets: Vec, pub interactive: bool, } + +impl_data_any!(OpenDoOpt); diff --git a/yazi-core/src/mgr/search.rs b/yazi-core/src/mgr/search.rs index 35f11b69..6be41d22 100644 --- a/yazi-core/src/mgr/search.rs +++ b/yazi-core/src/mgr/search.rs @@ -1,8 +1,10 @@ use anyhow::bail; use serde::Deserialize; use strum::{EnumString, IntoStaticStr}; +use yazi_macro::impl_data_any; use yazi_shared::{SStr, event::ActionCow, url::{UrlBuf, UrlLike}}; +// --- SearchOpt #[derive(Clone, Debug)] pub struct SearchOpt { pub via: SearchVia, @@ -12,6 +14,8 @@ pub struct SearchOpt { pub r#in: Option, } +impl_data_any!(SearchOpt); + impl TryFrom for SearchOpt { type Error = anyhow::Error; @@ -37,7 +41,7 @@ impl TryFrom for SearchOpt { } } -// Via +// --- Via #[derive(Clone, Copy, Debug, Deserialize, EnumString, Eq, IntoStaticStr, PartialEq)] #[serde(rename_all = "kebab-case")] #[strum(serialize_all = "kebab-case")] diff --git a/yazi-core/src/notify/option.rs b/yazi-core/src/notify/option.rs index 0bc33b1b..aa836c9f 100644 --- a/yazi-core/src/notify/option.rs +++ b/yazi-core/src/notify/option.rs @@ -4,6 +4,7 @@ use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use serde_with::{DurationSecondsWithFrac, serde_as}; use yazi_binding::SER_OPT; +use yazi_macro::impl_data_any; use yazi_shared::event::ActionCow; use crate::notify::MessageLevel; @@ -21,6 +22,8 @@ pub struct MessageOpt { pub timeout: Duration, } +impl_data_any!(MessageOpt); + impl TryFrom for MessageOpt { type Error = anyhow::Error; diff --git a/yazi-core/src/spot/lock.rs b/yazi-core/src/spot/lock.rs index 86c84c20..eb221c2d 100644 --- a/yazi-core/src/spot/lock.rs +++ b/yazi-core/src/spot/lock.rs @@ -1,5 +1,6 @@ use mlua::Table; use yazi_binding::{FileRef, elements::Renderable}; +use yazi_macro::impl_data_any; use yazi_shared::Id; #[derive(Clone, Debug)] @@ -13,6 +14,8 @@ pub struct SpotLock { pub data: Vec, } +impl_data_any!(SpotLock); + impl TryFrom
for SpotLock { type Error = mlua::Error; diff --git a/yazi-core/src/tab/preview_lock.rs b/yazi-core/src/tab/preview_lock.rs index b643ceae..8a23cba3 100644 --- a/yazi-core/src/tab/preview_lock.rs +++ b/yazi-core/src/tab/preview_lock.rs @@ -1,5 +1,6 @@ use mlua::Table; use yazi_binding::{FileRef, elements::{Rect, Renderable}}; +use yazi_macro::impl_data_any; use yazi_shared::pool::{InternStr, Symbol}; #[derive(Clone, Debug, Default)] @@ -13,6 +14,8 @@ pub struct PreviewLock { pub data: Vec, } +impl_data_any!(PreviewLock); + impl TryFrom
for PreviewLock { type Error = mlua::Error; diff --git a/yazi-core/src/tasks/option.rs b/yazi-core/src/tasks/option.rs index fe861cd7..67d6c4ea 100644 --- a/yazi-core/src/tasks/option.rs +++ b/yazi-core/src/tasks/option.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use yazi_macro::impl_data_any; use yazi_scheduler::{TaskIn, file::FileInCut, plugin::PluginInEntry}; use yazi_shared::{Id, SStr}; @@ -10,6 +11,8 @@ pub enum TaskOpt { Plugin(PluginInEntry), } +impl_data_any!(TaskOpt); + impl TaskIn for TaskOpt { type Prog = (); diff --git a/yazi-core/src/which/option.rs b/yazi-core/src/which/option.rs index 4a95f83e..b6a5c53a 100644 --- a/yazi-core/src/which/option.rs +++ b/yazi-core/src/which/option.rs @@ -1,16 +1,19 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value}; use tokio::sync::mpsc; use yazi_config::{KEYMAP, keymap::{ChordCow, Key}}; +use yazi_macro::impl_data_any; use yazi_shared::{Layer, event::ActionCow}; #[derive(Clone, Debug)] pub struct WhichOpt { - pub tx: Option>>, + pub tx: Option>>, pub cands: Vec, pub silent: bool, pub times: usize, } +impl_data_any!(WhichOpt); + impl TryFrom for WhichOpt { type Error = anyhow::Error; @@ -21,7 +24,7 @@ impl TryFrom for WhichOpt { Ok(Self { tx: a.take_any2("tx").transpose()?, - cands: a.take_any_iter::().map(Into::into).collect(), + cands: a.take_any_iter::().map(Into::into).collect(), silent: a.bool("silent"), times: a.get("times").unwrap_or(0), }) @@ -35,7 +38,7 @@ impl From<(Layer, Key)> for WhichOpt { cands: KEYMAP .get(layer) .iter() - .filter(|c| c.on.len() > 1 && c.on[0] == key) + .filter(|&c| c.on.len() > 1 && c.on[0] == key) .map(Into::into) .collect(), times: 1, @@ -54,7 +57,7 @@ impl FromLua for WhichOpt { tx: t.raw_get::>("tx").ok().map(|t| t.0), cands: t .raw_get::
("cands")? - .sequence_values::() + .sequence_values::() .map(|c| c.map(Into::into)) .collect::>>()?, times: t.raw_get("times").unwrap_or_default(), @@ -69,7 +72,7 @@ impl IntoLua for WhichOpt { lua .create_table_from([ ("tx", self.tx.map(yazi_binding::MpscUnboundedTx).into_lua(lua)?), - ("cands", lua.create_sequence_from(self.cands.into_iter().map(yazi_binding::ChordCow))?.into_lua(lua)?), + ("cands", lua.create_sequence_from(self.cands.into_iter().map(yazi_binding::keymap::ChordCow))?.into_lua(lua)?), ("times", self.times.into_lua(lua)?), ("silent", self.silent.into_lua(lua)?), ])? diff --git a/yazi-core/src/which/which.rs b/yazi-core/src/which/which.rs index 3c50a3b4..6b772761 100644 --- a/yazi-core/src/which/which.rs +++ b/yazi-core/src/which/which.rs @@ -4,7 +4,7 @@ use yazi_macro::{emit, render_and}; #[derive(Default)] pub struct Which { - pub tx: Option>>, + pub tx: Option>>, pub times: usize, pub cands: Vec, diff --git a/yazi-dds/Cargo.toml b/yazi-dds/Cargo.toml index 5ee95c22..ad770cab 100644 --- a/yazi-dds/Cargo.toml +++ b/yazi-dds/Cargo.toml @@ -29,6 +29,7 @@ yazi-version = { path = "../yazi-version", version = "26.5.6" } anyhow = { workspace = true } hashbrown = { workspace = true } indexmap = { workspace = true } +inventory = { workspace = true } mlua = { workspace = true } ordered-float = { workspace = true } parking_lot = { workspace = true } diff --git a/yazi-dds/src/ember/yank.rs b/yazi-dds/src/ember/yank.rs index a22eb4f1..a9e6bbfc 100644 --- a/yazi-dds/src/ember/yank.rs +++ b/yazi-dds/src/ember/yank.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; use indexmap::IndexSet; -use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods, Value}; +use mlua::{AnyUserData, FromLua, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods, Value}; use serde::{Deserialize, Serialize}; use yazi_binding::get_metatable; +use yazi_macro::impl_data_any; use yazi_shared::url::UrlBufCov; use super::Ember; @@ -19,6 +20,8 @@ pub struct EmberYank<'a> { pub urls: Cow<'a, IndexSet>, } +impl_data_any!(EmberYank<'static>, from_into_lua = inherit); + impl<'a> EmberYank<'a> { pub fn borrowed(cut: bool, urls: &'a IndexSet) -> Ember<'a> { Self { cut, urls: Cow::Borrowed(urls) }.into() @@ -35,6 +38,19 @@ impl<'a> From> for Ember<'a> { fn from(value: EmberYank<'a>) -> Self { Self::Yank(value) } } +impl FromLua for EmberYank<'static> { + fn from_lua(value: Value, lua: &Lua) -> mlua::Result { + match value { + Value::UserData(ud) => ud.take::()?.collect(lua), + _ => Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "EmberYank".to_owned(), + message: Some("expected EmberYankIter userdata".to_owned()), + }), + } + } +} + impl IntoLua for EmberYank<'_> { fn into_lua(self, lua: &Lua) -> mlua::Result { let len = self.urls.len(); diff --git a/yazi-dds/src/payload.rs b/yazi-dds/src/payload.rs index 5d3ffe77..cb2983ad 100644 --- a/yazi-dds/src/payload.rs +++ b/yazi-dds/src/payload.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, io::Write, str::FromStr}; use anyhow::{Result, anyhow}; use mlua::{IntoLua, Lua, Value}; use yazi_boot::BOOT; -use yazi_macro::{emit, relay}; +use yazi_macro::{emit, impl_data_any, relay}; use yazi_shared::{Id, event::ActionCow}; use crate::{ID, ember::Ember}; @@ -15,6 +15,8 @@ pub struct Payload<'a> { pub body: Ember<'a>, } +impl_data_any!(Payload<'static>); + impl<'a> Payload<'a> { pub fn new(body: Ember<'a>) -> Self { Self { receiver: Id::ZERO, sender: *ID, body } } diff --git a/yazi-dds/src/sendable.rs b/yazi-dds/src/sendable.rs index 860213e7..93d8f6dc 100644 --- a/yazi-dds/src/sendable.rs +++ b/yazi-dds/src/sendable.rs @@ -3,26 +3,23 @@ use std::{any::TypeId, borrow::Cow}; use hashbrown::HashMap; use mlua::{ExternalError, IntoLua, Lua, MultiValue, Table, Value}; use ordered_float::OrderedFloat; -use yazi_shared::{data::{Data, DataKey}, replace_cow}; - -use crate::ember; +use yazi_shared::{any_data::AnyData, data::{Data, DataInventory, DataKey}, replace_cow}; pub struct Sendable; impl Sendable { pub fn value_to_data(lua: &Lua, value: Value) -> mlua::Result { - Ok(match value { - Value::Nil => Data::Nil, - Value::Boolean(b) => Data::Boolean(b), - Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?, - Value::Integer(i) => Data::Integer(i), - Value::Number(n) => Data::Number(n), + match &value { + Value::Nil => return Ok(Data::Nil), + Value::Boolean(b) => return Ok(Data::Boolean(*b)), + Value::Integer(i) => return Ok(Data::Integer(*i)), + Value::Number(n) => return Ok(Data::Number(*n)), Value::String(b) => { - if let Ok(s) = b.to_str() { + return Ok(if let Ok(s) = b.to_str() { Data::String(s.to_owned().into()) } else { Data::Bytes(b.as_bytes().to_owned()) - } + }); } Value::Table(t) => { let (mut i, mut map) = (1, HashMap::with_capacity(t.raw_len())); @@ -36,38 +33,40 @@ impl Sendable { map.insert(k, Self::value_to_data(lua, v)?); } - if map.len() == i as usize - 1 { + return Ok(if map.len() == i as usize - 1 { Data::List((1..i).map(|i| map.remove(&DataKey::Integer(i)).unwrap()).collect()) } else { Data::Dict(map) - } + }); } - Value::Function(_) => Err("function is not supported".into_lua_err())?, - Value::Thread(_) => Err("thread is not supported".into_lua_err())?, Value::UserData(ud) => match ud.type_id() { Some(t) if t == TypeId::of::() => { - Data::Url(ud.take::()?.into()) + return Ok(Data::Url(ud.take::()?.into())); } Some(t) if t == TypeId::of::() => { - Data::Path(ud.take::()?.into()) + return Ok(Data::Path(ud.take::()?.into())); } Some(t) if t == TypeId::of::() => { - Data::Id(**ud.borrow::()?) + return Ok(Data::Id(**ud.borrow::()?)); } - Some(t) if t == TypeId::of::() => { - Data::Any(Box::new(ud.take::()?)) - } - Some(t) if t == TypeId::of::() => { - Data::Any(Box::new(ud.take::()?.collect(lua)?)) - } - Some(t) if t == TypeId::of::() => { - Data::Any(Box::new(ud.take::()?)) - } - _ => Err(format!("unsupported userdata included: {ud:?}").into_lua_err())?, + Some(t) if t == TypeId::of::() => return Ok(Data::Any(ud.take::()?.0)), + _ => {} }, - Value::Error(_) => Err("error is not supported".into_lua_err())?, - Value::Other(..) => Err("unknown data is not supported".into_lua_err())?, - }) + Value::LightUserData(_) => {} + Value::Function(_) => {} + Value::Thread(_) => {} + Value::Error(_) => {} + Value::Other(_) => {} + } + + for inv in inventory::iter:: { + match (inv.from_lua)(value.clone(), lua) { + Ok(data) => return Ok(Data::Any(data)), + Err(mlua::Error::UserDataTypeMismatch) => continue, + Err(e) => return Err(e), + } + } + Err(format!("unsupported value included: {value:?}").into_lua_err())? } pub fn data_to_value(lua: &Lua, data: Data) -> mlua::Result { @@ -91,23 +90,7 @@ impl Sendable { Data::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?, Data::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?, Data::Bytes(b) => Value::String(lua.create_external_string(b)?), - Data::Any(b) => { - let mut b = b.into_any(); - macro_rules! try_cast { - ($f:expr) => { - match b.downcast() { - Ok(v) => return $f(*v)?.into_lua(lua), - #[allow(unused_assignments)] - Err(e) => b = e, - } - }; - } - - try_cast!(|v: yazi_fs::FilesOp| lua.create_any_userdata(v)); - try_cast!(|v: ember::EmberYank| v.into_lua(lua)); - try_cast!(|v: yazi_binding::ChordCow| v.into_lua(lua)); - Err("unsupported DataAny included".into_lua_err())? - } + Data::Any(a) => a.into_lua(lua)?, _ => Self::data_to_value_ref(lua, &data)?, }) } @@ -138,20 +121,7 @@ impl Sendable { Data::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?, Data::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?, Data::Bytes(b) => Value::String(lua.create_string(b)?), - Data::Any(b) => { - macro_rules! try_cast { - ($f:expr) => { - if let Some(v) = b.as_any().downcast_ref() { - return $f(Clone::clone(v))?.into_lua(lua); - } - }; - } - - try_cast!(|v: yazi_fs::FilesOp| lua.create_any_userdata(v)); - try_cast!(|v: ember::EmberYank| v.into_lua(lua)); - try_cast!(|v: yazi_binding::ChordCow| v.into_lua(lua)); - Err("unsupported DataAny included".into_lua_err())? - } + Data::Any(a) => a.to_lua(lua)?, }) } diff --git a/yazi-fm/src/help/help.rs b/yazi-fm/src/help/help.rs index 70f10fb5..b63c3756 100644 --- a/yazi-fm/src/help/help.rs +++ b/yazi-fm/src/help/help.rs @@ -12,7 +12,7 @@ impl<'a> Help<'a> { pub fn new(core: &'a Core) -> Self { Self { core } } fn tips() -> String { - match KEYMAP.help.iter().find(|&c| c.run.iter().any(|c| c.name == "filter")) { + match KEYMAP.help.load().iter().find(|&c| c.run.iter().any(|a| a.name == "filter")) { Some(c) => format!(" (Press `{}` to filter)", c.on()), None => String::new(), } diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index 35cbd55f..5b0b0d48 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -36,7 +36,8 @@ impl<'a> Router<'a> { } fn matches(&mut self, layer: Layer, key: Key) -> bool { - for chord @ Chord { on, .. } in KEYMAP.get(layer) { + for chord in &*KEYMAP.get(layer) { + let Chord { on, .. } = &**chord; if on.is_empty() || on[0] != key { continue; } diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index aa42cf51..1746f8d4 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -27,6 +27,7 @@ either = { workspace = true } foldhash = { workspace = true } hashbrown = { workspace = true } libc = { workspace = true } +mlua = { workspace = true } parking_lot = { workspace = true } percent-encoding = { workspace = true } rand = { workspace = true } diff --git a/yazi-fs/src/op.rs b/yazi-fs/src/op.rs index ad4eb92e..ae648d81 100644 --- a/yazi-fs/src/op.rs +++ b/yazi-fs/src/op.rs @@ -1,7 +1,7 @@ use std::path::Path; use hashbrown::{HashMap, HashSet}; -use yazi_macro::relay; +use yazi_macro::{impl_data_any, relay}; use yazi_shared::{Id, Ids, path::PathBufDyn, url::{UrlBuf, UrlLike, UrlMapExt}}; use super::File; @@ -23,6 +23,8 @@ pub enum FilesOp { Upserting(UrlBuf, HashMap), } +impl_data_any!(FilesOp); + impl FilesOp { #[inline] pub fn cwd(&self) -> &UrlBuf { diff --git a/yazi-macro/src/data.rs b/yazi-macro/src/data.rs new file mode 100644 index 00000000..80c3f56e --- /dev/null +++ b/yazi-macro/src/data.rs @@ -0,0 +1,116 @@ +#[macro_export] +macro_rules! impl_data_any { + ($ty:ty) => { + impl ::yazi_shared::data::DataAny for $ty { + $crate::impl_data_any!(@core); + $crate::impl_data_any!(@into_lua_default); + } + }; + ($ty:ty => $($target:ty),+) => { + impl ::yazi_shared::data::DataAny for $ty { + $crate::impl_data_any!(@core $($target),+); + $crate::impl_data_any!(@into_lua_default); + } + }; + // --- from_into_lua + ($ty:ty, from_into_lua = inherit) => { + impl ::yazi_shared::data::DataAny for $ty { + $crate::impl_data_any!(@core); + $crate::impl_data_any!(@from_lua_inherit); + $crate::impl_data_any!(@into_lua_inherit); + } + $crate::impl_data_any!(@from_lua_register $ty); + }; + ($ty:ty => $($target:ty),+ ; from_into_lua = inherit) => { + impl ::yazi_shared::data::DataAny for $ty { + $crate::impl_data_any!(@core $($target),+); + $crate::impl_data_any!(@from_lua_inherit); + $crate::impl_data_any!(@into_lua_inherit); + } + $crate::impl_data_any!(@from_lua_register $ty); + }; + // --- to_lua + ($ty:ty, to_lua = $body:expr) => { + impl ::yazi_shared::data::DataAny for $ty { + $crate::impl_data_any!(@core); + $crate::impl_data_any!(@from_lua_inherit); + $crate::impl_data_any!(@into_lua_default); + $crate::impl_data_any!(@to_lua $body); + } + $crate::impl_data_any!(@from_lua_register $ty); + }; + ($ty:ty => $($target:ty),+ ; to_lua = $body:expr) => { + impl ::yazi_shared::data::DataAny for $ty { + $crate::impl_data_any!(@core $($target),+); + $crate::impl_data_any!(@from_lua_inherit); + $crate::impl_data_any!(@into_lua_default); + $crate::impl_data_any!(@to_lua $body); + } + $crate::impl_data_any!(@from_lua_register $ty); + }; + + (@core $($target:ty),*) => { + fn as_any(&self, id: std::any::TypeId) -> Option<&dyn std::any::Any> { + use std::any::{TypeId, Any}; + + if id == TypeId::of::() { + return Some(self as &dyn Any); + } + $( + if id == TypeId::of::<$target>() { + return Some(>::as_ref(self) as &dyn Any); + } + )* + None + } + + fn into_any(self: Box, id: std::any::TypeId) -> Result, Box> { + use std::any::TypeId; + + if id == TypeId::of::() { return Ok(self); } + $( + if id == TypeId::of::<$target>() { + return Ok(Box::new(>::into(*self))); + } + )* + Err(self) + } + }; + (@into_lua_default) => { + fn into_lua(self: Box, lua: &mlua::Lua) -> mlua::Result { + use ::yazi_shared::any_data::AnyData; + use mlua::IntoLua; + + AnyData(self).into_lua(lua) + } + }; + (@into_lua_inherit) => { + fn into_lua(self: Box, lua: &mlua::Lua) -> mlua::Result { + use mlua::IntoLua; + + IntoLua::into_lua(*self, lua) + } + }; + (@to_lua $body:expr) => { + fn to_lua(&self, lua: &mlua::Lua) -> mlua::Result { + use mlua::{self, Lua, Value}; + + ($body as fn(&Self, &Lua) -> mlua::Result)(self, lua) + } + }; + (@from_lua_inherit) => { + fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result> { + use mlua::FromLua; + + ::from_lua(value, lua) + .map(|v| Box::new(v) as Box) + } + }; + (@from_lua_register $ty:ty) => { + ::inventory::submit! { + ::yazi_shared::data::DataInventory { + from_lua: <$ty as ::yazi_shared::data::DataAny>::from_lua, + } + } + }; +} diff --git a/yazi-macro/src/lib.rs b/yazi-macro/src/lib.rs index 7f3b9e48..40110145 100644 --- a/yazi-macro/src/lib.rs +++ b/yazi-macro/src/lib.rs @@ -2,6 +2,7 @@ mod actor; mod asset; mod confirm; mod context; +mod data; mod event; mod fmt; mod fs; diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 60cbf122..55ca4c6d 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -20,6 +20,7 @@ vendored-lua = [ "mlua/vendored" ] yazi-adapter = { path = "../yazi-adapter", version = "26.5.6" } yazi-binding = { path = "../yazi-binding", version = "26.5.6" } yazi-boot = { path = "../yazi-boot", version = "26.5.6" } +yazi-codegen = { path = "../yazi-codegen", version = "26.5.6" } yazi-config = { path = "../yazi-config", version = "26.5.6" } yazi-core = { path = "../yazi-core", version = "26.5.6" } yazi-dds = { path = "../yazi-dds", version = "26.5.6" } @@ -39,8 +40,8 @@ yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" } ansi-to-tui = { workspace = true } anyhow = { workspace = true } futures = { workspace = true } +inventory = { workspace = true } mlua = { workspace = true } -paste = { workspace = true } percent-encoding = { workspace = true } ratatui = { workspace = true } serde_json = { workspace = true } diff --git a/yazi-plugin/src/fs/op.rs b/yazi-plugin/src/fs/op.rs index 93dcd2c7..ec0ce770 100644 --- a/yazi-plugin/src/fs/op.rs +++ b/yazi-plugin/src/fs/op.rs @@ -1,8 +1,21 @@ -use mlua::{IntoLua, Lua, Table, Value}; +use mlua::{Lua, Table, UserData}; use yazi_binding::{Cha, File, Id, Path, Url}; +use yazi_codegen::FromLuaOwned; +use yazi_macro::impl_data_any; +#[derive(Clone, FromLuaOwned)] pub(super) struct FilesOp(yazi_fs::FilesOp); +impl_data_any!(FilesOp => yazi_fs::FilesOp; from_into_lua = inherit); + +impl From for yazi_fs::FilesOp { + fn from(op: FilesOp) -> Self { op.0 } +} + +impl AsRef for FilesOp { + fn as_ref(&self) -> &yazi_fs::FilesOp { &self.0 } +} + impl FilesOp { pub(super) fn part(_: &Lua, t: Table) -> mlua::Result { let id: Id = t.raw_get("id")?; @@ -41,8 +54,4 @@ impl FilesOp { } } -impl IntoLua for FilesOp { - fn into_lua(self, lua: &Lua) -> mlua::Result { - lua.create_any_userdata(self.0)?.into_lua(lua) - } -} +impl UserData for FilesOp {} diff --git a/yazi-plugin/src/keymap/keymap.rs b/yazi-plugin/src/keymap/keymap.rs new file mode 100644 index 00000000..c84aefe6 --- /dev/null +++ b/yazi-plugin/src/keymap/keymap.rs @@ -0,0 +1,23 @@ +use mlua::{IntoLua, Lua, Value}; +use yazi_binding::{Composer, ComposerGet, ComposerSet, keymap::KeymapSection}; +use yazi_shared::Layer; + +pub fn compose() -> Composer { + fn get(lua: &Lua, key: &[u8]) -> mlua::Result { + match key { + b"mgr" => KeymapSection::try_from(Layer::Mgr)?.into_lua(lua), + b"tasks" => KeymapSection::try_from(Layer::Tasks)?.into_lua(lua), + b"spot" => KeymapSection::try_from(Layer::Spot)?.into_lua(lua), + b"pick" => KeymapSection::try_from(Layer::Pick)?.into_lua(lua), + b"input" => KeymapSection::try_from(Layer::Input)?.into_lua(lua), + b"confirm" => KeymapSection::try_from(Layer::Confirm)?.into_lua(lua), + b"cmp" => KeymapSection::try_from(Layer::Cmp)?.into_lua(lua), + b"help" => KeymapSection::try_from(Layer::Help)?.into_lua(lua), + _ => Ok(Value::Nil), + } + } + + fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result { Ok(value) } + + Composer::new(get, set) +} diff --git a/yazi-plugin/src/keymap/mod.rs b/yazi-plugin/src/keymap/mod.rs new file mode 100644 index 00000000..bfbbc6dd --- /dev/null +++ b/yazi-plugin/src/keymap/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(keymap); diff --git a/yazi-plugin/src/lib.rs b/yazi-plugin/src/lib.rs index a716d0dc..9a3fad99 100644 --- a/yazi-plugin/src/lib.rs +++ b/yazi-plugin/src/lib.rs @@ -1,4 +1,4 @@ -yazi_macro::mod_pub!(elements external fs pubsub runtime tasks theme utils); +yazi_macro::mod_pub!(elements external fs keymap pubsub runtime tasks theme utils); yazi_macro::mod_flat!(slim standard); diff --git a/yazi-plugin/src/slim.rs b/yazi-plugin/src/slim.rs index 8d618749..7bd11008 100644 --- a/yazi-plugin/src/slim.rs +++ b/yazi-plugin/src/slim.rs @@ -8,6 +8,7 @@ pub fn slim_lua(lua: &Lua) -> mlua::Result<()> { globals.raw_set("ya", crate::utils::compose(true))?; globals.raw_set("fs", crate::fs::compose())?; globals.raw_set("rt", crate::runtime::compose())?; + globals.raw_set("km", crate::keymap::compose())?; globals.raw_set("th", crate::theme::compose().into_lua(lua)?)?; yazi_binding::Cha::install(lua)?; diff --git a/yazi-plugin/src/standard.rs b/yazi-plugin/src/standard.rs index 0dbbb8e5..69fc4c9b 100644 --- a/yazi-plugin/src/standard.rs +++ b/yazi-plugin/src/standard.rs @@ -27,6 +27,7 @@ fn stage_1(lua: &Lua) -> Result<()> { globals.raw_set("fs", crate::fs::compose())?; globals.raw_set("ps", crate::pubsub::compose())?; globals.raw_set("rt", crate::runtime::compose())?; + globals.raw_set("km", crate::keymap::compose())?; globals.raw_set("th", crate::theme::compose())?; yazi_binding::Error::install(lua)?; diff --git a/yazi-plugin/src/utils/layer.rs b/yazi-plugin/src/utils/layer.rs index 096a0aef..4dd058d8 100644 --- a/yazi-plugin/src/utils/layer.rs +++ b/yazi-plugin/src/utils/layer.rs @@ -25,6 +25,7 @@ impl Utils { .map(|(i, cand)| { let cand = cand?; Ok(ChordCow::Owned(Chord { + id: yazi_config::keymap::chord_id(), on: Self::parse_keys(cand.raw_get("on")?)?, run: vec![relay!(which:callback, [i + 1])], desc: cand.raw_get("desc").unwrap_or_default(), diff --git a/yazi-scheduler/src/process/option.rs b/yazi-scheduler/src/process/option.rs index 2bf5b6c6..6b95af14 100644 --- a/yazi-scheduler/src/process/option.rs +++ b/yazi-scheduler/src/process/option.rs @@ -1,5 +1,6 @@ use std::ffi::OsString; +use yazi_macro::impl_data_any; use yazi_shared::url::{UrlBuf, UrlCow}; // TODO: remove in favor of ShellForm @@ -13,3 +14,5 @@ pub struct ProcessOpt { pub spread: bool, // TODO: remove } + +impl_data_any!(ProcessOpt); diff --git a/yazi-scheduler/src/summary.rs b/yazi-scheduler/src/summary.rs index 4e0134c6..4b6c192d 100644 --- a/yazi-scheduler/src/summary.rs +++ b/yazi-scheduler/src/summary.rs @@ -1,5 +1,6 @@ use ordered_float::OrderedFloat; use serde::Serialize; +use yazi_macro::impl_data_any; use crate::{Ongoing, Progress}; @@ -11,6 +12,8 @@ pub struct TaskSummary { pub percent: Option>, } +impl_data_any!(TaskSummary); + impl From<&Ongoing> for TaskSummary { fn from(value: &Ongoing) -> Self { let mut summary = Self::default(); diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index cdc99fba..e398cf5b 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -23,7 +23,9 @@ dyn-clone = { workspace = true } foldhash = { workspace = true } futures = { workspace = true } hashbrown = { workspace = true } +inventory = { workspace = true } memchr = "2.8.1" +mlua = { workspace = true } ordered-float = { workspace = true } parking_lot = { workspace = true } paste = { workspace = true } diff --git a/yazi-shared/src/any_data.rs b/yazi-shared/src/any_data.rs new file mode 100644 index 00000000..ecd4208e --- /dev/null +++ b/yazi-shared/src/any_data.rs @@ -0,0 +1,8 @@ +use mlua::UserData; + +use crate::data::DataAny; + +#[derive(Debug)] +pub struct AnyData(pub Box); + +impl UserData for AnyData {} diff --git a/yazi-shared/src/completion_token.rs b/yazi-shared/src/completion_token.rs index 029f5ef0..84504050 100644 --- a/yazi-shared/src/completion_token.rs +++ b/yazi-shared/src/completion_token.rs @@ -1,12 +1,15 @@ use std::sync::{Arc, atomic::{AtomicU8, Ordering}}; use tokio::sync::Notify; +use yazi_macro::impl_data_any; #[derive(Clone, Debug, Default)] pub struct CompletionToken { inner: Arc<(AtomicU8, Notify)>, } +impl_data_any!(CompletionToken); + impl CompletionToken { pub fn complete(&self, success: bool) { let new = if success { 1 } else { 2 }; diff --git a/yazi-shared/src/data/any.rs b/yazi-shared/src/data/any.rs index e11a284e..39edc3b5 100644 --- a/yazi-shared/src/data/any.rs +++ b/yazi-shared/src/data/any.rs @@ -1,20 +1,39 @@ -use std::{any::Any, fmt::{self, Debug}}; +use std::{any::{self, Any, TypeId}, fmt::{self, Debug}}; use dyn_clone::DynClone; +use mlua::{IntoLua, Lua, Value}; + +use crate::any_data::AnyData; pub trait DataAny: Any + Send + Sync + DynClone { - fn as_any(&self) -> &dyn Any; + fn as_any(&self, id: TypeId) -> Option<&dyn Any>; - fn into_any(self: Box) -> Box; + fn into_any(self: Box, id: TypeId) -> Result, Box>; + + fn into_lua(self: Box, lua: &Lua) -> mlua::Result; + + fn to_lua(&self, lua: &Lua) -> mlua::Result { dyn_clone::clone_box(self).into_lua(lua) } + + fn from_lua(_value: Value, _lua: &Lua) -> mlua::Result> + where + Self: Sized, + { + Err(mlua::Error::runtime(format!( + "DataAny::from_lua is not implemented for `{}`", + any::type_name::(), + ))) + } } -impl DataAny for T -where - T: Any + Send + Sync + DynClone, -{ - fn as_any(&self) -> &dyn Any { self } +impl dyn DataAny { + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any(TypeId::of::()).and_then(|a| a.downcast_ref::()) + } - fn into_any(self: Box) -> Box { self } + pub fn downcast(self: Box) -> Result, Box> { + let id = TypeId::of::(); + self.into_any(id).map_or_else(|me| Err(me as Box), |a| a.downcast::()) + } } impl Clone for Box { @@ -26,3 +45,27 @@ impl Debug for dyn DataAny { f.debug_struct("DataAny").finish_non_exhaustive() } } + +impl DataAny for Vec { + fn as_any(&self, id: TypeId) -> Option<&dyn Any> { + (id == TypeId::of::()).then(|| self as &dyn Any) + } + + fn into_any(self: Box, id: TypeId) -> Result, Box> { + if id == TypeId::of::() { Ok(self) } else { Err(self) } + } + + fn into_lua(self: Box, lua: &Lua) -> mlua::Result { AnyData(self).into_lua(lua) } +} + +impl DataAny for tokio::sync::mpsc::UnboundedSender { + fn as_any(&self, id: TypeId) -> Option<&dyn Any> { + (id == TypeId::of::()).then(|| self as &dyn Any) + } + + fn into_any(self: Box, id: TypeId) -> Result, Box> { + if id == TypeId::of::() { Ok(self) } else { Err(self) } + } + + fn into_lua(self: Box, lua: &Lua) -> mlua::Result { AnyData(self).into_lua(lua) } +} diff --git a/yazi-shared/src/data/data.rs b/yazi-shared/src/data/data.rs index 8b85d56a..887fef06 100644 --- a/yazi-shared/src/data/data.rs +++ b/yazi-shared/src/data/data.rs @@ -257,7 +257,7 @@ impl Data { pub fn as_any(&self) -> Option<&T> { match self { - Self::Any(b) => (**b).as_any().downcast_ref::(), + Self::Any(a) => a.downcast_ref::(), _ => None, } } @@ -271,15 +271,15 @@ impl Data { pub fn into_any(self) -> Option { match self { - Self::Any(b) => b.into_any().downcast::().ok().map(|b| *b), + Self::Any(a) => a.downcast::().ok().map(|b| *b), _ => None, } } // FIXME: find a better name pub fn into_any2(self) -> Result { - if let Self::Any(b) = self - && let Ok(t) = b.into_any().downcast::() + if let Self::Any(a) = self + && let Ok(t) = a.downcast::() { Ok(*t) } else { diff --git a/yazi-shared/src/data/inventory.rs b/yazi-shared/src/data/inventory.rs new file mode 100644 index 00000000..b4114236 --- /dev/null +++ b/yazi-shared/src/data/inventory.rs @@ -0,0 +1,9 @@ +use mlua::{Lua, Value}; + +use crate::data::DataAny; + +pub struct DataInventory { + pub from_lua: fn(Value, &Lua) -> mlua::Result>, +} + +inventory::collect!(DataInventory); diff --git a/yazi-shared/src/data/mod.rs b/yazi-shared/src/data/mod.rs index 1f290c8a..1df7c51a 100644 --- a/yazi-shared/src/data/mod.rs +++ b/yazi-shared/src/data/mod.rs @@ -1,4 +1,4 @@ #[macro_use] mod macros; -yazi_macro::mod_flat!(any data de de_bytes de_key de_owned key); +yazi_macro::mod_flat!(any data de de_bytes de_key de_owned inventory key); diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 98317f74..c0ec258b 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -1,4 +1,6 @@ -yazi_macro::mod_pub!(data event loc path pool scheme shell strand translit url wtf8); +extern crate self as yazi_shared; + +yazi_macro::mod_pub!(any_data data event loc path pool scheme shell strand translit url wtf8); yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id kebab_cased_string last_value layer localset natsort non_empty_string os predictor snake_cased_string source tests throttle time); diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index cf3bbc25..c471775b 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, path::{Pat use anyhow::Result; use serde::{Deserialize, Serialize, de::{self, IntoDeserializer}}; +use yazi_macro::impl_data_any; use crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::{Scheme, SchemeLike}, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlDeserializer, UrlLike}}; @@ -13,6 +14,8 @@ pub enum UrlBuf { Sftp { loc: LocBuf, domain: Symbol }, } +impl_data_any!(UrlBuf); + // FIXME: remove impl Default for UrlBuf { fn default() -> Self { Self::Regular(Default::default()) } diff --git a/yazi-shim/Cargo.toml b/yazi-shim/Cargo.toml index ad580071..8c2a1b52 100644 --- a/yazi-shim/Cargo.toml +++ b/yazi-shim/Cargo.toml @@ -33,4 +33,4 @@ twox-hash = { workspace = true } unicode-width = { workspace = true } [dependencies.unicode-segmentation] -version = "1.13.2" +version = "1.13.3" diff --git a/yazi-shim/src/mlua/traits.rs b/yazi-shim/src/mlua/traits.rs index 35b98e79..68b037b5 100644 --- a/yazi-shim/src/mlua/traits.rs +++ b/yazi-shim/src/mlua/traits.rs @@ -14,7 +14,7 @@ pub trait DeserializeOverLua: DeserializeOverWith { impl DeserializeOverLua for T {} -// --- IntoLua +// --- LuaTableExt pub trait LuaTableExt { fn sequence_iter(&self, lua: &Lua) -> SequenceIter; } diff --git a/yazi-term/Cargo.toml b/yazi-term/Cargo.toml index f3977dbe..46f62cce 100644 --- a/yazi-term/Cargo.toml +++ b/yazi-term/Cargo.toml @@ -24,6 +24,7 @@ bitflags = { workspace = true } futures = { workspace = true } parking_lot = { workspace = true } ratatui = { workspace = true } +serde = { workspace = true } strum = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/yazi-term/src/event/keyboard.rs b/yazi-term/src/event/keyboard.rs index 5781e756..95841580 100644 --- a/yazi-term/src/event/keyboard.rs +++ b/yazi-term/src/event/keyboard.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use serde::{Deserialize, Serialize}; use crate::{ParseError, Result, bail, event::Modifiers}; @@ -71,7 +72,8 @@ impl KeyEventState { } // --- Code -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(tag = "type", content = "value")] pub enum KeyCode { Char(char), Enter, @@ -217,7 +219,8 @@ impl KeyCode { } // --- Modifier key -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(untagged)] pub enum ModifierKeyCode { LeftShift, LeftControl, @@ -250,7 +253,8 @@ impl ModifierKeyCode { } // --- Media key -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(untagged)] pub enum MediaKeyCode { Play, Pause,