feat: experimental %y, %Y, %t, %T, %yN, %YN, %tN, %TN shell formatting parameters (#4108)

This commit is contained in:
三咲雅 misaki masa 2026-07-08 17:04:12 +08:00 committed by GitHub
parent aa850f9a3b
commit dbb0cc0d55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
89 changed files with 561 additions and 649 deletions

View file

@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Bulk create ([#3793]) - Bulk create ([#3793])
- Make help menu a command palette ([#4074]) - Make help menu a command palette ([#4074])
- Input history ([#4104]) - Input history ([#4104])
- Experimental `%y`, `%Y`, `%t`, `%T`, `%yN`, `%YN`, `%tN`, `%TN` shell formatting parameters ([#4108])
- Make visual mode support wraparound scrolling ([#4101]) - Make visual mode support wraparound scrolling ([#4101])
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970]) - H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
- Context-aware icons for inputs ([#4080]) - Context-aware icons for inputs ([#4080])
@ -1773,3 +1774,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#4096]: https://github.com/sxyazi/yazi/pull/4096 [#4096]: https://github.com/sxyazi/yazi/pull/4096
[#4101]: https://github.com/sxyazi/yazi/pull/4101 [#4101]: https://github.com/sxyazi/yazi/pull/4101
[#4104]: https://github.com/sxyazi/yazi/pull/4104 [#4104]: https://github.com/sxyazi/yazi/pull/4104
[#4108]: https://github.com/sxyazi/yazi/pull/4108

171
Cargo.lock generated
View file

@ -543,9 +543,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.65" version = "1.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -780,18 +780,18 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.15" version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [ dependencies = [
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
@ -799,18 +799,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.18" version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]] [[package]]
name = "crunchy" name = "crunchy"
@ -1194,14 +1194,16 @@ dependencies = [
[[package]] [[package]]
name = "exr" name = "exr"
version = "1.74.0" version = "1.74.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" checksum = "6be87932f10230a4339ab394edd8e4611fcb72553d8295b4d52ea55249b3daa5"
dependencies = [ dependencies = [
"bit_field", "bit_field",
"half", "half",
"lebe", "lebe",
"miniz_oxide", "miniz_oxide",
"num-complex",
"pulp",
"smallvec", "smallvec",
"zune-inflate", "zune-inflate",
] ]
@ -1695,9 +1697,9 @@ dependencies = [
[[package]] [[package]]
name = "inotify" name = "inotify"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" checksum = "dd854a95a4ac672fed8c054136039fd32c22cf039ff09ead7280afe920486483"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"inotify-sys", "inotify-sys",
@ -1706,9 +1708,9 @@ dependencies = [
[[package]] [[package]]
name = "inotify-sys" name = "inotify-sys"
version = "0.1.7" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea94e891b3606826e9c998be69ddca42247dad8ad50b1649a5cb7e1c9ae06fd" checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1791,11 +1793,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.34" version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.4.3",
"libc", "libc",
] ]
@ -1954,18 +1956,18 @@ dependencies = [
[[package]] [[package]]
name = "lua-src" name = "lua-src"
version = "550.0.0" version = "550.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e836dc8ae16806c9bdcf42003a88da27d163433e3f9684c52f0301258004a4fb" checksum = "75c110c2fa33f34e0de05448e1f3eb2e0631e7a69e2d8ae1586cffc9fc9f9949"
dependencies = [ dependencies = [
"cc", "cc",
] ]
[[package]] [[package]]
name = "luajit-src" name = "luajit-src"
version = "210.6.6+707c12b" version = "210.7.2+b925b3e"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a86cc925d4053d0526ae7f5bc765dbd0d7a5d1a63d43974f4966cb349ca63295" checksum = "920cf654b23d217c550ceea57c32cd2a413ea27b6d47ed77b5ee0cf655adefa6"
dependencies = [ dependencies = [
"cc", "cc",
"which", "which",
@ -1997,9 +1999,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.2" version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -2048,31 +2050,31 @@ dependencies = [
[[package]] [[package]]
name = "mlua" name = "mlua"
version = "0.11.6" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccd36acfa49ce6ee56d1307a061dd302c564eee757e6e4cd67eb4f7204846fab" checksum = "ad72ffa037cf5970c9860674f32f703fda25d86cf217475fe7a79c5f9961bcaa"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bstr", "bstr",
"either", "either",
"erased-serde", "erased-serde",
"futures-util", "futures-util",
"inventory",
"libc", "libc",
"mlua-sys", "mlua-sys",
"mlua_derive", "mlua_derive",
"num-traits", "num-traits",
"parking_lot", "parking_lot",
"rustc-hash", "rustc-hash",
"rustversion",
"serde", "serde",
"serde-value", "serde-value",
] ]
[[package]] [[package]]
name = "mlua-sys" name = "mlua-sys"
version = "0.10.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f1c3a7fc7580227ece249fd90aa2fa3b39eb2b49d3aec5e103b3e85f2c3dfc8" checksum = "92136787b906d4e55cfe96cd6c62e010bb1a56889d0d6cf83eb016dbad07576b"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@ -2084,16 +2086,12 @@ dependencies = [
[[package]] [[package]]
name = "mlua_derive" name = "mlua_derive"
version = "0.11.0" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465bddde514c4eb3b50b543250e97c1d4b284fa3ef7dc0ba2992c77545dbceb2" checksum = "0232231813b214d44b57c7678e7dbbcfaac35bd1a01acef0dad7c5dfdc1b1973"
dependencies = [ dependencies = [
"itertools",
"once_cell",
"proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex",
"syn", "syn",
] ]
@ -2206,6 +2204,16 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"bytemuck",
"num-traits",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.2" version = "0.2.2"
@ -2694,28 +2702,6 @@ dependencies = [
"wnaf", "wnaf",
] ]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@ -2745,10 +2731,33 @@ dependencies = [
] ]
[[package]] [[package]]
name = "pxfm" name = "pulp"
version = "0.1.29" version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a"
dependencies = [
"bytemuck",
"cfg-if",
"libm",
"num-complex",
"paste",
"pulp-wasm-simd-flag",
"raw-cpuid",
"reborrow",
"version_check",
]
[[package]]
name = "pulp-wasm-simd-flag"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740"
[[package]]
name = "pxfm"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]] [[package]]
name = "qoi" name = "qoi"
@ -2973,6 +2982,15 @@ dependencies = [
"rgb", "rgb",
] ]
[[package]]
name = "raw-cpuid"
version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.12.0" version = "1.12.0"
@ -2993,6 +3011,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "reborrow"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.18" version = "0.5.18"
@ -3113,9 +3137,9 @@ dependencies = [
[[package]] [[package]]
name = "russh" name = "russh"
version = "0.62.1" version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20d2039c3e50abb1ec3bdadea27098679da12a33befa9ab4e62adad9a13ebcb2" checksum = "ca6c3c1dd0a9ad3a9915a2f6e907d158f9a7e9ac32ddc772b003faafc027d9a8"
dependencies = [ dependencies = [
"aes", "aes",
"bitflags", "bitflags",
@ -3242,9 +3266,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]] [[package]]
name = "ryu" name = "ryu"
@ -3813,9 +3837,9 @@ dependencies = [
[[package]] [[package]]
name = "tikv-jemalloc-sys" name = "tikv-jemalloc-sys"
version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" version = "0.7.1+5.3.1-0-g81034ce1f1373e37dc865038e1bc8eeecf559ce8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" checksum = "1a2825c78386b4ae0314074867860ba9577875de945f05992c38815cbec327f0"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -3823,9 +3847,9 @@ dependencies = [
[[package]] [[package]]
name = "tikv-jemallocator" name = "tikv-jemallocator"
version = "0.6.1" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" checksum = "249f09e49ab1609436f34c776e84231bead18d6a955f119f939bdc1d847561bd"
dependencies = [ dependencies = [
"libc", "libc",
"tikv-jemalloc-sys", "tikv-jemalloc-sys",
@ -5287,6 +5311,7 @@ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"bitflags", "bitflags",
"compact_str",
"futures", "futures",
"mlua", "mlua",
"parking_lot", "parking_lot",
@ -5421,18 +5446,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.52" version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.52" version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -42,6 +42,7 @@ base64 = "0.22.1"
bitflags = { version = "2.13.0", features = [ "serde" ] } bitflags = { version = "2.13.0", features = [ "serde" ] }
chrono = "0.4.45" chrono = "0.4.45"
clap = { version = "4.6.1", features = [ "derive" ] } clap = { version = "4.6.1", features = [ "derive" ] }
compact_str = { version = "0.9.1", features = [ "serde" ] }
core-foundation-sys = "0.8.7" core-foundation-sys = "0.8.7"
dirs = "6.0.0" dirs = "6.0.0"
dyn-clone = "1.0.20" dyn-clone = "1.0.20"
@ -55,7 +56,7 @@ indexmap = { version = "2.14.0", features = [ "serde" ] }
inventory = "0.3.24" inventory = "0.3.24"
libc = "0.2.186" libc = "0.2.186"
lru = "0.18.0" lru = "0.18.0"
mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] } mlua = { version = "0.12.0", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] }
objc2 = "0.6.4" objc2 = "0.6.4"
ordered-float = { version = "5.3.0", features = [ "serde" ] } ordered-float = { version = "5.3.0", features = [ "serde" ] }
parking_lot = "0.12.5" parking_lot = "0.12.5"
@ -65,7 +66,7 @@ rand = { version = "0.10.2", default-features = false, features =
ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] } ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] }
ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] } ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] }
regex = "1.12.4" regex = "1.12.4"
russh = { version = "0.62.1", default-features = false, features = [ "ring", "rsa" ] } russh = { version = "0.62.2", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] } serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.150" serde_json = "1.0.150"

12
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1779877693, "lastModified": 1783279667,
"narHash": "sha256-NOF9NAREhxr50bbBfVcVOq+ArCMSoe8dP79Pk2uyARk=", "narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4100e830e085863741bc69b156ec4ccd53ab5be0", "rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -48,11 +48,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1779851998, "lastModified": 1783320166,
"narHash": "sha256-UkkMh3bX9QW4Luqkm98nUaOqKWrU6i65mUnph3WeSSw=", "narHash": "sha256-l7C/OsjcnWDOk2K3ssj+SBduwL67LashjBqis9+t468=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "6cddd512fa2bf7231f098d3a2f92f6e4cff71e0a", "rev": "20ee15370c9256669d66968b89ee20a4b0a4e673",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use mlua::Value; use mlua::{LuaString, Value};
use ratatui_core::layout::Position; use ratatui_core::layout::Position;
use tracing::error; use tracing::error;
use yazi_actor::lives::Lives; use yazi_actor::lives::Lives;
@ -30,7 +30,7 @@ impl Actor for Reflow {
continue; continue;
}; };
let id: mlua::String = t.get("_id")?; let id: LuaString = t.get("_id")?;
match &*id.as_bytes() { match &*id.as_bytes() {
b"current" => layout.current = *t.raw_get::<yazi_binding::elements::Rect>("_area")?, b"current" => layout.current = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
b"preview" => layout.preview = *t.raw_get::<yazi_binding::elements::Rect>("_area")?, b"preview" => layout.preview = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,

View file

@ -108,6 +108,9 @@ impl<'a> Ctx<'a> {
#[inline] #[inline]
pub fn hovered(&self) -> Option<&File> { self.tab().hovered() } pub fn hovered(&self) -> Option<&File> { self.tab().hovered() }
#[inline]
pub fn hovered_url(&self) -> Option<&UrlBuf> { self.tab().hovered_url() }
#[inline] #[inline]
pub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() } pub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() }

View file

@ -1,6 +1,6 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataMethods, UserDataRef, Value}; use mlua::{AnyUserData, IntoLua, LuaString, MetaMethod, UserData, UserDataMethods, UserDataRef, Value};
use paste::paste; use paste::paste;
use super::{Lives, PtrCell}; use super::{Lives, PtrCell};
@ -43,7 +43,7 @@ impl Core {
impl UserData for Core { impl UserData for Core {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: mlua::String| { methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: LuaString| {
macro_rules! reuse { macro_rules! reuse {
($key:ident, $value:expr) => { ($key:ident, $value:expr) => {
match paste! { &me.[<c_ $key>] } { match paste! { &me.[<c_ $key>] } {

View file

@ -8,8 +8,8 @@ use yazi_fs::{FilesOp, Splatter, file::File, provider::{Provider, local::Local}}
use yazi_macro::{succ, writef}; use yazi_macro::{succ, writef};
use yazi_parser::VoidForm; use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy}; use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_shim::path::CROSS_SEPARATOR; use yazi_shim::path::CROSS_SEPARATOR;
use yazi_term::YIELD_TO_SUBPROCESS; use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::{TTY, sequence::EraseScreen}; use yazi_tty::{TTY, sequence::EraseScreen};
@ -40,13 +40,12 @@ impl Actor for BulkCreate {
}); });
} }
TasksProxy::process_exec( TasksProxy::process_exec(ShellOpt {
cwd.clone(), cwd: cwd.clone(),
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run), cmd: Splatter::new(&[tmp.as_url()]).splat(&opener.run),
vec![UrlCow::default(), UrlBuf::from(&tmp).into()], block: opener.block,
opener.block, orphan: opener.orphan,
opener.orphan, })
)
.await; .await;
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume()); let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());

View file

@ -11,8 +11,8 @@ use yazi_fs::{FilesOp, Splatter, file::File, max_common_root, path::skip_url, pr
use yazi_macro::{err, succ, writef}; use yazi_macro::{err, succ, writef};
use yazi_parser::VoidForm; use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy}; use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_term::YIELD_TO_SUBPROCESS; use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::{TTY, sequence::EraseScreen}; use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
@ -63,13 +63,12 @@ impl Actor for BulkRename {
} }
batcher.prime(&tmp); batcher.prime(&tmp);
TasksProxy::process_exec( TasksProxy::process_exec(ShellOpt {
cwd, cwd,
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run), cmd: Splatter::new(&[tmp.as_url()]).splat(&opener.run),
vec![UrlCow::default(), UrlBuf::from(&tmp).into()], block: opener.block,
opener.block, orphan: opener.orphan,
opener.orphan, })
)
.await; .await;
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume()); let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());

View file

@ -18,7 +18,7 @@ impl Actor for Copy {
let mut s = Vec::<u8>::new(); let mut s = Vec::<u8>::new();
let mut it = if form.hovered { let mut it = if form.hovered {
Box::new(cx.hovered().map(|h| &h.url).into_iter()) Box::new(cx.hovered_url().into_iter())
} else { } else {
cx.tab().selected_or_hovered_urls() cx.tab().selected_or_hovered_urls()
} }

View file

@ -36,7 +36,7 @@ impl Actor for Hover {
// Publish through DDS // Publish through DDS
let tab = tab!(cx); let tab = tab!(cx);
err!(Pubsub::pub_after_hover(tab.id, tab.hovered().map(|h| &h.url))); err!(Pubsub::pub_after_hover(tab.id, tab.hovered_url()));
succ!(); succ!();
} }
} }

View file

@ -24,7 +24,7 @@ impl Actor for Open {
succ!(if !opt.targets.is_empty() { succ!(if !opt.targets.is_empty() {
Quit::with_selected(opt.targets) Quit::with_selected(opt.targets)
} else if opt.hovered { } else if opt.hovered {
Quit::with_selected(cx.hovered().map(|h| &h.url)) Quit::with_selected(cx.hovered_url())
} else { } else {
act!(mgr:escape_visual, cx)?; act!(mgr:escape_visual, cx)?;
Quit::with_selected(cx.tab().selected_or_hovered_urls()) Quit::with_selected(cx.tab().selected_or_hovered_urls())

View file

@ -1,13 +1,13 @@
use anyhow::Result; use anyhow::Result;
use hashbrown::HashMap; use hashbrown::HashMap;
use indexmap::IndexSet; use indexmap::IndexSet;
use yazi_config::YAZI; use yazi_config::{YAZI, opener::OpenerRule};
use yazi_fs::file::File; use yazi_fs::{Splatter, file::File};
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::OpenDoForm; use yazi_parser::mgr::OpenDoForm;
use yazi_proxy::{PickProxy, TasksProxy}; use yazi_proxy::{PickProxy, TasksProxy};
use yazi_scheduler::process::ProcessOpt; use yazi_scheduler::process::ShellOpt;
use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}}; use yazi_shared::{data::Data, url::UrlBuf};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -32,7 +32,7 @@ impl Actor for OpenDo {
if targets.is_empty() { if targets.is_empty() {
succ!(); succ!();
} else if !opt.interactive { } else if !opt.interactive {
succ!(Self::match_and_open(cx, opt.cwd, targets)); succ!(Self::match_and_open(opt.cwd, targets));
} }
let openers: IndexSet<_> = let openers: IndexSet<_> =
@ -42,20 +42,10 @@ impl Actor for OpenDo {
} }
let pick = PickProxy::show(YAZI.pick.open(openers.iter().map(|o| o.desc()).collect())); let pick = PickProxy::show(YAZI.pick.open(openers.iter().map(|o| o.desc()).collect()));
let urls: Vec<_> = [UrlCow::default()] let urls: Vec<_> = targets.into_iter().map(|(file, _)| file.url).collect();
.into_iter()
.chain(targets.into_iter().map(|(file, _)| file.url.into()))
.collect();
tokio::spawn(async move { tokio::spawn(async move {
if let Some(choice) = pick.await { if let Some(choice) = pick.await {
TasksProxy::open_shell_compat(ProcessOpt { Self::open_with(&openers[choice], &opt.cwd, &urls);
cwd: opt.cwd,
cmd: openers[choice].run.clone().into(),
args: urls,
block: openers[choice].block,
orphan: openers[choice].orphan,
spread: openers[choice].spread,
});
} }
}); });
succ!(); succ!();
@ -63,24 +53,28 @@ impl Actor for OpenDo {
} }
impl OpenDo { impl OpenDo {
// TODO: remove fn match_and_open(cwd: UrlBuf, targets: Vec<(File, &str)>) {
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(File, &str)>) { let mut openers: HashMap<_, Vec<_>> = Default::default();
let mut openers = HashMap::new();
for (file, mime) in targets { for (file, mime) in targets {
if let Some(open) = YAZI.open.matches(&file, mime) if let Some(open) = YAZI.open.matches(&file, mime)
&& let Some(opener) = YAZI.opener.first(&open) && let Some(opener) = YAZI.opener.first(&open)
{ {
openers.entry(opener).or_insert_with(|| vec![UrlCow::default()]).push(file.url.into()); openers.entry(opener).or_default().push(file.url);
} }
} }
for (opener, args) in openers { for (opener, urls) in openers {
cx.tasks.open_shell_compat(ProcessOpt { Self::open_with(&opener, &cwd, &urls);
cwd: cwd.clone(), }
cmd: opener.run.clone().into(), }
args,
block: opener.block, fn open_with(opener: &OpenerRule, cwd: &UrlBuf, urls: &[UrlBuf]) {
let size = if opener.spread { urls.len().max(1) } else { 1 };
for urls in urls.chunks(size) {
TasksProxy::process_open(ShellOpt {
cwd: cwd.clone(),
cmd: Splatter::new(urls).splat(&opener.run),
block: opener.block,
orphan: opener.orphan, orphan: opener.orphan,
spread: opener.spread,
}); });
} }
} }

View file

@ -2,10 +2,12 @@ use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_core::mgr::MgrSnap;
use yazi_fs::Splatter;
use yazi_macro::{act, input, succ}; use yazi_macro::{act, input, succ};
use yazi_parser::mgr::ShellForm; use yazi_parser::mgr::ShellForm;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::process::ProcessOpt; use yazi_scheduler::process::ShellOpt;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -22,7 +24,7 @@ impl Actor for Shell {
act!(mgr:escape_visual, cx)?; act!(mgr:escape_visual, cx)?;
let cwd = form.cwd.take().unwrap_or_else(|| cx.cwd().clone()); let cwd = form.cwd.take().unwrap_or_else(|| cx.cwd().clone());
let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect(); let snap = MgrSnap::from(&cx.mgr);
let input = if form.interactive { let input = if form.interactive {
Some(input!( Some(input!(
@ -44,13 +46,11 @@ impl Actor for Shell {
return; return;
} }
TasksProxy::open_shell_compat(ProcessOpt { TasksProxy::process_open(ShellOpt {
cwd, cwd,
cmd: form.run.to_string().into(), cmd: Splatter::new(snap).splat(&*form.run),
args: selected,
block: form.block, block: form.block,
orphan: form.orphan, orphan: form.orphan,
spread: true,
}); });
}); });

View file

@ -13,7 +13,7 @@ impl Actor for UpdatePeeked {
const NAME: &str = "update_peeked"; const NAME: &str = "update_peeked";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(hovered) = cx.hovered().map(|h| &h.url) else { let Some(hovered) = cx.hovered_url() else {
succ!(cx.tab_mut().preview.reset()); succ!(cx.tab_mut().preview.reset());
}; };

View file

@ -14,7 +14,7 @@ impl Actor for UpdateSpotted {
fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut(); let tab = cx.tab_mut();
let Some(hovered) = tab.hovered().map(|h| &h.url) else { let Some(hovered) = tab.hovered_url() else {
succ!(tab.spot.reset()); succ!(tab.spot.reset());
}; };

View file

@ -1 +1 @@
yazi_macro::mod_flat!(arrow cancel close inspect open_shell_compat process_open show spawn update_succeed); yazi_macro::mod_flat!(arrow cancel close inspect process_open show spawn update_succeed);

View file

@ -1,19 +0,0 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::tasks::ProcessOpenForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct OpenShellCompat;
// TODO: remove
impl Actor for OpenShellCompat {
type Form = ProcessOpenForm;
const NAME: &str = "open_shell_compat";
fn act(cx: &mut Ctx, Self::Form { opt, .. }: Self::Form) -> Result<Data> {
succ!(cx.tasks.open_shell_compat(opt));
}
}

View file

@ -1,4 +1,4 @@
use mlua::{ExternalError, FromLua, IntoLua, IntoLuaMulti, UserData, UserDataMethods, Value}; use mlua::{FromLua, IntoLua, IntoLuaMulti, UserData, UserDataMethods, Value};
use yazi_codegen::FromLuaOwned; use yazi_codegen::FromLuaOwned;
use crate::Error; use crate::Error;
@ -54,16 +54,13 @@ impl<T: IntoLua + 'static> UserData for MpscUnboundedRx<T> {
} }
#[derive(FromLuaOwned)] #[derive(FromLuaOwned)]
pub struct OneshotTx<T: FromLua + 'static>(pub Option<tokio::sync::oneshot::Sender<T>>); pub struct OneshotTx<T: FromLua + 'static>(pub tokio::sync::oneshot::Sender<T>);
pub struct OneshotRx<T: IntoLua + 'static>(pub Option<tokio::sync::oneshot::Receiver<T>>); pub struct OneshotRx<T: IntoLua + 'static>(pub tokio::sync::oneshot::Receiver<T>);
impl<T: FromLua> UserData for OneshotTx<T> { impl<T: FromLua> UserData for OneshotTx<T> {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("send", |lua, me, value: Value| { methods.add_method_once("send", |lua, me, value: Value| {
let Some(tx) = me.0.take() else { match me.0.send(T::from_lua(value, lua)?) {
return Err("Oneshot sender already used".into_lua_err());
};
match tx.send(T::from_lua(value, lua)?) {
Ok(()) => true.into_lua_multi(lua), Ok(()) => true.into_lua_multi(lua),
Err(_) => (false, Error::custom("Oneshot receiver closed")).into_lua_multi(lua), Err(_) => (false, Error::custom("Oneshot receiver closed")).into_lua_multi(lua),
} }
@ -73,11 +70,8 @@ impl<T: FromLua> UserData for OneshotTx<T> {
impl<T: IntoLua + 'static> UserData for OneshotRx<T> { impl<T: IntoLua + 'static> UserData for OneshotRx<T> {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method_mut("recv", |lua, mut me, ()| async move { methods.add_async_method_once("recv", |lua, me, ()| async move {
let Some(rx) = me.0.take() else { match me.0.await {
return Err("Oneshot receiver already used".into_lua_err());
};
match rx.await {
Ok(value) => value.into_lua_multi(&lua), Ok(value) => value.into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua),
} }

View file

@ -1,5 +1,5 @@
use hashbrown::HashMap; use hashbrown::HashMap;
use mlua::{Lua, MetaMethod, UserData, UserDataMethods, Value}; use mlua::{Lua, LuaString, MetaMethod, UserData, UserDataMethods, Value};
pub type ComposerGet = fn(&Lua, &[u8]) -> mlua::Result<Value>; pub type ComposerGet = fn(&Lua, &[u8]) -> mlua::Result<Value>;
pub type ComposerSet = fn(&Lua, &[u8], Value) -> mlua::Result<Value>; pub type ComposerSet = fn(&Lua, &[u8], Value) -> mlua::Result<Value>;
@ -24,7 +24,7 @@ where
S: Fn(&Lua, &[u8], Value) -> mlua::Result<Value> + 'static, S: Fn(&Lua, &[u8], Value) -> mlua::Result<Value> + 'static,
{ {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: mlua::String| { methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: LuaString| {
let key = key.as_bytes(); let key = key.as_bytes();
if let Some(v) = me.cache.get(key.as_ref()) { if let Some(v) = me.cache.get(key.as_ref()) {
return Ok(v.clone()); return Ok(v.clone());
@ -38,7 +38,7 @@ where
methods.add_meta_method_mut( methods.add_meta_method_mut(
MetaMethod::NewIndex, MetaMethod::NewIndex,
|lua, me, (key, value): (mlua::String, Value)| { |lua, me, (key, value): (LuaString, Value)| {
let key = key.as_bytes(); let key = key.as_bytes();
let value = (me.set)(lua, key.as_ref(), value)?; let value = (me.set)(lua, key.as_ref(), value)?;

View file

@ -2,7 +2,7 @@ use std::str::FromStr;
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value};
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default, UserData)]
pub struct Color(pub ratatui_core::style::Color); pub struct Color(pub ratatui_core::style::Color);
impl Color { impl Color {
@ -32,5 +32,3 @@ impl FromLua for Color {
})) }))
} }
} }
impl UserData for Color {}

View file

@ -1,6 +1,6 @@
use mlua::{FromLua, IntoLua, Lua, UserData, Value}; use mlua::{FromLua, IntoLua, Lua, UserData, Value};
#[derive(Clone, Copy, Default, FromLua)] #[derive(Clone, Copy, Default, FromLua, UserData)]
pub struct Constraint(pub(super) ratatui_core::layout::Constraint); pub struct Constraint(pub(super) ratatui_core::layout::Constraint);
impl Constraint { impl Constraint {
@ -23,5 +23,3 @@ impl Constraint {
impl From<Constraint> for ratatui_core::layout::Constraint { impl From<Constraint> for ratatui_core::layout::Constraint {
fn from(value: Constraint) -> Self { value.0 } fn from(value: Constraint) -> Self { value.0 }
} }
impl UserData for Constraint {}

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, mem, ops::{Deref, DerefMut}}; use std::{borrow::Cow, mem, ops::{Deref, DerefMut}};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, Function, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, Function, IntoLua, Lua, LuaString, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui_core::widgets::Widget; use ratatui_core::widgets::Widget;
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
@ -31,7 +31,7 @@ impl Line {
pub fn compose(lua: &Lua) -> mlua::Result<Value> { pub fn compose(lua: &Lua) -> mlua::Result<Value> {
let new = lua.create_function(|_, (_, line): (Table, Self)| Ok(line))?; let new = lua.create_function(|_, (_, line): (Table, Self)| Ok(line))?;
let parse = lua.create_function(|_, code: mlua::String| { let parse = lua.create_function(|_, code: LuaString| {
let code = code.as_bytes(); let code = code.as_bytes();
let Some(line) = code.split_inclusive(|&b| b == b'\n').next() else { let Some(line) = code.split_inclusive(|&b| b == b'\n').next() else {
return Ok(Self::default()); return Ok(Self::default());

View file

@ -1,7 +1,7 @@
use std::{any::TypeId, mem}; use std::{any::TypeId, mem};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, LuaString, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui_core::widgets::Widget; use ratatui_core::widgets::Widget;
use yazi_shim::SStr; use yazi_shim::SStr;
@ -24,7 +24,7 @@ impl Text {
pub fn compose(lua: &Lua) -> mlua::Result<Value> { pub fn compose(lua: &Lua) -> mlua::Result<Value> {
let new = lua.create_function(|_, (_, text): (Table, Self)| Ok(text))?; let new = lua.create_function(|_, (_, text): (Table, Self)| Ok(text))?;
let parse = lua.create_function(|_, code: mlua::String| { let parse = lua.create_function(|_, code: LuaString| {
Ok(Self { inner: code.as_bytes().into_text().into_lua_err()?, ..Default::default() }) Ok(Self { inner: code.as_bytes().into_text().into_lua_err()?, ..Default::default() })
})?; })?;

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, fmt::Display}; use std::{borrow::Cow, fmt::Display};
use mlua::{ExternalError, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{ExternalError, Lua, LuaString, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
use yazi_codegen::FromLuaOwned; use yazi_codegen::FromLuaOwned;
use yazi_shim::SStr; use yazi_shim::SStr;
@ -19,9 +19,9 @@ impl Error {
let fs = lua.create_function(|_, value: Value| { let fs = lua.create_function(|_, value: Value| {
Ok(Self::Fs(match value { Ok(Self::Fs(match value {
Value::Table(t) => yazi_shim::fs::Error::custom( Value::Table(t) => yazi_shim::fs::Error::custom(
&t.raw_get::<mlua::String>("kind")?.to_str()?, &t.raw_get::<LuaString>("kind")?.to_str()?,
t.raw_get("code")?, t.raw_get("code")?,
&t.raw_get::<mlua::String>("message")?.to_str()?, &t.raw_get::<LuaString>("message")?.to_str()?,
)?, )?,
_ => Err("expected a table".into_lua_err())?, _ => Err("expected a table".into_lua_err())?,
})) }))

View file

@ -46,6 +46,6 @@ impl Drop for Permit {
impl UserData for Permit { impl UserData for Permit {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method_mut("drop", |_, mut me, ()| async move { Ok(me.dropping().await) }); methods.add_async_method_once("drop", |_, mut me, ()| async move { Ok(me.dropping().await) });
} }
} }

View file

@ -1,6 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, LuaString, MetaMethod, Table, UserData, UserDataFields, UserDataMethods, Value};
use yazi_shim::strum::IntoStr; use yazi_shim::strum::IntoStr;
use crate::{elements::Pad, position::{Offset, Origin, Position}}; use crate::{elements::Pad, position::{Offset, Origin, Position}};
@ -20,7 +20,7 @@ impl TryFrom<Table> for Position {
fn try_from(t: Table) -> Result<Self, Self::Error> { fn try_from(t: Table) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
origin: Origin::from_str(&t.raw_get::<mlua::String>(1)?.to_str()?).into_lua_err()?, origin: Origin::from_str(&t.raw_get::<LuaString>(1)?.to_str()?).into_lua_err()?,
offset: Offset { offset: Offset {
x: t.raw_get("x").unwrap_or_default(), x: t.raw_get("x").unwrap_or_default(),
y: t.raw_get("y").unwrap_or_default(), y: t.raw_get("y").unwrap_or_default(),

View file

@ -1,7 +1,7 @@
use std::{ops::DerefMut, process::ExitStatus, time::Duration}; use std::{ops::DerefMut, process::ExitStatus, time::Duration};
use futures::future::try_join3; use futures::future::try_join3;
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, UserDataMethods, Value}; use mlua::{ExternalError, IntoLua, IntoLuaMulti, LuaString, Table, UserData, UserDataMethods, Value};
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select}; use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
use super::Status; use super::Status;
@ -133,7 +133,7 @@ impl UserData for Child {
} }
}); });
methods.add_async_method_mut("write_all", |lua, mut me, src: mlua::String| async move { methods.add_async_method_mut("write_all", |lua, mut me, src: LuaString| async move {
let Some(stdin) = &mut me.stdin else { let Some(stdin) = &mut me.stdin else {
return Err("stdin is not piped".into_lua_err()); return Err("stdin is not piped".into_lua_err());
}; };
@ -158,8 +158,8 @@ impl UserData for Child {
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }
}); });
methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move { methods.add_async_method_once("wait_with_output", |lua, me, ()| async move {
match ud.take::<Self>()?.wait_with_output().await { match me.wait_with_output().await {
Ok(output) => Output::new(output).into_lua_multi(&lua), Ok(output) => Output::new(output).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }

View file

@ -1,6 +1,6 @@
use std::{any::TypeId, ffi::OsStr, io, process::Stdio}; use std::{any::TypeId, ffi::OsStr, io, process::Stdio};
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Lua, LuaString, MetaMethod, Table, UserData, UserDataMethods, Value};
use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
use yazi_shim::wtf8::FromWtf8; use yazi_shim::wtf8::FromWtf8;
@ -152,7 +152,7 @@ impl UserData for Command {
me.inner.arg(OsStr::from_wtf8(&s.as_bytes())?); me.inner.arg(OsStr::from_wtf8(&s.as_bytes())?);
} }
Value::Table(t) => { Value::Table(t) => {
for s in t.sequence_values::<mlua::String>() { for s in t.sequence_values::<LuaString>() {
me.inner.arg(OsStr::from_wtf8(&s?.as_bytes())?); me.inner.arg(OsStr::from_wtf8(&s?.as_bytes())?);
} }
} }
@ -160,19 +160,16 @@ impl UserData for Command {
} }
ud.into_lua(lua) ud.into_lua(lua)
}); });
methods.add_function("cwd", |_, (ud, dir): (AnyUserData, mlua::String)| { methods.add_function("cwd", |_, (ud, dir): (AnyUserData, LuaString)| {
ud.borrow_mut::<Self>()?.inner.current_dir(dir.to_str()?.as_ref()); ud.borrow_mut::<Self>()?.inner.current_dir(dir.to_str()?.as_ref());
Ok(ud) Ok(ud)
}); });
methods.add_function( methods.add_function("env", |_, (ud, key, value): (AnyUserData, LuaString, LuaString)| {
"env", ud.borrow_mut::<Self>()?
|_, (ud, key, value): (AnyUserData, mlua::String, mlua::String)| { .inner
ud.borrow_mut::<Self>()? .env(OsStr::from_wtf8(&key.as_bytes())?, OsStr::from_wtf8(&value.as_bytes())?);
.inner Ok(ud)
.env(OsStr::from_wtf8(&key.as_bytes())?, OsStr::from_wtf8(&value.as_bytes())?); });
Ok(ud)
},
);
methods.add_function("stdin", |_, (ud, stdio): (AnyUserData, Value)| { methods.add_function("stdin", |_, (ud, stdio): (AnyUserData, Value)| {
ud.borrow_mut::<Self>()?.inner.stdin(make_stdio(stdio)?); ud.borrow_mut::<Self>()?.inner.stdin(make_stdio(stdio)?);
Ok(ud) Ok(ud)

View file

@ -196,21 +196,13 @@ pub fn overlay(input: TokenStream) -> TokenStream {
pub fn from_lua(input: TokenStream) -> TokenStream { pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput); let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
let ident_str = ident.to_string();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! { quote! {
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause { impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline] #[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> { fn from_lua(value: ::mlua::Value, lua: &::mlua::Lua) -> ::mlua::Result<Self> {
match value { <::mlua::UserDataOwned<Self> as ::mlua::FromLua>::from_lua(value, lua).map(|ud| ud.0)
::mlua::Value::UserData(ud) => ud.take::<Self>(),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_owned(),
message: None,
}),
}
} }
} }
} }

View file

@ -19,9 +19,14 @@ pub struct Key {
impl Key { impl Key {
pub fn plain(&self) -> Option<char> { pub fn plain(&self) -> Option<char> {
match self.code { if self.ctrl || self.alt || self.super_ {
KeyCode::Char(c) if !self.ctrl && !self.alt && !self.super_ => Some(c), None
_ => None, } else if self.shift && !self.code.implies_shift() {
None
} else if let KeyCode::Char(c) = self.code {
Some(c)
} else {
None
} }
} }
} }
@ -130,7 +135,7 @@ impl Display for Key {
if self.alt { if self.alt {
write!(f, "A-")?; write!(f, "A-")?;
} }
if self.shift && !matches!(self.code, KeyCode::Char(_)) { if self.shift && !self.code.implies_shift() {
write!(f, "S-")?; write!(f, "S-")?;
} }

View file

@ -2,7 +2,7 @@ use std::{mem, ops::Deref, sync::Arc};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use hashbrown::HashMap; use hashbrown::HashMap;
use mlua::{ExternalError, FromLua, IntoLua, IntoLuaMulti, MetaMethod, UserData, UserDataMethods, Value}; use mlua::{ExternalError, FromLua, IntoLua, IntoLuaMulti, LuaString, MetaMethod, UserData, UserDataMethods, Value};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}}; use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}};
@ -86,7 +86,7 @@ impl DeserializeOverWith for Opener {
impl UserData for &'static Opener { impl UserData for &'static Opener {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Index, |lua, &me, key: mlua::String| { methods.add_meta_method(MetaMethod::Index, |lua, &me, key: LuaString| {
let key = key.to_str()?; let key = key.to_str()?;
match me.load().get(&*key) { match me.load().get(&*key) {
Some(rules) => rules.clone().into_lua(lua), Some(rules) => rules.clone().into_lua(lua),
@ -94,22 +94,19 @@ impl UserData for &'static Opener {
} }
}); });
methods.add_meta_method( methods.add_meta_method(MetaMethod::NewIndex, |lua, &me, (key, value): (LuaString, Value)| {
MetaMethod::NewIndex, let key = key.to_str()?;
|lua, &me, (key, value): (mlua::String, Value)| { match value {
let key = key.to_str()?; t @ Value::Table(_) => {
match value { me.insert(&key, &OpenerRulesArc::from_lua(t, lua)?);
t @ Value::Table(_) => {
me.insert(&key, &OpenerRulesArc::from_lua(t, lua)?);
}
Value::Nil => {
me.remove(&key);
}
_ => return Err("expected a table or nil".into_lua_err()),
} }
Ok(()) Value::Nil => {
}, me.remove(&key);
); }
_ => return Err("expected a table or nil".into_lua_err()),
}
Ok(())
});
methods.add_meta_method(MetaMethod::Pairs, |lua, &me, ()| { methods.add_meta_method(MetaMethod::Pairs, |lua, &me, ()| {
let mut matcher = OpenerRulesMatcher::from(me); let mut matcher = OpenerRulesMatcher::from(me);

View file

@ -32,15 +32,5 @@ impl OpenerRule {
} }
} }
pub fn fill(&mut self) { pub fn fill(&mut self) { self.spread = Splatter::<()>::spread(&self.run); }
#[cfg(unix)]
{
self.spread =
Splatter::<()>::spread(&self.run) || self.run.contains("$@") || self.run.contains("$*");
}
#[cfg(windows)]
{
self.spread = Splatter::<()>::spread(&self.run) || self.run.contains("%*");
}
}
} }

View file

@ -1,7 +1,7 @@
use std::{ops::Deref, sync::Arc}; use std::{ops::Deref, sync::Arc};
use hashbrown::HashMap; use hashbrown::HashMap;
use mlua::{MetaMethod, UserData, UserDataMethods}; use mlua::{LuaString, MetaMethod, UserData, UserDataMethods};
use yazi_shared::SnakeCasedString; use yazi_shared::SnakeCasedString;
use crate::theme::{CustomField, CustomSection}; use crate::theme::{CustomField, CustomSection};
@ -20,7 +20,7 @@ impl From<&CustomSection> for CustomSectionArc {
impl UserData for CustomSectionArc { impl UserData for CustomSectionArc {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Index, |_, me, key: mlua::String| { methods.add_meta_method(MetaMethod::Index, |_, me, key: LuaString| {
Ok(me.get(&*key.to_str()?).cloned()) Ok(me.get(&*key.to_str()?).cloned())
}); });
} }

View file

@ -25,7 +25,7 @@ pub struct Help {
} }
impl Help { impl Help {
pub fn r#type(&mut self, key: KeyEvent) -> Result<bool> { pub fn r#type(&mut self, key: &KeyEvent) -> Result<bool> {
if !self.input.r#type(key)? { if !self.input.r#type(key)? {
return Ok(false); return Ok(false);
} }

View file

@ -1,9 +1,6 @@
use std::iter;
use ratatui_core::layout::Rect; use ratatui_core::layout::Rect;
use yazi_binding::position::{Origin, Position}; use yazi_binding::position::{Origin, Position};
use yazi_fs::Splatable; use yazi_shared::url::UrlBuf;
use yazi_shared::url::{AsUrl, Url, UrlBuf};
use yazi_term::TERM; use yazi_term::TERM;
use yazi_watcher::Watcher; use yazi_watcher::Watcher;
@ -52,35 +49,12 @@ impl Mgr {
#[inline] #[inline]
pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() } pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() }
#[inline]
pub fn current(&self) -> &Folder { &self.active().current }
#[inline] #[inline]
pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current } pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current }
#[inline] #[inline]
pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.active_mut().parent.as_mut() } pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.active_mut().parent.as_mut() }
} }
impl Splatable for Mgr {
fn tab(&self) -> usize { self.tabs.cursor }
fn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
idx = idx.and_then(|i| i.checked_sub(1));
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.map(|tab| tab.selected_or_hovered_urls())
.unwrap_or_else(|| Box::new(iter::empty()))
.skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX })
.map(|u| u.as_url())
}
fn hovered(&self, tab: usize) -> Option<Url<'_>> {
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.and_then(|tab| tab.hovered())
.map(|h| h.url.as_url())
}
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { self.yanked.iter().map(|f| f.url.as_url()) }
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(batcher cd displace filter find mgr mimetype open search tabs yanked); yazi_macro::mod_flat!(batcher cd displace filter find mgr mimetype open search snap tabs yanked);

54
yazi-core/src/mgr/snap.rs Normal file
View file

@ -0,0 +1,54 @@
use yazi_fs::Splatable;
use yazi_shared::url::{AsUrl, Url, UrlBuf};
use crate::{mgr::Mgr, tab::TabSnap};
pub struct MgrSnap {
tab: usize,
tabs: Vec<TabSnap>,
yanked: Vec<UrlBuf>,
}
impl From<&Mgr> for MgrSnap {
fn from(value: &Mgr) -> Self {
Self {
tab: value.tabs.cursor,
tabs: value.tabs.iter().map(Into::into).collect(),
yanked: value.yanked.urls().cloned().collect(),
}
}
}
impl Splatable for MgrSnap {
fn tab(&self) -> usize { self.tab + 1 }
fn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
idx = idx.and_then(|i| i.checked_sub(1));
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.map_or_else(|| &[][..], |s| &s.selected)
.into_iter()
.skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX })
.map(AsUrl::as_url)
}
fn hovered(&self, tab: usize) -> Option<Url<'_>> {
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.and_then(|tab| tab.hovered.as_ref())
.map(AsUrl::as_url)
}
fn yanked(&self, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
idx = idx.and_then(|i| i.checked_sub(1));
self
.yanked
.iter()
.skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX })
.map(AsUrl::as_url)
}
}

View file

@ -1,13 +1,13 @@
use mlua::Table; use mlua::Table;
use yazi_fs::file::FileRef; use yazi_fs::{cha::Cha, file::FileRef};
use yazi_macro::impl_data_any; use yazi_macro::impl_data_any;
use yazi_shared::id::Id; use yazi_shared::{id::Id, url::UrlBuf};
use yazi_widgets::Renderable; use yazi_widgets::Renderable;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SpotLock { pub struct SpotLock {
pub url: yazi_shared::url::UrlBuf, pub url: UrlBuf,
pub cha: yazi_fs::cha::Cha, pub cha: Cha,
pub mime: String, pub mime: String,
pub id: Id, pub id: Id,

View file

@ -169,6 +169,9 @@ impl Folder {
#[inline] #[inline]
pub fn hovered_mut(&mut self) -> Option<&mut File> { self.entries.get_mut(self.cursor) } pub fn hovered_mut(&mut self) -> Option<&mut File> { self.entries.get_mut(self.cursor) }
#[inline]
pub fn hovered_url(&self) -> Option<&UrlBuf> { self.hovered().map(|f| &f.url) }
pub fn paginate(&self, page: usize) -> &[File] { pub fn paginate(&self, page: usize) -> &[File] {
let len = self.entries.len(); let len = self.entries.len();
let limit = LAYOUT.get().folder_limit(); let limit = LAYOUT.get().folder_limit();

View file

@ -1 +1 @@
yazi_macro::mod_flat!(backstack finder folder history visual mode preference preview preview_lock selected tab); yazi_macro::mod_flat!(backstack finder folder history mode preference preview preview_lock selected snap tab visual);

View file

@ -1,14 +1,14 @@
use mlua::Table; use mlua::{LuaString, Table};
use yazi_binding::elements::Rect; use yazi_binding::elements::Rect;
use yazi_fs::file::FileRef; use yazi_fs::{cha::Cha, file::FileRef};
use yazi_macro::impl_data_any; use yazi_macro::impl_data_any;
use yazi_shared::pool::{InternStr, Symbol}; use yazi_shared::{pool::{InternStr, Symbol}, url::UrlBuf};
use yazi_widgets::Renderable; use yazi_widgets::Renderable;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PreviewLock { pub struct PreviewLock {
pub url: yazi_shared::url::UrlBuf, pub url: UrlBuf,
pub cha: yazi_fs::cha::Cha, pub cha: Cha,
pub mime: Symbol<str>, pub mime: Symbol<str>,
pub skip: usize, pub skip: usize,
@ -27,7 +27,7 @@ impl TryFrom<Table> for PreviewLock {
Ok(Self { Ok(Self {
url: f.url_owned(), url: f.url_owned(),
cha: f.cha, cha: f.cha,
mime: t.raw_get::<mlua::String>("mime")?.to_str()?.intern(), mime: t.raw_get::<LuaString>("mime")?.to_str()?.intern(),
skip: t.raw_get("skip")?, skip: t.raw_get("skip")?,
area: t.raw_get("area")?, area: t.raw_get("area")?,

17
yazi-core/src/tab/snap.rs Normal file
View file

@ -0,0 +1,17 @@
use yazi_shared::url::UrlBuf;
use crate::tab::Tab;
pub struct TabSnap {
pub hovered: Option<UrlBuf>,
pub selected: Vec<UrlBuf>,
}
impl From<&Tab> for TabSnap {
fn from(value: &Tab) -> Self {
Self {
hovered: value.hovered_url().cloned(),
selected: value.selected.urls().cloned().collect(),
}
}
}

View file

@ -78,6 +78,9 @@ impl Tab {
#[inline] #[inline]
pub fn hovered(&self) -> Option<&File> { self.current.hovered() } pub fn hovered(&self) -> Option<&File> { self.current.hovered() }
#[inline]
pub fn hovered_url(&self) -> Option<&UrlBuf> { self.current.hovered_url() }
#[inline] #[inline]
pub fn hovered_mut(&mut self) -> Option<&mut File> { self.current.hovered_mut() } pub fn hovered_mut(&mut self) -> Option<&mut File> { self.current.hovered_mut() }
@ -109,7 +112,7 @@ impl Tab {
pub fn selected_or_hovered_urls(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> { pub fn selected_or_hovered_urls(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> {
if self.selected.is_empty() { if self.selected.is_empty() {
Box::new(self.hovered().map(|h| &h.url).into_iter()) Box::new(self.hovered_url().into_iter())
} else { } else {
Box::new(self.selected.urls()) Box::new(self.selected.urls())
} }

View file

@ -1,4 +1,4 @@
yazi_macro::mod_flat!(file option prework process tasks); yazi_macro::mod_flat!(file option prework tasks);
pub const TASKS_BORDER: u16 = 2; pub const TASKS_BORDER: u16 = 2;
pub const TASKS_PADDING: u16 = 2; pub const TASKS_PADDING: u16 = 2;

View file

@ -1,37 +0,0 @@
use std::mem;
use yazi_fs::Splatter;
use yazi_scheduler::process::ProcessOpt;
use super::Tasks;
impl Tasks {
// TODO: remove
pub fn open_shell_compat(&self, mut opt: ProcessOpt) {
if opt.spread {
opt.cmd = Splatter::new(&opt.args).splat(opt.cmd);
self.scheduler.process_open(opt);
return;
}
if opt.args.is_empty() {
return;
}
if opt.args.len() == 2 {
opt.cmd = Splatter::new(&opt.args).splat(opt.cmd);
self.scheduler.process_open(opt);
return;
}
let hovered = mem::take(&mut opt.args[0]);
for target in opt.args.into_iter().skip(1) {
let args = vec![hovered.clone(), target];
self.scheduler.process_open(ProcessOpt {
cwd: opt.cwd.clone(),
cmd: Splatter::new(&args).splat(&opt.cmd),
args,
block: opt.block,
orphan: opt.orphan,
spread: opt.spread,
});
}
}
}

View file

@ -59,7 +59,7 @@ libc = { workspace = true }
signal-hook-tokio = { version = "0.4.0", features = [ "futures-v0_3" ] } signal-hook-tokio = { version = "0.4.0", features = [ "futures-v0_3" ] }
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies] [target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
tikv-jemallocator = "0.6.1" tikv-jemallocator = "0.7.0"
[[bin]] [[bin]]
name = "yazi" name = "yazi"

View file

@ -182,7 +182,6 @@ impl<'a> Executor<'a> {
on!(inspect); on!(inspect);
on!(cancel); on!(cancel);
on!(process_open); on!(process_open);
on!(open_shell_compat);
match action.name.as_ref() { match action.name.as_ref() {
// Help // Help

View file

@ -19,12 +19,12 @@ impl<'a> Router<'a> {
use Layer as L; use Layer as L;
let core = &mut self.app.core; let core = &mut self.app.core;
if core.help.visible && core.help.r#type(key)? { if core.help.visible && core.help.r#type(&key)? {
return Ok(true); return Ok(true);
} }
if let Some(mut guard) = core.input.lock_mut() if let Some(mut guard) = core.input.lock_mut()
&& guard.r#type(key)? && guard.r#type(&key)?
{ {
return Ok(true); return Ok(true);
} }

View file

@ -4,7 +4,7 @@ use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::{cell::Cell, ffi::{OsStr, OsString}, iter::{self, Peekable}, mem}; use std::{cell::Cell, ffi::{OsStr, OsString}, iter::{self, Peekable}, mem};
use yazi_shared::url::{AsUrl, Url, UrlCow}; use yazi_shared::url::{AsUrl, Url};
use crate::FsUrl; use crate::FsUrl;
@ -31,7 +31,7 @@ pub trait Splatable {
fn hovered(&self, tab: usize) -> Option<Url<'_>>; fn hovered(&self, tab: usize) -> Option<Url<'_>>;
fn yanked(&self) -> impl Iterator<Item = Url<'_>>; fn yanked(&self, idx: Option<usize>) -> impl Iterator<Item = Url<'_>>;
} }
#[cfg(unix)] #[cfg(unix)]
@ -50,7 +50,7 @@ impl<T> Splatter<T>
where where
T: Splatable, T: Splatable,
{ {
pub fn new(src: T) -> Self { Self { tab: src.tab() + 1, src } } pub fn new(src: T) -> Self { Self { tab: src.tab(), src } }
pub fn splat(mut self, cmd: impl AsRef<OsStr>) -> OsString { pub fn splat(mut self, cmd: impl AsRef<OsStr>) -> OsString {
#[cfg(unix)] #[cfg(unix)]
@ -82,8 +82,6 @@ where
Some('t') | Some('T') => self.visit_tab(it, buf), Some('t') | Some('T') => self.visit_tab(it, buf),
Some('y') | Some('Y') => self.visit_yanked(it, buf), Some('y') | Some('Y') => self.visit_yanked(it, buf),
Some('%') => self.visit_escape(it, buf), Some('%') => self.visit_escape(it, buf),
Some('*') => self.visit_selected(it, buf), // TODO: remove this
Some(c) if c.is_ascii_digit() => self.visit_digit(it, buf),
_ => self.visit_unknown(it, buf), _ => self.visit_unknown(it, buf),
} }
} }
@ -154,32 +152,12 @@ where
self.tab = old; self.tab = old;
} }
fn visit_digit(&mut self, it: &mut Iter, buf: &mut Buf) {
// TODO: remove
match self.consume_digit(it) {
Some(0) => {
cue(buf, self.src.hovered(self.tab).map(|u| u.unified_path_str()).unwrap_or_default());
}
Some(n) => {
cue(
buf,
self
.src
.selected(self.tab, Some(n))
.next()
.map(|u| u.unified_path_str())
.unwrap_or_default(),
);
}
None => unreachable!(),
}
}
fn visit_yanked(&mut self, it: &mut Iter, buf: &mut Buf) { fn visit_yanked(&mut self, it: &mut Iter, buf: &mut Buf) {
let c = it.next().and_then(b2c); let c = it.next().and_then(b2c);
let idx = self.consume_digit(it);
let mut first = true; let mut first = true;
for url in self.src.yanked() { for url in self.src.yanked(idx) {
if !mem::replace(&mut first, false) { if !mem::replace(&mut first, false) {
buf.push(b' ' as _); buf.push(b' ' as _);
} }
@ -190,6 +168,9 @@ where
cue(buf, url.unified_path_str()); cue(buf, url.unified_path_str());
} }
} }
if first && idx.is_some() {
cue(buf, "");
}
} }
fn visit_escape(&mut self, it: &mut Iter, buf: &mut Buf) { buf.push(it.next().unwrap()); } fn visit_escape(&mut self, it: &mut Iter, buf: &mut Buf) { buf.push(it.next().unwrap()); }
@ -232,10 +213,7 @@ impl<T> Splatter<T> {
fn hovered(&self, _tab: usize) -> Option<Url<'_>> { None } fn hovered(&self, _tab: usize) -> Option<Url<'_>> { None }
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { fn yanked(&self, _idx: Option<usize>) -> impl Iterator<Item = Url<'_>> { iter::empty() }
self.0.set(true);
iter::empty()
}
} }
let src = Source(Cell::new(false)); let src = Source(Cell::new(false));
@ -244,28 +222,27 @@ impl<T> Splatter<T> {
} }
} }
// TODO: remove impl<'a, I, T> Splatable for &'a I
impl<'a, T> Splatable for &'a T
where where
T: AsRef<[UrlCow<'a>]>, I: ?Sized,
&'a I: IntoIterator<Item = &'a T>,
T: AsUrl + 'a,
{ {
fn tab(&self) -> usize { 0 } fn tab(&self) -> usize { 1 }
fn selected(&self, tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>> { fn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
self idx = idx.and_then(|i| i.checked_sub(1));
.as_ref() (*self)
.iter() .into_iter()
.filter(move |_| tab == 1) .filter(move |_| tab == 1)
.map(|u| u.as_url()) .map(|u| u.as_url())
.skip(idx.unwrap_or(1)) .skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX }) .take(if idx.is_some() { 1 } else { usize::MAX })
} }
fn hovered(&self, tab: usize) -> Option<Url<'_>> { fn hovered(&self, _tab: usize) -> Option<Url<'_>> { None }
self.as_ref().first().filter(|_| tab == 1).map(|u| u.as_url())
}
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { iter::empty() } fn yanked(&self, _idx: Option<usize>) -> impl Iterator<Item = Url<'_>> { iter::empty() }
} }
#[cfg(test)] #[cfg(test)]
@ -300,8 +277,12 @@ mod tests {
} }
} }
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { fn yanked(&self, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
[Url::regular("y1"), Url::regular("y 2"), Url::regular("y3")].into_iter() idx = idx.and_then(|i| i.checked_sub(1));
[Url::regular("y1"), Url::regular("y 2"), Url::regular("y3")]
.into_iter()
.skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX })
} }
} }
@ -310,41 +291,40 @@ mod tests {
fn test_unix() { fn test_unix() {
let cases = [ let cases = [
// Selected // Selected
(Source(0), r#"ls %s"#, r#"ls t1/s1 t1/s2"#), (Source(1), r#"ls %s"#, r#"ls t1/s1 t1/s2"#),
(Source(0), r#"ls %s1 %s2 %s3"#, r#"ls t1/s1 t1/s2 ''"#), (Source(1), r#"ls %s1 %s2 %s3"#, r#"ls t1/s1 t1/s2 ''"#),
(Source(0), r#"ls %s %s2 %s"#, r#"ls t1/s1 t1/s2 t1/s2 t1/s1 t1/s2"#), (Source(1), r#"ls %s %s2 %s"#, r#"ls t1/s1 t1/s2 t1/s2 t1/s1 t1/s2"#),
(Source(1), r#"ls %s"#, r#"ls 't 2/s 1' 't 2/s 2'"#), (Source(2), r#"ls %s"#, r#"ls 't 2/s 1' 't 2/s 2'"#),
(Source(1), r#"ls %s1 %s3 %s2"#, r#"ls 't 2/s 1' '' 't 2/s 2'"#), (Source(2), r#"ls %s1 %s3 %s2"#, r#"ls 't 2/s 1' '' 't 2/s 2'"#),
(Source(2), r#"ls %s"#, r#"ls "#), (Source(3), r#"ls %s"#, r#"ls "#),
(Source(2), r#"ls %s1 %s %s2"#, r#"ls '' ''"#), (Source(3), r#"ls %s1 %s %s2"#, r#"ls '' ''"#),
// Hovered // Hovered
(Source(0), r#"ls %h"#, r#"ls hovered"#), (Source(1), r#"ls %h"#, r#"ls hovered"#),
(Source(1), r#"ls %h"#, r#"ls 'hover ed'"#), (Source(2), r#"ls %h"#, r#"ls 'hover ed'"#),
(Source(2), r#"ls %h"#, r#"ls ''"#), (Source(3), r#"ls %h"#, r#"ls ''"#),
// Dirname // Dirname
(Source(0), r#"cd %d"#, r#"cd t1 t1"#), (Source(1), r#"cd %d"#, r#"cd t1 t1"#),
(Source(1), r#"cd %d"#, r#"cd 't 2' 't 2'"#), (Source(2), r#"cd %d"#, r#"cd 't 2' 't 2'"#),
(Source(1), r#"cd %d1 %d3 %d2"#, r#"cd 't 2' '' 't 2'"#), (Source(2), r#"cd %d1 %d3 %d2"#, r#"cd 't 2' '' 't 2'"#),
(Source(2), r#"cd %d %d1"#, r#"cd ''"#), (Source(3), r#"cd %d %d1"#, r#"cd ''"#),
// Yanked // Yanked
(Source(0), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
(Source(1), r#"cd %y"#, r#"cd y1 'y 2' y3"#), (Source(1), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
(Source(2), r#"cd %y"#, r#"cd y1 'y 2' y3"#), (Source(2), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
(Source(3), r#"cd %y"#, r#"cd y1 'y 2' y3"#),
(Source(1), r#"cd %y1 %y3 %y2 %y4"#, r#"cd y1 y3 'y 2' ''"#),
// Tab // Tab
(Source(0), r#"ls %s %ts %s"#, r#"ls t1/s1 t1/s2 't 2/s 1' 't 2/s 2' t1/s1 t1/s2"#), (Source(1), r#"ls %s %ts %s"#, r#"ls t1/s1 t1/s2 't 2/s 1' 't 2/s 2' t1/s1 t1/s2"#),
(Source(1), r#"ls %s1 %ts %s2"#, r#"ls 't 2/s 1' 't 2/s 2'"#), (Source(2), r#"ls %s1 %ts %s2"#, r#"ls 't 2/s 1' 't 2/s 2'"#),
(Source(1), r#"ls %s1 %Ts1 %s2 %Ts2"#, r#"ls 't 2/s 1' t1/s1 't 2/s 2' t1/s2"#), (Source(2), r#"ls %s1 %Ts1 %s2 %Ts2"#, r#"ls 't 2/s 1' t1/s1 't 2/s 2' t1/s2"#),
(Source(0), r#"ls %s1 %Ts1 %s2 %Ts2"#, r#"ls t1/s1 '' t1/s2 ''"#), (Source(1), r#"ls %s1 %Ts1 %s2 %Ts2"#, r#"ls t1/s1 '' t1/s2 ''"#),
(Source(0), r#"ls %ty"#, r#"ls y1 'y 2' y3"#), (Source(1), r#"ls %ty"#, r#"ls y1 'y 2' y3"#),
(Source(0), r#"ls %Ty"#, r#"ls y1 'y 2' y3"#), (Source(1), r#"ls %Ty"#, r#"ls y1 'y 2' y3"#),
// Escape // Escape
( (
Source(0), Source(1),
r#"echo % %% %s2 %%h %d %%%y %%%%ts %%%%%ts1"#, r#"echo % %% %s2 %%h %d %%%y %%%%ts %%%%%ts1"#,
r#"echo % % t1/s2 %h t1 t1 %y1 'y 2' y3 %%ts %%'t 2/s 1'"#, r#"echo % % t1/s2 %h t1 t1 %y1 'y 2' y3 %%ts %%'t 2/s 1'"#,
), ),
// TODO: remove
(Source(0), r#"ls %1 %* %2 %0 %3"#, r#"ls t1/s1 t1/s1 t1/s2 t1/s2 hovered ''"#),
]; ];
for (src, cmd, expected) in cases { for (src, cmd, expected) in cases {

View file

@ -1,11 +1,11 @@
use anyhow::anyhow; use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_scheduler::process::ProcessOpt; use yazi_scheduler::process::ShellOpt;
use yazi_shared::event::{ActionCow, Replier}; use yazi_shared::event::{ActionCow, Replier};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ProcessOpenForm { pub struct ProcessOpenForm {
pub opt: ProcessOpt, pub opt: ShellOpt,
pub replier: Option<Replier>, pub replier: Option<Replier>,
} }

View file

@ -1,6 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value}; use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, LuaString, Table, Value};
use yazi_binding::{Composer, ComposerGet, ComposerSet, Error}; use yazi_binding::{Composer, ComposerGet, ComposerSet, Error};
use yazi_config::Pattern; use yazi_config::Pattern;
use yazi_fs::{file::File, mounts::PARTITIONS, provider::{Attrs, DirReader, FileHolder}}; use yazi_fs::{file::File, mounts::PARTITIONS, provider::{Attrs, DirReader, FileHolder}};
@ -81,7 +81,7 @@ fn copy(lua: &Lua) -> mlua::Result<Function> {
} }
fn create(lua: &Lua) -> mlua::Result<Function> { fn create(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move { lua.create_async_function(|lua, (r#type, url): (LuaString, UrlRef)| async move {
let result = match &*r#type.as_bytes() { let result = match &*r#type.as_bytes() {
b"dir" => provider::create_dir(&*url).await, b"dir" => provider::create_dir(&*url).await,
b"dir_all" => provider::create_dir_all(&*url).await, b"dir_all" => provider::create_dir_all(&*url).await,
@ -132,7 +132,7 @@ fn file(lua: &Lua) -> mlua::Result<Function> {
} }
fn op(lua: &Lua) -> mlua::Result<Function> { fn op(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (name, t): (mlua::String, Table)| match &*name.as_bytes() { lua.create_function(|lua, (name, t): (LuaString, Table)| match &*name.as_bytes() {
b"part" => super::FilesOp::part(lua, t), b"part" => super::FilesOp::part(lua, t),
b"done" => super::FilesOp::done(lua, t), b"done" => super::FilesOp::done(lua, t),
b"size" => super::FilesOp::size(lua, t), b"size" => super::FilesOp::size(lua, t),
@ -162,7 +162,7 @@ fn partitions(lua: &Lua) -> mlua::Result<Function> {
fn read_dir(lua: &Lua) -> mlua::Result<Function> { fn read_dir(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move { lua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move {
let pat = if let Ok(s) = options.raw_get::<mlua::String>("glob") { let pat = if let Ok(s) = options.raw_get::<LuaString>("glob") {
Some(Pattern::from_str(&s.to_str()?)?) Some(Pattern::from_str(&s.to_str()?)?)
} else { } else {
None None
@ -202,7 +202,7 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
} }
fn remove(lua: &Lua) -> mlua::Result<Function> { fn remove(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move { lua.create_async_function(|lua, (r#type, url): (LuaString, UrlRef)| async move {
let result = match &*r#type.as_bytes() { let result = match &*r#type.as_bytes() {
b"file" => provider::remove_file(&*url).await, b"file" => provider::remove_file(&*url).await,
b"dir" => provider::remove_dir(&*url).await, b"dir" => provider::remove_dir(&*url).await,
@ -228,7 +228,7 @@ fn rename(lua: &Lua) -> mlua::Result<Function> {
} }
fn unique(lua: &Lua) -> mlua::Result<Function> { fn unique(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move { lua.create_async_function(|lua, (r#type, url): (LuaString, UrlRef)| async move {
let result = match &*r#type.as_bytes() { let result = match &*r#type.as_bytes() {
b"dir" => yazi_vfs::unique_file(url.clone(), true).await, b"dir" => yazi_vfs::unique_file(url.clone(), true).await,
b"file" => yazi_vfs::unique_file(url.clone(), false).await, b"file" => yazi_vfs::unique_file(url.clone(), false).await,
@ -243,7 +243,7 @@ fn unique(lua: &Lua) -> mlua::Result<Function> {
} }
fn write(lua: &Lua) -> mlua::Result<Function> { fn write(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move { lua.create_async_function(|lua, (url, data): (UrlRef, LuaString)| async move {
match provider::write(&*url, data.as_bytes()).await { match provider::write(&*url, data.as_bytes()).await {
Ok(()) => true.into_lua_multi(&lua), Ok(()) => true.into_lua_multi(&lua),
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),

View file

@ -3,7 +3,7 @@ use yazi_codegen::FromLuaOwned;
use yazi_macro::impl_data_any; use yazi_macro::impl_data_any;
use yazi_shared::url::UrlBuf; use yazi_shared::url::UrlBuf;
#[derive(Clone, FromLuaOwned)] #[derive(Clone, FromLuaOwned, UserData)]
pub(super) struct FilesOp(yazi_fs::FilesOp); pub(super) struct FilesOp(yazi_fs::FilesOp);
impl_data_any!(FilesOp => yazi_fs::FilesOp; from_into_lua = inherit); impl_data_any!(FilesOp => yazi_fs::FilesOp; from_into_lua = inherit);
@ -44,5 +44,3 @@ impl FilesOp {
Ok(Self(yazi_fs::FilesOp::Size(url, sizes.pairs().collect::<mlua::Result<_>>()?))) Ok(Self(yazi_fs::FilesOp::Size(url, sizes.pairs().collect::<mlua::Result<_>>()?)))
} }
} }
impl UserData for FilesOp {}

View file

@ -1,4 +1,4 @@
use mlua::{ExternalResult, Function, Lua, Value}; use mlua::{ExternalResult, Function, Lua, LuaString, Value};
use yazi_binding::runtime; use yazi_binding::runtime;
use yazi_dds::ember::Ember; use yazi_dds::ember::Ember;
use yazi_shared::id::Id; use yazi_shared::id::Id;
@ -7,20 +7,20 @@ pub struct Pubsub;
impl Pubsub { impl Pubsub {
pub(super) fn r#pub(lua: &Lua) -> mlua::Result<Function> { pub(super) fn r#pub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (kind, value): (mlua::String, Value)| { lua.create_function(|lua, (kind, value): (LuaString, Value)| {
yazi_dds::Pubsub::r#pub(Ember::from_lua(lua, &kind.to_str()?, value)?).into_lua_err() yazi_dds::Pubsub::r#pub(Ember::from_lua(lua, &kind.to_str()?, value)?).into_lua_err()
}) })
} }
pub(super) fn pub_to(lua: &Lua) -> mlua::Result<Function> { pub(super) fn pub_to(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (receiver, kind, value): (Id, mlua::String, Value)| { lua.create_function(|lua, (receiver, kind, value): (Id, LuaString, Value)| {
yazi_dds::Pubsub::pub_to(receiver, Ember::from_lua(lua, &kind.to_str()?, value)?) yazi_dds::Pubsub::pub_to(receiver, Ember::from_lua(lua, &kind.to_str()?, value)?)
.into_lua_err() .into_lua_err()
}) })
} }
pub(super) fn sub(lua: &Lua) -> mlua::Result<Function> { pub(super) fn sub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (kind, f): (mlua::String, Function)| { lua.create_function(|lua, (kind, f): (LuaString, Function)| {
let rt = runtime!(lua)?; let rt = runtime!(lua)?;
if !yazi_dds::Pubsub::sub(rt.current()?, &kind.to_str()?, f) { if !yazi_dds::Pubsub::sub(rt.current()?, &kind.to_str()?, f) {
return Err("`sub()` called twice").into_lua_err(); return Err("`sub()` called twice").into_lua_err();
@ -30,7 +30,7 @@ impl Pubsub {
} }
pub(super) fn sub_remote(lua: &Lua) -> mlua::Result<Function> { pub(super) fn sub_remote(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (kind, f): (mlua::String, Function)| { lua.create_function(|lua, (kind, f): (LuaString, Function)| {
let rt = runtime!(lua)?; let rt = runtime!(lua)?;
if !yazi_dds::Pubsub::sub_remote(rt.current()?, &kind.to_str()?, f) { if !yazi_dds::Pubsub::sub_remote(rt.current()?, &kind.to_str()?, f) {
return Err("`sub_remote()` called twice").into_lua_err(); return Err("`sub_remote()` called twice").into_lua_err();
@ -40,14 +40,14 @@ impl Pubsub {
} }
pub(super) fn unsub(lua: &Lua) -> mlua::Result<Function> { pub(super) fn unsub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, kind: mlua::String| { lua.create_function(|lua, kind: LuaString| {
let rt = runtime!(lua)?; let rt = runtime!(lua)?;
Ok(yazi_dds::Pubsub::unsub(rt.current()?, &kind.to_str()?)) Ok(yazi_dds::Pubsub::unsub(rt.current()?, &kind.to_str()?))
}) })
} }
pub(super) fn unsub_remote(lua: &Lua) -> mlua::Result<Function> { pub(super) fn unsub_remote(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, kind: mlua::String| { lua.create_function(|lua, kind: LuaString| {
let rt = runtime!(lua)?; let rt = runtime!(lua)?;
Ok(yazi_dds::Pubsub::unsub_remote(rt.current()?, &kind.to_str()?)) Ok(yazi_dds::Pubsub::unsub_remote(rt.current()?, &kind.to_str()?))
}) })

View file

@ -1,4 +1,4 @@
use mlua::{AnyUserData, UserData, UserDataMethods}; use mlua::{AnyUserData, LuaString, UserData, UserDataMethods};
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::TaskIn; use yazi_scheduler::TaskIn;
@ -9,7 +9,7 @@ pub(crate) struct TaskOpt(pub(crate) yazi_core::tasks::TaskOpt);
impl UserData for TaskOpt { impl UserData for TaskOpt {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("name", |_, (ud, name): (AnyUserData, mlua::String)| { methods.add_function("name", |_, (ud, name): (AnyUserData, LuaString)| {
ud.borrow_mut::<Self>()?.0.set_title(name.to_string_lossy()); ud.borrow_mut::<Self>()?.0.set_title(name.to_string_lossy());
Ok(ud) Ok(ud)
}); });

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, iter}; use std::{borrow::Cow, iter};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, ObjectLike, Table, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, LuaString, ObjectLike, Table, Value};
use tracing::error; use tracing::error;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use yazi_binding::{Permit, PermitRef, elements::{Line, Rect, Span, Wrap}, runtime}; use yazi_binding::{Permit, PermitRef, elements::{Line, Rect, Span, Wrap}, runtime};
@ -12,7 +12,7 @@ use yazi_shim::ratatui::LineIter;
use yazi_term::YIELD_TO_SUBPROCESS; use yazi_term::YIELD_TO_SUBPROCESS;
pub(super) fn area(lua: &Lua) -> mlua::Result<Value> { pub(super) fn area(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|_, s: mlua::String| { let f = lua.create_function(|_, s: LuaString| {
let layout = LAYOUT.get(); let layout = LAYOUT.get();
Ok(match &*s.as_bytes() { Ok(match &*s.as_bytes() {
b"current" => Rect(layout.current), b"current" => Rect(layout.current),
@ -46,7 +46,7 @@ pub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {
} }
pub(super) fn lines(lua: &Lua) -> mlua::Result<Value> { pub(super) fn lines(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|lua, (s, opts): (mlua::String, Table)| { let f = lua.create_function(|lua, (s, opts): (LuaString, Table)| {
let b = s.as_bytes(); let b = s.as_bytes();
let s = &*String::from_utf8_lossy(&b); let s = &*String::from_utf8_lossy(&b);
@ -74,7 +74,7 @@ pub(super) fn lines(lua: &Lua) -> mlua::Result<Value> {
} }
pub(super) fn printable(lua: &Lua) -> mlua::Result<Value> { pub(super) fn printable(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|lua, s: mlua::String| { let f = lua.create_function(|lua, s: LuaString| {
Ok(match replace_to_printable(&s.as_bytes(), false, 1, true) { Ok(match replace_to_printable(&s.as_bytes(), false, 1, true) {
Cow::Borrowed(_) => s, Cow::Borrowed(_) => s,
Cow::Owned(new) => lua.create_external_string(new)?, Cow::Owned(new) => lua.create_external_string(new)?,
@ -86,7 +86,7 @@ pub(super) fn printable(lua: &Lua) -> mlua::Result<Value> {
pub(super) fn redraw(lua: &Lua) -> mlua::Result<Value> { pub(super) fn redraw(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|lua, c: Table| { let f = lua.create_function(|lua, c: Table| {
let id: mlua::String = c.get("_id")?; let id: LuaString = c.get("_id")?;
let mut layout = LAYOUT.get(); let mut layout = LAYOUT.get();
match &*id.as_bytes() { match &*id.as_bytes() {
@ -135,7 +135,7 @@ pub(super) fn truncate(lua: &Lua) -> mlua::Result<Value> {
(idx, last, adv > max) (idx, last, adv > max)
} }
let f = lua.create_function(|lua, (s, t): (mlua::String, Table)| { let f = lua.create_function(|lua, (s, t): (LuaString, Table)| {
let b = s.as_bytes(); let b = s.as_bytes();
if b.is_empty() { if b.is_empty() {
return Ok(s); return Ok(s);

View file

@ -1,6 +1,6 @@
use std::any::TypeId; use std::any::TypeId;
use mlua::{AnyUserData, ExternalError, Function, Lua}; use mlua::{AnyUserData, ExternalError, Function, Lua, LuaString};
use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
use yazi_vfs::provider::RwFile; use yazi_vfs::provider::RwFile;
@ -8,7 +8,7 @@ use super::Utils;
impl Utils { impl Utils {
pub(super) fn id(lua: &Lua) -> mlua::Result<Function> { pub(super) fn id(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, r#type: mlua::String| { lua.create_function(|_, r#type: LuaString| {
Ok(match &*r#type.as_bytes() { Ok(match &*r#type.as_bytes() {
b"app" => *yazi_dds::ID, b"app" => *yazi_dds::ID,
b"ft" => yazi_fs::FILES_TICKET.next(), b"ft" => yazi_fs::FILES_TICKET.next(),

View file

@ -1,4 +1,4 @@
use mlua::{Function, IntoLuaMulti, Lua, LuaSerdeExt, Value}; use mlua::{Function, IntoLuaMulti, Lua, LuaSerdeExt, LuaString, Value};
use yazi_binding::Error; use yazi_binding::Error;
use yazi_shim::mlua::SER_OPT; use yazi_shim::mlua::SER_OPT;
@ -15,7 +15,7 @@ impl Utils {
} }
pub(super) fn json_decode(lua: &Lua) -> mlua::Result<Function> { pub(super) fn json_decode(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, s: mlua::String| async move { lua.create_async_function(|lua, s: LuaString| async move {
match serde_json::from_slice::<serde_json::Value>(&s.as_bytes()) { match serde_json::from_slice::<serde_json::Value>(&s.as_bytes()) {
Ok(v) => lua.to_value_with(&v, SER_OPT)?.into_lua_multi(&lua), Ok(v) => lua.to_value_with(&v, SER_OPT)?.into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Serde(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Serde(e)).into_lua_multi(&lua),

View file

@ -1,6 +1,6 @@
use std::{str::FromStr, time::Duration}; use std::{str::FromStr, time::Duration};
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value}; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, LuaString, Table, Value};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use yazi_binding::{elements::{Line, Text}, runtime}; use yazi_binding::{elements::{Line, Text}, runtime};
use yazi_config::{Platform, keymap::{Chord, ChordArc, Key}, popup::ConfirmCfg}; use yazi_config::{Platform, keymap::{Chord, ChordArc, Key}, popup::ConfirmCfg};
@ -99,7 +99,7 @@ impl Utils {
} }
Value::Table(t) => { Value::Table(t) => {
let mut v = Vec::with_capacity(10); let mut v = Vec::with_capacity(10);
for s in t.sequence_values::<mlua::String>() { for s in t.sequence_values::<LuaString>() {
v.push(Key::from_str(&s?.to_str()?).into_lua_err()?); v.push(Key::from_str(&s?.to_str()?).into_lua_err()?);
} }
v v

View file

@ -1,8 +1,7 @@
use std::{any::TypeId, fmt::Write}; use std::fmt::Write;
use mlua::{Function, Lua, MultiValue, Value}; use mlua::{Function, Lua, MultiValue};
use tracing::{debug, error}; use tracing::{debug, error};
use yazi_shared::{id::Id, path::PathBufDyn, url::UrlBuf};
use super::Utils; use super::Utils;
@ -21,27 +20,8 @@ impl Utils {
if !s.is_empty() { if !s.is_empty() {
s.push(' '); s.push(' ');
} }
Self::format_one(&mut s, value)?; write!(s, "{value:#?}")?;
} }
Ok(s) Ok(s)
} }
fn format_one(buf: &mut String, value: Value) -> anyhow::Result<()> {
let Value::UserData(ud) = &value else {
return Ok(write!(buf, "{value:#?}")?);
};
let id = ud.type_id();
let ptr = ud.to_pointer();
Ok(match id {
Some(t) if t == TypeId::of::<UrlBuf>() => {
write!(buf, "Url({ptr:?}): {:?}", *ud.borrow::<UrlBuf>()?)?
}
Some(t) if t == TypeId::of::<PathBufDyn>() => {
write!(buf, "Path({ptr:?}): {:?}", *ud.borrow::<PathBufDyn>()?)?
}
Some(t) if t == TypeId::of::<Id>() => write!(buf, "Id({ptr:?}): {}", *ud.borrow::<Id>()?)?,
_ => write!(buf, "{value:#?}")?,
})
}
} }

View file

@ -1,6 +1,6 @@
use anyhow::Context; use anyhow::Context;
use futures::future::join_all; use futures::future::join_all;
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic}; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, LuaString, MultiValue, Table, Value, Variadic};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_binding::{Handle, MpscRx, MpscTx, MpscUnboundedRx, MpscUnboundedTx, OneshotRx, OneshotTx, runtime, runtime_mut}; use yazi_binding::{Handle, MpscRx, MpscTx, MpscUnboundedRx, MpscUnboundedTx, OneshotRx, OneshotTx, runtime, runtime_mut};
use yazi_core::{AppProxy, app::PluginOpt}; use yazi_core::{AppProxy, app::PluginOpt};
@ -88,7 +88,7 @@ impl Utils {
} }
pub(super) fn chan(lua: &Lua) -> mlua::Result<Function> { pub(super) fn chan(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (r#type, buffer): (mlua::String, Option<usize>)| { lua.create_function(|lua, (r#type, buffer): (LuaString, Option<usize>)| {
match (&*r#type.as_bytes(), buffer) { match (&*r#type.as_bytes(), buffer) {
(b"mpsc", Some(buffer)) if buffer < 1 => { (b"mpsc", Some(buffer)) if buffer < 1 => {
Err("Buffer size must be greater than 0".into_lua_err()) Err("Buffer size must be greater than 0".into_lua_err())
@ -103,7 +103,7 @@ impl Utils {
} }
(b"oneshot", _) => { (b"oneshot", _) => {
let (tx, rx) = tokio::sync::oneshot::channel::<Value>(); let (tx, rx) = tokio::sync::oneshot::channel::<Value>();
(OneshotTx(Some(tx)), OneshotRx(Some(rx))).into_lua_multi(lua) (OneshotTx(tx), OneshotRx(rx)).into_lua_multi(lua)
} }
_ => Err("Channel type must be `mpsc` or `oneshot`".into_lua_err()), _ => Err("Channel type must be `mpsc` or `oneshot`".into_lua_err()),
} }

View file

@ -1,4 +1,4 @@
use mlua::{ExternalError, FromLua, Function, Lua, Value}; use mlua::{ExternalError, FromLua, Function, Lua, LuaString, Value};
use yazi_core::tasks; use yazi_core::tasks;
use super::Utils; use super::Utils;
@ -6,7 +6,7 @@ use crate::tasks::TaskOpt;
impl Utils { impl Utils {
pub(super) fn task(lua: &Lua) -> mlua::Result<Function> { pub(super) fn task(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (kind, value): (mlua::String, Value)| { lua.create_function(|lua, (kind, value): (LuaString, Value)| {
Ok(TaskOpt(match &*kind.as_bytes() { Ok(TaskOpt(match &*kind.as_bytes() {
b"cut" => tasks::TaskOpt::Cut(<_>::from_lua(value, lua)?), b"cut" => tasks::TaskOpt::Cut(<_>::from_lua(value, lua)?),

View file

@ -1,6 +1,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use mlua::{Function, Lua}; use mlua::{Function, Lua, LuaString};
use twox_hash::XxHash3_128; use twox_hash::XxHash3_128;
use yazi_shim::RFC_3986; use yazi_shim::RFC_3986;
use yazi_widgets::CLIPBOARD; use yazi_widgets::CLIPBOARD;
@ -9,13 +9,13 @@ use super::Utils;
impl Utils { impl Utils {
pub(super) fn hash(lua: &Lua) -> mlua::Result<Function> { pub(super) fn hash(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(move |_, s: mlua::String| async move { lua.create_async_function(move |_, s: LuaString| async move {
Ok(format!("{:x}", XxHash3_128::oneshot(&s.as_bytes()))) Ok(format!("{:x}", XxHash3_128::oneshot(&s.as_bytes())))
}) })
} }
pub(super) fn quote(lua: &Lua) -> mlua::Result<Function> { pub(super) fn quote(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (s, unix): (mlua::String, Option<bool>)| { lua.create_function(|lua, (s, unix): (LuaString, Option<bool>)| {
let b = s.as_bytes(); let b = s.as_bytes();
let s = match unix { let s = match unix {
Some(true) => yazi_shared::shell::unix::escape_os_bytes(&b), Some(true) => yazi_shared::shell::unix::escape_os_bytes(&b),
@ -38,7 +38,7 @@ impl Utils {
} }
pub(super) fn percent_decode(lua: &Lua) -> mlua::Result<Function> { pub(super) fn percent_decode(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, s: mlua::String| { lua.create_function(|lua, s: LuaString| {
let b = s.as_bytes(); let b = s.as_bytes();
match percent_encoding::percent_decode(&b).into() { match percent_encoding::percent_decode(&b).into() {
Cow::Borrowed(_) => Ok(s), Cow::Borrowed(_) => Ok(s),
@ -48,7 +48,7 @@ impl Utils {
} }
pub(super) fn percent_encode(lua: &Lua) -> mlua::Result<Function> { pub(super) fn percent_encode(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, s: mlua::String| { lua.create_function(|lua, s: LuaString| {
let b = s.as_bytes(); let b = s.as_bytes();
match percent_encoding::percent_encode(&b, RFC_3986).into() { match percent_encoding::percent_encode(&b, RFC_3986).into() {
Cow::Borrowed(_) => Ok(s), Cow::Borrowed(_) => Ok(s),

View file

@ -1,11 +1,9 @@
use std::ffi::OsString;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_core::tasks::TaskOpt; use yazi_core::tasks::TaskOpt;
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
use yazi_scheduler::process::ProcessOpt; use yazi_scheduler::process::ShellOpt;
use yazi_shared::{id::Id, url::{UrlBuf, UrlCow}}; use yazi_shared::id::Id;
pub struct TasksProxy; pub struct TasksProxy;
@ -17,24 +15,13 @@ impl TasksProxy {
rx.recv().await.ok_or_else(|| anyhow!("channel closed"))??.try_into() rx.recv().await.ok_or_else(|| anyhow!("channel closed"))??.try_into()
} }
// TODO: remove pub fn process_open(opt: ShellOpt) {
pub fn open_shell_compat(opt: ProcessOpt) { emit!(Call(relay!(tasks:process_open).with_any("opt", opt)));
emit!(Call(relay!(tasks:open_shell_compat).with_any("opt", opt)));
} }
pub async fn process_exec( pub async fn process_exec(opt: ShellOpt) {
cwd: UrlBuf,
cmd: OsString,
args: Vec<UrlCow<'static>>,
block: bool,
orphan: bool,
) {
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
emit!(Call( emit!(Call(relay!(tasks:process_open).with_any("opt", opt).with_replier(tx)));
relay!(tasks:process_open)
.with_any("opt", ProcessOpt { cwd, cmd, args, block, orphan, spread: false })
.with_replier(tx)
));
rx.recv().await; rx.recv().await;
} }
} }

View file

@ -1,6 +1,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use mlua::{AsChunk, ChunkMode}; use mlua::chunk::{AsChunk, ChunkMode};
use yazi_shared::natsort; use yazi_shared::natsort;
pub struct Chunk { pub struct Chunk {

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Deref};
use anyhow::{Context, Result, bail, ensure}; use anyhow::{Context, Result, bail, ensure};
use hashbrown::HashMap; use hashbrown::HashMap;
use mlua::{ChunkMode, ExternalError, Lua, Table}; use mlua::{ExternalError, Lua, Table, chunk::ChunkMode};
use parking_lot::RwLock; use parking_lot::RwLock;
use yazi_fs::{Xdg, provider::local::Local}; use yazi_fs::{Xdg, provider::local::Local};
use yazi_macro::plugin_preset as preset; use yazi_macro::plugin_preset as preset;

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
use mlua::{ExternalResult, Function, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, Table, Value}; use mlua::{ExternalResult, Function, IntoLua, Lua, LuaString, MetaMethod, MultiValue, ObjectLike, Table, Value};
use yazi_binding::{runtime, runtime_mut}; use yazi_binding::{runtime, runtime_mut};
use super::LOADER; use super::LOADER;
@ -11,7 +11,7 @@ impl Require {
pub(super) fn install(lua: &Lua) -> mlua::Result<()> { pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
lua.globals().raw_set( lua.globals().raw_set(
"require", "require",
lua.create_async_function(|lua, id: mlua::String| async move { lua.create_async_function(|lua, id: LuaString| async move {
let id = id.to_str()?; let id = id.to_str()?;
let id = Self::absolute_id(&lua, &id)?; let id = Self::absolute_id(&lua, &id)?;
LOADER.ensure(&id, |_| ()).await.into_lua_err()?; LOADER.ensure(&id, |_| ()).await.into_lua_err()?;
@ -30,7 +30,7 @@ impl Require {
let mt = lua.create_table_from([ let mt = lua.create_table_from([
( (
MetaMethod::Index.name(), MetaMethod::Index.name(),
lua.create_function(move |lua, (ts, key): (Table, mlua::String)| { lua.create_function(move |lua, (ts, key): (Table, LuaString)| {
match ts.raw_get::<Table>("__mod")?.raw_get::<Value>(&key)? { match ts.raw_get::<Table>("__mod")?.raw_get::<Value>(&key)? {
Value::Function(_) => { Value::Function(_) => {
Self::create_wrapper(lua, id.clone(), &key.to_str()?)?.into_lua(lua) Self::create_wrapper(lua, id.clone(), &key.to_str()?)?.into_lua(lua)
@ -41,7 +41,7 @@ impl Require {
), ),
( (
MetaMethod::NewIndex.name(), MetaMethod::NewIndex.name(),
lua.create_function(move |_, (ts, key, value): (Table, mlua::String, Value)| { lua.create_function(move |_, (ts, key, value): (Table, LuaString, Value)| {
ts.raw_get::<Table>("__mod")?.raw_set(key, value) ts.raw_get::<Table>("__mod")?.raw_set(key, value)
})?, })?,
), ),

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::OsString}; use std::{borrow::Cow, ffi::OsString};
use yazi_shared::{CompletionToken, id::Id, url::{UrlBuf, UrlCow}}; use yazi_shared::{CompletionToken, id::Id, url::UrlBuf};
use super::ShellOpt; use super::ShellOpt;
use crate::{TaskIn, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}}; use crate::{TaskIn, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
@ -46,10 +46,9 @@ impl TaskIn for ProcessIn {
// --- Block // --- Block
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ProcessInBlock { pub(crate) struct ProcessInBlock {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
} }
impl TaskIn for ProcessInBlock { impl TaskIn for ProcessInBlock {
@ -67,17 +66,16 @@ impl TaskIn for ProcessInBlock {
impl From<ProcessInBlock> for ShellOpt { impl From<ProcessInBlock> for ShellOpt {
fn from(r#in: ProcessInBlock) -> Self { fn from(r#in: ProcessInBlock) -> Self {
Self { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: false, orphan: false } Self { cwd: r#in.cwd, cmd: r#in.cmd, block: true, orphan: false }
} }
} }
// --- Orphan // --- Orphan
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ProcessInOrphan { pub(crate) struct ProcessInOrphan {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
} }
impl TaskIn for ProcessInOrphan { impl TaskIn for ProcessInOrphan {
@ -95,7 +93,7 @@ impl TaskIn for ProcessInOrphan {
impl From<ProcessInOrphan> for ShellOpt { impl From<ProcessInOrphan> for ShellOpt {
fn from(r#in: ProcessInOrphan) -> Self { fn from(r#in: ProcessInOrphan) -> Self {
Self { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: false, orphan: true } Self { cwd: r#in.cwd, cmd: r#in.cmd, block: false, orphan: true }
} }
} }
@ -105,7 +103,6 @@ pub(crate) struct ProcessInBg {
pub(crate) id: Id, pub(crate) id: Id,
pub(crate) cwd: UrlBuf, pub(crate) cwd: UrlBuf,
pub(crate) cmd: OsString, pub(crate) cmd: OsString,
pub(crate) args: Vec<UrlCow<'static>>,
pub(crate) done: CompletionToken, pub(crate) done: CompletionToken,
} }
@ -124,6 +121,6 @@ impl TaskIn for ProcessInBg {
impl From<ProcessInBg> for ShellOpt { impl From<ProcessInBg> for ShellOpt {
fn from(r#in: ProcessInBg) -> Self { fn from(r#in: ProcessInBg) -> Self {
Self { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: true, orphan: false } Self { cwd: r#in.cwd, cmd: r#in.cmd, block: false, orphan: false }
} }
} }

View file

@ -1,4 +1,4 @@
#[macro_use] #[macro_use]
mod macros; mod macros;
yazi_macro::mod_flat!(option out process progress r#in shell); yazi_macro::mod_flat!(out process progress r#in shell);

View file

@ -1,18 +0,0 @@
use std::ffi::OsString;
use yazi_macro::impl_data_any;
use yazi_shared::url::{UrlBuf, UrlCow};
// TODO: remove in favor of ShellForm
#[derive(Clone, Debug)]
pub struct ProcessOpt {
pub cwd: UrlBuf,
pub cmd: OsString,
pub args: Vec<UrlCow<'static>>,
pub block: bool,
pub orphan: bool,
pub spread: bool, // TODO: remove
}
impl_data_any!(ProcessOpt);

View file

@ -54,14 +54,8 @@ impl Process {
} }
pub(crate) async fn bg(&self, task: ProcessInBg) -> Result<(), ProcessOutBg> { pub(crate) async fn bg(&self, task: ProcessInBg) -> Result<(), ProcessOutBg> {
let mut child = super::shell(ShellOpt { let mut child =
cwd: task.cwd, super::shell(ShellOpt { cwd: task.cwd, cmd: task.cmd, block: false, orphan: false }).await?;
cmd: task.cmd,
args: task.args,
piped: true,
orphan: false,
})
.await?;
let done = task.done; let done = task.done;
let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines(); let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines();

View file

@ -3,25 +3,28 @@ use std::{ffi::OsString, process::Stdio};
use anyhow::Result; use anyhow::Result;
use tokio::process::{Child, Command}; use tokio::process::{Child, Command};
use yazi_fs::Cwd; use yazi_fs::Cwd;
use yazi_shared::url::{AsUrl, UrlBuf, UrlCow}; use yazi_macro::impl_data_any;
use yazi_shared::url::{AsUrl, UrlBuf};
pub(crate) struct ShellOpt { #[derive(Clone, Debug)]
pub(crate) cwd: UrlBuf, pub struct ShellOpt {
pub(crate) cmd: OsString, pub cwd: UrlBuf,
pub(crate) args: Vec<UrlCow<'static>>, pub cmd: OsString,
pub(crate) piped: bool, pub block: bool,
pub(crate) orphan: bool, pub orphan: bool,
} }
impl_data_any!(ShellOpt);
impl ShellOpt { impl ShellOpt {
#[inline] #[inline]
fn stdio(&self) -> Stdio { fn stdio(&self) -> Stdio {
if self.orphan { if self.block {
Stdio::null()
} else if self.piped {
Stdio::piped()
} else {
Stdio::inherit() Stdio::inherit()
} else if self.orphan {
Stdio::null()
} else {
Stdio::piped()
} }
} }
} }
@ -32,21 +35,16 @@ pub(crate) async fn shell(opt: ShellOpt) -> Result<Child> {
#[cfg(unix)] #[cfg(unix)]
return Ok(unsafe { return Ok(unsafe {
use yazi_fs::FsUrl;
use yazi_shared::url::AsUrl;
Command::new("sh") Command::new("sh")
.stdin(opt.stdio()) .stdin(opt.stdio())
.stdout(opt.stdio()) .stdout(opt.stdio())
.stderr(opt.stdio()) .stderr(opt.stdio())
.arg("-c") .arg("-c")
.arg(opt.cmd) .arg(opt.cmd)
// TODO: remove
.args(opt.args.iter().map(|u| u.as_url().unified_path_str()))
.current_dir(cwd) .current_dir(cwd)
.kill_on_drop(!opt.orphan) .kill_on_drop(!opt.orphan)
.pre_exec(move || { .pre_exec(move || {
if (opt.piped || opt.orphan) && libc::setsid() < 0 { if !opt.block && libc::setsid() < 0 {
return Err(std::io::Error::last_os_error()); return Err(std::io::Error::last_os_error());
} }
Ok(()) Ok(())

View file

@ -5,7 +5,7 @@ use yazi_config::{YAZI, plugin::{FetcherArc, PreloaderArc}};
use yazi_fs::FsHash64; use yazi_fs::FsHash64;
use yazi_shared::{CompletionToken, Throttle, id::Id, url::{UrlBuf, UrlLike}}; use yazi_shared::{CompletionToken, Throttle, id::Id, url::{UrlBuf, UrlLike}};
use crate::{Behavior, HIGH, LOW, NORMAL, Task, TaskIn, TaskProg, Worker, fetch::FetchIn, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInPreload, HookInTrash, HookInUpload}, plugin::PluginInEntry, preload::PreloadIn, process::{ProcessIn, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOpt}, size::SizeIn}; use crate::{Behavior, HIGH, LOW, NORMAL, Task, TaskIn, TaskProg, Worker, fetch::FetchIn, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInPreload, HookInTrash, HookInUpload}, plugin::PluginInEntry, preload::PreloadIn, process::{ProcessIn, ProcessInBg, ProcessInBlock, ProcessInOrphan, ShellOpt}, size::SizeIn};
pub struct Scheduler { pub struct Scheduler {
pub worker: Worker, pub worker: Worker,
@ -227,20 +227,14 @@ impl Scheduler {
} }
} }
pub fn process_open(&self, opt: ProcessOpt) -> CompletionToken { pub fn process_open(&self, opt: ShellOpt) -> CompletionToken {
let mut r#in: ProcessIn = if opt.block { let mut r#in: ProcessIn = if opt.block {
ProcessInBlock { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }.into() ProcessInBlock { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd }.into()
} else if opt.orphan { } else if opt.orphan {
ProcessInOrphan { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }.into() ProcessInOrphan { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd }.into()
} else { } else {
ProcessInBg { ProcessInBg { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd, done: CompletionToken::default() }
id: Id::ZERO, .into()
cwd: opt.cwd,
cmd: opt.cmd,
args: opt.args,
done: CompletionToken::default(),
}
.into()
}; };
let done = match &mut r#in { let done = match &mut r#in {

View file

@ -26,7 +26,7 @@ futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
memchr = "2.8.2" memchr = "2.8.3"
mlua = { workspace = true } mlua = { workspace = true }
ordered-float = { workspace = true } ordered-float = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -2,7 +2,5 @@ use mlua::UserData;
use crate::data::DataAny; use crate::data::DataAny;
#[derive(Debug)] #[derive(Debug, UserData)]
pub struct AnyData(pub Box<dyn DataAny>); pub struct AnyData(pub Box<dyn DataAny>);
impl UserData for AnyData {}

View file

@ -1,6 +1,6 @@
use mlua::{ExternalError, ExternalResult, FromLua, Lua, UserData, UserDataFields, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
use crate::id::Id; use crate::{LOG_LEVEL, id::Id};
impl FromLua for Id { impl FromLua for Id {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
@ -16,4 +16,12 @@ impl UserData for Id {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("value", |_, me| Ok(me.get())); fields.add_field_method_get("value", |_, me| Ok(me.get()));
} }
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
if !LOG_LEVEL.get().is_none() {
methods.add_meta_function(MetaMethod::ToDebugString, |_, ud: AnyUserData| {
Ok(format!("Id({:?}): {}", ud.to_pointer(), *ud.borrow::<Self>()?))
});
}
}
} }

View file

@ -1,4 +1,4 @@
use mlua::{ExternalError, ExternalResult, FromLua, MetaMethod, UserData, UserDataMethods, Value}; use mlua::{ExternalError, ExternalResult, FromLua, Lua, MetaMethod, UserData, UserDataMethods, Value};
use serde::Deserialize; use serde::Deserialize;
use strum::{Display, EnumString, FromRepr, IntoStaticStr}; use strum::{Display, EnumString, FromRepr, IntoStaticStr};
use yazi_shim::strum::IntoStr; use yazi_shim::strum::IntoStr;
@ -41,7 +41,7 @@ impl Layer {
} }
impl FromLua for Layer { impl FromLua for Layer {
fn from_lua(value: Value, _: &mlua::Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value { Ok(match value {
Value::String(s) => s.to_str()?.parse().into_lua_err()?, Value::String(s) => s.to_str()?.parse().into_lua_err()?,
Value::UserData(ud) => *ud.borrow::<Self>()?, Value::UserData(ud) => *ud.borrow::<Self>()?,

View file

@ -1,7 +1,7 @@
use mlua::{ExternalError, ExternalResult, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, Lua, LuaString, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_shim::mlua::UserDataFieldsExt; use yazi_shim::mlua::UserDataFieldsExt;
use crate::{path::{PathBufDyn, PathLike, StripPrefixError}, strand::{AsStrand, StrandCow}}; use crate::{LOG_LEVEL, path::{PathBufDyn, PathLike, StripPrefixError}, strand::{AsStrand, StrandCow}};
pub type PathRef = UserDataRef<PathBufDyn>; pub type PathRef = UserDataRef<PathBufDyn>;
@ -11,7 +11,7 @@ impl PathBufDyn {
"Path", "Path",
lua.create_table_from([( lua.create_table_from([(
"os", "os",
lua.create_function(|_, s: mlua::String| { lua.create_function(|_, s: LuaString| {
Ok(Self::from(s.as_bytes().as_strand().as_os_path().into_lua_err()?)) Ok(Self::from(s.as_bytes().as_strand().as_os_path().into_lua_err()?))
})?, })?,
)])?, )])?,
@ -89,11 +89,17 @@ impl UserData for PathBufDyn {
methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base)); methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base));
methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base)); methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base));
methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| { methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: LuaString| {
lua.create_external_string([lhs.encoded_bytes(), &rhs.as_bytes()].concat()) lua.create_external_string([lhs.encoded_bytes(), &rhs.as_bytes()].concat())
}); });
methods.add_meta_method(MetaMethod::Eq, |_, me, other: PathRef| Ok(*me == *other)); methods.add_meta_method(MetaMethod::Eq, |_, me, other: PathRef| Ok(*me == *other));
methods methods
.add_meta_method(MetaMethod::ToString, |lua, me, ()| lua.create_string(me.encoded_bytes())); .add_meta_method(MetaMethod::ToString, |lua, me, ()| lua.create_string(me.encoded_bytes()));
if !LOG_LEVEL.get().is_none() {
methods.add_meta_function(MetaMethod::ToDebugString, |_, ud: AnyUserData| {
Ok(format!("Path({:?}): {:?}", ud.to_pointer(), *ud.borrow::<Self>()?))
});
}
} }
} }

View file

@ -1,7 +1,7 @@
use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, LuaString, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value};
use yazi_shim::mlua::UserDataFieldsExt; use yazi_shim::mlua::UserDataFieldsExt;
use crate::{path::{PathBufDyn, PathLike, StripPrefixError}, scheme::{SchemeCow, SchemeLike}, strand::{StrandLike, ToStrand}, url::{UrlBuf, UrlBufInventory, UrlCow, UrlLike}}; use crate::{LOG_LEVEL, path::{PathBufDyn, PathLike, StripPrefixError}, scheme::{SchemeCow, SchemeLike}, strand::{StrandLike, ToStrand}, url::{UrlBuf, UrlBufInventory, UrlCow, UrlLike}};
pub type UrlRef = UserDataRef<UrlBuf>; pub type UrlRef = UserDataRef<UrlBuf>;
@ -124,17 +124,23 @@ impl UserData for UrlBuf {
methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base)); methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base));
methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base)); methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base));
methods.add_function("into_search", |_, (ud, domain): (AnyUserData, mlua::String)| { methods.add_method_once("into_search", |_, me, domain: LuaString| {
ud.take::<Self>()?.into_search(domain.to_str()?).into_lua_err() me.into_search(domain.to_str()?).into_lua_err()
}); });
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(*me == *other)); methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(*me == *other));
methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| { methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {
lua.create_string(me.to_strand().encoded_bytes()) lua.create_string(me.to_strand().encoded_bytes())
}); });
methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| { methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: LuaString| {
lua.create_external_string([lhs.to_strand().encoded_bytes(), &rhs.as_bytes()].concat()) lua.create_external_string([lhs.to_strand().encoded_bytes(), &rhs.as_bytes()].concat())
}); });
if !LOG_LEVEL.get().is_none() {
methods.add_meta_function(MetaMethod::ToDebugString, |_, ud: AnyUserData| {
Ok(format!("Url({:?}): {:?}", ud.to_pointer(), *ud.borrow::<Self>()?))
});
}
} }
fn register(registry: &mut UserDataRegistry<Self>) { fn register(registry: &mut UserDataRegistry<Self>) {

View file

@ -1,4 +1,4 @@
use mlua::SerializeOptions; use mlua::serde::SerializeOptions;
pub const SER_OPT: SerializeOptions = pub const SER_OPT: SerializeOptions =
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false); SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);

View file

@ -1,12 +1,12 @@
use std::{fmt, ops::Deref}; use std::{fmt, ops::Deref};
use mlua::{FromLua, IntoLua, Lua, Value}; use mlua::{FromLua, IntoLua, Lua, LuaString, Value};
#[derive(Clone)] #[derive(Clone)]
pub struct ByteString(mlua::String); pub struct ByteString(LuaString);
impl Deref for ByteString { impl Deref for ByteString {
type Target = mlua::String; type Target = LuaString;
fn deref(&self) -> &Self::Target { &self.0 } fn deref(&self) -> &Self::Target { &self.0 }
} }
@ -17,7 +17,7 @@ impl fmt::Display for ByteString {
impl FromLua for ByteString { impl FromLua for ByteString {
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
Ok(Self(mlua::String::from_lua(value, lua)?)) Ok(Self(LuaString::from_lua(value, lua)?))
} }
} }

View file

@ -21,6 +21,7 @@ yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
anyhow = { workspace = true } anyhow = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
bitflags = { workspace = true } bitflags = { workspace = true }
compact_str = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -1,41 +1,39 @@
use bitflags::bitflags; use bitflags::bitflags;
use compact_str::CompactString;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ParseError, Result, bail, event::Modifiers}; use crate::{ParseError, Result, bail, event::Modifiers};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)] #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct KeyEvent { pub struct KeyEvent {
#[serde(flatten)] #[serde(flatten)]
pub code: KeyCode, pub code: KeyCode,
pub kind: KeyEventKind, pub kind: KeyEventKind,
pub modifiers: Modifiers, pub modifiers: Modifiers,
pub state: KeyEventState, pub state: KeyEventState,
#[serde(skip_serializing_if = "CompactString::is_empty")]
pub text: CompactString,
} }
impl KeyEvent { impl KeyEvent {
pub const fn new(code: KeyCode, modifiers: Modifiers) -> Self { pub fn new(code: KeyCode, modifiers: Modifiers) -> Self {
Self { code, kind: KeyEventKind::Press, modifiers, state: KeyEventState::empty() } Self { code, modifiers, ..Default::default() }
} }
pub fn plain(&self) -> Option<char> { pub fn text<'a>(&'a self, buf: &'a mut [u8; 4]) -> Option<&'a str> {
use Modifiers as M; use Modifiers as M;
match self.code { match self.code {
KeyCode::Char(c) if !self.modifiers.intersects(M::CONTROL | M::ALT | M::SUPER) => Some(c), _ if self.modifiers.intersects(M::CONTROL | M::ALT | M::SUPER) => None,
KeyCode::Char(c) if self.text.is_empty() => Some(c.encode_utf8(buf)),
KeyCode::Char(_) | KeyCode::Null if !self.text.is_empty() => Some(&self.text),
_ => None, _ => None,
} }
} }
} }
impl From<KeyCode> for KeyEvent { impl From<KeyCode> for KeyEvent {
fn from(code: KeyCode) -> Self { fn from(code: KeyCode) -> Self { Self { code, ..Default::default() } }
Self {
code,
kind: KeyEventKind::Press,
modifiers: Modifiers::empty(),
state: KeyEventState::empty(),
}
}
} }
// --- Kind // --- Kind
@ -116,6 +114,8 @@ pub enum KeyCode {
} }
impl KeyCode { impl KeyCode {
pub fn implies_shift(self) -> bool { matches!(self, Self::Char(c) if c.is_uppercase()) }
pub(crate) fn from_xterm_modifier(r#final: u8) -> Result<Self> { pub(crate) fn from_xterm_modifier(r#final: u8) -> Result<Self> {
Ok(match r#final { Ok(match r#final {
b'A' => Self::Up, b'A' => Self::Up,
@ -138,6 +138,7 @@ impl KeyCode {
} }
let code = match char::from_u32(codepoint).ok_or(ParseError::Invalid)? { let code = match char::from_u32(codepoint).ok_or(ParseError::Invalid)? {
'\0' => Self::Null,
'\x1B' => Self::Escape, '\x1B' => Self::Escape,
'\r' => Self::Enter, '\r' => Self::Enter,
'\t' => Self::Tab, '\t' => Self::Tab,

View file

@ -1,5 +1,7 @@
use std::str::{self, FromStr}; use std::str::{self, FromStr};
use compact_str::CompactString;
use super::parser::Parser; use super::parser::Parser;
use crate::{ParseError, Result, bail, event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, Modifiers, MouseEvent, MouseEventKind}}; use crate::{ParseError, Result, bail, event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, Modifiers, MouseEvent, MouseEventKind}};
@ -84,8 +86,10 @@ impl Parser {
let (mut code, state_from_keycode) = KeyCode::from_codepoint(parse_next(&mut codepoints)?)?; let (mut code, state_from_keycode) = KeyCode::from_codepoint(parse_next(&mut codepoints)?)?;
let (mut modifiers, kind, state_from_modifiers) = parse_mks(&mut it).unwrap_or_default(); let (mut modifiers, kind, state_from_modifiers) = parse_mks(&mut it).unwrap_or_default();
let text = parse_text(it.next().unwrap_or_default())?;
if let KeyCode::Modifier(c) = code if let KeyCode::Modifier(c) = code
&& kind != KeyEventKind::Release
&& let Some(m) = c.to_modifier() && let Some(m) = c.to_modifier()
{ {
modifiers |= m; modifiers |= m;
@ -97,9 +101,10 @@ impl Parser {
// which contains the shifted character according to the keyboard layout. // which contains the shifted character according to the keyboard layout.
if modifiers.contains(Modifiers::SHIFT) if modifiers.contains(Modifiers::SHIFT)
&& let Ok(Some(shifted)) = parse_next(&mut codepoints).map(char::from_u32) && let Ok(Some(shifted)) = parse_next(&mut codepoints).map(char::from_u32)
&& code != KeyCode::Char(shifted)
{ {
code = KeyCode::Char(shifted); code = KeyCode::Char(shifted);
modifiers.remove(Modifiers::SHIFT); modifiers.set(Modifiers::SHIFT, code.implies_shift());
} }
Ok(Event::Key(KeyEvent { Ok(Event::Key(KeyEvent {
@ -107,6 +112,7 @@ impl Parser {
modifiers, modifiers,
kind, kind,
state: state_from_keycode | state_from_modifiers, state: state_from_keycode | state_from_modifiers,
text,
})) }))
} }
@ -123,7 +129,7 @@ impl Parser {
let (modifiers, kind, _) = parse_mks(&mut it).unwrap_or_default(); let (modifiers, kind, _) = parse_mks(&mut it).unwrap_or_default();
let code = KeyCode::from_xterm_modifier(seq[seq.len() - 1])?; let code = KeyCode::from_xterm_modifier(seq[seq.len() - 1])?;
Ok(Event::Key(KeyEvent { code, modifiers, kind, state: KeyEventState::empty() })) Ok(Event::Key(KeyEvent { code, modifiers, kind, ..Default::default() }))
} }
/// Parses legacy `CSI modifier final` - no semicolon, modifier digit /// Parses legacy `CSI modifier final` - no semicolon, modifier digit
@ -138,10 +144,9 @@ impl Parser {
} }
Ok(Event::Key(KeyEvent { Ok(Event::Key(KeyEvent {
code: KeyCode::from_xterm_modifier(seq[seq.len() - 1])?, code: KeyCode::from_xterm_modifier(seq[seq.len() - 1])?,
modifiers: Modifiers::from_vt_mask(modifier - b'0'), modifiers: Modifiers::from_vt_mask(modifier - b'0'),
kind: KeyEventKind::Press, ..Default::default()
state: KeyEventState::empty(),
})) }))
} }
@ -173,7 +178,7 @@ impl Parser {
_ => bail!(), _ => bail!(),
}; };
let event = Event::Key(KeyEvent { code, modifiers, kind, state }); let event = Event::Key(KeyEvent { code, modifiers, kind, state, ..Default::default() });
Ok(event) Ok(event)
} }
@ -277,3 +282,13 @@ where
KeyEventState::from_vt_mask(mask), KeyEventState::from_vt_mask(mask),
)) ))
} }
fn parse_text(s: &str) -> Result<CompactString> {
if s.is_empty() {
return Ok(CompactString::default());
}
s.split(':')
.map(|codepoint| char::from_u32(codepoint.parse()?).ok_or(ParseError::Invalid))
.collect()
}

View file

@ -51,13 +51,6 @@ impl Display for RequestCursorBlink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b[?12$p") } fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b[?12$p") }
} }
/// Request keyboard enhancement flags (CSI u query)
pub struct RequestKeyboardFlags;
impl Display for RequestKeyboardFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b[?u") }
}
/// Device Status Report (DSR) /// Device Status Report (DSR)
pub struct RequestDeviceStatus; pub struct RequestDeviceStatus;

View file

@ -1,4 +1,4 @@
use std::{io::{self, Write}, ops::Deref}; use std::{io, ops::Deref};
use anyhow::Result; use anyhow::Result;
use ratatui_core::{buffer::Buffer, layout::Rect, terminal::{CompletedFrame, Frame, Terminal}}; use ratatui_core::{buffer::Buffer, layout::Rect, terminal::{CompletedFrame, Frame, Terminal}};
@ -8,7 +8,7 @@ use yazi_macro::writef;
use yazi_proxy::AppProxy; use yazi_proxy::AppProxy;
use yazi_shim::cell::SyncCell; use yazi_shim::cell::SyncCell;
use yazi_term::{TERM, event::{Event, KeyEventKind}, stream::EventStream}; use yazi_term::{TERM, event::{Event, KeyEventKind}, stream::EventStream};
use yazi_tty::{TTY, TtyWriter, sequence::{DisableBracketedPaste, DisableDrag, DisableDrop, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableDrag, EnableDrop, EnableFocusChange, EnableMouseCapture, EnterAlternateScreen, If, LeaveAlternateScreen, PopKeyboardFlags, PushKeyboardFlags, RequestCursorBlink, RequestCursorStyle, RequestDA1, RequestKeyboardFlags, RestoreCursorStyle, SetTitle, ShowCursor}}; use yazi_tty::{TTY, TtyWriter, sequence::{DisableBracketedPaste, DisableDrag, DisableDrop, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableDrag, EnableDrop, EnableFocusChange, EnableMouseCapture, EnterAlternateScreen, If, LeaveAlternateScreen, PopKeyboardFlags, PushKeyboardFlags, RequestCursorBlink, RequestCursorStyle, RequestDA1, RestoreCursorStyle, SetTitle, ShowCursor}};
use crate::{RatermBackend, RatermOption, RatermState}; use crate::{RatermBackend, RatermOption, RatermState};
@ -43,9 +43,12 @@ impl Raterm {
let opt = RatermOption::default(); let opt = RatermOption::default();
writef!( writef!(
TTY.writer(), TTY.writer(),
"{}{RequestCursorStyle}{RequestCursorBlink}{RequestKeyboardFlags}{RequestDA1}{}{EnableBracketedPaste}{EnableFocusChange}{}{}{}", "{}{RequestCursorStyle}{RequestCursorBlink}{RequestDA1}{}{EnableBracketedPaste}{EnableFocusChange}{}{}{}{}",
If(!TMUX.get(), EnterAlternateScreen), If(!TMUX.get(), EnterAlternateScreen),
If(TMUX.get(), EnterAlternateScreen), If(TMUX.get(), EnterAlternateScreen),
PushKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES
| PushKeyboardFlags::REPORT_ALTERNATE_KEYS
| PushKeyboardFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES,
EnableDrag(""), EnableDrag(""),
EnableDrop(&["text/uri-list"]), EnableDrop(&["text/uri-list"]),
If(opt.mouse, EnableMouseCapture), If(opt.mouse, EnableMouseCapture),
@ -55,14 +58,6 @@ impl Raterm {
Mux::tmux_drain()?; Mux::tmux_drain()?;
STATE.set(RatermState::new(&resp, &opt)); STATE.set(RatermState::new(&resp, &opt));
if STATE.get().csi_u {
write!(
TTY.writer(),
"{}",
PushKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES | PushKeyboardFlags::REPORT_ALTERNATE_KEYS,
)?;
}
let mut term = Self { let mut term = Self {
inner: Terminal::new(RatermBackend::new(TTY.writer()))?, inner: Terminal::new(RatermBackend::new(TTY.writer()))?,
stream: EventStream::from(&*TERM), stream: EventStream::from(&*TERM),
@ -82,9 +77,8 @@ impl Raterm {
_ = writef!( _ = writef!(
TTY.writer(), TTY.writer(),
"{}{DisableDrop}{DisableDrag}{}{}{}{DisableFocusChange}{DisableBracketedPaste}{LeaveAlternateScreen}{ShowCursor}", "{}{PopKeyboardFlags}{DisableDrop}{DisableDrag}{}{}{DisableFocusChange}{DisableBracketedPaste}{LeaveAlternateScreen}{ShowCursor}",
If(state.mouse, DisableMouseCapture), If(state.mouse, DisableMouseCapture),
If(state.csi_u, PopKeyboardFlags),
RestoreCursorStyle { shape: state.cursor_shape, blink: state.cursor_blink }, RestoreCursorStyle { shape: state.cursor_shape, blink: state.cursor_blink },
If(state.title, SetTitle("")), If(state.title, SetTitle("")),
); );
@ -100,7 +94,7 @@ impl Raterm {
loop { loop {
match rx.recv().await { match rx.recv().await {
Some(Ok(event)) => match event { Some(Ok(event)) => match event {
Event::Key(key) if key.kind != KeyEventKind::Press => continue, Event::Key(key) if key.kind == KeyEventKind::Release => continue,
Event::Mouse(mouse) if !YAZI.mgr.mouse_events.get().contains(mouse.kind.into()) => { Event::Mouse(mouse) if !YAZI.mgr.mouse_events.get().contains(mouse.kind.into()) => {
continue; continue;
} }

View file

@ -2,7 +2,6 @@ use crate::RatermOption;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct RatermState { pub struct RatermState {
pub csi_u: bool,
pub mouse: bool, pub mouse: bool,
pub title: bool, pub title: bool,
pub cursor_shape: u8, pub cursor_shape: u8,
@ -11,18 +10,10 @@ pub struct RatermState {
impl RatermState { impl RatermState {
pub(super) const fn default() -> Self { pub(super) const fn default() -> Self {
Self { Self { mouse: false, title: false, cursor_shape: 0, cursor_blink: false }
csi_u: false,
mouse: false,
title: false,
cursor_shape: 0,
cursor_blink: false,
}
} }
pub(super) fn new(resp: &str, opt: &RatermOption) -> Self { pub(super) fn new(resp: &str, opt: &RatermOption) -> Self {
let csi_u = resp.contains("\x1b[?0u");
let cursor_shape = resp let cursor_shape = resp
.split_once("\x1bP1$r") .split_once("\x1bP1$r")
.and_then(|(_, s)| s.bytes().next()) .and_then(|(_, s)| s.bytes().next())
@ -31,6 +22,6 @@ impl RatermState {
let cursor_blink = resp.contains("\x1b[?12;1$y"); let cursor_blink = resp.contains("\x1b[?12;1$y");
Self { csi_u, mouse: opt.mouse, title: false, cursor_shape, cursor_blink } Self { mouse: opt.mouse, title: false, cursor_shape, cursor_blink }
} }
} }

View file

@ -1,4 +1,4 @@
use mlua::{IntoLuaMulti, UserData, UserDataMethods, Value}; use mlua::{IntoLuaMulti, LuaString, UserData, UserDataMethods, Value};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use yazi_binding::Error; use yazi_binding::Error;
@ -22,7 +22,7 @@ impl UserData for RwFile {
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }
}); });
methods.add_async_method_mut("write_all", |lua, mut me, src: mlua::String| async move { methods.add_async_method_mut("write_all", |lua, mut me, src: LuaString| async move {
match me.write_all(&src.as_bytes()).await { match me.write_all(&src.as_bytes()).await {
Ok(()) => true.into_lua_multi(&lua), Ok(()) => true.into_lua_multi(&lua),
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),

View file

@ -6,14 +6,15 @@ use yazi_term::event::KeyEvent;
use crate::input::{Input, InputMode}; use crate::input::{Input, InputMode};
impl Input { impl Input {
pub fn r#type(&mut self, key: KeyEvent) -> Result<bool> { pub fn r#type(&mut self, key: &KeyEvent) -> Result<bool> {
let Some(c) = key.plain() else { return Ok(false) }; let mut buf = [0; 4];
let Some(text) = key.text(&mut buf) else { return Ok(false) };
if self.mode() == InputMode::Insert { if self.mode() == InputMode::Insert {
self.type_str(c.encode_utf8(&mut [0; 4]))?; self.type_str(text)?;
return Ok(true); return Ok(true);
} else if self.mode() == InputMode::Replace { } else if self.mode() == InputMode::Replace {
self.replace_str(c.encode_utf8(&mut [0; 4]))?; self.replace_str(text)?;
return Ok(true); return Ok(true);
} }