Merge branch 'sxyazi:main' into add-create-function-to-fs-module

This commit is contained in:
hankertrix 2024-11-02 18:49:18 +08:00 committed by GitHub
commit 560b7752a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
160 changed files with 2022 additions and 1273 deletions

54
.github/DISCUSSION_TEMPLATE/1-q-a.yml vendored Normal file
View file

@ -0,0 +1,54 @@
body:
- type: dropdown
id: os
attributes:
label: What system are you running Yazi on?
options:
- Linux X11
- Linux Wayland
- macOS
- Windows
- Windows WSL
- FreeBSD X11
- FreeBSD Wayland
- Android
validations:
required: true
- type: input
id: terminal
attributes:
label: What terminal are you running Yazi in?
placeholder: "ex: kitty v0.32.2"
validations:
required: true
- type: textarea
id: debug
attributes:
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
render: Shell
validations:
required: true
- type: textarea
id: description
attributes:
label: Describe the question
description: A clear and concise description of what the question is
placeholder: Tell us what you want to know
validations:
required: true
- type: textarea
id: other
attributes:
label: Anything else?
description: |
Add any other context about the problem here. You can attach screenshots by clicking
this area to highlight it and then drag the files in.
- type: checkboxes
id: validations
attributes:
label: Validations
description: Before submitting the post, please make sure you have completed the following
options:
- label: I have searched the existing discussions/issues
required: true

View file

@ -15,6 +15,7 @@ body:
- Windows WSL
- FreeBSD X11
- FreeBSD Wayland
- Android
validations:
required: true
- type: input
@ -32,15 +33,6 @@ body:
render: Shell
validations:
required: true
- type: dropdown
id: tried_main
attributes:
label: Did you try the latest nightly build to see if the problem got fixed?
options:
- Yes, and I updated the debug information above (`yazi --debug`) to the nightly that I tried
- No, and I'll explain why below
validations:
required: true
- type: textarea
id: description
attributes:
@ -65,3 +57,14 @@ body:
description: |
Add any other context about the problem here. You can attach screenshots by clicking
this area to highlight it and then drag the files in.
- type: checkboxes
id: validations
attributes:
label: Validations
description: Before submitting the issue, please make sure you have completed the following
options:
- label: I tried the [latest nightly build](https://yazi-rs.github.io/docs/installation#official-binaries), and the issue is still reproducible
required: true
- label: I updated the debug information (`yazi --debug`) input box to the nightly that I tried
required: true
- label: I can reproduce it after disabling all custom configs/plugins (`mv ~/.config/yazi ~/.config/yazi-backup`)

View file

@ -3,9 +3,9 @@ contact_links:
- name: 📝 Documentation Improvement
url: https://github.com/yazi-rs/yazi-rs.github.io
about: If you'd like to help improve the documentation
- name: 💬 GitHub Discussions
url: https://github.com/sxyazi/yazi/discussions/new?category=1-q-a
about: When you have questions that are not bug reports or feature requests
- name: 🌐 Discord Server / Telegram Group
url: https://github.com/sxyazi/yazi#discussion
about: If you'd prefer more realtime conversation with the community
- name: 💬 GitHub Discussions
url: https://github.com/sxyazi/yazi/discussions
about: When you have questions that are not bug reports or feature requests

View file

@ -45,5 +45,5 @@ body:
options:
- label: I have searched the existing issues/discussions
required: true
- label: The latest nightly build of Yazi doesn't already have this feature
- label: The [latest nightly build](https://yazi-rs.github.io/docs/installation/#official-binaries) doesn't already have this feature
required: true

View file

@ -19,6 +19,9 @@ jobs:
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ubuntu-latest@debug
- name: Clippy
run: cargo clippy --all
@ -36,6 +39,9 @@ jobs:
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ubuntu-latest@debug
- name: Rustfmt
run: cargo +nightly fmt --all -- --check

View file

@ -32,6 +32,15 @@ jobs:
echo "JEMALLOC_SYS_WITH_LG_PAGE=16" >> $GITHUB_ENV
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: Setup Rust toolchain
run: rustup toolchain install stable --profile minimal --target ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.target }}@release
- name: Build
run: ./scripts/build.sh ${{ matrix.target }}
@ -54,10 +63,13 @@ jobs:
- uses: actions/checkout@v4
- name: Setup Rust toolchain
run: rustup toolchain install stable --profile minimal
run: rustup toolchain install stable --profile minimal --target ${{ matrix.target }}
- name: Add target
run: rustup target add ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.target }}@release
- name: Build
env:
@ -98,6 +110,15 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Add musl target
run: rustup target add ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.target }}@release
- name: Build
run: ./scripts/build.sh ${{ matrix.target }}
@ -117,10 +138,16 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Build snap
uses: snapcore/action-build@v1
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.target }}@release
- name: Build
uses: snapcore/action-build@v1
- name: Rename snap
run: mv yazi_*.snap yazi-${{ matrix.target }}.snap
- name: Upload artifact

View file

@ -23,7 +23,7 @@ jobs:
I'm going to lock this issue because it has been closed for _30 days_. ⏳
This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new
If you have found a problem that seems similar to this, please file a new
issue and complete the issue template so we can capture all the details
necessary to investigate further.
pr-inactive-days: "30"

View file

@ -23,6 +23,9 @@ jobs:
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.os }}@debug
- name: Build
run: cargo build --verbose

287
Cargo.lock generated
View file

@ -61,9 +61,9 @@ dependencies = [
[[package]]
name = "ansi-to-tui"
version = "6.0.0"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00c4af0bef1b514c9b6a32a773caf604c1390fa7913f4eaa23bfe76f251d6a42"
checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c"
dependencies = [
"nom",
"ratatui",
@ -74,9 +74,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.15"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
dependencies = [
"anstyle",
"anstyle-parse",
@ -89,43 +89,43 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.8"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.89"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
[[package]]
name = "arbitrary"
@ -133,12 +133,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arg_enum_proc_macro"
version = "0.3.4"
@ -147,7 +141,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -187,9 +181,9 @@ dependencies = [
[[package]]
name = "avif-serialize"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
dependencies = [
"arrayvec",
]
@ -288,9 +282,9 @@ dependencies = [
[[package]]
name = "built"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
[[package]]
name = "bumpalo"
@ -300,9 +294,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.18.0"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
[[package]]
name = "byteorder"
@ -318,9 +312,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.7.2"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "cassowary"
@ -339,9 +333,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.30"
version = "1.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
dependencies = [
"jobserver",
"libc",
@ -400,9 +394,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.33"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb"
checksum = "07a13ab5b8cb13dbe35e68b83f6c12f9293b2f601797b71bc9f23befdb329feb"
dependencies = [
"clap",
]
@ -436,7 +430,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -462,9 +456,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "compact_str"
@ -561,7 +555,7 @@ dependencies = [
"filedescriptor",
"futures-core",
"libc",
"mio 1.0.2",
"mio",
"parking_lot",
"rustix",
"signal-hook",
@ -615,7 +609,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -626,7 +620,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -656,7 +650,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -666,7 +660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -773,9 +767,9 @@ dependencies = [
[[package]]
name = "fdeflate"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab"
checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
dependencies = [
"simd-adler32",
]
@ -825,9 +819,9 @@ dependencies = [
[[package]]
name = "flume"
version = "0.11.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"spin",
]
@ -918,7 +912,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -1084,9 +1078,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.2"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
dependencies = [
"bytemuck",
"byteorder-lite",
@ -1106,9 +1100,9 @@ dependencies = [
[[package]]
name = "image-webp"
version = "0.1.3"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
dependencies = [
"byteorder-lite",
"quick-error",
@ -1122,9 +1116,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "imgref"
version = "1.10.1"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
[[package]]
name = "indexmap"
@ -1137,6 +1131,12 @@ dependencies = [
"serde",
]
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "inotify"
version = "0.10.2"
@ -1164,7 +1164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c"
dependencies = [
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -1184,7 +1184,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -1235,15 +1235,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kamadak-exif"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077"
dependencies = [
"mutate_once",
]
[[package]]
name = "kqueue"
version = "1.0.8"
@ -1278,9 +1269,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.159"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libfuzzer-sys"
@ -1413,18 +1404,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.2"
@ -1458,9 +1437,9 @@ dependencies = [
[[package]]
name = "mlua-sys"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe026d6bd1583a9cf9080e189030ddaea7e6f5f0deb366a8e26f8a26c4135b8"
checksum = "e9eebac25c35a13285456c88ee2fde93d9aee8bcfdaf03f9d6d12be3391351ec"
dependencies = [
"cc",
"cfg-if",
@ -1481,15 +1460,9 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
name = "mutate_once"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
@ -1513,10 +1486,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "notify-fork"
version = "6.1.1"
name = "notify"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f610737468f7a95610d2b320b5d5e246b3cd1c3603782680c0f454ac5825b64b"
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
dependencies = [
"bitflags 2.6.0",
"filetime",
@ -1525,17 +1498,17 @@ dependencies = [
"kqueue",
"libc",
"log",
"mio 0.8.11",
"notify-types-fork",
"mio",
"notify-types",
"walkdir",
"windows-sys 0.52.0",
]
[[package]]
name = "notify-types-fork"
name = "notify-types"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc01866a421a4ce1eddc764da8a95b30b82aa0d9d7f33508265c196d4863b18"
checksum = "7393c226621f817964ffb3dc5704f9509e107a8b024b489cc2c1b217378785df"
dependencies = [
"instant",
]
@ -1574,7 +1547,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -1750,9 +1723,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "pin-utils"
@ -1833,30 +1806,30 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.87"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "profiling"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
dependencies = [
"profiling-procmacros",
]
[[package]]
name = "profiling-procmacros"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
dependencies = [
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -1924,23 +1897,23 @@ dependencies = [
[[package]]
name = "ratatui"
version = "0.28.1"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d"
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags 2.6.0",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"instability",
"itertools 0.13.0",
"lru",
"paste",
"strum",
"strum_macros",
"unicode-segmentation",
"unicode-truncate",
"unicode-width",
"unicode-width 0.2.0",
]
[[package]]
@ -1980,9 +1953,9 @@ dependencies = [
[[package]]
name = "ravif"
version = "0.11.10"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd"
checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
dependencies = [
"avif-serialize",
"imgref",
@ -2034,9 +2007,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@ -2066,9 +2039,6 @@ name = "rgb"
version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
dependencies = [
"bytemuck",
]
[[package]]
name = "rustc-demangle"
@ -2084,9 +2054,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustix"
version = "0.38.37"
version = "0.38.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
dependencies = [
"bitflags 2.6.0",
"errno",
@ -2097,9 +2067,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]]
name = "ryu"
@ -2124,9 +2094,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
dependencies = [
"serde_derive",
]
@ -2143,20 +2113,20 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
name = "serde_json"
version = "1.0.128"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
@ -2211,7 +2181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio 1.0.2",
"mio",
"signal-hook",
]
@ -2322,7 +2292,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -2337,9 +2307,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.79"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
@ -2388,22 +2358,22 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "thiserror"
version = "1.0.64"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -2486,14 +2456,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.40.0"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio 1.0.2",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
@ -2510,7 +2480,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -2603,7 +2573,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -2643,9 +2613,9 @@ dependencies = [
[[package]]
name = "trash"
version = "5.1.1"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33caf2a9be1812a263a4bfce74d2de225fcde12ee7b77001361abd2b34ffdcc4"
checksum = "defe1fdd4232e407b312377885a2c5396764972bddad87baf304753374a1bfc8"
dependencies = [
"chrono",
"libc",
@ -2705,7 +2675,7 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools 0.13.0",
"unicode-segmentation",
"unicode-width",
"unicode-width 0.1.14",
]
[[package]]
@ -2714,6 +2684,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "url"
version = "2.5.2"
@ -2785,7 +2761,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -2882,7 +2858,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
"wasm-bindgen-shared",
]
@ -2904,7 +2880,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3003,7 +2979,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -3014,7 +2990,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -3195,14 +3171,12 @@ version = "0.3.3"
dependencies = [
"ansi-to-tui",
"anyhow",
"arc-swap",
"base64",
"color_quant",
"crossterm",
"futures",
"image",
"imagesize",
"kamadak-exif",
"ratatui",
"scopeguard",
"tokio",
@ -3255,7 +3229,7 @@ name = "yazi-codegen"
version = "0.3.3"
dependencies = [
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]
@ -3263,7 +3237,6 @@ name = "yazi-config"
version = "0.3.3"
dependencies = [
"anyhow",
"arc-swap",
"bitflags 2.6.0",
"crossterm",
"globset",
@ -3287,7 +3260,7 @@ dependencies = [
"dirs",
"futures",
"libc",
"notify-fork",
"notify",
"parking_lot",
"ratatui",
"scopeguard",
@ -3296,7 +3269,7 @@ dependencies = [
"tokio-stream",
"tokio-util",
"tracing",
"unicode-width",
"unicode-width 0.2.0",
"yazi-adapter",
"yazi-boot",
"yazi-codegen",
@ -3403,7 +3376,7 @@ dependencies = [
"tokio-stream",
"tokio-util",
"tracing",
"unicode-width",
"unicode-width 0.2.0",
"uzers",
"yazi-adapter",
"yazi-boot",
@ -3495,7 +3468,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.85",
]
[[package]]

View file

@ -10,9 +10,8 @@ panic = "abort"
strip = true
[workspace.dependencies]
ansi-to-tui = "6.0.0"
anyhow = "1.0.89"
arc-swap = "1.7.1"
ansi-to-tui = "7.0.0"
anyhow = "1.0.91"
base64 = "0.22.1"
bitflags = "2.6.0"
clap = { version = "4.5.20", features = [ "derive" ] }
@ -20,19 +19,19 @@ crossterm = { version = "0.28.1", features = [ "event-stream" ] }
dirs = "5.0.1"
futures = "0.3.31"
globset = "0.4.15"
libc = "0.2.159"
libc = "0.2.161"
md-5 = "0.10.6"
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.3"
ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info" ] }
regex = "1.11.0"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
regex = "1.11.1"
scopeguard = "1.2.0"
serde = { version = "1.0.210", features = [ "derive" ] }
serde_json = "1.0.128"
serde = { version = "1.0.213", features = [ "derive" ] }
serde_json = "1.0.132"
shell-words = "1.1.0"
tokio = { version = "1.40.0", features = [ "full" ] }
tokio = { version = "1.41.0", features = [ "full" ] }
tokio-stream = "0.1.16"
tokio-util = "0.7.12"
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
unicode-width = "0.1.14"
unicode-width = "0.2.0"
uzers = "0.12.1"

View file

@ -2,6 +2,8 @@
For breaking changes, see [Migrating to Yazi v0.4.0](https://github.com/sxyazi/yazi/issues/1772).
<br><br>
<div align="center">
<img src="assets/logo.png" alt="Yazi logo" width="20%">
</div>
@ -43,25 +45,39 @@ https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265
## Image Preview
| Platform | Protocol | Support |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) | [Kitty unicode placeholders](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) | ✅ Built-in |
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol](https://github.com/sxyazi/yazi/blob/main/yazi-adapter/src/kgp_old.rs) | ✅ Built-in |
| [iTerm2](https://iterm2.com) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Mintty](https://github.com/mintty/mintty) (Git Bash) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in |
| [Ghostty](https://mitchellh.com/ghostty) | [Kitty unicode placeholders](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) | ✅ Built-in |
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.2702.0) | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++](https://github.com/jstkdng/ueberzugpp) required |
| Fallback | [ASCII art (Unicode block)](https://en.wikipedia.org/wiki/ASCII_art) | ☑️ [Chafa](https://hpjansson.org/chafa/) required |
| --------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Ghostty](https://mitchellh.com/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.2702.0) | [Sixel graphics format][sixel] | ✅ Built-in |
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio doesn't correctly clear images (#1786) |
| [Mintty](https://github.com/mintty/mintty) (Git Bash) | [Inline images protocol][iip] | ✅ Built-in |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required |
See https://yazi-rs.github.io/docs/image-preview for details.
<!-- Protocols -->
[kgp]: https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders
[kgp-old]: https://github.com/sxyazi/yazi/blob/main/yazi-adapter/src/kgp_old.rs
[iip]: https://iterm2.com/documentation-images.html
[sixel]: https://www.vt100.net/docs/vt3xx-gp/chapter14.html
[ascii-art]: https://en.wikipedia.org/wiki/ASCII_art
<!-- Dependencies -->
[ueberzug]: https://github.com/jstkdng/ueberzugpp
[chafa]: https://hpjansson.org/chafa/
## License
Yazi is MIT-licensed. For more information check the [LICENSE](LICENSE) file.

View file

@ -1 +1 @@
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname"],"flagWords":[],"version":"0.2","language":"en"}
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod"],"version":"0.2","language":"en"}

23
flake.lock generated
View file

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1722415718,
"narHash": "sha256-5US0/pgxbMksF92k1+eOa8arJTJiPvsdZj9Dl+vJkM4=",
"lastModified": 1729265718,
"narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c3392ad349a5227f4a3464dce87bcc5046692fce",
"rev": "ccc0c2126893dd20963580b6478d1a10a4512185",
"type": "github"
},
"original": {
@ -43,22 +43,19 @@
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
"nixpkgs": ["nixpkgs"]
},
"locked": {
"lastModified": 1721441897,
"narHash": "sha256-gYGX9/22tPNeF7dR6bWN5rsrpU4d06GnQNNgZ6ZiXz0=",
"lastModified": 1729391507,
"narHash": "sha256-as0I9xieJUHf7kiK2a9znDsVZQTFWhM1pLivII43Gi0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "b7996075da11a2d441cfbf4e77c2939ce51506fd",
"rev": "784981a9feeba406de38c1c9a3decf966d853cca",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "b7996075da11a2d441cfbf4e77c2939ce51506fd",
"type": "github"
}
},

View file

@ -3,7 +3,7 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay/b7996075da11a2d441cfbf4e77c2939ce51506fd"; # FIX: pin to a specific commit until cargo-c is updated
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};

View file

@ -5,13 +5,6 @@ export ARTIFACT_NAME="yazi-$1"
export YAZI_GEN_COMPLETIONS=1
export MACOSX_DEPLOYMENT_TARGET="10.11"
# Setup Rust toolchain
if [[ "$1" == *-musl ]]; then
rustup target add "$1"
else
rustup toolchain install stable --profile minimal --target "$1"
fi
# Build for the target
cargo build --release --locked --target "$1"

View file

@ -16,14 +16,12 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External dependencies
ansi-to-tui = { workspace = true }
anyhow = { workspace = true }
arc-swap = { workspace = true }
base64 = { workspace = true }
color_quant = "1.1.0"
crossterm = { workspace = true }
futures = { workspace = true }
image = { version = "0.25.2", default-features = false, features = [ "rayon", "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
image = { version = "0.25.4", default-features = false, features = [ "rayon", "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
imagesize = "0.13.0"
kamadak-exif = "0.5.5"
ratatui = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }

View file

@ -1,4 +1,4 @@
use std::{env, fmt::Display, path::Path, sync::Arc};
use std::{env, fmt::Display, path::Path};
use anyhow::Result;
use ratatui::layout::Rect;
@ -52,7 +52,7 @@ impl Adapter {
}
pub fn image_hide(self) -> Result<()> {
if let Some(area) = SHOWN.swap(None) { self.image_erase(*area) } else { Ok(()) }
if let Some(area) = SHOWN.replace(None) { self.image_erase(area) } else { Ok(()) }
}
pub fn image_erase(self, area: Rect) -> Result<()> {
@ -67,10 +67,10 @@ impl Adapter {
}
#[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) }
pub fn shown_load(self) -> Option<Rect> { SHOWN.get() }
#[inline]
pub(super) fn shown_store(area: Rect) { SHOWN.store(Some(Arc::new(area))); }
pub(super) fn shown_store(area: Rect) { SHOWN.set(Some(area)); }
pub(super) fn start(self) { Ueberzug::start(self); }

View file

@ -1,8 +1,7 @@
use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
use std::path::{Path, PathBuf};
use anyhow::Result;
use exif::{In, Tag};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, ImageError, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::{self, FilterType}};
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS};
@ -12,38 +11,27 @@ pub struct Image;
impl Image {
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
let orientation = Self::orientation(path).await?;
let path = path.to_owned();
let mut img = tokio::task::spawn_blocking(move || {
Self::set_limits(image::ImageReader::open(path)?.with_guessed_format()?).decode()
})
.await??;
let (mut w, mut h) = (PREVIEW.max_width, PREVIEW.max_height);
if (5..=8).contains(&orientation) {
(w, h) = (h, w);
}
let (mut img, orientation, icc) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, (PREVIEW.max_width, PREVIEW.max_height));
let buf = tokio::task::spawn_blocking(move || {
if img.width() > w || img.height() > h {
img = img.resize(w, h, Self::filter());
}
if orientation != Orientation::NoTransforms {
img.apply_orientation(orientation);
}
let mut buf = Vec::new();
img = Self::rotate(img, orientation);
if img.color().has_alpha() {
let rgba = img.into_rgba8();
PngEncoder::new(&mut buf).write_image(
&rgba,
rgba.width(),
rgba.height(),
ExtendedColorType::Rgba8,
)?;
let mut encoder = PngEncoder::new(&mut buf);
icc.map(|b| encoder.set_icc_profile(b));
encoder.write_image(&rgba, rgba.width(), rgba.height(), ExtendedColorType::Rgba8)?;
} else {
JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality)
.encode_image(&img.into_rgb8())?;
let mut encoder = JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality);
icc.map(|b| encoder.set_icc_profile(b));
encoder.encode_image(&img.into_rgb8())?;
}
Ok::<_, ImageError>(buf)
@ -54,31 +42,26 @@ impl Image {
}
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
let orientation = Self::orientation(path).await?;
let path = path.to_owned();
let mut img = tokio::task::spawn_blocking(move || {
Self::set_limits(image::ImageReader::open(path)?.with_guessed_format()?).decode()
})
.await??;
let (mut w, mut h) = Self::max_pixel(rect);
if (5..=8).contains(&orientation) {
(w, h) = (h, w);
}
let (mut img, orientation, _) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
// Fast path.
if img.width() <= w && img.height() <= h && orientation <= 1 {
if img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {
return Ok(img);
}
tokio::task::spawn_blocking(move || {
let img = tokio::task::spawn_blocking(move || {
if img.width() > w || img.height() > h {
img = img.resize(w, h, Self::filter())
}
Ok(Self::rotate(img, orientation))
if orientation != Orientation::NoTransforms {
img.apply_orientation(orientation);
}
img
})
.await?
.await?;
Ok(img)
}
pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
@ -113,53 +96,7 @@ impl Image {
}
}
async fn orientation(path: &Path) -> Result<u8> {
// We don't want to read the orientation of the cached image that has been
// rotated in the `Self::precache()` step.
if path.parent() == Some(&PREVIEW.cache_dir) {
return Ok(0);
}
let path = path.to_owned();
tokio::task::spawn_blocking(move || {
let file = std::fs::File::open(path)?;
let mut reader = std::io::BufReader::new(&file);
let Ok(exif) = exif::Reader::new().read_from_container(&mut reader) else {
return Ok(0);
};
Ok(match exif.get_field(Tag::Orientation, In::PRIMARY) {
Some(orientation) => match orientation.value.get_uint(0) {
Some(v @ 1..=8) => v as u8,
_ => 1,
},
None => 1,
})
})
.await?
}
// https://magnushoff.com/articles/jpeg-orientation/
fn rotate(mut img: DynamicImage, orientation: u8) -> DynamicImage {
let alpha = img.color().has_alpha();
img = match orientation {
2 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
3 => DynamicImage::ImageRgba8(imageops::rotate180(&img)),
4 => DynamicImage::ImageRgba8(imageops::flip_vertical(&img)),
5 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&imageops::rotate90(&img))),
6 => DynamicImage::ImageRgba8(imageops::rotate90(&img)),
7 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&imageops::rotate270(&img))),
8 => DynamicImage::ImageRgba8(imageops::rotate270(&img)),
_ => img,
};
if !alpha {
img = DynamicImage::ImageRgb8(img.into_rgb8());
}
img
}
fn set_limits(mut r: image::ImageReader<BufReader<File>>) -> image::ImageReader<BufReader<File>> {
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
let mut limits = Limits::no_limits();
if TASKS.image_alloc > 0 {
limits.max_alloc = Some(TASKS.image_alloc as u64);
@ -170,7 +107,27 @@ impl Image {
if TASKS.image_bound[1] > 0 {
limits.max_image_height = Some(TASKS.image_bound[1] as u32);
}
r.limits(limits);
r
let path = path.to_owned();
tokio::task::spawn_blocking(move || {
let mut reader = ImageReader::open(path)?;
reader.limits(limits);
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
let icc = decoder.icc_profile().unwrap_or_default();
Ok((DynamicImage::from_decoder(decoder)?, orientation, icc))
})
.await
.map_err(|e| ImageError::IoError(e.into()))?
}
fn flip_size(orientation: Orientation, (w, h): (u32, u32)) -> (u32, u32) {
use image::metadata::Orientation::{Rotate90, Rotate90FlipH, Rotate270, Rotate270FlipH};
match orientation {
Rotate90 | Rotate270 | Rotate90FlipH | Rotate270FlipH => (h, w),
_ => (w, h),
}
}
}

View file

@ -4,7 +4,7 @@ yazi_macro::mod_flat!(
adapter chafa dimension emulator iip image kgp kgp_old mux sixel ueberzug
);
use yazi_shared::{RoCell, env_exists, in_wsl};
use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl};
pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// Tmux support
@ -17,7 +17,7 @@ static CLOSE: RoCell<&'static str> = RoCell::new();
pub static WSL: RoCell<bool> = RoCell::new();
// Image state
static SHOWN: RoCell<arc_swap::ArcSwapOption<ratatui::layout::Rect>> = RoCell::new();
static SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);
pub fn init() {
// Tmux support
@ -38,9 +38,6 @@ pub fn init() {
// WSL support
WSL.init(in_wsl());
// Image state
SHOWN.with(<_>::default);
ADAPTOR.init(Adapter::matches());
ADAPTOR.start();
}

View file

@ -21,7 +21,7 @@ serde = { workspace = true }
[build-dependencies]
clap = { workspace = true }
clap_complete = "4.5.33"
clap_complete = "4.5.35"
clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.4"
vergen-gitcl = { version = "1.0.1", features = [ "build" ] }

View file

@ -22,11 +22,10 @@ fn main() -> Result<(), Box<dyn Error>> {
let out = "completions";
std::fs::create_dir_all(out)?;
generate_to(Shell::Bash, cmd, bin, out)?;
generate_to(Shell::Fish, cmd, bin, out)?;
generate_to(Shell::Zsh, cmd, bin, out)?;
generate_to(Shell::Elvish, cmd, bin, out)?;
generate_to(Shell::PowerShell, cmd, bin, out)?;
for sh in [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] {
generate_to(sh, cmd, bin, out)?;
}
generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;
generate_to(clap_complete_fig::Fig, cmd, bin, out)?;

View file

@ -27,7 +27,7 @@ toml_edit = "0.22.22"
# External build dependencies
anyhow = { workspace = true }
clap = { workspace = true }
clap_complete = "4.5.33"
clap_complete = "4.5.35"
clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.4"
serde_json = { workspace = true }

View file

@ -22,11 +22,10 @@ fn main() -> Result<(), Box<dyn Error>> {
let out = "completions";
std::fs::create_dir_all(out)?;
generate_to(Shell::Bash, cmd, bin, out)?;
generate_to(Shell::Fish, cmd, bin, out)?;
generate_to(Shell::Zsh, cmd, bin, out)?;
generate_to(Shell::Elvish, cmd, bin, out)?;
generate_to(Shell::PowerShell, cmd, bin, out)?;
for sh in [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] {
generate_to(sh, cmd, bin, out)?;
}
generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;
generate_to(clap_complete_fig::Fig, cmd, bin, out)?;

View file

@ -47,7 +47,6 @@ async fn main() -> anyhow::Result<()> {
Command::Pack(cmd) => {
package::init()?;
package::Package::migrate().await?;
if cmd.install {
package::Package::install_from_config("plugin", false).await?;
package::Package::install_from_config("flavor", false).await?;
@ -57,15 +56,7 @@ async fn main() -> anyhow::Result<()> {
} else if cmd.upgrade {
package::Package::install_from_config("plugin", true).await?;
package::Package::install_from_config("flavor", true).await?;
} else if let Some(mut repo) = cmd.add {
// TODO: remove this in the future
if repo.contains("#") {
repo = repo.replace("#", ":");
println!(
"WARNING: `#` has been deprecated in Yazi 0.3.1, please use `:` instead. See https://github.com/sxyazi/yazi/issues/1471 for more information."
);
}
} else if let Some(repo) = cmd.add {
package::Package::add_to_config(&repo).await?;
}
}

View file

@ -6,33 +6,6 @@ use yazi_shared::Xdg;
use super::Package;
impl Package {
// TODO: remove this in the future
pub(crate) async fn migrate() -> Result<()> {
let path = Xdg::config_dir().join("package.toml");
let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?;
fn impl_(deps: &mut Array) -> Result<()> {
for dep in deps.iter_mut() {
let dep = dep.as_inline_table_mut().context("Dependency must be an inline table")?;
let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?;
if use_.contains("#") {
dep["use"] = use_.replace("#", ":").into();
}
if let Some(commit) = dep.get("commit").map(ToOwned::to_owned) {
dep.remove("commit");
dep.insert("rev", commit);
}
}
Ok(())
}
impl_(doc["plugin"]["deps"].as_array_mut().unwrap())?;
impl_(doc["flavor"]["deps"].as_array_mut().unwrap())?;
fs::write(path, doc.to_string()).await?;
Ok(())
}
pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
let mut package = Self::new(use_, None);
let Some(name) = package.name() else { bail!("Invalid package `use`") };

View file

@ -13,5 +13,5 @@ proc-macro = true
[dependencies]
# External dependencies
syn = "2.0.79"
syn = "2.0.85"
quote = "1.0.37"

View file

@ -14,7 +14,6 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External dependencies
anyhow = { workspace = true }
arc-swap = { workspace = true }
bitflags = { workspace = true }
crossterm = { workspace = true }
globset = { workspace = true }

View file

@ -98,6 +98,20 @@ selected = { reversed = true }
# : }}}
# : Confirm {{{
[confirm]
border = { fg = "blue" }
title = { fg = "blue" }
content = {}
list = {}
btn_yes = { reversed = true }
btn_no = {}
btn_labels = [ " [Y]es ", " (N)o " ]
# : }}}
# : Completion {{{
[completion]

View file

@ -1,5 +1,6 @@
use std::{collections::HashSet, str::FromStr};
use anyhow::Context;
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer};
use yazi_shared::Layer;
@ -20,7 +21,7 @@ pub struct Keymap {
impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Chord> {
pub fn get(&self, layer: Layer) -> &[Chord] {
match layer {
Layer::App => unreachable!(),
Layer::Manager => &self.manager,
@ -36,9 +37,11 @@ impl Keymap {
}
impl FromStr for Keymap {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse your keymap.toml")
}
}
impl<'de> Deserialize<'de> for Keymap {

View file

@ -1,7 +1,14 @@
use ratatui::layout::Rect;
#[derive(Clone, Copy, Default)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Layout {
pub current: Rect,
pub preview: Rect,
pub progress: Rect,
}
impl Layout {
pub const fn default() -> Self {
Self { current: Rect::ZERO, preview: Rect::ZERO, progress: Rect::ZERO }
}
}

View file

@ -6,9 +6,7 @@ yazi_macro::mod_flat!(layout pattern preset priority);
use std::str::FromStr;
use yazi_shared::{RoCell, Xdg};
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
use yazi_shared::{RoCell, SyncCell, Xdg};
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
pub static LOG: RoCell<log::Log> = RoCell::new();
@ -23,26 +21,28 @@ pub static CONFIRM: RoCell<popup::Confirm> = RoCell::new();
pub static PICK: RoCell<popup::Pick> = RoCell::new();
pub static WHICH: RoCell<which::Which> = RoCell::new();
pub static LAYOUT: SyncCell<Layout> = SyncCell::new(Layout::default());
pub fn init() -> anyhow::Result<()> {
let config_dir = Xdg::config_dir();
let yazi_toml = &Preset::yazi(&config_dir)?;
let keymap_toml = &Preset::keymap(&config_dir)?;
let theme_toml = &Preset::theme(&config_dir)?;
if let Err(e) = try_init(true) {
eprintln!("{e}");
if let Some(src) = e.source() {
eprintln!("\nCaused by:\n{src}");
}
LAYOUT.with(<_>::default);
use crossterm::style::{Attribute, Print, SetAttributes};
crossterm::execute!(
std::io::stderr(),
SetAttributes(Attribute::Reverse.into()),
SetAttributes(Attribute::Bold.into()),
Print("Press <Enter> to continue with preset settings..."),
SetAttributes(Attribute::Reset.into()),
Print("\n"),
)?;
KEYMAP.init(<_>::from_str(keymap_toml)?);
LOG.init(<_>::from_str(yazi_toml)?);
MANAGER.init(<_>::from_str(yazi_toml)?);
OPEN.init(<_>::from_str(yazi_toml)?);
PLUGIN.init(<_>::from_str(yazi_toml)?);
PREVIEW.init(<_>::from_str(yazi_toml)?);
TASKS.init(<_>::from_str(yazi_toml)?);
THEME.init(<_>::from_str(theme_toml)?);
INPUT.init(<_>::from_str(yazi_toml)?);
CONFIRM.init(<_>::from_str(yazi_toml)?);
PICK.init(<_>::from_str(yazi_toml)?);
WHICH.init(<_>::from_str(yazi_toml)?);
std::io::stdin().read_line(&mut String::new())?;
try_init(false)?;
}
// TODO: Remove in v0.3.2
for c in &KEYMAP.manager {
@ -78,3 +78,41 @@ Please change `create_title = "Create:"` to `create_title = ["Create:", "Create
Ok(())
}
fn try_init(merge: bool) -> anyhow::Result<()> {
let (yazi_toml, keymap_toml, theme_toml) = if merge {
let p = Xdg::config_dir();
(Preset::yazi(&p)?, Preset::keymap(&p)?, Preset::theme(&p)?)
} else {
use yazi_macro::config_preset as preset;
(preset!("yazi"), preset!("keymap"), preset!("theme"))
};
let keymap = <_>::from_str(&keymap_toml)?;
let log = <_>::from_str(&yazi_toml)?;
let manager = <_>::from_str(&yazi_toml)?;
let open = <_>::from_str(&yazi_toml)?;
let plugin = <_>::from_str(&yazi_toml)?;
let preview = <_>::from_str(&yazi_toml)?;
let tasks = <_>::from_str(&yazi_toml)?;
let theme = <_>::from_str(&theme_toml)?;
let input = <_>::from_str(&yazi_toml)?;
let confirm = <_>::from_str(&yazi_toml)?;
let pick = <_>::from_str(&yazi_toml)?;
let which = <_>::from_str(&yazi_toml)?;
KEYMAP.init(keymap);
LOG.init(log);
MANAGER.init(manager);
OPEN.init(open);
PLUGIN.init(plugin);
PREVIEW.init(preview);
TASKS.init(tasks);
THEME.init(theme);
INPUT.init(input);
CONFIRM.init(confirm);
PICK.init(pick);
WHICH.init(which);
Ok(())
}

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Deserializer};
#[derive(Debug)]
@ -8,9 +9,11 @@ pub struct Log {
}
impl FromStr for Log {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse the [log] section in your yazi.toml")
}
}
impl<'de> Deserialize<'de> for Log {

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Serialize};
use validator::Validate;
@ -35,9 +36,10 @@ impl FromStr for Manager {
manager: Manager,
}
let manager = toml::from_str::<Outer>(s)?.manager;
manager.validate()?;
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [manager] section in your yazi.toml")?;
outer.manager.validate()?;
Ok(manager)
Ok(outer.manager)
}
}

View file

@ -1,5 +1,6 @@
use std::{collections::HashMap, path::Path, str::FromStr};
use anyhow::Context;
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer};
use yazi_shared::MIME_DIR;
@ -55,9 +56,11 @@ impl Open {
}
impl FromStr for Open {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse the [open] or [opener] section in your yazi.toml")
}
}
impl<'de> Deserialize<'de> for Open {

View file

@ -1,4 +1,4 @@
use std::path::Path;
use std::{path::Path, str::FromStr};
use globset::GlobBuilder;
use serde::Deserialize;
@ -9,6 +9,8 @@ pub struct Pattern {
inner: globset::GlobMatcher,
is_dir: bool,
is_star: bool,
#[cfg(windows)]
sep_lit: bool,
}
impl Pattern {
@ -19,7 +21,20 @@ impl Pattern {
#[inline]
pub fn match_path(&self, path: impl AsRef<Path>, is_dir: bool) -> bool {
is_dir == self.is_dir && (self.is_star || self.inner.is_match(path))
if is_dir != self.is_dir {
return false;
} else if self.is_star {
return true;
}
#[cfg(windows)]
let path = if self.sep_lit {
yazi_shared::fs::backslash_to_slash(path.as_ref())
} else {
std::borrow::Cow::Borrowed(path.as_ref())
};
self.inner.is_match(path)
}
#[inline]
@ -29,27 +44,115 @@ impl Pattern {
pub fn any_dir(&self) -> bool { self.is_star && self.is_dir }
}
impl TryFrom<&str> for Pattern {
type Error = anyhow::Error;
impl FromStr for Pattern {
type Err = globset::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let a = s.trim_start_matches("\\s");
let b = a.trim_end_matches('/');
let sep_lit = b.contains('/');
let inner = GlobBuilder::new(b)
.case_insensitive(a.len() == s.len())
.literal_separator(false)
.literal_separator(sep_lit)
.backslash_escape(false)
.empty_alternates(true)
.build()?
.compile_matcher();
Ok(Self { inner, is_dir: b.len() < a.len(), is_star: b == "*" })
Ok(Self {
inner,
is_dir: b.len() < a.len(),
is_star: b == "*",
#[cfg(windows)]
sep_lit,
})
}
}
impl TryFrom<String> for Pattern {
type Error = anyhow::Error;
type Error = globset::Error;
fn try_from(s: String) -> Result<Self, Self::Error> { Self::try_from(s.as_str()) }
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(s.as_str()) }
}
#[cfg(test)]
mod tests {
use super::*;
fn matches(glob: &str, path: &str) -> bool {
Pattern::from_str(glob).unwrap().match_path(path, false)
}
#[cfg(unix)]
#[test]
fn test_unix() {
// Wildcard
assert!(matches("*", "/foo"));
assert!(matches("*", "/foo/bar"));
assert!(matches("**", "foo"));
assert!(matches("**", "/foo"));
assert!(matches("**", "/foo/bar"));
// Filename
assert!(matches("*.md", "foo.md"));
assert!(matches("*.md", "/foo.md"));
assert!(matches("*.md", "/foo/bar.md"));
// 1-star
assert!(matches("/*", "/foo"));
assert!(matches("/*/*.md", "/foo/bar.md"));
// 2-star
assert!(matches("/**", "/foo"));
assert!(matches("/**", "/foo/bar"));
assert!(matches("**/**", "/foo"));
assert!(matches("**/**", "/foo/bar"));
assert!(matches("/**/*", "/foo"));
assert!(matches("/**/*", "/foo/bar"));
// Failures
assert!(!matches("/*/*", "/foo"));
assert!(!matches("/*/*.md", "/foo.md"));
assert!(!matches("/*", "/foo/bar"));
assert!(!matches("/*.md", "/foo/bar.md"));
}
#[cfg(windows)]
#[test]
fn test_windows() {
// Wildcard
assert!(matches("*", r#"C:\foo"#));
assert!(matches("*", r#"C:\foo\bar"#));
assert!(matches("**", r#"foo"#));
assert!(matches("**", r#"C:\foo"#));
assert!(matches("**", r#"C:\foo\bar"#));
// Filename
assert!(matches("*.md", r#"foo.md"#));
assert!(matches("*.md", r#"C:\foo.md"#));
assert!(matches("*.md", r#"C:\foo\bar.md"#));
// 1-star
assert!(matches(r#"C:/*"#, r#"C:\foo"#));
assert!(matches(r#"C:/*/*.md"#, r#"C:\foo\bar.md"#));
// 2-star
assert!(matches(r#"C:/**"#, r#"C:\foo"#));
assert!(matches(r#"C:/**"#, r#"C:\foo\bar"#));
assert!(matches(r#"**/**"#, r#"C:\foo"#));
assert!(matches(r#"**/**"#, r#"C:\foo\bar"#));
assert!(matches(r#"C:/**/*"#, r#"C:\foo"#));
assert!(matches(r#"C:/**/*"#, r#"C:\foo\bar"#));
// Drive letter
assert!(matches(r#"*:/*"#, r#"C:\foo"#));
assert!(matches(r#"*:/**/*.md"#, r#"C:\foo\bar.md"#));
// Failures
assert!(!matches(r#"C:/*/*"#, r#"C:\foo"#));
assert!(!matches(r#"C:/*/*.md"#, r#"C:\foo.md"#));
assert!(!matches(r#"C:/*"#, r#"C:\foo\bar"#));
assert!(!matches(r#"C:/*.md"#, r#"C:\foo\bar.md"#));
}
}

View file

@ -1,11 +1,11 @@
use std::{collections::HashSet, path::Path, str::FromStr};
use serde::Deserialize;
use anyhow::Context;
use serde::{Deserialize, Deserializer};
use super::{Fetcher, Preloader, Previewer};
use crate::{Preset, plugin::MAX_PREWORKERS};
#[derive(Deserialize)]
pub struct Plugin {
pub fetchers: Vec<Fetcher>,
pub preloaders: Vec<Preloader>,
@ -55,9 +55,18 @@ impl Plugin {
}
impl FromStr for Plugin {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse the [plugin] section in your yazi.toml")
}
}
impl<'de> Deserialize<'de> for Plugin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
plugin: Shadow,
@ -84,7 +93,7 @@ impl FromStr for Plugin {
append_previewers: Vec<Previewer>,
}
let mut shadow = toml::from_str::<Outer>(s)?.plugin;
let mut shadow = Outer::deserialize(deserializer)?.plugin;
if shadow.append_previewers.iter().any(|r| r.any_file()) {
shadow.previewers.retain(|r| !r.any_file());
}

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use super::{Offset, Origin};
@ -30,7 +31,7 @@ pub struct Confirm {
}
impl FromStr for Confirm {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
@ -38,7 +39,10 @@ impl FromStr for Confirm {
confirm: Confirm,
}
Ok(toml::from_str::<Outer>(s)?.confirm)
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [confirm] section in your yazi.toml")?;
Ok(outer.confirm)
}
}

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use super::{Offset, Origin};
@ -49,7 +50,7 @@ impl Input {
}
impl FromStr for Input {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
@ -57,7 +58,10 @@ impl FromStr for Input {
input: Input,
}
Ok(toml::from_str::<Outer>(s)?.input)
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [input] section in your yazi.toml")?;
Ok(outer.input)
}
}

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use super::{Offset, Origin};
@ -17,7 +18,7 @@ impl Pick {
}
impl FromStr for Pick {
type Err = toml::de::Error;
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
@ -25,6 +26,9 @@ impl FromStr for Pick {
pick: Pick,
}
Ok(toml::from_str::<Outer>(s)?.pick)
let outer =
toml::from_str::<Outer>(s).context("Failed to parse the [pick] section in your yazi.toml")?;
Ok(outer.pick)
}
}

View file

@ -9,15 +9,15 @@ use crate::theme::Flavor;
pub(crate) struct Preset;
impl Preset {
pub(crate) fn yazi(p: &Path) -> Result<Cow<str>> {
pub(crate) fn yazi(p: &Path) -> Result<Cow<'static, str>> {
Self::merge_path(p.join("yazi.toml"), preset!("yazi"))
}
pub(crate) fn keymap(p: &Path) -> Result<Cow<str>> {
pub(crate) fn keymap(p: &Path) -> Result<Cow<'static, str>> {
Self::merge_path(p.join("keymap.toml"), preset!("keymap"))
}
pub(crate) fn theme(p: &Path) -> Result<Cow<str>> {
pub(crate) fn theme(p: &Path) -> Result<Cow<'static, str>> {
let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else {
return Ok(preset!("theme"));
};

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, path::PathBuf, str::FromStr, time::{SystemTime, UNIX_EPOCH}};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use validator::Validate;
use yazi_shared::fs::expand_path;
@ -49,6 +49,20 @@ impl FromStr for Preview {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let preview: Self =
toml::from_str(s).context("Failed to parse the [preview] section in your yazi.toml")?;
std::fs::create_dir_all(&preview.cache_dir).context("Failed to create cache directory")?;
Ok(preview)
}
}
impl<'de> Deserialize<'de> for Preview {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
preview: Shadow,
@ -74,12 +88,8 @@ impl FromStr for Preview {
ueberzug_offset: (f32, f32, f32, f32),
}
let preview = toml::from_str::<Outer>(s)?.preview;
preview.validate()?;
let cache_dir =
preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path);
std::fs::create_dir_all(&cache_dir).context("Failed to create cache directory")?;
let preview = Outer::deserialize(deserializer)?.preview;
preview.validate().map_err(serde::de::Error::custom)?;
Ok(Preview {
wrap: preview.wrap,
@ -87,7 +97,10 @@ impl FromStr for Preview {
max_width: preview.max_width,
max_height: preview.max_height,
cache_dir,
cache_dir: preview
.cache_dir
.filter(|p| !p.is_empty())
.map_or_else(Xdg::cache_dir, expand_path),
image_delay: preview.image_delay,
image_filter: preview.image_filter,

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use validator::Validate;
@ -27,9 +28,10 @@ impl FromStr for Tasks {
tasks: Tasks,
}
let tasks = toml::from_str::<Outer>(s)?.tasks;
tasks.validate()?;
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [tasks] section in your yazi.toml")?;
outer.tasks.validate()?;
Ok(tasks)
Ok(outer.tasks)
}
}

View file

@ -1,5 +1,6 @@
use std::{path::PathBuf, str::FromStr};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_shared::{Xdg, fs::expand_path, theme::Style};
@ -12,6 +13,7 @@ pub struct Theme {
pub manager: Manager,
status: Status,
pub input: Input,
pub confirm: Confirm,
pub pick: Pick,
pub completion: Completion,
pub tasks: Tasks,
@ -30,7 +32,7 @@ impl FromStr for Theme {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut theme: Self = toml::from_str(s)?;
let mut theme: Self = toml::from_str(s).context("Failed to parse your yazi.toml")?;
theme.manager.validate()?;
theme.which.validate()?;
@ -114,6 +116,17 @@ pub struct Input {
pub selected: Style,
}
#[derive(Deserialize, Serialize)]
pub struct Confirm {
pub border: Style,
pub title: Style,
pub content: Style,
pub list: Style,
pub btn_yes: Style,
pub btn_no: Style,
pub btn_labels: [String; 2],
}
#[derive(Deserialize, Serialize)]
pub struct Pick {
pub border: Style,

View file

@ -1,5 +1,6 @@
use std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Serialize};
use validator::Validate;
@ -23,6 +24,9 @@ impl FromStr for Which {
which: Which,
}
Ok(toml::from_str::<Outer>(s)?.which)
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [which] section in your yazi.toml")?;
Ok(outer.which)
}
}

View file

@ -27,7 +27,7 @@ bitflags = { workspace = true }
crossterm = { workspace = true }
dirs = { workspace = true }
futures = { workspace = true }
notify = { package = "notify-fork", version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
notify = { version = "7.0.0", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = { workspace = true }
ratatui = { workspace = true }
scopeguard = { workspace = true }

View file

@ -53,7 +53,8 @@ impl Manager {
return Ok(());
}
let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect();
let (old, new) = old.into_iter().zip(new).filter(|(o, n)| o != n).unzip();
let todo = Self::prioritized_paths(old, new);
if todo.is_empty() {
return Ok(());
}
@ -117,4 +118,90 @@ impl Manager {
stdin().read_exact(&mut [0]).await?;
Ok(())
}
fn prioritized_paths(old: Vec<PathBuf>, new: Vec<PathBuf>) -> Vec<(PathBuf, PathBuf)> {
let orders: HashMap<_, _> = old.iter().enumerate().map(|(i, p)| (p, i)).collect();
let mut incomes: HashMap<_, _> = old.iter().map(|p| (p, false)).collect();
let mut todos: HashMap<_, _> = old
.iter()
.zip(new)
.map(|(o, n)| {
incomes.get_mut(&n).map(|b| *b = true);
(o, n)
})
.collect();
let mut sorted = Vec::with_capacity(old.len());
while !todos.is_empty() {
// Paths that are non-incomes and don't need to be prioritized in this round
let mut outcomes: Vec<_> = incomes.iter().filter(|(_, &b)| !b).map(|(&p, _)| p).collect();
outcomes.sort_unstable_by(|a, b| orders[b].cmp(&orders[a]));
// If there're no outcomes, it means there are cycles in the renaming
if outcomes.is_empty() {
let mut remain: Vec<_> = todos.into_iter().map(|(o, n)| (o.clone(), n)).collect();
remain.sort_unstable_by(|(a, _), (b, _)| orders[a].cmp(&orders[b]));
sorted.reverse();
sorted.extend(remain);
return sorted;
}
for old in outcomes {
let Some(new) = todos.remove(old) else { unreachable!() };
incomes.remove(&old);
incomes.get_mut(&new).map(|b| *b = false);
sorted.push((old.clone(), new));
}
}
sorted.reverse();
sorted
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sort() {
fn cmp(input: &[(&str, &str)], expected: &[(&str, &str)]) {
let sorted = Manager::prioritized_paths(
input.iter().map(|&(o, _)| o.into()).collect(),
input.iter().map(|&(_, n)| n.into()).collect(),
);
let sorted: Vec<_> =
sorted.iter().map(|(o, n)| (o.to_str().unwrap(), n.to_str().unwrap())).collect();
assert_eq!(sorted, expected);
}
#[rustfmt::skip]
cmp(
&[("2", "3"), ("1", "2"), ("3", "4")],
&[("3", "4"), ("2", "3"), ("1", "2")]
);
#[rustfmt::skip]
cmp(
&[("1", "3"), ("2", "3"), ("3", "4")],
&[("3", "4"), ("1", "3"), ("2", "3")]
);
#[rustfmt::skip]
cmp(
&[("2", "1"), ("1", "2")],
&[("2", "1"), ("1", "2")]
);
#[rustfmt::skip]
cmp(
&[("3", "2"), ("2", "1"), ("1", "3"), ("a", "b"), ("b", "c")],
&[("b", "c"), ("a", "b"), ("3", "2"), ("2", "1"), ("1", "3")]
);
#[rustfmt::skip]
cmp(
&[("b", "b_"), ("a", "a_"), ("c", "c_")],
&[("b", "b_"), ("a", "a_"), ("c", "c_")],
);
}
}

View file

@ -2,21 +2,18 @@ use std::{collections::HashSet, path::PathBuf};
use yazi_dds::Pubsub;
use yazi_macro::render;
use yazi_shared::{event::{Cmd, Data}, fs::{Url, Urn}};
use yazi_shared::{Id, event::{Cmd, Data}, fs::{Url, Urn}};
use crate::manager::Manager;
struct Opt {
url: Option<Url>,
tab: Option<usize>,
tab: Option<Id>,
}
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
Self {
url: c.take_first().and_then(Data::into_url),
tab: c.get("tab").and_then(Data::as_usize),
}
Self { url: c.take_first().and_then(Data::into_url), tab: c.get("tab").and_then(Data::as_id) }
}
}
impl From<Option<Url>> for Opt {
@ -49,10 +46,10 @@ impl Manager {
self.watcher.watch(to_watch);
// Publish through DDS
Pubsub::pub_from_hover(self.active().idx, self.hovered().map(|h| &h.url));
Pubsub::pub_from_hover(self.active().id, self.hovered().map(|h| &h.url));
}
fn hover_do(&mut self, url: Url, tab: Option<usize>) {
fn hover_do(&mut self, url: Url, tab: Option<Id>) {
// Hover on the file
if let Ok(p) = url.strip_prefix(&self.current_or(tab).url).map(PathBuf::from) {
render!(self.current_or_mut(tab).repos(Some(Urn::new(&p))));

View file

@ -1,3 +1,4 @@
use yazi_proxy::HIDER;
use yazi_shared::{event::{Cmd, Data}, fs::Url};
use crate::manager::Manager;
@ -30,6 +31,9 @@ impl Manager {
let Some(hovered) = self.hovered().cloned() else {
return self.active_mut().preview.reset();
};
if HIDER.try_acquire().is_err() {
return self.active_mut().preview.reset_image();
}
let mime = self.mimetype.get_owned(&hovered.url).unwrap_or_default();
let folder = self.active().hovered_folder().map(|f| (f.offset, f.cha));

View file

@ -24,11 +24,12 @@ impl Tabs {
}
self.items.remove(opt.idx).shutdown();
if opt.idx <= self.cursor {
if opt.idx > self.cursor {
self.set_idx(self.cursor);
} else {
self.set_idx(self.absolute(1));
}
self.reorder();
render!();
}
}

View file

@ -36,8 +36,7 @@ impl Tabs {
return;
}
let mut tab = Tab { idx: self.cursor + 1, ..Default::default() };
let mut tab = Tab::default();
if !opt.current {
tab.cd(opt.url);
} else if let Some(h) = self.active().hovered() {
@ -52,7 +51,6 @@ impl Tabs {
self.items.insert(self.cursor + 1, tab);
self.set_idx(self.cursor + 1);
self.reorder();
render!();
}
}

View file

@ -21,7 +21,6 @@ impl Tabs {
self.items.swap(self.cursor, idx);
self.set_idx(idx);
self.reorder();
render!();
}
}

View file

@ -87,7 +87,7 @@ impl Manager {
return;
}
ManagerProxy::hover(None, tab.idx); // Re-hover in next loop
ManagerProxy::hover(None, tab.id); // Re-hover in next loop
ManagerProxy::update_paged(); // Update for paged files in next loop
if calc {
tasks.prework_sorted(&tab.current.files);

View file

@ -2,7 +2,7 @@ use ratatui::layout::Rect;
use yazi_adapter::Dimension;
use yazi_config::popup::{Origin, Position};
use yazi_fs::Folder;
use yazi_shared::fs::{File, Url};
use yazi_shared::{Id, fs::{File, Url}};
use super::{Mimetype, Tabs, Watcher, Yanked};
use crate::tab::Tab;
@ -48,10 +48,10 @@ impl Manager {
pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() }
#[inline]
pub fn active_or(&self, idx: Option<usize>) -> &Tab { self.tabs.active_or(idx) }
pub fn active_or(&self, id: Option<Id>) -> &Tab { self.tabs.active_or(id) }
#[inline]
pub fn active_or_mut(&mut self, idx: Option<usize>) -> &mut Tab { self.tabs.active_or_mut(idx) }
pub fn active_or_mut(&mut self, id: Option<Id>) -> &mut Tab { self.tabs.active_or_mut(id) }
#[inline]
pub fn current(&self) -> &Folder { &self.active().current }
@ -60,10 +60,10 @@ impl Manager {
pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current }
#[inline]
pub fn current_or(&self, idx: Option<usize>) -> &Folder { &self.active_or(idx).current }
pub fn current_or(&self, idx: Option<Id>) -> &Folder { &self.active_or(idx).current }
#[inline]
pub fn current_or_mut(&mut self, idx: Option<usize>) -> &mut Folder {
pub fn current_or_mut(&mut self, idx: Option<Id>) -> &mut Folder {
&mut self.active_or_mut(idx).current
}

View file

@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut};
use yazi_boot::BOOT;
use yazi_dds::Pubsub;
use yazi_proxy::ManagerProxy;
use yazi_shared::fs::Url;
use yazi_shared::{Id, fs::Url};
use crate::tab::Tab;
@ -16,7 +16,6 @@ impl Tabs {
pub fn make() -> Self {
let mut tabs =
Self { cursor: 0, items: (0..BOOT.cwds.len()).map(|_| Tab::default()).collect() };
tabs.reorder();
for (i, tab) in tabs.iter_mut().enumerate() {
let file = &BOOT.files[i];
@ -29,7 +28,6 @@ impl Tabs {
tabs
}
#[inline]
pub(super) fn absolute(&self, rel: isize) -> usize {
if rel > 0 {
(self.cursor + rel as usize).min(self.items.len() - 1)
@ -38,16 +36,7 @@ impl Tabs {
}
}
#[inline]
pub(super) fn reorder(&mut self) {
self.items.iter_mut().enumerate().for_each(|(i, tab)| tab.idx = i);
}
pub(super) fn set_idx(&mut self, idx: usize) {
if self.cursor == idx {
return;
}
// Reset the preview of the last active tab
if let Some(active) = self.items.get_mut(self.cursor) {
active.preview.reset_image();
@ -68,16 +57,16 @@ impl Tabs {
pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] }
#[inline]
pub fn active_or(&self, idx: Option<usize>) -> &Tab {
idx.and_then(|i| self.items.get(i)).unwrap_or(&self.items[self.cursor])
pub fn active_or(&self, id: Option<Id>) -> &Tab {
id.and_then(|id| self.iter().find(|&t| t.id == id)).unwrap_or(self.active())
}
#[inline]
pub(super) fn active_or_mut(&mut self, idx: Option<usize>) -> &mut Tab {
if let Some(i) = idx.filter(|&i| i < self.items.len()) {
pub(super) fn active_or_mut(&mut self, id: Option<Id>) -> &mut Tab {
if let Some(i) = id.and_then(|id| self.iter().position(|t| t.id == id)) {
&mut self.items[i]
} else {
&mut self.items[self.cursor]
self.active_mut()
}
}
}

View file

@ -42,7 +42,7 @@ impl Tab {
}
}
ManagerProxy::hover(None, self.idx);
ManagerProxy::hover(None, self.id);
render!();
}
}

View file

@ -66,7 +66,7 @@ impl Tab {
self.backstack.push(opt.target.clone());
}
Pubsub::pub_from_cd(self.idx, self.cwd());
Pubsub::pub_from_cd(self.id, self.cwd());
ManagerProxy::refresh();
render!();
}

View file

@ -35,6 +35,11 @@ impl Tab {
}
}
// Copy the CWD path regardless even if the directory is empty
if s.is_empty() && opt.type_ == "dirname" {
s.push(self.cwd());
}
futures::executor::block_on(CLIPBOARD.set(s));
}
}

View file

@ -27,7 +27,7 @@ impl Tab {
self.current.repos(hovered.as_ref().map(|u| u.as_urn()));
if self.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u.as_urn()) {
ManagerProxy::hover(None, self.idx);
ManagerProxy::hover(None, self.id);
}
render!();

View file

@ -15,7 +15,7 @@ impl Tab {
self.apply_files_attrs();
if hovered.as_ref() != self.hovered().map(|f| &f.url) {
ManagerProxy::hover(hovered, self.idx);
ManagerProxy::hover(hovered, self.id);
} else if self.hovered().is_some_and(|f| f.is_dir()) {
ManagerProxy::peek(true);
}

View file

@ -30,6 +30,6 @@ impl Tab {
self.cd(parent.clone());
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]).emit();
ManagerProxy::hover(Some(opt.target), self.idx);
ManagerProxy::hover(Some(opt.target), self.id);
}
}

View file

@ -7,14 +7,13 @@ use yazi_adapter::Dimension;
use yazi_config::{LAYOUT, popup::{Origin, Position}};
use yazi_fs::{Folder, FolderStage};
use yazi_macro::render;
use yazi_shared::fs::{File, Url};
use yazi_shared::{Id, Ids, fs::{File, Url}};
use super::{Backstack, Config, Finder, History, Mode, Preview};
use crate::tab::Selected;
#[derive(Default)]
pub struct Tab {
pub idx: usize,
pub id: Id,
pub mode: Mode,
pub conf: Config,
pub current: Folder,
@ -29,6 +28,28 @@ pub struct Tab {
pub search: Option<JoinHandle<Result<()>>>,
}
impl Default for Tab {
fn default() -> Self {
static IDS: Ids = Ids::new();
Self {
id: IDS.next(),
mode: Default::default(),
conf: Default::default(),
current: Default::default(),
parent: Default::default(),
backstack: Default::default(),
history: Default::default(),
selected: Default::default(),
preview: Default::default(),
finder: Default::default(),
search: Default::default(),
}
}
}
impl Tab {
pub fn shutdown(&mut self) {
if let Some(handle) = self.search.take() {
@ -48,7 +69,7 @@ impl Tab {
pub fn hovered_rect(&self) -> Option<Rect> {
let y = self.current.files.position(self.hovered()?.urn())? - self.current.offset;
let mut rect = LAYOUT.load().current;
let mut rect = LAYOUT.get().current;
rect.y = rect.y.saturating_sub(1) + y as u16;
rect.height = 1;
Some(rect)

View file

@ -1,7 +1,7 @@
use serde::Serialize;
use yazi_scheduler::Ongoing;
#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct TasksProgress {
pub total: u32,
pub succ: u32,

View file

@ -2,13 +2,13 @@ use std::borrow::Cow;
use mlua::{IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use yazi_shared::fs::Url;
use yazi_shared::{Id, fs::Url};
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyCd<'a> {
pub tab: usize,
pub tab: Id,
pub url: Cow<'a, Url>,
#[serde(skip)]
dummy: bool,
@ -16,14 +16,14 @@ pub struct BodyCd<'a> {
impl<'a> BodyCd<'a> {
#[inline]
pub fn borrowed(tab: usize, url: &'a Url) -> Body<'a> {
pub fn borrowed(tab: Id, url: &'a Url) -> Body<'a> {
Self { tab, url: Cow::Borrowed(url), dummy: false }.into()
}
}
impl BodyCd<'static> {
#[inline]
pub fn dummy(tab: usize) -> Body<'static> {
pub fn dummy(tab: Id) -> Body<'static> {
Self { tab, url: Default::default(), dummy: true }.into()
}
}
@ -36,11 +36,11 @@ impl IntoLua<'_> for BodyCd<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
if let Some(Cow::Owned(url)) = Some(self.url).filter(|_| !self.dummy) {
lua.create_table_from([
("tab", self.tab.into_lua(lua)?),
("tab", self.tab.get().into_lua(lua)?),
("url", lua.create_any_userdata(url)?.into_lua(lua)?),
])?
} else {
lua.create_table_from([("tab", self.tab)])?
lua.create_table_from([("tab", self.tab.get())])?
}
.into_lua(lua)
}

View file

@ -2,26 +2,26 @@ use std::borrow::Cow;
use mlua::{IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use yazi_shared::fs::Url;
use yazi_shared::{Id, fs::Url};
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyHover<'a> {
pub tab: usize,
pub tab: Id,
pub url: Option<Cow<'a, Url>>,
}
impl<'a> BodyHover<'a> {
#[inline]
pub fn borrowed(tab: usize, url: Option<&'a Url>) -> Body<'a> {
pub fn borrowed(tab: Id, url: Option<&'a Url>) -> Body<'a> {
Self { tab, url: url.map(Cow::Borrowed) }.into()
}
}
impl BodyHover<'static> {
#[inline]
pub fn dummy(tab: usize) -> Body<'static> { Self { tab, url: None }.into() }
pub fn dummy(tab: Id) -> Body<'static> { Self { tab, url: None }.into() }
}
impl<'a> From<BodyHover<'a>> for Body<'a> {
@ -32,11 +32,11 @@ impl IntoLua<'_> for BodyHover<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
if let Some(Cow::Owned(url)) = self.url {
lua.create_table_from([
("tab", self.tab.into_lua(lua)?),
("tab", self.tab.get().into_lua(lua)?),
("url", lua.create_any_userdata(url)?.into_lua(lua)?),
])?
} else {
lua.create_table_from([("tab", self.tab)])?
lua.create_table_from([("tab", self.tab.get())])?
}
.into_lua(lua)
}

View file

@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use mlua::Function;
use parking_lot::RwLock;
use yazi_boot::BOOT;
use yazi_shared::{RoCell, fs::Url};
use yazi_shared::{Id, RoCell, fs::Url};
use crate::{Client, ID, PEERS, body::{Body, BodyBulk, BodyCd, BodyDelete, BodyHi, BodyHover, BodyMove, BodyMoveItem, BodyRename, BodyTab, BodyTrash, BodyYank}};
@ -88,7 +88,7 @@ impl Pubsub {
true
}
pub fn pub_from_cd(tab: usize, url: &Url) {
pub fn pub_from_cd(tab: Id, url: &Url) {
if LOCAL.read().contains_key("cd") {
Self::pub_(BodyCd::dummy(tab));
}
@ -100,7 +100,7 @@ impl Pubsub {
}
}
pub fn pub_from_hover(tab: usize, url: Option<&Url>) {
pub fn pub_from_hover(tab: Id, url: Option<&Url>) {
if LOCAL.read().contains_key("hover") {
Self::pub_(BodyHover::dummy(tab));
}

View file

@ -4,6 +4,7 @@ yazi_macro::mod_flat!(
notify
plugin
quit
reflow
render
resize
resume

View file

@ -0,0 +1,55 @@
use mlua::Value;
use ratatui::layout::Position;
use tracing::error;
use yazi_config::LAYOUT;
use yazi_macro::render;
use yazi_shared::event::Cmd;
use crate::{Root, app::App, lives::Lives};
struct Opt;
impl From<Cmd> for Opt {
fn from(_: Cmd) -> Self { Self }
}
impl From<()> for Opt {
fn from(_: ()) -> Self { Self }
}
impl App {
#[yazi_codegen::command]
pub fn reflow(&mut self, _: Opt) {
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return };
let mut layout = LAYOUT.get();
let result = Lives::scope(&self.cx, |_| {
let comps = Root::reflow((Position::ORIGIN, size).into())?;
for v in comps.sequence_values::<Value>() {
let Value::Table(t) = v? else {
error!("`reflow()` must return a table of components");
continue;
};
let id: mlua::String = t.get("_id")?;
match id.to_str()? {
"current" => layout.current = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
"preview" => layout.preview = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
"progress" => layout.progress = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
_ => {}
}
}
Ok(())
});
if layout != LAYOUT.get() {
LAYOUT.set(layout);
render!();
}
if let Err(e) = result {
error!("Failed to `reflow()` the `Root` component:\n{e}");
}
}
}

View file

@ -31,7 +31,7 @@ impl App {
Self::patch(frame, self.cx.cursor());
}
if !self.cx.notify.messages.is_empty() {
self.render_notify();
self.render_partially();
}
// Reload preview if collision is resolved
@ -40,18 +40,19 @@ impl App {
}
}
pub(crate) fn render_notify(&mut self) {
let Some(term) = &mut self.term else {
return;
};
pub(crate) fn render_partially(&mut self) {
let Some(term) = &mut self.term else { return };
if !term.can_partial() {
return self.render();
}
let frame = term
.draw_partial(|f| {
f.render_widget(crate::notify::Layout::new(&self.cx), f.area());
_ = Lives::scope(&self.cx, |_| {
f.render_widget(crate::tasks::Progress, f.area());
f.render_widget(crate::notify::Notify::new(&self.cx), f.area());
Ok(())
});
if let Some(pos) = self.cx.cursor() {
f.set_cursor_position(pos);

View file

@ -16,7 +16,7 @@ impl App {
#[yazi_codegen::command]
pub fn resize(&mut self, _: Opt) {
self.cx.manager.active_mut().preview.reset();
self.render();
self.reflow(());
self.cx.manager.current_mut().sync_page(true);
self.cx.manager.hover(None);

View file

@ -9,14 +9,14 @@ impl App {
pub(crate) fn update_notify(&mut self, cmd: Cmd) {
let WindowSize { rows, columns, .. } = Dimension::available();
let area =
notify::Layout::available(Rect { x: 0, y: 0, width: columns, height: rows });
notify::Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });
self.cx.notify.tick(cmd, area);
if self.cx.notify.messages.is_empty() {
self.render();
} else {
self.render_notify();
self.render_partially();
}
}
}

View file

@ -1,9 +1,8 @@
use ratatui::backend::Backend;
use yazi_core::tasks::TasksProgress;
use yazi_macro::render;
use yazi_shared::event::Cmd;
use crate::{app::App, components::Progress, lives::Lives};
use crate::app::App;
pub struct Opt {
progress: TasksProgress,
@ -19,41 +18,28 @@ impl TryFrom<Cmd> for Opt {
impl App {
pub(crate) fn update_progress(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else {
return;
};
let Ok(opt) = opt.try_into() else { return };
// Update the progress of all tasks.
let tasks = &mut self.cx.tasks;
let progressed = tasks.progress != opt.progress;
tasks.progress = opt.progress;
// If the task manager is visible, update the summaries with a complete render.
if tasks.visible {
let new = tasks.paginate();
if new.len() != tasks.summaries.len()
|| new.iter().zip(&tasks.summaries).any(|(a, b)| a.name != b.name)
{
if tasks.summaries != new {
tasks.summaries = new;
tasks.arrow(0);
return render!();
}
}
// Otherwise, only partially update the progress.
let Some(term) = &mut self.term else {
return;
};
_ = Lives::scope(&self.cx, |_| {
for patch in Progress::partial_render(term.current_buffer_mut()) {
term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell)))?;
if let Some(pos) = self.cx.cursor() {
term.show_cursor()?;
term.set_cursor_position(pos)?;
if !progressed {
} else if tasks.progress.total == 0 {
render!();
} else {
self.render_partially();
}
term.backend_mut().flush()?;
}
Ok(())
});
}
}

View file

@ -1,3 +0,0 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(preview progress);

View file

@ -1,41 +0,0 @@
use std::mem;
use mlua::{AnyUserData, Table, TableExt};
use tracing::error;
use yazi_plugin::{LUA, cast_to_renderable};
pub(crate) struct Progress;
impl Progress {
pub(crate) fn partial_render(
buf: &mut ratatui::buffer::Buffer,
) -> Vec<Vec<(u16, u16, ratatui::buffer::Cell)>> {
let mut patches = vec![];
let mut f = || {
let comp: Table = LUA.globals().raw_get("Progress")?;
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_render", ())? {
let Some(w) = cast_to_renderable(&widget) else { continue };
let area = w.area();
w.render(buf);
let mut patch = Vec::with_capacity(area.width as usize * area.height as usize);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
patch.push((x, y, mem::take(&mut buf[(x, y)])));
}
}
buf.reset();
patches.push(patch);
}
Ok::<_, anyhow::Error>(())
};
if let Err(e) = f() {
error!("{e}");
}
patches
}
}

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, layout::{Constraint, Rect}, style::Stylize, text::Span, widgets::{Paragraph, Widget}};
use ratatui::{buffer::Buffer, layout::{Constraint, Rect}, text::Span, widgets::{Paragraph, Widget}};
use yazi_config::THEME;
pub(crate) struct Buttons;
@ -7,7 +8,11 @@ impl Widget for Buttons {
let chunks =
ratatui::layout::Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area);
Paragraph::new(Span::raw(" [Y]es ").reversed()).centered().render(chunks[0], buf);
Paragraph::new(Span::raw(" (N)o ")).centered().render(chunks[1], buf);
Paragraph::new(Span::raw(&THEME.confirm.btn_labels[0]).style(THEME.confirm.btn_yes))
.centered()
.render(chunks[0], buf);
Paragraph::new(Span::raw(&THEME.confirm.btn_labels[1]).style(THEME.confirm.btn_no))
.centered()
.render(chunks[1], buf);
}
}

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, layout::{Alignment, Constraint, Layout, Margin, Rect}, style::{Style, Stylize}, text::Line, widgets::{Block, BorderType, Widget}};
use ratatui::{buffer::Buffer, layout::{Alignment, Constraint, Layout, Margin, Rect}, text::Line, widgets::{Block, BorderType, Widget}};
use yazi_config::THEME;
use crate::Ctx;
@ -19,8 +20,8 @@ impl<'a> Widget for Confirm<'a> {
Block::bordered()
.border_type(BorderType::Rounded)
.border_style(Style::new().blue())
.title(Line::styled(&confirm.title, Style::new().blue()))
.border_style(THEME.confirm.border)
.title(Line::styled(&confirm.title, THEME.confirm.title))
.title_alignment(Alignment::Center)
.render(area, buf);

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, style::{Style, Stylize}, widgets::{Block, Borders, Paragraph, Widget}};
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{Block, Borders, Paragraph, Widget}};
use yazi_config::THEME;
pub(crate) struct Content<'a> {
p: Paragraph<'a>,
@ -14,9 +15,14 @@ impl<'a> Widget for Content<'a> {
let inner = area.inner(Margin::new(1, 0));
// Bottom border
let block = Block::new().borders(Borders::BOTTOM).border_style(Style::new().blue());
let block = Block::new().borders(Borders::BOTTOM).border_style(THEME.confirm.border);
block.clone().render(area.inner(Margin::new(1, 0)), buf);
self.p.alignment(ratatui::layout::Alignment::Center).block(block).render(inner, buf);
self
.p
.alignment(ratatui::layout::Alignment::Center)
.block(block)
.style(THEME.confirm.content)
.render(inner, buf);
}
}

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, style::{Style, Stylize}, widgets::{Block, Borders, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget, Wrap}};
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{Block, Borders, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget, Wrap}};
use yazi_config::THEME;
use crate::Ctx;
@ -16,7 +17,7 @@ impl<'a> Widget for List<'a> {
let inner = area.inner(Margin::new(2, 0));
// Bottom border
let block = Block::new().borders(Borders::BOTTOM).border_style(Style::new().blue());
let block = Block::new().borders(Borders::BOTTOM).border_style(THEME.confirm.border);
block.clone().render(area.inner(Margin::new(1, 0)), buf);
let list = self
@ -26,6 +27,7 @@ impl<'a> Widget for List<'a> {
.clone()
.scroll((self.cx.confirm.offset as u16, 0))
.block(block)
.style(THEME.confirm.list)
.wrap(Wrap { trim: false });
// Vertical scrollbar

View file

@ -4,11 +4,11 @@ use yazi_config::{KEYMAP, THEME};
use super::Bindings;
use crate::Ctx;
pub(crate) struct Layout<'a> {
pub(crate) struct Help<'a> {
cx: &'a Ctx,
}
impl<'a> Layout<'a> {
impl<'a> Help<'a> {
pub fn new(cx: &'a Ctx) -> Self { Self { cx } }
fn tips() -> String {
@ -19,7 +19,7 @@ impl<'a> Layout<'a> {
}
}
impl<'a> Widget for Layout<'a> {
impl<'a> Widget for Help<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let help = &self.cx.help;
yazi_plugin::elements::Clear::default().render(area, buf);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(bindings layout);
yazi_macro::mod_flat!(bindings help);

View file

@ -1,6 +1,6 @@
use std::ops::Deref;
use mlua::{AnyUserData, Lua};
use mlua::{AnyUserData, Lua, MetaMethod, UserDataMethods};
use super::SCOPE;
@ -21,6 +21,8 @@ impl Finder {
}
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|_| {})
lua.register_userdata_type::<Self>(|reg| {
reg.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.filter.to_string()));
})
}
}

View file

@ -28,7 +28,7 @@ impl Folder {
let window = match window {
Some(w) => w,
None => {
let limit = LAYOUT.load().preview.height as usize;
let limit = LAYOUT.get().preview.height as usize;
inner.offset..inner.files.len().min(inner.offset + limit)
}
};

View file

@ -28,7 +28,7 @@ impl Preview {
me.tab()
.hovered_folder()
.map(|f| {
let limit = LAYOUT.load().preview.height as usize;
let limit = LAYOUT.get().preview.height as usize;
Folder::make(Some(me.skip..f.files.len().min(me.skip + limit)), f, me.tab())
})
.transpose()

View file

@ -23,6 +23,7 @@ impl Tab {
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|reg| {
reg.add_field_method_get("id", |_, me| Ok(me.id.get()));
reg.add_method("name", |lua, me, ()| {
lua.create_string(me.current.url.name().as_encoded_bytes())
});

View file

@ -5,9 +5,7 @@
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
yazi_macro::mod_pub!(
app completion components confirm help input lives notify pick tasks which
);
yazi_macro::mod_pub!(app completion confirm help input lives manager notify pick tasks which);
yazi_macro::mod_flat!(context executor logs panic root router signals term);

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(preview);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(layout);
yazi_macro::mod_flat!(notify);

View file

@ -3,11 +3,11 @@ use yazi_core::notify::Message;
use crate::Ctx;
pub(crate) struct Layout<'a> {
pub(crate) struct Notify<'a> {
cx: &'a Ctx,
}
impl<'a> Layout<'a> {
impl<'a> Notify<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(crate) fn available(area: Rect) -> Rect {
@ -38,7 +38,7 @@ impl<'a> Layout<'a> {
}
}
impl<'a> Widget for Layout<'a> {
impl<'a> Widget for Notify<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let notify = &self.cx.notify;

View file

@ -3,8 +3,8 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use tracing::error;
use yazi_plugin::{LUA, elements::render_widgets};
use super::{completion, confirm, input, pick, tasks, which};
use crate::{Ctx, components, help};
use super::{completion, confirm, help, input, manager, pick, tasks, which};
use crate::Ctx;
pub(super) struct Root<'a> {
cx: &'a Ctx,
@ -12,6 +12,12 @@ pub(super) struct Root<'a> {
impl<'a> Root<'a> {
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(super) fn reflow<'lua>(area: Rect) -> mlua::Result<Table<'lua>> {
let area = yazi_plugin::elements::Rect::from(area);
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
root.call_method("reflow", ())
}
}
impl<'a> Widget for Root<'a> {
@ -20,17 +26,17 @@ impl<'a> Widget for Root<'a> {
let area = yazi_plugin::elements::Rect::from(area);
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
render_widgets(root.call_method("render", ())?, buf);
render_widgets(root.call_method("redraw", ())?, buf);
Ok::<_, mlua::Error>(())
};
if let Err(e) = f() {
error!("Failed to render the `Root` component:\n{e}");
error!("Failed to redraw the `Root` component:\n{e}");
}
components::Preview::new(self.cx).render(area, buf);
manager::Preview::new(self.cx).render(area, buf);
if self.cx.tasks.visible {
tasks::Layout::new(self.cx).render(area, buf);
tasks::Tasks::new(self.cx).render(area, buf);
}
if self.cx.pick.visible {
@ -46,7 +52,7 @@ impl<'a> Widget for Root<'a> {
}
if self.cx.help.visible {
help::Layout::new(self.cx).render(area, buf);
help::Help::new(self.cx).render(area, buf);
}
if self.cx.completion.visible {

View file

@ -1 +1 @@
yazi_macro::mod_flat!(layout);
yazi_macro::mod_flat!(progress tasks);

View file

@ -0,0 +1,23 @@
use mlua::{Table, TableExt};
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use tracing::error;
use yazi_config::LAYOUT;
use yazi_plugin::{LUA, elements::render_widgets};
pub(crate) struct Progress;
impl Widget for Progress {
fn render(self, _: Rect, buf: &mut Buffer) {
let mut f = || {
let area = yazi_plugin::elements::Rect::from(LAYOUT.get().progress);
let progress =
LUA.globals().raw_get::<_, Table>("Progress")?.call_method::<_, Table>("use", area)?;
render_widgets(progress.call_method("redraw", ())?, buf);
Ok::<_, mlua::Error>(())
};
if let Err(e) = f() {
error!("Failed to redraw the `Progress` component:\n{e}");
}
}
}

View file

@ -4,11 +4,11 @@ use yazi_core::tasks::TASKS_PERCENT;
use crate::Ctx;
pub(crate) struct Layout<'a> {
pub(crate) struct Tasks<'a> {
cx: &'a Ctx,
}
impl<'a> Layout<'a> {
impl<'a> Tasks<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(super) fn area(area: Rect) -> Rect {
@ -28,7 +28,7 @@ impl<'a> Layout<'a> {
}
}
impl<'a> Widget for Layout<'a> {
impl<'a> Widget for Tasks<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = Self::area(area);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(cand layout);
yazi_macro::mod_flat!(cand which);

View file

@ -1,15 +1,15 @@
use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Ordering};
use std::{collections::{HashMap, HashSet}, mem, ops::Deref};
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_config::{MANAGER, manager::SortBy};
use yazi_shared::fs::{Cha, FILES_TICKET, File, FilesOp, Url, Urn, UrnBuf, maybe_exists};
use yazi_shared::{Id, fs::{Cha, FILES_TICKET, File, FilesOp, Url, Urn, UrnBuf, maybe_exists}};
use super::{FilesSorter, Filter};
pub struct Files {
hidden: Vec<File>,
items: Vec<File>,
ticket: u64,
ticket: Id,
version: u64,
pub revision: u64,
@ -118,7 +118,7 @@ impl Files {
impl Files {
pub fn update_full(&mut self, files: Vec<File>) {
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
self.ticket = FILES_TICKET.next();
(self.hidden, self.items) = self.split_files(files);
if !self.items.is_empty() {
@ -126,7 +126,7 @@ impl Files {
}
}
pub fn update_part(&mut self, files: Vec<File>, ticket: u64) {
pub fn update_part(&mut self, files: Vec<File>, ticket: Id) {
if !files.is_empty() {
if ticket != self.ticket {
return;
@ -162,7 +162,7 @@ impl Files {
}
pub fn update_ioerr(&mut self) {
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
self.ticket = FILES_TICKET.next();
self.hidden.clear();
self.items.clear();
}
@ -349,7 +349,7 @@ impl Files {
// --- Ticket
#[inline]
pub fn ticket(&self) -> u64 { self.ticket }
pub fn ticket(&self) -> Id { self.ticket }
// --- Sorter
#[inline]

View file

@ -94,7 +94,7 @@ impl Folder {
}
pub fn sync_page(&mut self, force: bool) {
let limit = LAYOUT.load().current.height as usize;
let limit = LAYOUT.get().current.height as usize;
if limit == 0 {
return;
}
@ -109,7 +109,7 @@ impl Folder {
let old = (self.cursor, self.offset);
let len = self.files.len();
let limit = LAYOUT.load().current.height as usize;
let limit = LAYOUT.get().current.height as usize;
let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize);
self.cursor = step.add(self.cursor, limit).min(len.saturating_sub(1));
@ -126,7 +126,7 @@ impl Folder {
let old = (self.cursor, self.offset);
let max = self.files.len().saturating_sub(1);
let limit = LAYOUT.load().current.height as usize;
let limit = LAYOUT.get().current.height as usize;
let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize);
self.cursor = step.add(self.cursor, limit).min(max);
@ -143,13 +143,13 @@ impl Folder {
let old = self.offset;
let len = self.files.len();
let limit = LAYOUT.load().current.height as usize;
let limit = LAYOUT.get().current.height as usize;
let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize);
self.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) {
len.saturating_sub(limit).min(self.offset)
} else {
len.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + scrolloff)
len.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1 + scrolloff)
};
old != self.offset
@ -162,7 +162,7 @@ impl Folder {
pub fn paginate(&self, page: usize) -> &[File] {
let len = self.files.len();
let limit = LAYOUT.load().current.height as usize;
let limit = LAYOUT.get().current.height as usize;
let start = (page.saturating_sub(1) * limit).min(len.saturating_sub(1));
let end = ((page + 2) * limit).min(len);

View file

@ -22,7 +22,11 @@ impl FilesSorter {
if self.sensitive {
self.cmp(a.name(), b.name(), self.promote(a, b))
} else {
self.cmp(a.name().to_ascii_uppercase(), b.name().to_ascii_uppercase(), self.promote(a, b))
self.cmp_insensitive(
a.name().as_encoded_bytes(),
b.name().as_encoded_bytes(),
self.promote(a, b),
)
}
};
@ -40,9 +44,9 @@ impl FilesSorter {
let ord = if self.sensitive {
self.cmp(a.url.extension(), b.url.extension(), self.promote(a, b))
} else {
self.cmp(
a.url.extension().map(|s| s.to_ascii_lowercase()),
b.url.extension().map(|s| s.to_ascii_lowercase()),
self.cmp_insensitive(
a.url.extension().map_or(&[], |s| s.as_encoded_bytes()),
b.url.extension().map_or(&[], |s| s.as_encoded_bytes()),
self.promote(a, b),
)
};
@ -99,6 +103,25 @@ impl FilesSorter {
}
}
#[inline(always)]
fn cmp_insensitive(&self, a: &[u8], b: &[u8], promote: Ordering) -> Ordering {
if promote != Ordering::Equal {
return promote;
}
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 => (),
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 }

View file

@ -11,19 +11,21 @@ function Current:new(area, tab)
end
function Current:empty()
local line
local text
if self._folder.files.filter then
line = ui.Line("No filter results")
text = ui.Text("No filter results")
else
line = ui.Line(self._folder.stage.is_loading and "Loading..." or "No items")
text = ui.Text(self._folder.stage.is_loading and "Loading..." or "No items")
end
return {
ui.Text(line):area(self._area):align(ui.Text.CENTER),
text:area(self._area):align(ui.Text.CENTER),
}
end
function Current:render()
function Current:reflow() return { self } end
function Current:redraw()
local files = self._folder.window
if #files == 0 then
return self:empty()
@ -31,8 +33,8 @@ function Current:render()
local entities, linemodes = {}, {}
for _, f in ipairs(files) do
linemodes[#linemodes + 1] = Linemode:new(f):render()
entities[#entities + 1] = Entity:new(f):render()
entities[#entities + 1] = Entity:new(f):redraw()
linemodes[#linemodes + 1] = Linemode:new(f):redraw()
end
return {

View file

@ -76,7 +76,7 @@ function Entity:symlink()
return to and ui.Line(" -> " .. tostring(to)):italic() or ui.Line {}
end
function Entity:render()
function Entity:redraw()
local lines = {}
for _, c in ipairs(self._children) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)

View file

@ -17,6 +17,7 @@ function Header:new(area, tab)
return setmetatable({
_area = area,
_tab = tab,
_current = tab.current,
}, { __index = self })
end
@ -26,22 +27,26 @@ function Header:cwd()
return ui.Span("")
end
local s = ya.readable_path(tostring(self._tab.current.cwd)) .. self:flags()
local s = ya.readable_path(tostring(self._current.cwd)) .. self:flags()
return ui.Span(ya.truncate(s, { max = max, rtl = true })):style(THEME.manager.cwd)
end
function Header:flags()
local cwd = self._tab.current.cwd
local filter = self._tab.current.files.filter
local cwd = self._current.cwd
local filter = self._current.files.filter
local finder = self._tab.finder
local s = cwd.is_search and string.format(" (search: %s", cwd:frag()) or ""
if not filter then
return s == "" and s or s .. ")"
elseif s == "" then
return string.format(" (filter: %s)", tostring(filter))
else
return string.format("%s, filter: %s)", s, tostring(filter))
local t = {}
if cwd.is_search then
t[#t + 1] = string.format("search: %s", cwd:frag())
end
if filter then
t[#t + 1] = string.format("filter: %s", filter)
end
if finder then
t[#t + 1] = string.format("find: %s", finder)
end
return #t == 0 and "" or " (" .. table.concat(t, ", ") .. ")"
end
function Header:count()
@ -65,7 +70,7 @@ function Header:count()
return ui.Line {
ui.Span(string.format(" %d ", count)):style(style),
ui.Span(" "),
" ",
}
end
@ -90,11 +95,14 @@ function Header:tabs()
return ui.Line(spans)
end
function Header:render()
local right = self:children_render(self.RIGHT)
function Header:reflow() return { self } end
function Header:redraw()
local right = self:children_redraw(self.RIGHT)
self._right_width = right:width()
local left = self:children_render(self.LEFT)
local left = self:children_redraw(self.LEFT)
return {
ui.Text(left):area(self._area),
ui.Text(right):area(self._area):align(ui.Text.RIGHT),
@ -129,7 +137,7 @@ function Header:children_remove(id, side)
end
end
function Header:children_render(side)
function Header:children_redraw(side)
local lines = {}
for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)

Some files were not shown because too many files have changed in this diff Show more