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 - Windows WSL
- FreeBSD X11 - FreeBSD X11
- FreeBSD Wayland - FreeBSD Wayland
- Android
validations: validations:
required: true required: true
- type: input - type: input
@ -32,15 +33,6 @@ body:
render: Shell render: Shell
validations: validations:
required: true 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 - type: textarea
id: description id: description
attributes: attributes:
@ -65,3 +57,14 @@ body:
description: | description: |
Add any other context about the problem here. You can attach screenshots by clicking 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. 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 - name: 📝 Documentation Improvement
url: https://github.com/yazi-rs/yazi-rs.github.io url: https://github.com/yazi-rs/yazi-rs.github.io
about: If you'd like to help improve the documentation 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 - name: 🌐 Discord Server / Telegram Group
url: https://github.com/sxyazi/yazi#discussion url: https://github.com/sxyazi/yazi#discussion
about: If you'd prefer more realtime conversation with the community 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: options:
- label: I have searched the existing issues/discussions - label: I have searched the existing issues/discussions
required: true 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 required: true

View file

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

View file

@ -32,6 +32,15 @@ jobs:
echo "JEMALLOC_SYS_WITH_LG_PAGE=16" >> $GITHUB_ENV 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 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 - name: Build
run: ./scripts/build.sh ${{ matrix.target }} run: ./scripts/build.sh ${{ matrix.target }}
@ -54,10 +63,13 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain - name: Setup Rust toolchain
run: rustup toolchain install stable --profile minimal run: rustup toolchain install stable --profile minimal --target ${{ matrix.target }}
- name: Add target - name: Setup Rust cache
run: rustup target add ${{ matrix.target }} uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.target }}@release
- name: Build - name: Build
env: env:
@ -98,6 +110,15 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - 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 - name: Build
run: ./scripts/build.sh ${{ matrix.target }} run: ./scripts/build.sh ${{ matrix.target }}
@ -117,10 +138,16 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build snap - name: Setup Rust cache
uses: snapcore/action-build@v1 uses: Swatinem/rust-cache@v2
with:
prefix-key: rust
shared-key: ${{ matrix.target }}@release
- name: Build - name: Build
uses: snapcore/action-build@v1
- name: Rename snap
run: mv yazi_*.snap yazi-${{ matrix.target }}.snap run: mv yazi_*.snap yazi-${{ matrix.target }}.snap
- name: Upload artifact - 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_. ⏳ 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. 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 issue and complete the issue template so we can capture all the details
necessary to investigate further. necessary to investigate further.
pr-inactive-days: "30" pr-inactive-days: "30"

View file

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

287
Cargo.lock generated
View file

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

View file

@ -10,9 +10,8 @@ panic = "abort"
strip = true strip = true
[workspace.dependencies] [workspace.dependencies]
ansi-to-tui = "6.0.0" ansi-to-tui = "7.0.0"
anyhow = "1.0.89" anyhow = "1.0.91"
arc-swap = "1.7.1"
base64 = "0.22.1" base64 = "0.22.1"
bitflags = "2.6.0" bitflags = "2.6.0"
clap = { version = "4.5.20", features = [ "derive" ] } clap = { version = "4.5.20", features = [ "derive" ] }
@ -20,19 +19,19 @@ crossterm = { version = "0.28.1", features = [ "event-stream" ] }
dirs = "5.0.1" dirs = "5.0.1"
futures = "0.3.31" futures = "0.3.31"
globset = "0.4.15" globset = "0.4.15"
libc = "0.2.159" libc = "0.2.161"
md-5 = "0.10.6" md-5 = "0.10.6"
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.3" parking_lot = "0.12.3"
ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info" ] } ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
regex = "1.11.0" regex = "1.11.1"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.210", features = [ "derive" ] } serde = { version = "1.0.213", features = [ "derive" ] }
serde_json = "1.0.128" serde_json = "1.0.132"
shell-words = "1.1.0" 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-stream = "0.1.16"
tokio-util = "0.7.12" tokio-util = "0.7.12"
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] } 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" 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). For breaking changes, see [Migrating to Yazi v0.4.0](https://github.com/sxyazi/yazi/issues/1772).
<br><br>
<div align="center"> <div align="center">
<img src="assets/logo.png" alt="Yazi logo" width="20%"> <img src="assets/logo.png" alt="Yazi logo" width="20%">
</div> </div>
@ -43,25 +45,39 @@ https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265
## Image Preview ## Image Preview
| Platform | Protocol | Support | | Platform | Protocol | Support |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | | --------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) | [Kitty unicode placeholders](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) | ✅ Built-in | | [kitty](https://github.com/kovidgoyal/kitty) | [Kitty unicode placeholders][kgp] | ✅ 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][iip] | ✅ 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][iip] | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ 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][sixel] | ✅ 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][kgp] | ✅ 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][sixel] | ✅ 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 | | [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format](https://www.vt100.net/docs/vt3xx-gp/chapter14.html) | ✅ Built-in | | [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio doesn't correctly clear images (#1786) |
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | [Mintty](https://github.com/mintty/mintty) (Git Bash) | [Inline images protocol][iip] | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in | | [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++](https://github.com/jstkdng/ueberzugpp) required | | [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in |
| Fallback | [ASCII art (Unicode block)](https://en.wikipedia.org/wiki/ASCII_art) | ☑️ [Chafa](https://hpjansson.org/chafa/) required | | 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. 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 ## License
Yazi is MIT-licensed. For more information check the [LICENSE](LICENSE) file. 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" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1726560853,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1722415718, "lastModified": 1729265718,
"narHash": "sha256-5US0/pgxbMksF92k1+eOa8arJTJiPvsdZj9Dl+vJkM4=", "narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c3392ad349a5227f4a3464dce87bcc5046692fce", "rev": "ccc0c2126893dd20963580b6478d1a10a4512185",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -43,22 +43,19 @@
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": ["nixpkgs"]
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1721441897, "lastModified": 1729391507,
"narHash": "sha256-gYGX9/22tPNeF7dR6bWN5rsrpU4d06GnQNNgZ6ZiXz0=", "narHash": "sha256-as0I9xieJUHf7kiK2a9znDsVZQTFWhM1pLivII43Gi0=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "b7996075da11a2d441cfbf4e77c2939ce51506fd", "rev": "784981a9feeba406de38c1c9a3decf966d853cca",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "b7996075da11a2d441cfbf4e77c2939ce51506fd",
"type": "github" "type": "github"
} }
}, },

View file

@ -3,7 +3,7 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
rust-overlay = { 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"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };

View file

@ -5,13 +5,6 @@ export ARTIFACT_NAME="yazi-$1"
export YAZI_GEN_COMPLETIONS=1 export YAZI_GEN_COMPLETIONS=1
export MACOSX_DEPLOYMENT_TARGET="10.11" 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 # Build for the target
cargo build --release --locked --target "$1" cargo build --release --locked --target "$1"

View file

@ -16,14 +16,12 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
arc-swap = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
color_quant = "1.1.0" color_quant = "1.1.0"
crossterm = { workspace = true } crossterm = { workspace = true }
futures = { 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" imagesize = "0.13.0"
kamadak-exif = "0.5.5"
ratatui = { workspace = true } ratatui = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
tokio = { 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 anyhow::Result;
use ratatui::layout::Rect; use ratatui::layout::Rect;
@ -52,7 +52,7 @@ impl Adapter {
} }
pub fn image_hide(self) -> Result<()> { 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<()> { pub fn image_erase(self, area: Rect) -> Result<()> {
@ -67,10 +67,10 @@ impl Adapter {
} }
#[inline] #[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) } pub fn shown_load(self) -> Option<Rect> { SHOWN.get() }
#[inline] #[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); } 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 anyhow::Result;
use exif::{In, Tag}; use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, ImageError, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::{self, FilterType}};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS}; use yazi_config::{PREVIEW, TASKS};
@ -12,38 +11,27 @@ pub struct Image;
impl Image { impl Image {
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> { pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
let orientation = Self::orientation(path).await?; let (mut img, orientation, icc) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, (PREVIEW.max_width, PREVIEW.max_height));
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 buf = tokio::task::spawn_blocking(move || { let buf = tokio::task::spawn_blocking(move || {
if img.width() > w || img.height() > h { if img.width() > w || img.height() > h {
img = img.resize(w, h, Self::filter()); img = img.resize(w, h, Self::filter());
} }
if orientation != Orientation::NoTransforms {
img.apply_orientation(orientation);
}
let mut buf = Vec::new(); let mut buf = Vec::new();
img = Self::rotate(img, orientation);
if img.color().has_alpha() { if img.color().has_alpha() {
let rgba = img.into_rgba8(); let rgba = img.into_rgba8();
PngEncoder::new(&mut buf).write_image( let mut encoder = PngEncoder::new(&mut buf);
&rgba, icc.map(|b| encoder.set_icc_profile(b));
rgba.width(), encoder.write_image(&rgba, rgba.width(), rgba.height(), ExtendedColorType::Rgba8)?;
rgba.height(),
ExtendedColorType::Rgba8,
)?;
} else { } else {
JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality) let mut encoder = JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality);
.encode_image(&img.into_rgb8())?; icc.map(|b| encoder.set_icc_profile(b));
encoder.encode_image(&img.into_rgb8())?;
} }
Ok::<_, ImageError>(buf) Ok::<_, ImageError>(buf)
@ -54,31 +42,26 @@ impl Image {
} }
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> { pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
let orientation = Self::orientation(path).await?; let (mut img, orientation, _) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
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);
}
// Fast path. // Fast path.
if img.width() <= w && img.height() <= h && orientation <= 1 { if img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {
return Ok(img); return Ok(img);
} }
tokio::task::spawn_blocking(move || { let img = tokio::task::spawn_blocking(move || {
if img.width() > w || img.height() > h { if img.width() > w || img.height() > h {
img = img.resize(w, h, Self::filter()) 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) { pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
@ -113,53 +96,7 @@ impl Image {
} }
} }
async fn orientation(path: &Path) -> Result<u8> { async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<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>> {
let mut limits = Limits::no_limits(); let mut limits = Limits::no_limits();
if TASKS.image_alloc > 0 { if TASKS.image_alloc > 0 {
limits.max_alloc = Some(TASKS.image_alloc as u64); limits.max_alloc = Some(TASKS.image_alloc as u64);
@ -170,7 +107,27 @@ impl Image {
if TASKS.image_bound[1] > 0 { if TASKS.image_bound[1] > 0 {
limits.max_image_height = Some(TASKS.image_bound[1] as u32); 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 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(); pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// Tmux support // Tmux support
@ -17,7 +17,7 @@ static CLOSE: RoCell<&'static str> = RoCell::new();
pub static WSL: RoCell<bool> = RoCell::new(); pub static WSL: RoCell<bool> = RoCell::new();
// Image state // 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() { pub fn init() {
// Tmux support // Tmux support
@ -38,9 +38,6 @@ pub fn init() {
// WSL support // WSL support
WSL.init(in_wsl()); WSL.init(in_wsl());
// Image state
SHOWN.with(<_>::default);
ADAPTOR.init(Adapter::matches()); ADAPTOR.init(Adapter::matches());
ADAPTOR.start(); ADAPTOR.start();
} }

View file

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

View file

@ -22,11 +22,10 @@ fn main() -> Result<(), Box<dyn Error>> {
let out = "completions"; let out = "completions";
std::fs::create_dir_all(out)?; std::fs::create_dir_all(out)?;
generate_to(Shell::Bash, cmd, bin, out)?; for sh in [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] {
generate_to(Shell::Fish, cmd, bin, out)?; generate_to(sh, cmd, bin, out)?;
generate_to(Shell::Zsh, cmd, bin, out)?; }
generate_to(Shell::Elvish, cmd, bin, out)?;
generate_to(Shell::PowerShell, cmd, bin, out)?;
generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?; generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;
generate_to(clap_complete_fig::Fig, 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 # External build dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
clap_complete = "4.5.33" clap_complete = "4.5.35"
clap_complete_fig = "4.5.2" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.4" clap_complete_nushell = "4.5.4"
serde_json = { workspace = true } serde_json = { workspace = true }

View file

@ -22,11 +22,10 @@ fn main() -> Result<(), Box<dyn Error>> {
let out = "completions"; let out = "completions";
std::fs::create_dir_all(out)?; std::fs::create_dir_all(out)?;
generate_to(Shell::Bash, cmd, bin, out)?; for sh in [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] {
generate_to(Shell::Fish, cmd, bin, out)?; generate_to(sh, cmd, bin, out)?;
generate_to(Shell::Zsh, cmd, bin, out)?; }
generate_to(Shell::Elvish, cmd, bin, out)?;
generate_to(Shell::PowerShell, cmd, bin, out)?;
generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?; generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;
generate_to(clap_complete_fig::Fig, 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) => { Command::Pack(cmd) => {
package::init()?; package::init()?;
package::Package::migrate().await?;
if cmd.install { if cmd.install {
package::Package::install_from_config("plugin", false).await?; package::Package::install_from_config("plugin", false).await?;
package::Package::install_from_config("flavor", false).await?; package::Package::install_from_config("flavor", false).await?;
@ -57,15 +56,7 @@ async fn main() -> anyhow::Result<()> {
} else if cmd.upgrade { } else if cmd.upgrade {
package::Package::install_from_config("plugin", true).await?; package::Package::install_from_config("plugin", true).await?;
package::Package::install_from_config("flavor", true).await?; package::Package::install_from_config("flavor", true).await?;
} else if let Some(mut repo) = cmd.add { } else if let Some(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."
);
}
package::Package::add_to_config(&repo).await?; package::Package::add_to_config(&repo).await?;
} }
} }

View file

@ -6,33 +6,6 @@ use yazi_shared::Xdg;
use super::Package; use super::Package;
impl 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<()> { pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
let mut package = Self::new(use_, None); let mut package = Self::new(use_, None);
let Some(name) = package.name() else { bail!("Invalid package `use`") }; let Some(name) = package.name() else { bail!("Invalid package `use`") };

View file

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

View file

@ -14,7 +14,6 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
arc-swap = { workspace = true }
bitflags = { workspace = true } bitflags = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
globset = { 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 {{{
[completion] [completion]

View file

@ -1,5 +1,6 @@
use std::{collections::HashSet, str::FromStr}; use std::{collections::HashSet, str::FromStr};
use anyhow::Context;
use indexmap::IndexSet; use indexmap::IndexSet;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use yazi_shared::Layer; use yazi_shared::Layer;
@ -20,7 +21,7 @@ pub struct Keymap {
impl Keymap { impl Keymap {
#[inline] #[inline]
pub fn get(&self, layer: Layer) -> &Vec<Chord> { pub fn get(&self, layer: Layer) -> &[Chord] {
match layer { match layer {
Layer::App => unreachable!(), Layer::App => unreachable!(),
Layer::Manager => &self.manager, Layer::Manager => &self.manager,
@ -36,9 +37,11 @@ impl Keymap {
} }
impl FromStr for 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 { impl<'de> Deserialize<'de> for Keymap {

View file

@ -1,7 +1,14 @@
use ratatui::layout::Rect; use ratatui::layout::Rect;
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Layout { pub struct Layout {
pub current: Rect, pub current: Rect,
pub preview: 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 std::str::FromStr;
use yazi_shared::{RoCell, Xdg}; use yazi_shared::{RoCell, SyncCell, Xdg};
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new(); pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
pub static LOG: RoCell<log::Log> = 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 PICK: RoCell<popup::Pick> = RoCell::new();
pub static WHICH: RoCell<which::Which> = RoCell::new(); pub static WHICH: RoCell<which::Which> = RoCell::new();
pub static LAYOUT: SyncCell<Layout> = SyncCell::new(Layout::default());
pub fn init() -> anyhow::Result<()> { pub fn init() -> anyhow::Result<()> {
let config_dir = Xdg::config_dir(); if let Err(e) = try_init(true) {
let yazi_toml = &Preset::yazi(&config_dir)?; eprintln!("{e}");
let keymap_toml = &Preset::keymap(&config_dir)?; if let Some(src) = e.source() {
let theme_toml = &Preset::theme(&config_dir)?; 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)?); std::io::stdin().read_line(&mut String::new())?;
LOG.init(<_>::from_str(yazi_toml)?); try_init(false)?;
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)?);
// TODO: Remove in v0.3.2 // TODO: Remove in v0.3.2
for c in &KEYMAP.manager { for c in &KEYMAP.manager {
@ -78,3 +78,41 @@ Please change `create_title = "Create:"` to `create_title = ["Create:", "Create
Ok(()) 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 std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
#[derive(Debug)] #[derive(Debug)]
@ -8,9 +9,11 @@ pub struct Log {
} }
impl FromStr for 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 { impl<'de> Deserialize<'de> for Log {

View file

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
@ -35,9 +36,10 @@ impl FromStr for Manager {
manager: Manager, manager: Manager,
} }
let manager = toml::from_str::<Outer>(s)?.manager; let outer = toml::from_str::<Outer>(s)
manager.validate()?; .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 std::{collections::HashMap, path::Path, str::FromStr};
use anyhow::Context;
use indexmap::IndexSet; use indexmap::IndexSet;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use yazi_shared::MIME_DIR; use yazi_shared::MIME_DIR;
@ -55,9 +56,11 @@ impl Open {
} }
impl FromStr for 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 { 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 globset::GlobBuilder;
use serde::Deserialize; use serde::Deserialize;
@ -9,6 +9,8 @@ pub struct Pattern {
inner: globset::GlobMatcher, inner: globset::GlobMatcher,
is_dir: bool, is_dir: bool,
is_star: bool, is_star: bool,
#[cfg(windows)]
sep_lit: bool,
} }
impl Pattern { impl Pattern {
@ -19,7 +21,20 @@ impl Pattern {
#[inline] #[inline]
pub fn match_path(&self, path: impl AsRef<Path>, is_dir: bool) -> bool { 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] #[inline]
@ -29,27 +44,115 @@ impl Pattern {
pub fn any_dir(&self) -> bool { self.is_star && self.is_dir } pub fn any_dir(&self) -> bool { self.is_star && self.is_dir }
} }
impl TryFrom<&str> for Pattern { impl FromStr for Pattern {
type Error = anyhow::Error; 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 a = s.trim_start_matches("\\s");
let b = a.trim_end_matches('/'); let b = a.trim_end_matches('/');
let sep_lit = b.contains('/');
let inner = GlobBuilder::new(b) let inner = GlobBuilder::new(b)
.case_insensitive(a.len() == s.len()) .case_insensitive(a.len() == s.len())
.literal_separator(false) .literal_separator(sep_lit)
.backslash_escape(false) .backslash_escape(false)
.empty_alternates(true) .empty_alternates(true)
.build()? .build()?
.compile_matcher(); .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 { 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 std::{collections::HashSet, path::Path, str::FromStr};
use serde::Deserialize; use anyhow::Context;
use serde::{Deserialize, Deserializer};
use super::{Fetcher, Preloader, Previewer}; use super::{Fetcher, Preloader, Previewer};
use crate::{Preset, plugin::MAX_PREWORKERS}; use crate::{Preset, plugin::MAX_PREWORKERS};
#[derive(Deserialize)]
pub struct Plugin { pub struct Plugin {
pub fetchers: Vec<Fetcher>, pub fetchers: Vec<Fetcher>,
pub preloaders: Vec<Preloader>, pub preloaders: Vec<Preloader>,
@ -55,9 +55,18 @@ impl Plugin {
} }
impl FromStr for Plugin { impl FromStr for Plugin {
type Err = toml::de::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { 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)] #[derive(Deserialize)]
struct Outer { struct Outer {
plugin: Shadow, plugin: Shadow,
@ -84,7 +93,7 @@ impl FromStr for Plugin {
append_previewers: Vec<Previewer>, 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()) { if shadow.append_previewers.iter().any(|r| r.any_file()) {
shadow.previewers.retain(|r| !r.any_file()); shadow.previewers.retain(|r| !r.any_file());
} }

View file

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use super::{Offset, Origin}; use super::{Offset, Origin};
@ -30,7 +31,7 @@ pub struct Confirm {
} }
impl FromStr for Confirm { impl FromStr for Confirm {
type Err = toml::de::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
@ -38,7 +39,10 @@ impl FromStr for Confirm {
confirm: 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 std::str::FromStr;
use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use super::{Offset, Origin}; use super::{Offset, Origin};
@ -49,7 +50,7 @@ impl Input {
} }
impl FromStr for Input { impl FromStr for Input {
type Err = toml::de::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
@ -57,7 +58,10 @@ impl FromStr for Input {
input: 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 std::str::FromStr;
use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use super::{Offset, Origin}; use super::{Offset, Origin};
@ -17,7 +18,7 @@ impl Pick {
} }
impl FromStr for Pick { impl FromStr for Pick {
type Err = toml::de::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)] #[derive(Deserialize)]
@ -25,6 +26,9 @@ impl FromStr for Pick {
pick: 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; pub(crate) struct Preset;
impl 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")) 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")) 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 { let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else {
return Ok(preset!("theme")); return Ok(preset!("theme"));
}; };

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, path::PathBuf, str::FromStr, time::{SystemTime, UNIX_EPOCH}}; use std::{borrow::Cow, path::PathBuf, str::FromStr, time::{SystemTime, UNIX_EPOCH}};
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use validator::Validate; use validator::Validate;
use yazi_shared::fs::expand_path; use yazi_shared::fs::expand_path;
@ -49,6 +49,20 @@ impl FromStr for Preview {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { 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)] #[derive(Deserialize)]
struct Outer { struct Outer {
preview: Shadow, preview: Shadow,
@ -74,12 +88,8 @@ impl FromStr for Preview {
ueberzug_offset: (f32, f32, f32, f32), ueberzug_offset: (f32, f32, f32, f32),
} }
let preview = toml::from_str::<Outer>(s)?.preview; let preview = Outer::deserialize(deserializer)?.preview;
preview.validate()?; preview.validate().map_err(serde::de::Error::custom)?;
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")?;
Ok(Preview { Ok(Preview {
wrap: preview.wrap, wrap: preview.wrap,
@ -87,7 +97,10 @@ impl FromStr for Preview {
max_width: preview.max_width, max_width: preview.max_width,
max_height: preview.max_height, 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_delay: preview.image_delay,
image_filter: preview.image_filter, image_filter: preview.image_filter,

View file

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use validator::Validate; use validator::Validate;
@ -27,9 +28,10 @@ impl FromStr for Tasks {
tasks: Tasks, tasks: Tasks,
} }
let tasks = toml::from_str::<Outer>(s)?.tasks; let outer = toml::from_str::<Outer>(s)
tasks.validate()?; .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 std::{path::PathBuf, str::FromStr};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use yazi_shared::{Xdg, fs::expand_path, theme::Style}; use yazi_shared::{Xdg, fs::expand_path, theme::Style};
@ -12,6 +13,7 @@ pub struct Theme {
pub manager: Manager, pub manager: Manager,
status: Status, status: Status,
pub input: Input, pub input: Input,
pub confirm: Confirm,
pub pick: Pick, pub pick: Pick,
pub completion: Completion, pub completion: Completion,
pub tasks: Tasks, pub tasks: Tasks,
@ -30,7 +32,7 @@ impl FromStr for Theme {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { 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.manager.validate()?;
theme.which.validate()?; theme.which.validate()?;
@ -114,6 +116,17 @@ pub struct Input {
pub selected: Style, 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)] #[derive(Deserialize, Serialize)]
pub struct Pick { pub struct Pick {
pub border: Style, pub border: Style,

View file

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
@ -23,6 +24,9 @@ impl FromStr for Which {
which: 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 } crossterm = { workspace = true }
dirs = { workspace = true } dirs = { workspace = true }
futures = { 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 } parking_lot = { workspace = true }
ratatui = { workspace = true } ratatui = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }

View file

@ -53,7 +53,8 @@ impl Manager {
return Ok(()); 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() { if todo.is_empty() {
return Ok(()); return Ok(());
} }
@ -117,4 +118,90 @@ impl Manager {
stdin().read_exact(&mut [0]).await?; stdin().read_exact(&mut [0]).await?;
Ok(()) 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_dds::Pubsub;
use yazi_macro::render; 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; use crate::manager::Manager;
struct Opt { struct Opt {
url: Option<Url>, url: Option<Url>,
tab: Option<usize>, tab: Option<Id>,
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self { url: c.take_first().and_then(Data::into_url), tab: c.get("tab").and_then(Data::as_id) }
url: c.take_first().and_then(Data::into_url),
tab: c.get("tab").and_then(Data::as_usize),
}
} }
} }
impl From<Option<Url>> for Opt { impl From<Option<Url>> for Opt {
@ -49,10 +46,10 @@ impl Manager {
self.watcher.watch(to_watch); self.watcher.watch(to_watch);
// Publish through DDS // 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 // Hover on the file
if let Ok(p) = url.strip_prefix(&self.current_or(tab).url).map(PathBuf::from) { 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)))); 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 yazi_shared::{event::{Cmd, Data}, fs::Url};
use crate::manager::Manager; use crate::manager::Manager;
@ -30,6 +31,9 @@ impl Manager {
let Some(hovered) = self.hovered().cloned() else { let Some(hovered) = self.hovered().cloned() else {
return self.active_mut().preview.reset(); 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 mime = self.mimetype.get_owned(&hovered.url).unwrap_or_default();
let folder = self.active().hovered_folder().map(|f| (f.offset, f.cha)); 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(); 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.set_idx(self.absolute(1));
} }
self.reorder();
render!(); render!();
} }
} }

View file

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

View file

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

View file

@ -87,7 +87,7 @@ impl Manager {
return; 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 ManagerProxy::update_paged(); // Update for paged files in next loop
if calc { if calc {
tasks.prework_sorted(&tab.current.files); tasks.prework_sorted(&tab.current.files);

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,6 @@ impl Tab {
self.cd(parent.clone()); self.cd(parent.clone());
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]).emit(); 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_config::{LAYOUT, popup::{Origin, Position}};
use yazi_fs::{Folder, FolderStage}; use yazi_fs::{Folder, FolderStage};
use yazi_macro::render; 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 super::{Backstack, Config, Finder, History, Mode, Preview};
use crate::tab::Selected; use crate::tab::Selected;
#[derive(Default)]
pub struct Tab { pub struct Tab {
pub idx: usize, pub id: Id,
pub mode: Mode, pub mode: Mode,
pub conf: Config, pub conf: Config,
pub current: Folder, pub current: Folder,
@ -29,6 +28,28 @@ pub struct Tab {
pub search: Option<JoinHandle<Result<()>>>, 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 { impl Tab {
pub fn shutdown(&mut self) { pub fn shutdown(&mut self) {
if let Some(handle) = self.search.take() { if let Some(handle) = self.search.take() {
@ -48,7 +69,7 @@ impl Tab {
pub fn hovered_rect(&self) -> Option<Rect> { pub fn hovered_rect(&self) -> Option<Rect> {
let y = self.current.files.position(self.hovered()?.urn())? - self.current.offset; 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.y = rect.y.saturating_sub(1) + y as u16;
rect.height = 1; rect.height = 1;
Some(rect) Some(rect)

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use mlua::Function; use mlua::Function;
use parking_lot::RwLock; use parking_lot::RwLock;
use yazi_boot::BOOT; 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}}; 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 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") { if LOCAL.read().contains_key("cd") {
Self::pub_(BodyCd::dummy(tab)); 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") { if LOCAL.read().contains_key("hover") {
Self::pub_(BodyHover::dummy(tab)); Self::pub_(BodyHover::dummy(tab));
} }

View file

@ -4,6 +4,7 @@ yazi_macro::mod_flat!(
notify notify
plugin plugin
quit quit
reflow
render render
resize resize
resume 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()); Self::patch(frame, self.cx.cursor());
} }
if !self.cx.notify.messages.is_empty() { if !self.cx.notify.messages.is_empty() {
self.render_notify(); self.render_partially();
} }
// Reload preview if collision is resolved // Reload preview if collision is resolved
@ -40,18 +40,19 @@ impl App {
} }
} }
pub(crate) fn render_notify(&mut self) { pub(crate) fn render_partially(&mut self) {
let Some(term) = &mut self.term else { let Some(term) = &mut self.term else { return };
return;
};
if !term.can_partial() { if !term.can_partial() {
return self.render(); return self.render();
} }
let frame = term let frame = term
.draw_partial(|f| { .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() { if let Some(pos) = self.cx.cursor() {
f.set_cursor_position(pos); f.set_cursor_position(pos);

View file

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

View file

@ -9,14 +9,14 @@ impl App {
pub(crate) fn update_notify(&mut self, cmd: Cmd) { pub(crate) fn update_notify(&mut self, cmd: Cmd) {
let WindowSize { rows, columns, .. } = Dimension::available(); let WindowSize { rows, columns, .. } = Dimension::available();
let area = 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); self.cx.notify.tick(cmd, area);
if self.cx.notify.messages.is_empty() { if self.cx.notify.messages.is_empty() {
self.render(); self.render();
} else { } else {
self.render_notify(); self.render_partially();
} }
} }
} }

View file

@ -1,9 +1,8 @@
use ratatui::backend::Backend;
use yazi_core::tasks::TasksProgress; use yazi_core::tasks::TasksProgress;
use yazi_macro::render; use yazi_macro::render;
use yazi_shared::event::Cmd; use yazi_shared::event::Cmd;
use crate::{app::App, components::Progress, lives::Lives}; use crate::app::App;
pub struct Opt { pub struct Opt {
progress: TasksProgress, progress: TasksProgress,
@ -19,41 +18,28 @@ impl TryFrom<Cmd> for Opt {
impl App { impl App {
pub(crate) fn update_progress(&mut self, opt: impl TryInto<Opt>) { pub(crate) fn update_progress(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else { let Ok(opt) = opt.try_into() else { return };
return;
};
// Update the progress of all tasks. // Update the progress of all tasks.
let tasks = &mut self.cx.tasks; let tasks = &mut self.cx.tasks;
let progressed = tasks.progress != opt.progress;
tasks.progress = opt.progress; tasks.progress = opt.progress;
// If the task manager is visible, update the summaries with a complete render. // If the task manager is visible, update the summaries with a complete render.
if tasks.visible { if tasks.visible {
let new = tasks.paginate(); let new = tasks.paginate();
if new.len() != tasks.summaries.len() if tasks.summaries != new {
|| new.iter().zip(&tasks.summaries).any(|(a, b)| a.name != b.name)
{
tasks.summaries = new; tasks.summaries = new;
tasks.arrow(0); tasks.arrow(0);
return render!(); return render!();
} }
} }
// Otherwise, only partially update the progress. if !progressed {
let Some(term) = &mut self.term else { } else if tasks.progress.total == 0 {
return; render!();
}; } else {
self.render_partially();
_ = 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)?;
} }
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; pub(crate) struct Buttons;
@ -7,7 +8,11 @@ impl Widget for Buttons {
let chunks = let chunks =
ratatui::layout::Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area); 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(&THEME.confirm.btn_labels[0]).style(THEME.confirm.btn_yes))
Paragraph::new(Span::raw(" (N)o ")).centered().render(chunks[1], buf); .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; use crate::Ctx;
@ -19,8 +20,8 @@ impl<'a> Widget for Confirm<'a> {
Block::bordered() Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(Style::new().blue()) .border_style(THEME.confirm.border)
.title(Line::styled(&confirm.title, Style::new().blue())) .title(Line::styled(&confirm.title, THEME.confirm.title))
.title_alignment(Alignment::Center) .title_alignment(Alignment::Center)
.render(area, buf); .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> { pub(crate) struct Content<'a> {
p: Paragraph<'a>, p: Paragraph<'a>,
@ -14,9 +15,14 @@ impl<'a> Widget for Content<'a> {
let inner = area.inner(Margin::new(1, 0)); let inner = area.inner(Margin::new(1, 0));
// Bottom border // 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); 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; use crate::Ctx;
@ -16,7 +17,7 @@ impl<'a> Widget for List<'a> {
let inner = area.inner(Margin::new(2, 0)); let inner = area.inner(Margin::new(2, 0));
// Bottom border // 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); block.clone().render(area.inner(Margin::new(1, 0)), buf);
let list = self let list = self
@ -26,6 +27,7 @@ impl<'a> Widget for List<'a> {
.clone() .clone()
.scroll((self.cx.confirm.offset as u16, 0)) .scroll((self.cx.confirm.offset as u16, 0))
.block(block) .block(block)
.style(THEME.confirm.list)
.wrap(Wrap { trim: false }); .wrap(Wrap { trim: false });
// Vertical scrollbar // Vertical scrollbar

View file

@ -4,11 +4,11 @@ use yazi_config::{KEYMAP, THEME};
use super::Bindings; use super::Bindings;
use crate::Ctx; use crate::Ctx;
pub(crate) struct Layout<'a> { pub(crate) struct Help<'a> {
cx: &'a Ctx, cx: &'a Ctx,
} }
impl<'a> Layout<'a> { impl<'a> Help<'a> {
pub fn new(cx: &'a Ctx) -> Self { Self { cx } } pub fn new(cx: &'a Ctx) -> Self { Self { cx } }
fn tips() -> String { 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) { fn render(self, area: Rect, buf: &mut Buffer) {
let help = &self.cx.help; let help = &self.cx.help;
yazi_plugin::elements::Clear::default().render(area, buf); 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 std::ops::Deref;
use mlua::{AnyUserData, Lua}; use mlua::{AnyUserData, Lua, MetaMethod, UserDataMethods};
use super::SCOPE; use super::SCOPE;
@ -21,6 +21,8 @@ impl Finder {
} }
pub(super) fn register(lua: &Lua) -> mlua::Result<()> { 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 { let window = match window {
Some(w) => w, Some(w) => w,
None => { 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) inner.offset..inner.files.len().min(inner.offset + limit)
} }
}; };

View file

@ -28,7 +28,7 @@ impl Preview {
me.tab() me.tab()
.hovered_folder() .hovered_folder()
.map(|f| { .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()) Folder::make(Some(me.skip..f.files.len().min(me.skip + limit)), f, me.tab())
}) })
.transpose() .transpose()

View file

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

View file

@ -5,9 +5,7 @@
#[global_allocator] #[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
yazi_macro::mod_pub!( yazi_macro::mod_pub!(app completion confirm help input lives manager notify pick tasks which);
app completion components confirm help input lives notify pick tasks which
);
yazi_macro::mod_flat!(context executor logs panic root router signals term); 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; use crate::Ctx;
pub(crate) struct Layout<'a> { pub(crate) struct Notify<'a> {
cx: &'a Ctx, cx: &'a Ctx,
} }
impl<'a> Layout<'a> { impl<'a> Notify<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(crate) fn available(area: Rect) -> Rect { 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) { fn render(self, area: Rect, buf: &mut Buffer) {
let notify = &self.cx.notify; let notify = &self.cx.notify;

View file

@ -3,8 +3,8 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use tracing::error; use tracing::error;
use yazi_plugin::{LUA, elements::render_widgets}; use yazi_plugin::{LUA, elements::render_widgets};
use super::{completion, confirm, input, pick, tasks, which}; use super::{completion, confirm, help, input, manager, pick, tasks, which};
use crate::{Ctx, components, help}; use crate::Ctx;
pub(super) struct Root<'a> { pub(super) struct Root<'a> {
cx: &'a Ctx, cx: &'a Ctx,
@ -12,6 +12,12 @@ pub(super) struct Root<'a> {
impl<'a> Root<'a> { impl<'a> Root<'a> {
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } } 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> { 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 area = yazi_plugin::elements::Rect::from(area);
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", 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>(()) Ok::<_, mlua::Error>(())
}; };
if let Err(e) = f() { 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 { 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 { if self.cx.pick.visible {
@ -46,7 +52,7 @@ impl<'a> Widget for Root<'a> {
} }
if self.cx.help.visible { 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 { 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; use crate::Ctx;
pub(crate) struct Layout<'a> { pub(crate) struct Tasks<'a> {
cx: &'a Ctx, cx: &'a Ctx,
} }
impl<'a> Layout<'a> { impl<'a> Tasks<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(super) fn area(area: Rect) -> Rect { 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) { fn render(self, area: Rect, buf: &mut Buffer) {
let area = Self::area(area); 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 tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_config::{MANAGER, manager::SortBy}; 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}; use super::{FilesSorter, Filter};
pub struct Files { pub struct Files {
hidden: Vec<File>, hidden: Vec<File>,
items: Vec<File>, items: Vec<File>,
ticket: u64, ticket: Id,
version: u64, version: u64,
pub revision: u64, pub revision: u64,
@ -118,7 +118,7 @@ impl Files {
impl Files { impl Files {
pub fn update_full(&mut self, files: Vec<File>) { 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); (self.hidden, self.items) = self.split_files(files);
if !self.items.is_empty() { 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 !files.is_empty() {
if ticket != self.ticket { if ticket != self.ticket {
return; return;
@ -162,7 +162,7 @@ impl Files {
} }
pub fn update_ioerr(&mut self) { pub fn update_ioerr(&mut self) {
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed); self.ticket = FILES_TICKET.next();
self.hidden.clear(); self.hidden.clear();
self.items.clear(); self.items.clear();
} }
@ -349,7 +349,7 @@ impl Files {
// --- Ticket // --- Ticket
#[inline] #[inline]
pub fn ticket(&self) -> u64 { self.ticket } pub fn ticket(&self) -> Id { self.ticket }
// --- Sorter // --- Sorter
#[inline] #[inline]

View file

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

View file

@ -22,7 +22,11 @@ impl FilesSorter {
if self.sensitive { if self.sensitive {
self.cmp(a.name(), b.name(), self.promote(a, b)) self.cmp(a.name(), b.name(), self.promote(a, b))
} else { } 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 { let ord = if self.sensitive {
self.cmp(a.url.extension(), b.url.extension(), self.promote(a, b)) self.cmp(a.url.extension(), b.url.extension(), self.promote(a, b))
} else { } else {
self.cmp( self.cmp_insensitive(
a.url.extension().map(|s| s.to_ascii_lowercase()), a.url.extension().map_or(&[], |s| s.as_encoded_bytes()),
b.url.extension().map(|s| s.to_ascii_lowercase()), b.url.extension().map_or(&[], |s| s.as_encoded_bytes()),
self.promote(a, b), 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)] #[inline(always)]
fn promote(&self, a: &File, b: &File) -> Ordering { fn promote(&self, a: &File, b: &File) -> Ordering {
if self.dir_first { b.is_dir().cmp(&a.is_dir()) } else { Ordering::Equal } 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 end
function Current:empty() function Current:empty()
local line local text
if self._folder.files.filter then if self._folder.files.filter then
line = ui.Line("No filter results") text = ui.Text("No filter results")
else 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 end
return { return {
ui.Text(line):area(self._area):align(ui.Text.CENTER), text:area(self._area):align(ui.Text.CENTER),
} }
end end
function Current:render() function Current:reflow() return { self } end
function Current:redraw()
local files = self._folder.window local files = self._folder.window
if #files == 0 then if #files == 0 then
return self:empty() return self:empty()
@ -31,8 +33,8 @@ function Current:render()
local entities, linemodes = {}, {} local entities, linemodes = {}, {}
for _, f in ipairs(files) do for _, f in ipairs(files) do
linemodes[#linemodes + 1] = Linemode:new(f):render() entities[#entities + 1] = Entity:new(f):redraw()
entities[#entities + 1] = Entity:new(f):render() linemodes[#linemodes + 1] = Linemode:new(f):redraw()
end end
return { return {

View file

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