From c0f96f553bbd7f94cf6af1681de4fb2f9e1da275 Mon Sep 17 00:00:00 2001 From: SuniRein <64690248+SuniRein@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:00:58 +0800 Subject: [PATCH] feat: new `sort_fallback` option to control fallback sorting behavior (#3077) Co-authored-by: sxyazi --- CHANGELOG.md | 4 +- Cargo.lock | 283 ++++++++++++++++++++++----- Cargo.toml | 4 +- yazi-actor/src/lives/preference.rs | 1 + yazi-actor/src/mgr/sort.rs | 1 + yazi-config/preset/yazi-default.toml | 1 + yazi-config/src/mgr/mgr.rs | 3 +- yazi-core/src/tab/preference.rs | 5 +- yazi-fs/Cargo.toml | 2 +- yazi-fs/src/sorter.rs | 122 ++++++------ yazi-fs/src/sorting.rs | 29 ++- yazi-parser/src/mgr/sort.rs | 4 +- yazi-plugin/src/runtime/runtime.rs | 1 + 13 files changed, 346 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e10722..0e036620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): - Custom tab name ([#3666]) - Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638]) +- New `sort_fallback` option to control fallback sorting behavior ([#3077]) - New `fs.access()` API to access the filesystem ([#3668]) -- New `relay-notify-push` DDS event to allow custom notification handlers ([#3642]) +- New `relay-notify-push` DDS event to customize notification handlers ([#3642]) - New `ind-app-title` DDS event to customize the app title ([#3684]) - New `fs.unique()` creates a unique file or directory ([#3677]) - New `cx.which` API to access the which component state ([#3617]) @@ -1577,6 +1578,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#3038]: https://github.com/sxyazi/yazi/pull/3038 [#3059]: https://github.com/sxyazi/yazi/pull/3059 [#3067]: https://github.com/sxyazi/yazi/pull/3067 +[#3077]: https://github.com/sxyazi/yazi/pull/3077 [#3083]: https://github.com/sxyazi/yazi/pull/3083 [#3084]: https://github.com/sxyazi/yazi/pull/3084 [#3091]: https://github.com/sxyazi/yazi/pull/3091 diff --git a/Cargo.lock b/Cargo.lock index 4687aad5..9c86d96e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,9 +171,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -381,9 +381,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -541,9 +541,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -835,7 +835,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "crossterm_winapi", "mio", "parking_lot", @@ -852,7 +852,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", @@ -1154,9 +1154,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0-rc.12" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0182be35043efdd2df327a443bb600606e350cfb090cccb233e9451e76f5a3" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" dependencies = [ "const-oid 0.10.2", "pem-rfc7468 1.0.0", @@ -1240,9 +1240,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.12" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b37eb2004a3548553a44cc1e688aac70f0345b896c9d822b4a0e520bc9183b" +checksum = "f8bf3682cdec91817be507e4aa104314898b95b84d74f3d43882210101a545b6" dependencies = [ "block-buffer 0.11.0", "const-oid 0.10.2", @@ -1578,6 +1578,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1735,6 +1741,19 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "ghash" version = "0.5.1" @@ -1802,6 +1821,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1810,7 +1838,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", "serde", "serde_core", ] @@ -1936,6 +1964,12 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2019,7 +2053,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -2064,7 +2098,7 @@ checksum = "fe44f2bbd99fcb302e246e2d6bcf51aeda346d02a365f80296a07a8c711b6da6" dependencies = [ "argon2", "bcrypt-pbkdf", - "digest 0.11.0-rc.12", + "digest 0.11.0", "ecdsa", "ed25519-dalek", "hex", @@ -2185,6 +2219,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lebe" version = "0.5.3" @@ -2193,9 +2233,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libcrux-intrinsics" @@ -2285,7 +2325,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -2295,7 +2335,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2534,7 +2574,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -2572,7 +2612,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "fsevent-sys", "inotify", "kqueue", @@ -2590,7 +2630,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2733,7 +2773,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", ] @@ -2764,7 +2804,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "once_cell", "onig_sys", @@ -3101,7 +3141,7 @@ version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ - "der 0.8.0-rc.12", + "der 0.8.0", "spki 0.8.0-rc.4", ] @@ -3138,7 +3178,7 @@ version = "0.11.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" dependencies = [ - "der 0.8.0-rc.12", + "der 0.8.0", "spki 0.8.0-rc.4", ] @@ -3163,11 +3203,11 @@ dependencies = [ [[package]] name = "png" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", @@ -3218,6 +3258,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.115", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -3436,7 +3486,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "compact_str", "hashbrown 0.16.1", "indoc", @@ -3501,7 +3551,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.16.1", "indoc", "instability", @@ -3590,7 +3640,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -3692,7 +3742,7 @@ dependencies = [ "const-oid 0.10.2", "crypto-bigint 0.7.0-rc.18", "crypto-primes", - "digest 0.11.0-rc.12", + "digest 0.11.0", "pkcs1", "pkcs8 0.11.0-rc.11", "rand_core 0.10.0-rc-3", @@ -3709,7 +3759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01fe22d10a0e39c1134a971d5b8db8a40357b48ef22d81fa8d6eac22202dd782" dependencies = [ "aes", - "bitflags 2.10.0", + "bitflags 2.11.0", "block-padding", "byteorder", "bytes", @@ -3815,7 +3865,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3828,7 +3878,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -4057,7 +4107,7 @@ checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.12", + "digest 0.11.0", ] [[package]] @@ -4079,7 +4129,7 @@ checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.12", + "digest 0.11.0", ] [[package]] @@ -4166,7 +4216,7 @@ version = "3.0.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a96996ccff7dfa16f052bd995b4cecc72af22c35138738dc029f0ead6608d" dependencies = [ - "digest 0.11.0-rc.12", + "digest 0.11.0", "rand_core 0.10.0-rc-3", ] @@ -4242,7 +4292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" dependencies = [ "base64ct", - "der 0.8.0-rc.12", + "der 0.8.0", ] [[package]] @@ -4407,7 +4457,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.10.0", + "bitflags 2.11.0", "fancy-regex", "filedescriptor", "finl_unicode", @@ -4823,6 +4873,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -4853,12 +4909,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ "atomic", - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -4970,6 +5026,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -5029,6 +5094,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -5516,6 +5615,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.115", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.115", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "wyz" @@ -5684,7 +5865,7 @@ name = "yazi-config" version = "26.2.2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "crossterm 0.29.0", "dirs", "globset", @@ -5835,11 +6016,11 @@ version = "26.2.2" dependencies = [ "anyhow", "arc-swap", - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation-sys", "dirs", "either", - "foldhash", + "foldhash 0.2.0", "hashbrown 0.16.1", "libc", "objc2", @@ -5874,7 +6055,7 @@ name = "yazi-parser" version = "26.2.2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "crossterm 0.29.0", "dyn-clone", "hashbrown 0.16.1", @@ -5959,7 +6140,7 @@ version = "26.2.2" dependencies = [ "anyhow", "async-priority-channel", - "foldhash", + "foldhash 0.2.0", "hashbrown 0.16.1", "libc", "lru", @@ -5986,7 +6167,7 @@ dependencies = [ name = "yazi-sftp" version = "26.2.2" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "parking_lot", "russh", "serde", @@ -6001,7 +6182,7 @@ dependencies = [ "anyhow", "crossterm 0.29.0", "dyn-clone", - "foldhash", + "foldhash 0.2.0", "futures", "hashbrown 0.16.1", "libc", diff --git a/Cargo.toml b/Cargo.toml index 86a96f7c..75115c1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ debug = false ansi-to-tui = "8.0.1" anyhow = "1.0.101" base64 = "0.22.1" -bitflags = { version = "2.10.0", features = [ "serde" ] } +bitflags = { version = "2.11.0", features = [ "serde" ] } clap = { version = "4.5.58", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.29.0", features = [ "event-stream" ] } @@ -41,7 +41,7 @@ futures = "0.3.31" globset = "0.4.18" hashbrown = { version = "0.16.1", features = [ "serde" ] } indexmap = { version = "2.13.0", features = [ "serde" ] } -libc = "0.2.181" +libc = "0.2.182" lru = "0.16.3" mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] } objc2 = "0.6.3" diff --git a/yazi-actor/src/lives/preference.rs b/yazi-actor/src/lives/preference.rs index f9f29a5c..db96544c 100644 --- a/yazi-actor/src/lives/preference.rs +++ b/yazi-actor/src/lives/preference.rs @@ -46,5 +46,6 @@ impl UserData for Preference { fields.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse)); fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first)); fields.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit)); + fields.add_field_method_get("sort_fallback", |_, me| Ok(me.sort_fallback.to_string())); } } diff --git a/yazi-actor/src/mgr/sort.rs b/yazi-actor/src/mgr/sort.rs index 7ad3b322..81b77676 100644 --- a/yazi-actor/src/mgr/sort.rs +++ b/yazi-actor/src/mgr/sort.rs @@ -22,6 +22,7 @@ impl Actor for Sort { pref.sort_dir_first = opt.dir_first.unwrap_or(pref.sort_dir_first); pref.sort_sensitive = opt.sensitive.unwrap_or(pref.sort_sensitive); pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit); + pref.sort_fallback = opt.fallback.unwrap_or(pref.sort_fallback); let sorter = FilesSorter::from(&*pref); let hovered = cx.hovered().map(|f| f.urn().to_owned()); diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 78cbb112..319338cf 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -9,6 +9,7 @@ sort_sensitive = false sort_reverse = false sort_dir_first = true sort_translit = false +sort_fallback = "alphabetical" linemode = "none" show_hidden = false show_symlink = true diff --git a/yazi-config/src/mgr/mgr.rs b/yazi-config/src/mgr/mgr.rs index 63876440..52071d94 100644 --- a/yazi-config/src/mgr/mgr.rs +++ b/yazi-config/src/mgr/mgr.rs @@ -1,7 +1,7 @@ use anyhow::{Result, bail}; use serde::Deserialize; use yazi_codegen::DeserializeOver2; -use yazi_fs::SortBy; +use yazi_fs::{SortBy, SortFallback}; use yazi_shared::SyncCell; use super::{MgrRatio, MouseEvents}; @@ -16,6 +16,7 @@ pub struct Mgr { pub sort_reverse: SyncCell, pub sort_dir_first: SyncCell, pub sort_translit: SyncCell, + pub sort_fallback: SyncCell, // Display pub linemode: String, diff --git a/yazi-core/src/tab/preference.rs b/yazi-core/src/tab/preference.rs index 99a07b18..a31d1db8 100644 --- a/yazi-core/src/tab/preference.rs +++ b/yazi-core/src/tab/preference.rs @@ -1,5 +1,5 @@ use yazi_config::YAZI; -use yazi_fs::{FilesSorter, SortBy}; +use yazi_fs::{FilesSorter, SortBy, SortFallback}; #[derive(Clone, PartialEq)] pub struct Preference { @@ -14,6 +14,7 @@ pub struct Preference { pub sort_reverse: bool, pub sort_dir_first: bool, pub sort_translit: bool, + pub sort_fallback: SortFallback, } impl Default for Preference { @@ -30,6 +31,7 @@ impl Default for Preference { sort_reverse: YAZI.mgr.sort_reverse.get(), sort_dir_first: YAZI.mgr.sort_dir_first.get(), sort_translit: YAZI.mgr.sort_translit.get(), + sort_fallback: YAZI.mgr.sort_fallback.get(), } } } @@ -42,6 +44,7 @@ impl From<&Preference> for FilesSorter { reverse: value.sort_reverse, dir_first: value.sort_dir_first, translit: value.sort_translit, + fallback: value.sort_fallback, } } } diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index 2ac9c08a..bff6581b 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -19,7 +19,7 @@ yazi-shim = { path = "../yazi-shim", version = "26.2.2" } # External dependencies anyhow = { workspace = true } -arc-swap = "1.8.1" +arc-swap = "1.8.2" bitflags = { workspace = true } dirs = { workspace = true } either = { workspace = true } diff --git a/yazi-fs/src/sorter.rs b/yazi-fs/src/sorter.rs index 5f189d2f..9a889c7d 100644 --- a/yazi-fs/src/sorter.rs +++ b/yazi-fs/src/sorter.rs @@ -4,7 +4,7 @@ use hashbrown::HashMap; use rand::{RngCore, SeedableRng, rngs::SmallRng}; use yazi_shared::{natsort, path::PathBufDyn, translit::Transliterator, url::UrlLike}; -use crate::{File, SortBy}; +use crate::{File, SortBy, SortFallback}; #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct FilesSorter { @@ -13,6 +13,7 @@ pub struct FilesSorter { pub reverse: bool, pub dir_first: bool, pub translit: bool, + pub fallback: SortFallback, } impl FilesSorter { @@ -21,105 +22,116 @@ impl FilesSorter { return; } - let by_alphabetical = |a: &File, b: &File| { - if self.sensitive { - self.cmp(a.urn().encoded_bytes(), b.urn().encoded_bytes(), self.promote(a, b)) - } else { - self.cmp_insensitive(a.urn().encoded_bytes(), b.urn().encoded_bytes(), self.promote(a, b)) - } - }; + macro_rules! promote { + ($a:ident, $b:ident) => { + if self.dir_first { + match $b.is_dir().cmp(&$a.is_dir()) { + Ordering::Equal => {} + not_eq => return not_eq, + } + } + }; + } match self.by { SortBy::None => {} SortBy::Mtime => items.sort_unstable_by(|a, b| { - let ord = self.cmp(a.mtime, b.mtime, self.promote(a, b)); - if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord } + promote!(a, b); + self.fallback(a, b, self.cmp(a.mtime, b.mtime)) }), SortBy::Btime => items.sort_unstable_by(|a, b| { - let ord = self.cmp(a.btime, b.btime, self.promote(a, b)); - if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord } + promote!(a, b); + self.fallback(a, b, self.cmp(a.btime, b.btime)) }), SortBy::Extension => items.sort_unstable_by(|a, b| { + promote!(a, b); let aa = a.url.ext().filter(|_| a.is_file()); let bb = b.url.ext().filter(|_| b.is_file()); let ord = if self.sensitive { - self.cmp(aa, bb, self.promote(a, b)) + self.cmp(aa, bb) } else { self.cmp_insensitive( aa.map_or(&[], |s| s.encoded_bytes()), bb.map_or(&[], |s| s.encoded_bytes()), - self.promote(a, b), ) }; - if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord } + self.fallback(a, b, ord) + }), + SortBy::Alphabetical => items.sort_unstable_by(|a, b| { + promote!(a, b); + self.sort_alphabetically(a, b) + }), + SortBy::Natural => items.sort_unstable_by(|a, b| { + promote!(a, b); + self.sort_naturally(a, b) }), - SortBy::Alphabetical => items.sort_unstable_by(by_alphabetical), - SortBy::Natural => self.sort_naturally(items), SortBy::Size => items.sort_unstable_by(|a, b| { + promote!(a, b); let aa = if a.is_dir() { sizes.get(&a.urn()).copied() } else { None }; let bb = if b.is_dir() { sizes.get(&b.urn()).copied() } else { None }; - let ord = self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b)); - if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord } + self.fallback(a, b, self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len))) }), SortBy::Random => { let mut rng = SmallRng::from_os_rng(); - items.sort_unstable_by(|a, b| self.cmp(rng.next_u64(), rng.next_u64(), self.promote(a, b))) + items.sort_unstable_by(|a, b| { + promote!(a, b); + self.cmp(rng.next_u64(), rng.next_u64()) + }) } } } - fn sort_naturally(&self, items: &mut [File]) { - items.sort_unstable_by(|a, b| { - let promote = self.promote(a, b); - if promote != Ordering::Equal { - return promote; - } - - let ordering = if self.translit { - natsort( - a.urn().encoded_bytes().transliterate().as_bytes(), - b.urn().encoded_bytes().transliterate().as_bytes(), - !self.sensitive, - ) - } else { - natsort(a.urn().encoded_bytes(), b.urn().encoded_bytes(), !self.sensitive) - }; - - if self.reverse { ordering.reverse() } else { ordering } - }); - } - #[inline(always)] - #[allow(clippy::collapsible_else_if)] - fn cmp(&self, a: T, b: T, promote: Ordering) -> Ordering { - if promote != Ordering::Equal { - promote + fn sort_alphabetically(&self, a: &File, b: &File) -> Ordering { + if self.sensitive { + self.cmp(a.urn().encoded_bytes(), b.urn().encoded_bytes()) } else { - if self.reverse { b.cmp(&a) } else { a.cmp(&b) } + self.cmp_insensitive(a.urn().encoded_bytes(), b.urn().encoded_bytes()) } } #[inline(always)] - fn cmp_insensitive(&self, a: &[u8], b: &[u8], promote: Ordering) -> Ordering { - if promote != Ordering::Equal { - return promote; + fn sort_naturally(&self, a: &File, b: &File) -> Ordering { + let ordering = if self.translit { + natsort( + a.urn().encoded_bytes().transliterate().as_bytes(), + b.urn().encoded_bytes().transliterate().as_bytes(), + !self.sensitive, + ) + } else { + natsort(a.urn().encoded_bytes(), b.urn().encoded_bytes(), !self.sensitive) + }; + + if self.reverse { ordering.reverse() } else { ordering } + } + + #[inline(always)] + fn fallback(&self, a: &File, b: &File, ord: Ordering) -> Ordering { + if ord != Ordering::Equal { + return ord; } + match self.fallback { + SortFallback::Alphabetical => self.sort_alphabetically(a, b), + SortFallback::Natural => self.sort_naturally(a, b), + } + } + + #[inline(always)] + fn cmp(&self, a: T, b: T) -> Ordering { if self.reverse { b.cmp(&a) } else { a.cmp(&b) } } + + #[inline(always)] + fn cmp_insensitive(&self, a: &[u8], b: &[u8]) -> Ordering { let l = a.len().min(b.len()); let (lhs, rhs) = if self.reverse { (&b[..l], &a[..l]) } else { (&a[..l], &b[..l]) }; for i in 0..l { match lhs[i].to_ascii_lowercase().cmp(&rhs[i].to_ascii_lowercase()) { - Ordering::Equal => (), + Ordering::Equal => {} not_eq => return not_eq, } } if self.reverse { b.len().cmp(&a.len()) } else { a.len().cmp(&b.len()) } } - - #[inline(always)] - fn promote(&self, a: &File, b: &File) -> Ordering { - if self.dir_first { b.is_dir().cmp(&a.is_dir()) } else { Ordering::Equal } - } } diff --git a/yazi-fs/src/sorting.rs b/yazi-fs/src/sorting.rs index 14b8a976..1497c000 100644 --- a/yazi-fs/src/sorting.rs +++ b/yazi-fs/src/sorting.rs @@ -2,7 +2,8 @@ use std::{fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] +// --- by +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "kebab-case")] pub enum SortBy { #[default] @@ -38,3 +39,29 @@ impl Display for SortBy { }) } } + +// --- fallback +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum SortFallback { + #[default] + Alphabetical, + Natural, +} + +impl FromStr for SortFallback { + type Err = serde::de::value::Error; + + fn from_str(s: &str) -> Result { + Self::deserialize(serde::de::value::StrDeserializer::new(s)) + } +} + +impl Display for SortFallback { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Alphabetical => "alphabetical", + Self::Natural => "natural", + }) + } +} diff --git a/yazi-parser/src/mgr/sort.rs b/yazi-parser/src/mgr/sort.rs index 580ac4f6..5e4295a2 100644 --- a/yazi-parser/src/mgr/sort.rs +++ b/yazi-parser/src/mgr/sort.rs @@ -1,7 +1,7 @@ use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use yazi_binding::SER_OPT; -use yazi_fs::SortBy; +use yazi_fs::{SortBy, SortFallback}; use yazi_shared::event::CmdCow; #[derive(Debug, Default, Deserialize, Serialize)] @@ -11,6 +11,7 @@ pub struct SortOpt { pub dir_first: Option, pub sensitive: Option, pub translit: Option, + pub fallback: Option, } impl TryFrom for SortOpt { @@ -23,6 +24,7 @@ impl TryFrom for SortOpt { dir_first: c.get("dir-first").ok(), sensitive: c.get("sensitive").ok(), translit: c.get("translit").ok(), + fallback: c.get("fallback").ok().map(str::parse).transpose()?, }) } } diff --git a/yazi-plugin/src/runtime/runtime.rs b/yazi-plugin/src/runtime/runtime.rs index b040e68b..0724e3e6 100644 --- a/yazi-plugin/src/runtime/runtime.rs +++ b/yazi-plugin/src/runtime/runtime.rs @@ -48,6 +48,7 @@ fn mgr() -> Composer { b"sort_reverse" => m.sort_reverse.get().into_lua(lua)?, b"sort_dir_first" => m.sort_dir_first.get().into_lua(lua)?, b"sort_translit" => m.sort_translit.get().into_lua(lua)?, + b"sort_fallback" => lua.to_value_with(&m.sort_fallback, SER_OPT)?, b"linemode" => lua.create_string(&m.linemode)?.into_lua(lua)?, b"show_hidden" => m.show_hidden.get().into_lua(lua)?,