mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-22 23:31:05 +00:00
Merge remote-tracking branch 'upstream/main' into improve-rg-search
This commit is contained in:
commit
b1d0f10cec
172 changed files with 1803 additions and 1041 deletions
2
.github/workflows/cachix.yml
vendored
2
.github/workflows/cachix.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
|
|
|||
8
.github/workflows/check.yml
vendored
8
.github/workflows/check.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -45,12 +45,12 @@ jobs:
|
|||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Rustfmt
|
||||
run: rustfmt +nightly --check **/*.rs
|
||||
run: find . -name '*.rs' -not -path './target/*' -exec rustfmt +nightly --check {} +
|
||||
|
||||
stylua:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: JohnnyMorganz/stylua-action@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
15
.github/workflows/draft.yml
vendored
15
.github/workflows/draft.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
|
||||
CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install gcc
|
||||
if: matrix.gcc != ''
|
||||
|
|
@ -81,7 +81,7 @@ jobs:
|
|||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -97,10 +97,11 @@ jobs:
|
|||
env:
|
||||
TARGET_NAME: yazi-${{ matrix.target }}
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path ${env:TARGET_NAME}
|
||||
New-Item -ItemType Directory -Path "${env:TARGET_NAME}\completions"
|
||||
Copy-Item -Path "target\${{ matrix.target }}\release-windows\ya.exe" -Destination ${env:TARGET_NAME}
|
||||
Copy-Item -Path "target\${{ matrix.target }}\release-windows\yazi.exe" -Destination ${env:TARGET_NAME}
|
||||
Copy-Item -Path "yazi-boot\completions" -Destination ${env:TARGET_NAME} -Recurse
|
||||
Copy-Item -Path "yazi-cli\completions\*" -Destination "${env:TARGET_NAME}\completions"
|
||||
Copy-Item -Path "yazi-boot\completions\*" -Destination "${env:TARGET_NAME}\completions"
|
||||
Copy-Item -Path "README.md", "LICENSE" -Destination ${env:TARGET_NAME}
|
||||
Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath "${env:TARGET_NAME}.zip"
|
||||
|
||||
|
|
@ -120,7 +121,7 @@ jobs:
|
|||
container:
|
||||
image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -150,7 +151,7 @@ jobs:
|
|||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
@ -233,7 +234,7 @@ jobs:
|
|||
echo "Nightly changelog: https://github.com/sxyazi/yazi/blob/main/CHANGELOG.md#unreleased" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup toolchain install stable --profile minimal
|
||||
|
|
|
|||
2
.github/workflows/validate-form.yml
vendored
2
.github/workflows/validate-form.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
|
|
|||
14
CHANGELOG.md
14
CHANGELOG.md
|
|
@ -16,6 +16,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- Drag and drop ([#4005])
|
||||
- Bulk create ([#3793])
|
||||
- Make help menu a command palette ([#4074])
|
||||
- Make visual mode support wraparound scrolling ([#4101])
|
||||
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
|
||||
- Context-aware icons for inputs ([#4080])
|
||||
- Show file icons in trash/delete/overwrite confirmations ([#4096])
|
||||
- Dynamic keymap Lua API ([#4031])
|
||||
- New `ui.Input` element ([#4040])
|
||||
- Image preview with Überzug++ on Niri ([#3990])
|
||||
|
|
@ -24,11 +29,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Changed
|
||||
|
||||
- Rename `<BackTab>` to `<S-Tab>` ([#3989])
|
||||
- Make `mgr::Yanked`, `tab::Selected`, and the `@yank` DDS event return `File` instead of `Url` from `__pairs()` ([#4096])
|
||||
- Remove `help:filter` action since the filter input is now always available ([#4074])
|
||||
- `[help]` of `theme.toml`: supersede `on` with `chord`, supersede `run` and `desc` with `action`, remove `footer` ([#4074])
|
||||
- Remove Legacy Console Mode on Windows ([#3989])
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `backward --far` and `forward --far` in favor of `backward wide` and `forward wide`, respectively ([#4012])
|
||||
- Deprecate `tab::Mode.is_visual` in favor of the new `tab::Mode.is_normal` ([#4101])
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -1746,6 +1755,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3906]: https://github.com/sxyazi/yazi/pull/3906
|
||||
[#3934]: https://github.com/sxyazi/yazi/pull/3934
|
||||
[#3943]: https://github.com/sxyazi/yazi/pull/3943
|
||||
[#3970]: https://github.com/sxyazi/yazi/pull/3970
|
||||
[#3989]: https://github.com/sxyazi/yazi/pull/3989
|
||||
[#3990]: https://github.com/sxyazi/yazi/pull/3990
|
||||
[#4005]: https://github.com/sxyazi/yazi/pull/4005
|
||||
|
|
@ -1757,3 +1767,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#4065]: https://github.com/sxyazi/yazi/pull/4065
|
||||
[#4067]: https://github.com/sxyazi/yazi/pull/4067
|
||||
[#4068]: https://github.com/sxyazi/yazi/pull/4068
|
||||
[#4074]: https://github.com/sxyazi/yazi/pull/4074
|
||||
[#4080]: https://github.com/sxyazi/yazi/pull/4080
|
||||
[#4096]: https://github.com/sxyazi/yazi/pull/4096
|
||||
[#4101]: https://github.com/sxyazi/yazi/pull/4101
|
||||
|
|
|
|||
213
Cargo.lock
generated
213
Cargo.lock
generated
|
|
@ -41,9 +41,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.11.0-rc.4"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8"
|
||||
checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
|
|
@ -161,9 +161,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
|
|
@ -182,9 +182,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
|||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.9.1"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
|
||||
checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
"serde",
|
||||
|
|
@ -215,9 +215,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
|
|
@ -459,12 +459,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||
checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -567,9 +567,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
|
|
@ -628,9 +628,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.6.5"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772"
|
||||
checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
|
@ -820,9 +820,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
|||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.7.4"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97bb4a855e3b10f84c4e7e895a7de01db7f9a7b7eb7f73ed9773fd52ac686451"
|
||||
checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
|
||||
dependencies = [
|
||||
"cpubits",
|
||||
"ctutils",
|
||||
|
|
@ -877,9 +877,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "5.0.0-rc.0"
|
||||
version = "5.0.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf"
|
||||
checksum = "c906a87e53a36ff795d72e06e8162a83c5436e3ea89e942a9cb9fc083f0a384f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
|
|
@ -1043,9 +1043,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
|||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.17.0-rc.18"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96"
|
||||
checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
|
|
@ -1068,9 +1068,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "3.0.0-rc.0"
|
||||
version = "3.0.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60"
|
||||
checksum = "1685663e23882cd8517dcbcb1c23a6ebff4433c22dfb681d760219b62cd1b849"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
|
|
@ -1090,9 +1090,9 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
|||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.14.0-rc.33"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935"
|
||||
checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
|
|
@ -1102,7 +1102,6 @@ dependencies = [
|
|||
"group",
|
||||
"hkdf",
|
||||
"hybrid-array",
|
||||
"once_cell",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"rand_core 0.10.1",
|
||||
|
|
@ -1583,9 +1582,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
|
||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
||||
dependencies = [
|
||||
"ctutils",
|
||||
"subtle",
|
||||
|
|
@ -1707,9 +1706,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
checksum = "9ea94e891b3606826e9c998be69ddca42247dad8ad50b1649a5cb7e1c9ae06fd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -1745,7 +1744,7 @@ checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8"
|
|||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
|
|
@ -1802,9 +1801,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.102"
|
||||
version = "0.3.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
|
||||
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
|
|
@ -1898,9 +1897,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.17"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
||||
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -1931,9 +1930,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.32"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
|
|
@ -2199,9 +2198,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
|
|
@ -2369,9 +2368,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.14.0-rc.10"
|
||||
version = "0.14.0-rc.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e"
|
||||
checksum = "b6bb40a5099e2c38a09dd29321a7a7f045f165a54317679c7cdfb0cbaf8f6b1e"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
|
|
@ -2382,9 +2381,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.14.0-rc.10"
|
||||
version = "0.14.0-rc.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02"
|
||||
checksum = "492f329d7eb11d22dadc988626b9ea1f503b4ab043a8b1e4e2cc4ae45dabdd70"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
|
|
@ -2396,9 +2395,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "p521"
|
||||
version = "0.14.0-rc.10"
|
||||
version = "0.14.0-rc.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae"
|
||||
checksum = "ff42e4ace5424e3b6d7cb82514be89866b85af87015f80341e37dcc21a66ce6e"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"ecdsa",
|
||||
|
|
@ -2420,7 +2419,7 @@ dependencies = [
|
|||
"delegate",
|
||||
"futures",
|
||||
"log",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
|
|
@ -2556,11 +2555,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pkcs5"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "279a91971a1d8eb1260a30938eae3be9cb67b472dffecb222fbbbe2fd2dc1453"
|
||||
checksum = "63d440a804ec8d6fafbb6b84471e013286658d373248927692ab3366686220ca"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
"cbc",
|
||||
"der",
|
||||
"pbkdf2",
|
||||
|
|
@ -2590,9 +2590,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
|||
|
||||
[[package]]
|
||||
name = "plist"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
|
||||
checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"indexmap 2.14.0",
|
||||
|
|
@ -2669,9 +2669,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "primefield"
|
||||
version = "0.14.0-rc.12"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8675564771a62f69a0af716b03e89b917b963c7b173b5855575e84fd4f605ca0"
|
||||
checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"crypto-common",
|
||||
|
|
@ -2683,11 +2683,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.14.0-rc.10"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf"
|
||||
checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
"once_cell",
|
||||
"primefield",
|
||||
"serdect",
|
||||
"wnaf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2779,18 +2783,18 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.4"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
|
||||
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -2835,9 +2839,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.3",
|
||||
|
|
@ -3060,12 +3064,12 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3"
|
||||
checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3109,9 +3113,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "russh"
|
||||
version = "0.61.2"
|
||||
version = "0.62.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbf893f64684e58da8a68d56a5e84d1cf0440226274c515770fe267707a7d0b0"
|
||||
checksum = "20d2039c3e50abb1ec3bdadea27098679da12a33befa9ab4e62adad9a13ebcb2"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bitflags",
|
||||
|
|
@ -3154,7 +3158,7 @@ dependencies = [
|
|||
"pkcs5",
|
||||
"pkcs8",
|
||||
"polyval",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rand_core 0.10.1",
|
||||
"ring",
|
||||
"rsa",
|
||||
|
|
@ -3180,9 +3184,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "russh-cryptovec"
|
||||
version = "0.61.0"
|
||||
version = "0.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51"
|
||||
checksum = "3aec6cb630dbe85d72ffd7bcd95f07e1bd69f9f270ee8adfa1afe443a6331438"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix",
|
||||
|
|
@ -3210,9 +3214,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
|
|||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
|
|
@ -3586,17 +3590,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ssh-cipher"
|
||||
version = "0.3.0-rc.9"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36"
|
||||
checksum = "d801accda99469cde6d73da741422610fdf6508a72d9a69d1b55cb241c720597"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
"cbc",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ctutils",
|
||||
"des",
|
||||
"poly1305",
|
||||
|
|
@ -3606,9 +3608,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ssh-encoding"
|
||||
version = "0.3.0-rc.9"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a"
|
||||
checksum = "7b54d0ed0498daf3f78d82e00e28c8eec9d75a067c4cfbcc7a0f7d0f4077749e"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"bytes",
|
||||
|
|
@ -3621,9 +3623,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ssh-key"
|
||||
version = "0.7.0-rc.10"
|
||||
version = "0.7.0-rc.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45735ce3dea95690e4a9e414c4cfde7f79835063c3dcd35881df85a84118e74b"
|
||||
checksum = "f9a32fae177b74a22aa9c5b01bf7e68b33545be32d9e381e248058d2adc15ce3"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"bcrypt-pbkdf",
|
||||
|
|
@ -3831,9 +3833,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.49"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
|
||||
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"libc",
|
||||
|
|
@ -3853,9 +3855,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.29"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
|
||||
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
|
@ -4183,9 +4185,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
|||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "10.0.0"
|
||||
version = "10.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bdf18a54cf91b4d98a8e8b67f6321606539fbcdcac02536286ad1de37b53fd2"
|
||||
checksum = "b5574dd2f922b1a46a06a4b1dc11193a4012108fd54cf725e1816cb8183d8778"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bon",
|
||||
|
|
@ -4197,9 +4199,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "vergen-gitcl"
|
||||
version = "10.0.0"
|
||||
version = "10.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4961429ed12888cb3c6dd20f7dc9508c821091a3ba5fec0156ed5a654c1c4572"
|
||||
checksum = "23d9fead6d7c515ac3a658e1a65d36925525d5cfdea414e27eb99b99ffd92bfa"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bon",
|
||||
|
|
@ -4211,9 +4213,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "vergen-lib"
|
||||
version = "10.0.0"
|
||||
version = "10.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "910e8471e27130bbc019e9bfa6bda16dfc4c6dd7c5d0793da70a9256caeae984"
|
||||
checksum = "8cd42fd155c2c2971f6d00face12ec245fbb604fce011ccaf2306d014c2e97ca"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bon",
|
||||
|
|
@ -4253,9 +4255,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.125"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
|
||||
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
|
|
@ -4266,9 +4268,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.75"
|
||||
version = "0.4.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
|
||||
checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -4276,9 +4278,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.125"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
|
||||
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
|
@ -4286,9 +4288,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.125"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
|
||||
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
|
|
@ -4299,9 +4301,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.125"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
|
||||
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -4680,6 +4682,17 @@ version = "0.57.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wnaf"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"group",
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
|
|
@ -4704,6 +4717,7 @@ dependencies = [
|
|||
"futures",
|
||||
"hashbrown 0.17.1",
|
||||
"indexmap 2.14.0",
|
||||
"inventory",
|
||||
"libc",
|
||||
"mlua",
|
||||
"paste",
|
||||
|
|
@ -5033,7 +5047,7 @@ dependencies = [
|
|||
"objc2",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"regex",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
|
|
@ -5222,6 +5236,7 @@ dependencies = [
|
|||
"foldhash",
|
||||
"futures",
|
||||
"hashbrown 0.17.1",
|
||||
"indexmap 2.14.0",
|
||||
"inventory",
|
||||
"libc",
|
||||
"memchr",
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ debug = false
|
|||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "8.0.1"
|
||||
anyhow = "1.0.102"
|
||||
arc-swap = { version = "1.9.1", features = [ "serde" ] }
|
||||
anyhow = "1.0.103"
|
||||
arc-swap = { version = "1.9.2", features = [ "serde" ] }
|
||||
base64 = "0.22.1"
|
||||
bitflags = { version = "2.13.0", features = [ "serde" ] }
|
||||
chrono = "0.4.45"
|
||||
|
|
@ -61,11 +61,11 @@ ordered-float = { version = "5.3.0", features = [ "serde" ] }
|
|||
parking_lot = "0.12.5"
|
||||
paste = "1.0.15"
|
||||
percent-encoding = "2.3.2"
|
||||
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
|
||||
rand = { version = "0.10.2", default-features = false, features = [ "std", "sys_rng" ] }
|
||||
ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] }
|
||||
ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] }
|
||||
regex = "1.12.4"
|
||||
russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] }
|
||||
russh = { version = "0.62.1", default-features = false, features = [ "ring", "rsa" ] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.150"
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ either = { workspace = true }
|
|||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui-core = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -66,14 +66,8 @@ impl Actor for Trigger {
|
|||
|
||||
impl Trigger {
|
||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
||||
let sep = if cfg!(windows) {
|
||||
AnyAsciiChar::new(b"/\\").unwrap()
|
||||
} else {
|
||||
AnyAsciiChar::new(b"/").unwrap()
|
||||
};
|
||||
|
||||
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
||||
if path.is_empty() && !sep.predicate(s.bytes().last()?) {
|
||||
if path.is_empty() && !AnyAsciiChar::SEP.predicate(s.bytes().last()?) {
|
||||
return None; // We don't complete a `sftp://test`, but `sftp://test/`
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +78,7 @@ impl Trigger {
|
|||
}
|
||||
|
||||
// Child
|
||||
let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned();
|
||||
let child = path.rsplit_pred(AnyAsciiChar::SEP).map_or(path.as_path(), |(_, c)| c).to_owned();
|
||||
|
||||
// Parent
|
||||
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Actor for Arrow {
|
|||
let len = confirm.list.line_count(area.width);
|
||||
|
||||
let old = confirm.offset;
|
||||
confirm.offset = form.step.add(confirm.offset, len, area.height as _);
|
||||
confirm.offset = form.step.add(confirm.offset, len, area.height as _, 0, 0);
|
||||
|
||||
succ!(render!(old != confirm.offset));
|
||||
}
|
||||
|
|
|
|||
25
yazi-actor/src/help/close.rs
Normal file
25
yazi-actor/src/help/close.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{emit, render, succ};
|
||||
use yazi_parser::help::CloseForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Form = CloseForm;
|
||||
|
||||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
if let Some(chord) = help.bindings.get(help.cursor).filter(|_| opt.submit) {
|
||||
emit!(Seq(chord.to_seq(help.layer)));
|
||||
}
|
||||
|
||||
help.visible = false;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ use anyhow::Result;
|
|||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::InputMode;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -13,15 +14,11 @@ impl Actor for Escape {
|
|||
const NAME: &str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
if cx.help.keyword().is_none() {
|
||||
return act!(help:toggle, cx, cx.help.layer);
|
||||
if cx.help.input.mode() == InputMode::Normal {
|
||||
return act!(help:close, cx);
|
||||
}
|
||||
|
||||
let help = &mut cx.help;
|
||||
help.keyword = String::new();
|
||||
help.in_filter = None;
|
||||
help.filter_apply();
|
||||
|
||||
act!(escape, cx.help.input)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Filter;
|
||||
|
||||
impl Actor for Filter {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.in_filter = Some(Default::default());
|
||||
help.filter_apply();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(arrow escape filter toggle);
|
||||
yazi_macro::mod_flat!(arrow close escape toggle);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use ratatui_core::layout::Margin;
|
||||
use yazi_config::popup::Help;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::help::ToggleForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::Input;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -13,17 +16,24 @@ impl Actor for Toggle {
|
|||
const NAME: &str = "toggle";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let position = Help::position();
|
||||
let area = cx.mgr.area(position);
|
||||
let input_area = area.inner(Margin::new(1, 1));
|
||||
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.visible = !help.visible;
|
||||
help.visible = true;
|
||||
help.layer = form.layer;
|
||||
help.position = position;
|
||||
help.height = area.height;
|
||||
|
||||
help.keyword = String::new();
|
||||
help.in_filter = None;
|
||||
help.filter_apply();
|
||||
help.input = Input::default();
|
||||
help.input.repos(input_area);
|
||||
|
||||
help.keyword.clear();
|
||||
help.offset = 0;
|
||||
help.cursor = 0;
|
||||
help.filter_apply();
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@ impl Actor for Show {
|
|||
|
||||
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
||||
let input = &mut cx.input;
|
||||
input.main.visible = true;
|
||||
input.main.name = opt.name.clone();
|
||||
input.main.title = opt.title.clone();
|
||||
input.main.position = opt.position;
|
||||
input.main.visible = true;
|
||||
|
||||
opt.styles = (&THEME.input).into();
|
||||
opt.blinking = YAZI.input.cursor_blink;
|
||||
|
|
|
|||
|
|
@ -5,19 +5,19 @@ use yazi_shim::mlua::UserDataFieldsExt;
|
|||
|
||||
use super::{File, Filter, Lives, PtrCell};
|
||||
|
||||
pub(super) struct Files {
|
||||
pub(super) struct Entries {
|
||||
window: Range<usize>,
|
||||
folder: PtrCell<yazi_core::tab::Folder>,
|
||||
tab: PtrCell<yazi_core::tab::Tab>,
|
||||
}
|
||||
|
||||
impl Deref for Files {
|
||||
type Target = yazi_fs::Files;
|
||||
impl Deref for Entries {
|
||||
type Target = yazi_fs::Entries;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.folder.files }
|
||||
fn deref(&self) -> &Self::Target { &self.folder.entries }
|
||||
}
|
||||
|
||||
impl Files {
|
||||
impl Entries {
|
||||
pub(super) fn make(
|
||||
window: Range<usize>,
|
||||
folder: &yazi_core::tab::Folder,
|
||||
|
|
@ -27,7 +27,7 @@ impl Files {
|
|||
}
|
||||
}
|
||||
|
||||
impl UserData for Files {
|
||||
impl UserData for Entries {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_static_field("filter", |_, me| me.filter().map(Filter::make).transpose());
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ use std::{ops::Deref, ptr};
|
|||
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_binding::{Range, style::Style};
|
||||
use yazi_config::THEME;
|
||||
use yazi_fs::file::FileInventory;
|
||||
use yazi_shared::{path::AsPath, url::UrlLike};
|
||||
|
||||
use super::{FILE_CACHE, Lives};
|
||||
|
|
@ -17,7 +18,7 @@ pub(super) struct File {
|
|||
impl Deref for File {
|
||||
type Target = yazi_fs::file::File;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.folder.files[self.idx] }
|
||||
fn deref(&self) -> &Self::Target { &self.folder.entries[self.idx] }
|
||||
}
|
||||
|
||||
impl AsRef<yazi_fs::file::File> for File {
|
||||
|
|
@ -32,14 +33,16 @@ impl File {
|
|||
) -> mlua::Result<AnyUserData> {
|
||||
use hashbrown::hash_map::Entry;
|
||||
|
||||
Ok(match unsafe { (*FILE_CACHE.get()).assume_init_mut() }.entry(PtrCell(&folder.files[idx])) {
|
||||
Entry::Occupied(oe) => oe.into_mut().clone(),
|
||||
Entry::Vacant(ve) => {
|
||||
let ud = Lives::scoped_userdata(Self { idx, folder: folder.into(), tab: tab.into() })?;
|
||||
ve.insert(ud.clone());
|
||||
ud
|
||||
}
|
||||
})
|
||||
Ok(
|
||||
match unsafe { (*FILE_CACHE.get()).assume_init_mut() }.entry(PtrCell(&folder.entries[idx])) {
|
||||
Entry::Occupied(oe) => oe.into_mut().clone(),
|
||||
Entry::Vacant(ve) => {
|
||||
let ud = Lives::scoped_userdata(Self { idx, folder: folder.into(), tab: tab.into() })?;
|
||||
ve.insert(ud.clone());
|
||||
ud
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -49,7 +52,6 @@ impl File {
|
|||
impl UserData for File {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
yazi_binding::impl_file_fields!(fields);
|
||||
fields.add_cached_field("bare", |_, me| Ok(yazi_fs::file::File::from(&**me)));
|
||||
|
||||
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
||||
fields.add_field_method_get("is_hovered", |_, me| Ok(me.is_hovered()));
|
||||
|
|
@ -62,12 +64,16 @@ impl UserData for File {
|
|||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
yazi_binding::impl_file_methods!(methods);
|
||||
|
||||
methods.add_method("icon", |_, me, ()| {
|
||||
methods.add_method("icon", |lua, me, ()| {
|
||||
yazi_binding::deprecate!(
|
||||
lua,
|
||||
"{}: `File:icon()` is deprecated, use `th.icon:match(file)` instead"
|
||||
);
|
||||
// TODO: use a cache
|
||||
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
|
||||
});
|
||||
methods.add_method("size", |_, me, ()| {
|
||||
Ok(if me.is_dir() { me.folder.files.sizes.get(&me.urn()).copied() } else { Some(me.len) })
|
||||
Ok(if me.is_dir() { me.folder.entries.sizes.get(&me.urn()).copied() } else { Some(me.len) })
|
||||
});
|
||||
methods.add_method("mime", |lua, me, ()| {
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
|
|
@ -98,16 +104,16 @@ impl UserData for File {
|
|||
})
|
||||
});
|
||||
methods.add_method("is_marked", |_, me, ()| {
|
||||
use yazi_core::tab::Mode::*;
|
||||
if !me.tab.mode.is_visual() || me.folder.url != me.tab.current.url {
|
||||
let Some(visual) = me.tab.mode.visual() else {
|
||||
return Ok(0u8);
|
||||
};
|
||||
if !visual.contains(me.idx, me.tab.current.cursor, me.folder.entries.len()) {
|
||||
return Ok(0u8);
|
||||
}
|
||||
|
||||
Ok(match &me.tab.mode {
|
||||
Select(_, indices) if indices.contains(&me.idx) => 1u8,
|
||||
Unset(_, indices) if indices.contains(&me.idx) => 2u8,
|
||||
_ => 0u8,
|
||||
})
|
||||
if me.folder.url != me.tab.current.url {
|
||||
return Ok(0u8);
|
||||
}
|
||||
Ok(if me.tab.mode.is_select() { 1u8 } else { 2u8 })
|
||||
});
|
||||
methods.add_method("is_selected", |_, me, ()| Ok(me.tab.selected.contains(&me.url)));
|
||||
methods.add_method("found", |lua, me, ()| {
|
||||
|
|
@ -138,3 +144,10 @@ impl UserData for File {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit! {
|
||||
FileInventory {
|
||||
register: |_| {},
|
||||
borrow: |ud, f| f(&*ud.borrow::<File>()?),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use mlua::{AnyUserData, UserData, UserDataFields};
|
|||
use yazi_config::LAYOUT;
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use super::{File, Files, Lives, PtrCell};
|
||||
use super::{Entries, File, Lives, PtrCell};
|
||||
|
||||
pub(super) struct Folder {
|
||||
window: Range<usize>,
|
||||
|
|
@ -28,7 +28,7 @@ impl Folder {
|
|||
Some(w) => w,
|
||||
None => {
|
||||
let limit = LAYOUT.get().preview.height as usize;
|
||||
inner.offset..inner.files.len().min(inner.offset + limit)
|
||||
inner.offset..inner.entries.len().min(inner.offset + limit)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -39,9 +39,9 @@ impl Folder {
|
|||
impl UserData for Folder {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("cwd", |_, me| Ok(me.url.clone()));
|
||||
fields.add_static_field("files", |_, me| Files::make(0..me.files.len(), me, &me.tab));
|
||||
fields.add_static_field("files", |_, me| Entries::make(0..me.entries.len(), me, &me.tab));
|
||||
fields.add_cached_field("stage", |_, me| Ok(me.stage.clone()));
|
||||
fields.add_static_field("window", |_, me| Files::make(me.window.clone(), me, &me.tab));
|
||||
fields.add_static_field("window", |_, me| Entries::make(me.window.clone(), me, &me.tab));
|
||||
|
||||
fields.add_field_method_get("offset", |_, me| Ok(me.offset));
|
||||
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
yazi_macro::mod_flat!(behavior core file files filter finder folder input input_alt lives mode mut_cell preference preview ptr selected tab tabs task tasks which yanked);
|
||||
yazi_macro::mod_flat!(behavior core entries file filter finder folder input input_alt lives mode mut_cell preference preview ptr selected tab tabs task tasks which yanked);
|
||||
|
||||
pub(super) fn init() {
|
||||
unsafe { FILE_CACHE.get().write(std::mem::MaybeUninit::new(<_>::default())) };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_binding::deprecate;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
|
|
@ -22,9 +23,13 @@ impl Mode {
|
|||
|
||||
impl UserData for Mode {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("is_normal", |_, me| Ok(me.is_normal()));
|
||||
fields.add_field_method_get("is_select", |_, me| Ok(me.is_select()));
|
||||
fields.add_field_method_get("is_unset", |_, me| Ok(me.is_unset()));
|
||||
fields.add_field_method_get("is_visual", |_, me| Ok(me.is_visual()));
|
||||
fields.add_field_method_get("is_visual", |lua, me| {
|
||||
deprecate!(lua, "{}: `mode.is_visual` is deprecated, use `not mode.is_normal` instead. \nSee #4101 for more details: https://github.com/sxyazi/yazi/pull/4101.");
|
||||
Ok(!me.is_normal())
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl UserData for Preview {
|
|||
.hovered_folder()
|
||||
.map(|f| {
|
||||
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.entries.len().min(me.skip + limit)), f, &me.tab)
|
||||
})
|
||||
.transpose()
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use mlua::AnyUserData;
|
||||
use yazi_shared::url::UrlBuf;
|
||||
|
||||
use super::Lives;
|
||||
use crate::lives::PtrCell;
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct Selected;
|
||||
|
|
@ -12,7 +10,7 @@ impl Selected {
|
|||
let inner = PtrCell::from(inner);
|
||||
|
||||
Lives::scoped_userdata(yazi_binding::Iter::new(
|
||||
inner.as_static().values().map(UrlBuf::from),
|
||||
inner.as_static().files().cloned(),
|
||||
Some(inner.len()),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ impl Which {
|
|||
impl UserData for Which {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("tx", |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
|
||||
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
|
||||
fields.add_cached_field("cands", |lua, me| {
|
||||
lua.create_sequence_from(me.inner.cands.iter().cloned())
|
||||
});
|
||||
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
|
||||
|
||||
fields.add_field_method_get("active", |_, me| Ok(me.inner.active));
|
||||
fields.add_field_method_get("silent", |_, me| Ok(me.inner.silent));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::ops::Deref;
|
|||
|
||||
use mlua::{AnyUserData, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_binding::Iter;
|
||||
use yazi_shared::url::UrlBuf;
|
||||
use yazi_fs::file::File;
|
||||
use yazi_shim::mlua::get_metatable;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
|
@ -25,7 +25,7 @@ impl Yanked {
|
|||
Lives::scoped_userdata(Self {
|
||||
inner,
|
||||
iter: Lives::scoped_userdata(Iter::new(
|
||||
inner.as_static().iter().map(UrlBuf::from),
|
||||
inner.as_static().iter().map(File::from),
|
||||
Some(inner.len()),
|
||||
))?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ impl Actor for Arrow {
|
|||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
let old = tab.current.cursor;
|
||||
if !tab.current.arrow(form.step) {
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -22,9 +23,8 @@ impl Actor for Arrow {
|
|||
tab.current.retrace();
|
||||
|
||||
// Visual selection
|
||||
if let Some((start, items)) = tab.mode.visual_mut() {
|
||||
let end = tab.current.cursor;
|
||||
*items = (start.min(end)..=end.max(start)).collect();
|
||||
if let Some(visual) = tab.mode.visual_mut() {
|
||||
visual.arrow(form.step, old, tab.current.cursor);
|
||||
}
|
||||
|
||||
act!(mgr:hover, cx)?;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ impl Actor for BulkRename {
|
|||
succ!(NotifyProxy::push_warn("Bulk rename", "No text opener found"));
|
||||
};
|
||||
|
||||
let selected: Vec<_> = cx.tab().selected_or_hovered().cloned().collect();
|
||||
let selected: Vec<_> = cx.tab().selected_or_hovered_urls().cloned().collect();
|
||||
if selected.is_empty() {
|
||||
succ!(NotifyProxy::push_warn("Bulk rename", "No files selected"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl Actor for Copy {
|
|||
let mut it = if form.hovered {
|
||||
Box::new(cx.hovered().map(|h| &h.url).into_iter())
|
||||
} else {
|
||||
cx.tab().selected_or_hovered()
|
||||
cx.tab().selected_or_hovered_urls()
|
||||
}
|
||||
.peekable();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use yazi_config::{YAZI, popup::ConfirmCfg};
|
||||
use yazi_fs::{FilesOp, file::File};
|
||||
use yazi_macro::{input, ok_or_not_found, succ};
|
||||
use yazi_parser::mgr::CreateForm;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, strand::{StrandBuf, StrandLike}, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -18,28 +21,37 @@ impl Actor for Create {
|
|||
|
||||
const NAME: &str = "create";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, CreateForm { target, dir, force }: Self::Form) -> Result<Data> {
|
||||
let cwd = cx.cwd().to_owned();
|
||||
let mut input = input!(cx, YAZI.input.create(form.dir))?;
|
||||
|
||||
let mut target: Pin<Box<dyn Stream<Item = StrandBuf> + Send>> = if target.is_empty() {
|
||||
let input = input!(cx, YAZI.input.create(dir))?;
|
||||
Box::pin(
|
||||
UnboundedReceiverStream::new(input).filter_map(|event| async { event.map(Into::into) }),
|
||||
)
|
||||
} else {
|
||||
Box::pin(tokio_stream::iter(vec![target]))
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
||||
let Some(name) = target.next().await else { return Ok(()) };
|
||||
if name.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Ok(new) = cwd.try_join(&name) else {
|
||||
return;
|
||||
bail!("Failed to join new name with CWD");
|
||||
};
|
||||
|
||||
if !form.force
|
||||
&& maybe_exists(&new).await
|
||||
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
|
||||
if !force
|
||||
&& let Some(file) = File::maybe_new(&new).await?
|
||||
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&file)).await
|
||||
{
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
_ = Self::r#do(new, form.dir || name.ends_with('/') || name.ends_with('\\')).await;
|
||||
let end_sep = AnyAsciiChar::SEP.predicate(*name.encoded_bytes().last().unwrap());
|
||||
Self::r#do(new, dir || end_sep).await
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,15 +67,16 @@ impl Actor for EscapeVisual {
|
|||
let tab = cx.tab_mut();
|
||||
|
||||
let select = tab.mode.is_select();
|
||||
let Some((_, indices)) = tab.mode.take_visual() else { succ!(false) };
|
||||
let Some(indices) = tab.mode.take_visual(tab.current.cursor, tab.current.entries.len()) else {
|
||||
succ!(false)
|
||||
};
|
||||
|
||||
render!();
|
||||
let urls: Vec<_> =
|
||||
indices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect();
|
||||
let files: Vec<_> = indices.into_iter().filter_map(|i| tab.current.entries.get(i)).collect();
|
||||
|
||||
if !select {
|
||||
tab.selected.remove_many(urls);
|
||||
} else if urls.len() != tab.selected.add_many(urls) {
|
||||
tab.selected.remove_many(files);
|
||||
} else if files.len() != tab.selected.add_many(files) {
|
||||
NotifyProxy::push_warn(
|
||||
"Escape visual mode",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
|
|
@ -96,7 +97,7 @@ impl Actor for EscapeFilter {
|
|||
const NAME: &str = "escape_filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
if cx.current_mut().files.filter().is_none() {
|
||||
if cx.current_mut().entries.filter().is_none() {
|
||||
succ!(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ impl Actor for FilterDo {
|
|||
let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) };
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.urn().into());
|
||||
cx.current_mut().files.set_filter(filter);
|
||||
cx.current_mut().entries.set_filter(filter);
|
||||
|
||||
if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
act!(mgr:hover, cx, hovered)?;
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ impl Actor for FindArrow {
|
|||
|
||||
render!(finder.catchup(&tab.current));
|
||||
let offset = if form.prev {
|
||||
finder.prev(&tab.current.files, tab.current.cursor, false)
|
||||
finder.prev(&tab.current.entries, tab.current.cursor, false)
|
||||
} else {
|
||||
finder.next(&tab.current.files, tab.current.cursor, false)
|
||||
finder.next(&tab.current.entries, tab.current.cursor, false)
|
||||
};
|
||||
|
||||
if let Some(offset) = offset {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ impl Actor for FindDo {
|
|||
}
|
||||
|
||||
let step = if opt.prev {
|
||||
finder.prev(&cx.current().files, cx.current().cursor, true)
|
||||
finder.prev(&cx.current().entries, cx.current().cursor, true)
|
||||
} else {
|
||||
finder.next(&cx.current().files, cx.current().cursor, true)
|
||||
finder.next(&cx.current().entries, cx.current().cursor, true)
|
||||
};
|
||||
|
||||
if let Some(step) = step {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ impl Actor for Hidden {
|
|||
render!();
|
||||
false
|
||||
} else {
|
||||
f.files.set_show_hidden(state);
|
||||
render_and!(f.files.catchup_revision())
|
||||
f.entries.set_show_hidden(state);
|
||||
render_and!(f.entries.catchup_revision())
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ yazi_macro::mod_flat!(
|
|||
quit
|
||||
refresh
|
||||
remove
|
||||
remove_do
|
||||
rename
|
||||
reveal
|
||||
search
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl Actor for Open {
|
|||
Quit::with_selected(cx.hovered().map(|h| &h.url))
|
||||
} else {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
Quit::with_selected(cx.tab().selected_or_hovered())
|
||||
Quit::with_selected(cx.tab().selected_or_hovered_urls())
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ impl Actor for Open {
|
|||
cx.hovered().map(|h| vec![h.url.clone()]).unwrap_or_default()
|
||||
} else {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
cx.tab().selected_or_hovered().cloned().collect()
|
||||
cx.tab().selected_or_hovered_urls().cloned().collect()
|
||||
};
|
||||
}
|
||||
if opt.targets.is_empty() {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl Actor for Paste {
|
|||
if mgr.yanked.cut {
|
||||
cx.core.tasks.file_cut(&mgr.yanked, dest, form.force);
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&*mgr.yanked));
|
||||
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(mgr.yanked.urls()));
|
||||
act!(mgr:unyank, cx)
|
||||
} else {
|
||||
succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, form.force, form.follow));
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::tab::Folder;
|
||||
use yazi_fs::{CWD, Files, FilesOp, cha::Cha};
|
||||
use yazi_fs::{CWD, Entries, FilesOp, cha::Cha};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFiles, VfsFilesOp};
|
||||
use yazi_vfs::{VfsEntries, VfsFilesOp};
|
||||
use yazi_watcher::MgrProxy;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -29,7 +29,7 @@ impl Actor for Refresh {
|
|||
act!(mgr:watch, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
|
||||
cx.tasks.prework_sorted(&cx.current().files);
|
||||
cx.tasks.prework_sorted(&cx.current().entries);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -44,9 +44,9 @@ impl Refresh {
|
|||
// TODO: performance improvement
|
||||
fn trigger_dirs(folders: &[&Folder]) {
|
||||
async fn go(dir: UrlBuf, cha: Cha) {
|
||||
let Some(cha) = Files::assert_stale(&dir, cha).await else { return };
|
||||
let Some(cha) = Entries::assert_stale(&dir, cha).await else { return };
|
||||
|
||||
match Files::from_dir_bulk(&dir).await {
|
||||
match Entries::from_dir_bulk(&dir).await {
|
||||
Ok(files) => FilesOp::Full(dir, files, cha).emit(),
|
||||
Err(e) => FilesOp::issue_error(&dir, e).await,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use yazi_config::popup::ConfirmCfg;
|
||||
use yazi_fs::file::Files;
|
||||
use yazi_macro::{act, confirm, succ};
|
||||
use yazi_parser::mgr::RemoveForm;
|
||||
use yazi_parser::mgr::{RemoveDoForm, RemoveForm};
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
|
|
@ -14,58 +15,31 @@ impl Actor for Remove {
|
|||
|
||||
const NAME: &str = "remove";
|
||||
|
||||
fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
form.targets = if form.hovered {
|
||||
cx.hovered().map_or(vec![], |h| vec![h.url.clone()])
|
||||
let targets = Files(if form.hovered {
|
||||
cx.hovered().map_or(vec![], |h| vec![h.clone()])
|
||||
} else {
|
||||
cx.tab().selected_or_hovered().cloned().collect()
|
||||
};
|
||||
cx.tab().selected_or_hovered_files().cloned().collect()
|
||||
});
|
||||
|
||||
if form.targets.is_empty() {
|
||||
if targets.is_empty() {
|
||||
succ!();
|
||||
} else if form.force {
|
||||
return act!(mgr:remove_do, cx, form);
|
||||
return act!(mgr:remove_do, cx, RemoveDoForm { permanently: form.permanently, targets: targets.into() });
|
||||
}
|
||||
|
||||
let confirm = confirm!(
|
||||
cx,
|
||||
if form.permanently {
|
||||
ConfirmCfg::delete(&form.targets)
|
||||
} else {
|
||||
ConfirmCfg::trash(&form.targets)
|
||||
}
|
||||
if form.permanently { ConfirmCfg::delete(&targets) } else { ConfirmCfg::trash(&targets) }
|
||||
)?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if confirm.future().await {
|
||||
MgrProxy::remove_do(form.targets, form.permanently);
|
||||
MgrProxy::remove_do(form.permanently, targets);
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Do
|
||||
pub struct RemoveDo;
|
||||
|
||||
impl Actor for RemoveDo {
|
||||
type Form = RemoveForm;
|
||||
|
||||
const NAME: &str = "remove_do";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let mgr = &mut cx.mgr;
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&form.targets);
|
||||
});
|
||||
|
||||
mgr.yanked.remove_many(&form.targets);
|
||||
mgr.yanked.catchup_revision(false);
|
||||
|
||||
cx.tasks.file_remove(form.targets, form.permanently);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
yazi-actor/src/mgr/remove_do.rs
Normal file
28
yazi-actor/src/mgr/remove_do.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::RemoveDoForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct RemoveDo;
|
||||
|
||||
impl Actor for RemoveDo {
|
||||
type Form = RemoveDoForm;
|
||||
|
||||
const NAME: &str = "remove_do";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let mgr = &mut cx.mgr;
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&form.targets);
|
||||
});
|
||||
|
||||
mgr.yanked.remove_many(&form.targets);
|
||||
mgr.yanked.catchup_revision(false);
|
||||
|
||||
cx.tasks.file_remove(form.targets, form.permanently);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{Result, bail};
|
||||
use yazi_config::{YAZI, popup::ConfirmCfg};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{FilesOp, file::File};
|
||||
|
|
@ -6,7 +6,7 @@ use yazi_macro::{act, err, input, ok_or_not_found, succ};
|
|||
use yazi_parser::mgr::RenameForm;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::{data::Data, id::Id, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
|
|
@ -42,23 +42,23 @@ impl Actor for Rename {
|
|||
};
|
||||
|
||||
let (tab, old) = (cx.tab().id, hovered.url_owned());
|
||||
let mut input = input!(cx, YAZI.input.rename().with_value(name).with_cursor(cursor))?;
|
||||
let mut input =
|
||||
input!(cx, YAZI.input.rename(hovered.is_dir()).with_value(name).with_cursor(cursor))?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return Ok(()) };
|
||||
if name.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(Ok(new)) = old.parent().map(|u| u.try_join(name)) else {
|
||||
return;
|
||||
bail!("Failed to join new name with parent directory");
|
||||
};
|
||||
|
||||
if form.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await {
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
if form.force || Self::try_ask(&old, &new).await? {
|
||||
Self::r#do(tab, old, new).await?;
|
||||
}
|
||||
Ok::<(), anyhow::Error>(())
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -97,6 +97,17 @@ impl Rename {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_ask(old: &UrlBuf, new: &UrlBuf) -> Result<bool> {
|
||||
let Some(file) = File::maybe_new(&new).await? else {
|
||||
return Ok(true);
|
||||
};
|
||||
|
||||
Ok(
|
||||
provider::must_identical(old, new).await
|
||||
|| ConfirmProxy::show(ConfirmCfg::overwrite(&file)).await,
|
||||
)
|
||||
}
|
||||
|
||||
fn empty_url_part(url: &UrlBuf, by: &str) -> String {
|
||||
if by == "all" {
|
||||
return String::new();
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ impl Actor for Sort {
|
|||
render!();
|
||||
false
|
||||
} else {
|
||||
f.files.set_sorter(sorter);
|
||||
render_and!(f.files.catchup_revision())
|
||||
f.entries.set_sorter(sorter);
|
||||
render_and!(f.entries.catchup_revision())
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ impl Actor for Sort {
|
|||
{
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
cx.tasks.prework_sorted(&cx.mgr.tabs[cx.tab].current.files);
|
||||
cx.tasks.prework_sorted(&cx.mgr.tabs[cx.tab].current.entries);
|
||||
}
|
||||
|
||||
// Apply to hovered
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Actor for TabSwap {
|
|||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let tabs = cx.tabs_mut();
|
||||
|
||||
let new = form.step.add(tabs.cursor, tabs.len(), 0);
|
||||
let new = form.step.add(tabs.cursor, tabs.len(), 0, 0, 0);
|
||||
if new == tabs.cursor {
|
||||
succ!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ impl Actor for Toggle {
|
|||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
let Some(url) = form.url.or_else(|| tab.hovered().map(|h| h.url.clone())) else {
|
||||
let Some(file) = form.file.or_else(|| tab.hovered().cloned()) else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
let b = match form.state {
|
||||
Some(true) => render_and!(tab.selected.add(&url)),
|
||||
Some(false) => render_and!(tab.selected.remove(&url)) | true,
|
||||
None => render_and!(tab.selected.remove(&url) || tab.selected.add(&url)),
|
||||
Some(true) => render_and!(tab.selected.add(file)),
|
||||
Some(false) => render_and!(tab.selected.remove(&file.url)) | true,
|
||||
None => render_and!(tab.selected.remove(&file.url) || tab.selected.add(file)),
|
||||
};
|
||||
|
||||
if !b {
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ impl Actor for ToggleAll {
|
|||
use either::Either::*;
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
let it = tab.current.files.iter().map(|f| &f.url);
|
||||
let it = tab.current.entries.iter();
|
||||
let either = match form.state {
|
||||
Some(true) if form.urls.is_empty() => Left((vec![], it.collect())),
|
||||
Some(true) => Right((vec![], form.urls)),
|
||||
Some(false) if form.urls.is_empty() => Left((it.collect(), vec![])),
|
||||
Some(false) => Right((form.urls, vec![])),
|
||||
None if form.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains(u))),
|
||||
None => Right(form.urls.into_iter().partition(|u| tab.selected.contains(u))),
|
||||
Some(true) if form.files.is_empty() => Left((vec![], it.collect())),
|
||||
Some(true) => Right((vec![], form.files)),
|
||||
Some(false) if form.files.is_empty() => Left((it.collect(), vec![])),
|
||||
Some(false) => Right((form.files, vec![])),
|
||||
None if form.files.is_empty() => Left(it.partition(|&f| tab.selected.contains(f))),
|
||||
None => Right(form.files.into_iter().partition(|f| tab.selected.contains(f))),
|
||||
};
|
||||
|
||||
let warn = match either {
|
||||
|
|
@ -34,14 +34,14 @@ impl Actor for ToggleAll {
|
|||
}
|
||||
Right((removal, addition)) => {
|
||||
render!(tab.selected.remove_many(&removal) > 0);
|
||||
render!(tab.selected.add_many(&addition), > 0) != addition.len()
|
||||
addition.len() != render!(tab.selected.add_many(&addition), > 0)
|
||||
}
|
||||
};
|
||||
|
||||
if warn {
|
||||
NotifyProxy::push_warn(
|
||||
"Toggle all",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
"Some files cannot be selected due to path nesting conflict.",
|
||||
);
|
||||
}
|
||||
succ!();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Actor for UpdateFiles {
|
|||
const NAME: &str = "update_files";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let revision = cx.current().files.revision;
|
||||
let revision = cx.current().entries.revision;
|
||||
let linked: Vec<_> = LINKED.read().from_dir(form.op.cwd()).map(|u| form.op.chdir(u)).collect();
|
||||
|
||||
for op in [form.op].into_iter().chain(linked) {
|
||||
|
|
@ -28,7 +28,7 @@ impl Actor for UpdateFiles {
|
|||
act!(mgr:hidden, cx).ok();
|
||||
act!(mgr:sort, cx).ok();
|
||||
|
||||
if revision != cx.current().files.revision {
|
||||
if revision != cx.current().entries.revision {
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
|
|
@ -80,7 +80,7 @@ impl UpdateFiles {
|
|||
}
|
||||
|
||||
if calc {
|
||||
cx.tasks.prework_sorted(&cx.current().files);
|
||||
cx.tasks.prework_sorted(&cx.current().entries);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ impl Actor for UpdateYanked {
|
|||
const NAME: &str = "update_yanked";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
if form.urls.is_empty() && cx.mgr.yanked.is_empty() {
|
||||
if form.files.is_empty() && cx.mgr.yanked.is_empty() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
cx.mgr.yanked = Yanked::new(form.cut, form.0.urls.into_owned());
|
||||
cx.mgr.yanked = Yanked::new(form.cut, form.0.files.into_owned());
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_core::tab::Mode;
|
||||
use yazi_core::tab::{Mode, Visual};
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::mgr::VisualModeForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
|
@ -20,9 +18,9 @@ impl Actor for VisualMode {
|
|||
|
||||
let idx = tab.current.cursor;
|
||||
if form.unset {
|
||||
tab.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
||||
tab.mode = Mode::Unset(Visual::new(idx));
|
||||
} else {
|
||||
tab.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
||||
tab.mode = Mode::Select(Visual::new(idx));
|
||||
};
|
||||
|
||||
succ!(render!());
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use yazi_core::mgr::Yanked;
|
||||
use yazi_macro::{act, render};
|
||||
use yazi_parser::mgr::YankForm;
|
||||
use yazi_shared::{data::Data, url::UrlBufCov};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ impl Actor for Yank {
|
|||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
cx.mgr.yanked =
|
||||
Yanked::new(form.cut, cx.tab().selected_or_hovered().cloned().map(UrlBufCov).collect());
|
||||
Yanked::new(form.cut, cx.tab().selected_or_hovered_files().map(Into::into).collect());
|
||||
render!(cx.mgr.yanked.catchup_revision(true));
|
||||
|
||||
act!(mgr:escape_select, cx)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Actor for Arrow {
|
|||
let spot = &mut cx.tab_mut().spot;
|
||||
let Some(lock) = &mut spot.lock else { succ!() };
|
||||
|
||||
let new = form.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0);
|
||||
let new = form.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0, 0, 0);
|
||||
let Some(old) = lock.selected() else {
|
||||
return act!(mgr:spot, cx, new);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ impl Actor for Arrow {
|
|||
let tasks = &mut cx.tasks;
|
||||
|
||||
let old = tasks.cursor;
|
||||
tasks.cursor = form.step.add(tasks.cursor, tasks.snaps.len(), Tasks::limit());
|
||||
tasks.cursor = form.step.add(tasks.cursor, tasks.snaps.len(), Tasks::limit(), 0, 0);
|
||||
|
||||
succ!(render!(tasks.cursor != old));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ impl Actor for Activate {
|
|||
|
||||
let which = &mut cx.which;
|
||||
which.tx = opt.tx;
|
||||
which.times = opt.times;
|
||||
which.layer = opt.layer;
|
||||
which.cands = opt.cands;
|
||||
which.times = opt.times;
|
||||
|
||||
which.active = true;
|
||||
which.silent = opt.silent;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,6 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
|||
|
||||
# External dependencies
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.6.5"
|
||||
clap_complete = "4.6.7"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.6.0"
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
|||
# External build dependencies
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.6.5"
|
||||
clap_complete = "4.6.7"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.6.0"
|
||||
serde = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ proc-macro = true
|
|||
[dependencies]
|
||||
# External dependencies
|
||||
proc-macro2 = "1"
|
||||
quote = "1.0.45"
|
||||
quote = "1.0.46"
|
||||
syn = { version = "2.0.118", features = [ "full" ] }
|
||||
|
|
|
|||
|
|
@ -13,13 +13,17 @@ keymap = [
|
|||
{ on = "<C-c>", run = "close", desc = "Close the current tab, or quit if it's last" },
|
||||
{ on = "<C-z>", run = "suspend", desc = "Suspend the process" },
|
||||
|
||||
# Hopping
|
||||
{ on = "k", run = "arrow prev", desc = "Previous file" },
|
||||
{ on = "j", run = "arrow next", desc = "Next file" },
|
||||
|
||||
# Hop around
|
||||
{ on = "k", run = "arrow prev", desc = "Previous file" },
|
||||
{ on = "j", run = "arrow next", desc = "Next file" },
|
||||
{ on = "<Up>", run = "arrow prev", desc = "Previous file" },
|
||||
{ on = "<Down>", run = "arrow next", desc = "Next file" },
|
||||
|
||||
{ on = [ "g", "g" ], run = "arrow top", desc = "Go to top" },
|
||||
{ on = "G", run = "arrow bot", desc = "Go to bottom" },
|
||||
{ on = "<Home>", run = "arrow top", desc = "Go to top" },
|
||||
{ on = "<End>", run = "arrow bot", desc = "Go to bottom" },
|
||||
|
||||
{ on = "<C-u>", run = "arrow -50%", desc = "Move cursor up half page" },
|
||||
{ on = "<C-d>", run = "arrow 50%", desc = "Move cursor down half page" },
|
||||
{ on = "<C-b>", run = "arrow -100%", desc = "Move cursor up one page" },
|
||||
|
|
@ -30,9 +34,6 @@ keymap = [
|
|||
{ on = "<PageUp>", run = "arrow -100%", desc = "Move cursor up one page" },
|
||||
{ on = "<PageDown>", run = "arrow 100%", desc = "Move cursor down one page" },
|
||||
|
||||
{ on = [ "g", "g" ], run = "arrow top", desc = "Go to top" },
|
||||
{ on = "G", run = "arrow bot", desc = "Go to bottom" },
|
||||
|
||||
# Navigation
|
||||
{ on = "h", run = "leave", desc = "Back to the parent directory" },
|
||||
{ on = "l", run = "enter", desc = "Enter the child directory" },
|
||||
|
|
@ -360,9 +361,10 @@ keymap = [
|
|||
[help]
|
||||
|
||||
keymap = [
|
||||
{ on = "<Esc>", run = "escape", desc = "Clear the filter, or hide the help" },
|
||||
{ on = "<C-[>", run = "escape", desc = "Clear the filter, or hide the help" },
|
||||
{ on = "<C-c>", run = "close", desc = "Hide the help" },
|
||||
{ on = "<Esc>", run = "escape", desc = "Enter normal mode, or hide help menu" },
|
||||
{ on = "<C-[>", run = "escape", desc = "Enter normal mode, or hide help menu" },
|
||||
{ on = "<C-c>", run = "close", desc = "Close help menu" },
|
||||
{ on = "<Enter>", run = "close --submit", desc = "Close help menu and run selected action(s)" },
|
||||
|
||||
# Navigation
|
||||
{ on = "k", run = "arrow prev", desc = "Previous line" },
|
||||
|
|
@ -371,6 +373,6 @@ keymap = [
|
|||
{ on = "<Up>", run = "arrow prev", desc = "Previous line" },
|
||||
{ on = "<Down>", run = "arrow next", desc = "Next line" },
|
||||
|
||||
# Filtering
|
||||
{ on = "f", run = "filter", desc = "Filter help items" },
|
||||
{ on = "<C-p>", run = "arrow prev", desc = "Previous line" },
|
||||
{ on = "<C-n>", run = "arrow next", desc = "Next line" },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -226,11 +226,10 @@ hovered = { fg = "magenta", bold = true }
|
|||
# : Help menu {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "cyan" }
|
||||
run = { fg = "magenta" }
|
||||
desc = {}
|
||||
border = { fg = "blue" }
|
||||
chord = { fg = "cyan" }
|
||||
action = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "black", bg = "white" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,11 +226,10 @@ hovered = { fg = "magenta", bold = true }
|
|||
# : Help menu {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "cyan" }
|
||||
run = { fg = "magenta" }
|
||||
desc = {}
|
||||
border = { fg = "blue" }
|
||||
chord = { fg = "cyan" }
|
||||
action = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "black", bg = "white" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,15 @@ use crate::THEME;
|
|||
inventory::submit! {
|
||||
FileInventory {
|
||||
register: |registry| {
|
||||
registry.add_method("icon", |_, me, ()| {
|
||||
registry.add_method("icon", |lua, me, ()| {
|
||||
yazi_binding::deprecate!(
|
||||
lua,
|
||||
"{}: `File:icon()` is deprecated, use `th.icon:match(file)` instead"
|
||||
);
|
||||
// TODO: use a cache
|
||||
Ok(THEME.icon.matches(me, false))
|
||||
});
|
||||
}
|
||||
},
|
||||
borrow: |_, _| Err(mlua::Error::UserDataTypeMismatch),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, de};
|
|||
use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
|
||||
use yazi_binding::Iter;
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_shared::{Layer, event::{Actions, deserialize_actions}, id::Id};
|
||||
use yazi_shared::{event::{Actions, deserialize_actions}, id::Id};
|
||||
|
||||
use super::{Key, ids::chord_id};
|
||||
use crate::{Mixable, Platform, keymap::{ChordArc, Chords}};
|
||||
|
|
@ -14,12 +14,12 @@ use crate::{Mixable, Platform, keymap::{ChordArc, Chords}};
|
|||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Default, Deserialize, DeserializeOver2)]
|
||||
pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
|
||||
pub struct Chord {
|
||||
#[serde(skip, default = "chord_id")]
|
||||
pub id: Id,
|
||||
#[serde(deserialize_with = "deserialize_on")]
|
||||
pub on: Vec<Key>,
|
||||
#[serde(deserialize_with = "deserialize_actions::<L, _>")]
|
||||
#[serde(deserialize_with = "deserialize_actions")]
|
||||
pub run: Actions,
|
||||
#[serde(default)]
|
||||
pub desc: String,
|
||||
|
|
@ -27,7 +27,7 @@ pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
|
|||
pub r#for: Platform,
|
||||
}
|
||||
|
||||
impl<const L: u8> Clone for Chord<L> {
|
||||
impl Clone for Chord {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: chord_id(),
|
||||
|
|
@ -39,17 +39,21 @@ impl<const L: u8> Clone for Chord<L> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> PartialEq for Chord<L> {
|
||||
impl AsRef<Self> for Chord {
|
||||
fn as_ref(&self) -> &Self { self }
|
||||
}
|
||||
|
||||
impl PartialEq for Chord {
|
||||
fn eq(&self, other: &Self) -> bool { self.on == other.on }
|
||||
}
|
||||
|
||||
impl<const L: u8> Eq for Chord<L> {}
|
||||
impl Eq for Chord {}
|
||||
|
||||
impl<const L: u8> Hash for Chord<L> {
|
||||
impl Hash for Chord {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.on.hash(state) }
|
||||
}
|
||||
|
||||
impl<const L: u8> Chord<L> {
|
||||
impl Chord {
|
||||
pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() }
|
||||
|
||||
pub fn run(&self) -> String {
|
||||
|
|
@ -66,20 +70,13 @@ impl<const L: u8> Chord<L> {
|
|||
|
||||
pub fn desc_or_run(&self) -> Cow<'_, str> { self.desc().unwrap_or_else(|| self.run().into()) }
|
||||
|
||||
pub fn contains(&self, s: &str) -> bool {
|
||||
let s = s.to_lowercase();
|
||||
self.desc().map(|d| d.to_lowercase().contains(&s)) == Some(true)
|
||||
|| self.run().to_lowercase().contains(&s)
|
||||
|| self.on().to_lowercase().contains(&s)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn noop(&self) -> bool {
|
||||
self.run.len() == 1 && self.run[0].name == "noop" && self.run[0].args.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Mixable for Chord<L> {
|
||||
impl Mixable for Chord {
|
||||
fn filter(&self) -> bool { self.r#for.matches() && !self.noop() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +1,40 @@
|
|||
use std::{ops::{Deref, DerefMut}, sync::Arc};
|
||||
|
||||
use mlua::{UserData, UserDataFields, Value};
|
||||
use mlua::{FromLua, Lua, UserData, UserDataFields, Value};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::FromLuaOwned;
|
||||
use yazi_shared::{Layer, event::ActionCow};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::{Mixable, keymap::Chord};
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, FromLuaOwned)]
|
||||
pub struct ChordArc<const L: u8 = { Layer::Null as u8 }>(Arc<Chord<L>>);
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct ChordArc(Arc<Chord>);
|
||||
|
||||
impl<const L: u8> Deref for ChordArc<L> {
|
||||
type Target = Arc<Chord<L>>;
|
||||
impl Deref for ChordArc {
|
||||
type Target = Arc<Chord>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> DerefMut for ChordArc<L> {
|
||||
impl DerefMut for ChordArc {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&Self> for ChordArc<L> {
|
||||
impl AsRef<Chord> for ChordArc {
|
||||
fn as_ref(&self) -> &Chord { self }
|
||||
}
|
||||
|
||||
impl From<&Self> for ChordArc {
|
||||
fn from(value: &Self) -> Self { value.clone() }
|
||||
}
|
||||
|
||||
impl<const L: u8, const M: u8> From<Chord<L>> for ChordArc<M> {
|
||||
fn from(value: Chord<L>) -> Self { ChordArc(Arc::new(value)).into_erased() }
|
||||
impl From<Chord> for ChordArc {
|
||||
fn from(value: Chord) -> Self { Self(Arc::new(value)) }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<ChordArc<L>> for Chord<L> {
|
||||
fn from(value: ChordArc<L>) -> Self {
|
||||
impl From<ChordArc> for Chord {
|
||||
fn from(value: ChordArc) -> Self {
|
||||
match Arc::try_unwrap(value.0) {
|
||||
Ok(c) => c,
|
||||
Err(arc) => Self::clone(&arc),
|
||||
|
|
@ -39,60 +42,53 @@ impl<const L: u8> From<ChordArc<L>> for Chord<L> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&ChordArc<L>> for Chord<L> {
|
||||
fn from(value: &ChordArc<L>) -> Self { Self::clone(value) }
|
||||
impl From<&ChordArc> for Chord {
|
||||
fn from(value: &ChordArc) -> Self { Self::clone(value) }
|
||||
}
|
||||
|
||||
impl TryFrom<(Value, Layer)> for ChordArc {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from((value, layer): (Value, Layer)) -> Result<Self, Self::Error> {
|
||||
use Chord as C;
|
||||
use Layer as L;
|
||||
|
||||
let de = mlua::serde::Deserializer::new(value);
|
||||
Ok(match layer {
|
||||
L::Null => C::<{ L::Null as u8 }>::deserialize(de)?.into(),
|
||||
L::App => C::<{ L::App as u8 }>::deserialize(de)?.into(),
|
||||
L::Mgr => C::<{ L::Mgr as u8 }>::deserialize(de)?.into(),
|
||||
L::Tasks => C::<{ L::Tasks as u8 }>::deserialize(de)?.into(),
|
||||
L::Spot => C::<{ L::Spot as u8 }>::deserialize(de)?.into(),
|
||||
L::Pick => C::<{ L::Pick as u8 }>::deserialize(de)?.into(),
|
||||
L::Input => C::<{ L::Input as u8 }>::deserialize(de)?.into(),
|
||||
L::Confirm => C::<{ L::Confirm as u8 }>::deserialize(de)?.into(),
|
||||
L::Help => C::<{ L::Help as u8 }>::deserialize(de)?.into(),
|
||||
L::Cmp => C::<{ L::Cmp as u8 }>::deserialize(de)?.into(),
|
||||
L::Which => C::<{ L::Which as u8 }>::deserialize(de)?.into(),
|
||||
L::Notify => C::<{ L::Notify as u8 }>::deserialize(de)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> ChordArc<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> &ChordArc<M> {
|
||||
unsafe { &*(self as *const Self as *const ChordArc<M>) }
|
||||
impl ChordArc {
|
||||
pub fn to_seq(&self, layer: Layer) -> Vec<ActionCow> {
|
||||
self
|
||||
.run
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.map(|mut a| {
|
||||
a.layer = a.layer.or(layer);
|
||||
a.into()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn into_erased<const M: u8>(self) -> ChordArc<M> {
|
||||
ChordArc(unsafe { Arc::from_raw(Arc::into_raw(self.0) as *const Chord<M>) })
|
||||
}
|
||||
|
||||
pub fn to_seq(&self) -> Vec<ActionCow> {
|
||||
self.run.iter().rev().cloned().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn into_seq(self) -> Vec<ActionCow> {
|
||||
pub fn into_seq(self, layer: Layer) -> Vec<ActionCow> {
|
||||
match Arc::try_unwrap(self.0) {
|
||||
Ok(c) => c.run.into_iter().rev().map(Into::into).collect(),
|
||||
Err(arc) => Self(arc).to_seq(),
|
||||
Ok(c) => c
|
||||
.run
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|mut a| {
|
||||
a.layer = a.layer.or(layer);
|
||||
a.into()
|
||||
})
|
||||
.collect(),
|
||||
Err(arc) => Self(arc).to_seq(layer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Mixable for ChordArc<L> {
|
||||
impl Mixable for ChordArc {
|
||||
fn filter(&self) -> bool { self.0.filter() }
|
||||
}
|
||||
|
||||
impl FromLua for ChordArc {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
Ok(match value {
|
||||
Value::UserData(ud) => ud.take()?,
|
||||
_ => Chord::deserialize(mlua::serde::Deserializer::new(value))?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for ChordArc {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, me| Ok(me.id));
|
||||
|
|
|
|||
|
|
@ -1,53 +1,36 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use mlua::{ExternalError, ExternalResult, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{ExternalError, ExternalResult, MetaMethod, Table, UserData, UserDataMethods};
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{Layer, event::Actions};
|
||||
use yazi_shared::event::Actions;
|
||||
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, mlua::DeserializeOverLua, vec::{IndexAtError, VecExt}};
|
||||
|
||||
use crate::keymap::{Chord, ChordArc, ChordIter, ChordMatcher};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Chords<const L: u8 = { Layer::Null as u8 }> {
|
||||
pub chords: ArcSwap<Vec<ChordArc<L>>>,
|
||||
#[serde(skip, default = "layer_default::<L>")]
|
||||
pub layer: Layer,
|
||||
pub struct Chords(ArcSwap<Vec<ChordArc>>);
|
||||
|
||||
impl Deref for Chords {
|
||||
type Target = ArcSwap<Vec<ChordArc>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> Default for Chords<L> {
|
||||
fn default() -> Self {
|
||||
Self { chords: ArcSwap::default(), layer: Layer::from_repr(L).unwrap_or_default() }
|
||||
}
|
||||
impl From<Vec<ChordArc>> for Chords {
|
||||
fn from(inner: Vec<ChordArc>) -> Self { Self(inner.into_pointee()) }
|
||||
}
|
||||
|
||||
impl<const L: u8> Deref for Chords<L> {
|
||||
type Target = ArcSwap<Vec<ChordArc<L>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.chords }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<Vec<ChordArc<L>>> for Chords<L> {
|
||||
fn from(inner: Vec<ChordArc<L>>) -> Self {
|
||||
Self { chords: inner.into_pointee(), layer: Layer::from_repr(L).unwrap_or_default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Chords<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> Arc<Vec<ChordArc<M>>> {
|
||||
let chords = self.chords.load_full();
|
||||
unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec<ChordArc<M>>) }
|
||||
}
|
||||
|
||||
impl Chords {
|
||||
pub fn insert(&self, index: isize, rule: ChordArc) -> Result<(), IndexAtError> {
|
||||
self.chords.try_rcu(|rules| {
|
||||
self.0.try_rcu(|rules| {
|
||||
let (before, after) = rules.split_at(rules.index_at(index)?);
|
||||
Ok(
|
||||
before
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain([rule.as_erased().clone()])
|
||||
.chain([rule.clone()])
|
||||
.chain(after.iter().cloned())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
|
|
@ -57,9 +40,9 @@ impl<const L: u8> Chords<L> {
|
|||
}
|
||||
|
||||
pub fn remove(&self, matcher: ChordMatcher) {
|
||||
self.chords.rcu(|chords| {
|
||||
self.0.rcu(|chords| {
|
||||
let mut next = Vec::clone(chords);
|
||||
next.retain(|arc| !matcher.matches(arc.as_erased()));
|
||||
next.retain(|arc| !matcher.matches(arc));
|
||||
next
|
||||
});
|
||||
}
|
||||
|
|
@ -69,11 +52,11 @@ impl<const L: u8> Chords<L> {
|
|||
matcher: ChordMatcher,
|
||||
f: impl Fn(Chord) -> Result<Chord, E>,
|
||||
) -> Result<(), E> {
|
||||
self.chords.try_rcu(|rules| {
|
||||
self.0.try_rcu(|rules| {
|
||||
let mut next = Vec::clone(rules);
|
||||
for arc in &mut next {
|
||||
if matcher.matches(arc.as_erased()) {
|
||||
*arc = f(Chord::clone(arc.as_erased()))?.into();
|
||||
if matcher.matches(arc) {
|
||||
*arc = f(Chord::clone(arc))?.into();
|
||||
}
|
||||
}
|
||||
Ok(Arc::new(next))
|
||||
|
|
@ -82,8 +65,8 @@ impl<const L: u8> Chords<L> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<ChordArc<L>> {
|
||||
Arc::try_unwrap(self.chords.into_inner()).expect("unique chords arc")
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<ChordArc> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique chords arc")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,21 +74,19 @@ impl UserData for &Chords {
|
|||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("match", |_, &me, matcher: Option<ChordMatcher>| {
|
||||
Ok(match matcher {
|
||||
Some(matcher) => ChordIter { chords: me.as_erased(), matcher, ..Default::default() },
|
||||
Some(matcher) => ChordIter { chords: me.load_full(), matcher, ..Default::default() },
|
||||
None => me.into(),
|
||||
})
|
||||
});
|
||||
|
||||
methods.add_method("insert", |_, me, (index, value): (isize, Value)| {
|
||||
methods.add_method("insert", |_, me, (index, chord): (isize, ChordArc)| {
|
||||
let index = match index {
|
||||
1.. => index - 1,
|
||||
0 => return Err("index must be 1-based or negative".into_lua_err()),
|
||||
_ => index,
|
||||
};
|
||||
|
||||
let chord = ChordArc::try_from((value, me.layer))?;
|
||||
me.insert(index, chord.clone()).into_lua_err()?;
|
||||
|
||||
Ok(chord)
|
||||
});
|
||||
|
||||
|
|
@ -118,7 +99,7 @@ impl UserData for &Chords {
|
|||
let mut run: Option<Actions> = table.raw_get("run")?;
|
||||
if let Some(run) = &mut run {
|
||||
table.raw_remove("run")?;
|
||||
run.set(me.layer, yazi_shared::Source::Key);
|
||||
run.set_source(yazi_shared::Source::Key);
|
||||
}
|
||||
|
||||
me.update(matcher, |mut chord| {
|
||||
|
|
@ -133,5 +114,3 @@ impl UserData for &Chords {
|
|||
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.load().len()));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn layer_default<const L: u8>() -> Layer { Layer::from_repr(L).unwrap_or_default() }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -11,20 +11,20 @@ use crate::keymap::ChordArc;
|
|||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1)]
|
||||
pub struct Keymap {
|
||||
pub mgr: KeymapSection<{ Layer::Mgr as u8 }>,
|
||||
pub tasks: KeymapSection<{ Layer::Tasks as u8 }>,
|
||||
pub spot: KeymapSection<{ Layer::Spot as u8 }>,
|
||||
pub pick: KeymapSection<{ Layer::Pick as u8 }>,
|
||||
pub input: KeymapSection<{ Layer::Input as u8 }>,
|
||||
pub confirm: KeymapSection<{ Layer::Confirm as u8 }>,
|
||||
pub help: KeymapSection<{ Layer::Help as u8 }>,
|
||||
pub cmp: KeymapSection<{ Layer::Cmp as u8 }>,
|
||||
pub mgr: KeymapSection,
|
||||
pub tasks: KeymapSection,
|
||||
pub spot: KeymapSection,
|
||||
pub pick: KeymapSection,
|
||||
pub input: KeymapSection,
|
||||
pub confirm: KeymapSection,
|
||||
pub help: KeymapSection,
|
||||
pub cmp: KeymapSection,
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn chords(&self, layer: Layer) -> Arc<Vec<ChordArc>> {
|
||||
match self.section(layer) {
|
||||
Some(s) => s.deref().as_erased(),
|
||||
Some(s) => s.load_full(),
|
||||
None => Arc::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
|
@ -34,14 +34,14 @@ impl Keymap {
|
|||
|
||||
Some(match layer {
|
||||
L::Null | L::App => None?,
|
||||
L::Mgr => self.mgr.as_erased(),
|
||||
L::Tasks => self.tasks.as_erased(),
|
||||
L::Spot => self.spot.as_erased(),
|
||||
L::Pick => self.pick.as_erased(),
|
||||
L::Input => self.input.as_erased(),
|
||||
L::Confirm => self.confirm.as_erased(),
|
||||
L::Help => self.help.as_erased(),
|
||||
L::Cmp => self.cmp.as_erased(),
|
||||
L::Mgr => &self.mgr,
|
||||
L::Tasks => &self.tasks,
|
||||
L::Spot => &self.spot,
|
||||
L::Pick => &self.pick,
|
||||
L::Input => &self.input,
|
||||
L::Confirm => &self.confirm,
|
||||
L::Help => &self.help,
|
||||
L::Cmp => &self.cmp,
|
||||
L::Which => None?,
|
||||
L::Notify => None?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,53 +4,45 @@ use hashbrown::HashSet;
|
|||
use mlua::{UserData, UserDataFields};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_shared::Layer;
|
||||
use yazi_shim::{mlua::UserDataFieldsExt, toml::DeserializeOverHook};
|
||||
|
||||
use super::{Key, Chord, Chords, chords::layer_default};
|
||||
use super::{Chord, Chords, Key};
|
||||
use crate::{keymap::ChordArc, mix};
|
||||
|
||||
#[derive(Default, Deserialize, DeserializeOver2)]
|
||||
pub struct KeymapSection<const L: u8 = { Layer::Null as u8 }> {
|
||||
keymap: Chords<L>,
|
||||
pub struct KeymapSection {
|
||||
keymap: Chords,
|
||||
#[serde(default)]
|
||||
prepend_keymap: Vec<Chord<L>>,
|
||||
prepend_keymap: Vec<Chord>,
|
||||
#[serde(default)]
|
||||
append_keymap: Vec<Chord<L>>,
|
||||
#[serde(default = "layer_default::<L>")]
|
||||
layer: Layer,
|
||||
append_keymap: Vec<Chord>,
|
||||
}
|
||||
|
||||
impl<const L: u8> Deref for KeymapSection<L> {
|
||||
type Target = Chords<L>;
|
||||
impl Deref for KeymapSection {
|
||||
type Target = Chords;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.keymap }
|
||||
}
|
||||
|
||||
impl<const L: u8> KeymapSection<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> &KeymapSection<M> {
|
||||
unsafe { &*(self as *const Self as *const KeymapSection<M>) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> DeserializeOverHook for KeymapSection<L> {
|
||||
impl DeserializeOverHook for KeymapSection {
|
||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||
#[inline]
|
||||
fn on<const L: u8>(Chord { on, .. }: &Chord<L>) -> [Key; 2] {
|
||||
[on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()]
|
||||
fn on<T: AsRef<Chord>>(chord: T) -> [Key; 2] {
|
||||
let c = chord.as_ref();
|
||||
[c.on.first().copied().unwrap_or_default(), c.on.get(1).copied().unwrap_or_default()]
|
||||
}
|
||||
|
||||
let keymap = self.keymap.unwrap_unchecked();
|
||||
let a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect();
|
||||
let b_seen: HashSet<_> = keymap.iter().map(|c| on(c)).collect();
|
||||
let b_seen: HashSet<_> = keymap.iter().map(on).collect();
|
||||
|
||||
let keymap: Vec<ChordArc<L>> = mix(
|
||||
let keymap: Vec<ChordArc> = mix(
|
||||
self.prepend_keymap,
|
||||
keymap.into_iter().filter(|v| !a_seen.contains(&on(v))),
|
||||
self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))),
|
||||
);
|
||||
|
||||
Ok(Self { keymap: keymap.into(), layer: self.layer, ..Default::default() })
|
||||
Ok(Self { keymap: keymap.into(), ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ yazi_macro::mod_flat!(icon inject layout mixing pattern platform preset priority
|
|||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use yazi_macro::writef;
|
||||
use yazi_shim::{cell::{RoCell, SyncCell}, toml::{DeserializeOver, DeserializeOverWith}};
|
||||
use yazi_tty::{TTY, sequence::SetSgr};
|
||||
|
||||
|
|
@ -67,14 +68,14 @@ pub fn build_flavor(light: bool, merge: bool) -> anyhow::Result<theme::Theme> {
|
|||
}
|
||||
|
||||
fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {
|
||||
let stdout = &mut *TTY.lockout();
|
||||
let mut stdout = &mut *TTY.lockout();
|
||||
|
||||
writeln!(stdout, "{e}")?;
|
||||
if let Some(src) = e.source() {
|
||||
writeln!(stdout, "\nCaused by:\n{src}")?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
writef!(
|
||||
stdout,
|
||||
"{}{}Press <Enter> to continue with preset settings...{}",
|
||||
SetSgr::Reverse,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use anyhow::bail;
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_binding::deprecate;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(try_from = "[u16; 3]")]
|
||||
|
|
@ -29,3 +31,83 @@ impl TryFrom<[u16; 3]> for MgrRatio {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove ---
|
||||
impl MgrRatio {
|
||||
fn at(self, idx: usize) -> Option<u16> {
|
||||
match idx {
|
||||
0 => Some(self.parent),
|
||||
1 => Some(self.current),
|
||||
2 => Some(self.preview),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for MgrRatio {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
Value::Table(t) => from_table(&t, lua),
|
||||
_ => Err("expected a table".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_table(t: &Table, lua: &Lua) -> mlua::Result<MgrRatio> {
|
||||
if t.contains_key("parent")? || t.contains_key("current")? || t.contains_key("preview")? {
|
||||
deprecate!(
|
||||
lua,
|
||||
"{}: the table form of `rt.mgr.ratio` is deprecated, use an array `[parent, current, preview]` instead"
|
||||
);
|
||||
MgrRatio::try_from([t.get("parent")?, t.get("current")?, t.get("preview")?])
|
||||
.map_err(|e| e.into_lua_err())
|
||||
} else {
|
||||
MgrRatio::try_from([t.get(1)?, t.get(2)?, t.get(3)?]).map_err(|e| e.into_lua_err())
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for MgrRatio {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(3usize));
|
||||
|
||||
methods.add_meta_method(MetaMethod::Index, |lua, me, key: Value| {
|
||||
match key {
|
||||
Value::Integer(i) if (1..=3).contains(&i) => me.at((i - 1) as usize).into_lua(lua),
|
||||
Value::String(s) => match &*s.as_bytes() {
|
||||
b"parent" => {
|
||||
deprecate!(
|
||||
lua,
|
||||
"{}: `rt.mgr.ratio.parent` is deprecated, use `rt.mgr.ratio[1]` instead"
|
||||
);
|
||||
me.parent.into_lua(lua)
|
||||
}
|
||||
b"current" => {
|
||||
deprecate!(
|
||||
lua,
|
||||
"{}: `rt.mgr.ratio.current` is deprecated, use `rt.mgr.ratio[2]` instead"
|
||||
);
|
||||
me.current.into_lua(lua)
|
||||
}
|
||||
b"preview" => {
|
||||
deprecate!(
|
||||
lua,
|
||||
"{}: `rt.mgr.ratio.preview` is deprecated, use `rt.mgr.ratio[3]` instead"
|
||||
);
|
||||
me.preview.into_lua(lua)
|
||||
}
|
||||
b"all" => {
|
||||
deprecate!(
|
||||
lua,
|
||||
"{}: `rt.mgr.ratio.all` is deprecated, use `rt.mgr.ratio[1] + rt.mgr.ratio[2] + rt.mgr.ratio[3]` instead"
|
||||
);
|
||||
me.all.into_lua(lua)
|
||||
}
|
||||
_ => Ok(Value::Nil),
|
||||
},
|
||||
_ => Ok(Value::Nil),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// --- TODO: remove
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl TryFrom<Table> for OpenRuleMatcher<'static> {
|
|||
Ok(Self {
|
||||
rules: YAZI.open.load_full(),
|
||||
id,
|
||||
file: file.map(|f| f.clone().into()),
|
||||
file: file.map(TryInto::try_into).transpose()?,
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl TryFrom<Table> for FetcherMatcher<'static> {
|
|||
Ok(Self {
|
||||
fetchers: YAZI.plugin.fetchers.load_full(),
|
||||
id,
|
||||
file: file.map(|f| f.clone().into()),
|
||||
file: file.map(TryInto::try_into).transpose()?,
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ impl TryFrom<Table> for PreloaderMatcher<'static> {
|
|||
Ok(Self {
|
||||
preloaders: YAZI.plugin.preloaders.load_full(),
|
||||
id,
|
||||
file: file.map(|f| f.clone().into()),
|
||||
file: file.map(TryInto::try_into).transpose()?,
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ impl TryFrom<Table> for PreviewerMatcher<'static> {
|
|||
Ok(Self {
|
||||
previewers: YAZI.plugin.previewers.load_full(),
|
||||
id,
|
||||
file: file.map(|f| f.clone().into()),
|
||||
file: file.map(TryInto::try_into).transpose()?,
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ impl TryFrom<Table> for SpotterMatcher<'static> {
|
|||
Ok(Self {
|
||||
spotters: YAZI.plugin.spotters.load_full(),
|
||||
id,
|
||||
file: file.map(|f| f.clone().into()),
|
||||
file: file.map(TryInto::try_into).transpose()?,
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
})
|
||||
|
|
|
|||
9
yazi-config/src/popup/help.rs
Normal file
9
yazi-config/src/popup/help.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use yazi_binding::position::{Offset, Origin, Position};
|
||||
|
||||
pub struct Help;
|
||||
|
||||
impl Help {
|
||||
pub fn position() -> Position {
|
||||
Position::new(Origin::Center, Offset { x: 0, y: 0, width: 80, height: 25 })
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ pub struct Input {
|
|||
impl Input {
|
||||
pub fn cd(&self, cwd: Url) -> InputOpt {
|
||||
InputOpt {
|
||||
name: "cd".to_owned(),
|
||||
title: self.cd_title.clone(),
|
||||
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
||||
position: Position::new(self.cd_origin, self.cd_offset),
|
||||
|
|
@ -57,14 +58,16 @@ impl Input {
|
|||
|
||||
pub fn create(&self, dir: bool) -> InputOpt {
|
||||
InputOpt {
|
||||
name: format!("create-{}", if dir { "dir" } else { "file" }),
|
||||
title: self.create_title[dir as usize].clone(),
|
||||
position: Position::new(self.create_origin, self.create_offset),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename(&self) -> InputOpt {
|
||||
pub fn rename(&self, is_dir: bool) -> InputOpt {
|
||||
InputOpt {
|
||||
name: format!("rename-{}", if is_dir { "dir" } else { "file" }),
|
||||
title: self.rename_title.clone(),
|
||||
position: Position::new(self.rename_origin, self.rename_offset),
|
||||
..Default::default()
|
||||
|
|
@ -73,6 +76,7 @@ impl Input {
|
|||
|
||||
pub fn filter(&self) -> InputOpt {
|
||||
InputOpt {
|
||||
name: "filter".to_owned(),
|
||||
title: self.filter_title.clone(),
|
||||
position: Position::new(self.filter_origin, self.filter_offset),
|
||||
realtime: true,
|
||||
|
|
@ -82,6 +86,7 @@ impl Input {
|
|||
|
||||
pub fn find(&self, prev: bool) -> InputOpt {
|
||||
InputOpt {
|
||||
name: "find".to_owned(),
|
||||
title: self.find_title[prev as usize].clone(),
|
||||
position: Position::new(self.find_origin, self.find_offset),
|
||||
realtime: true,
|
||||
|
|
@ -91,6 +96,7 @@ impl Input {
|
|||
|
||||
pub fn search(&self, name: &str) -> InputOpt {
|
||||
InputOpt {
|
||||
name: "search".to_owned(),
|
||||
title: self.search_title.replace("{n}", name),
|
||||
position: Position::new(self.search_origin, self.search_offset),
|
||||
..Default::default()
|
||||
|
|
@ -99,6 +105,7 @@ impl Input {
|
|||
|
||||
pub fn shell(&self, block: bool) -> InputOpt {
|
||||
InputOpt {
|
||||
name: "shell".to_owned(),
|
||||
title: self.shell_title[block as usize].clone(),
|
||||
position: Position::new(self.shell_origin, self.shell_offset),
|
||||
..Default::default()
|
||||
|
|
@ -107,6 +114,7 @@ impl Input {
|
|||
|
||||
pub fn tab_rename(&self) -> InputOpt {
|
||||
InputOpt {
|
||||
name: "tab-rename".to_owned(),
|
||||
title: "Rename tab:".to_owned(),
|
||||
position: Position::new(Origin::TopCenter, Offset {
|
||||
x: 0,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(confirm input options pick);
|
||||
yazi_macro::mod_flat!(confirm help input options pick);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use ratatui_core::text::{Line, Text};
|
||||
use std::slice;
|
||||
|
||||
use ratatui_core::text::{Line, Span, Text};
|
||||
use ratatui_widgets::paragraph::{Paragraph, Wrap};
|
||||
use yazi_binding::position::Position;
|
||||
use yazi_fs::file::File;
|
||||
use yazi_macro::impl_data_any;
|
||||
use yazi_shared::{strand::ToStrand, url::UrlBuf};
|
||||
use yazi_shared::strand::ToStrand;
|
||||
|
||||
use crate::YAZI;
|
||||
use crate::{THEME, YAZI};
|
||||
|
||||
// --- PickCfg
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
|
@ -42,30 +45,30 @@ impl ConfirmCfg {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn trash(urls: &[yazi_shared::url::UrlBuf]) -> Self {
|
||||
pub fn trash(files: &[File]) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.trash_title, urls.len()),
|
||||
Self::replace_number(&YAZI.confirm.trash_title, files.len()),
|
||||
YAZI.confirm.trash_position(),
|
||||
None,
|
||||
Self::truncate_list(urls, urls.len(), 100),
|
||||
Self::truncate_files(files, 100),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete(urls: &[yazi_shared::url::UrlBuf]) -> Self {
|
||||
pub fn delete(files: &[File]) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.delete_title, urls.len()),
|
||||
Self::replace_number(&YAZI.confirm.delete_title, files.len()),
|
||||
YAZI.confirm.delete_position(),
|
||||
None,
|
||||
Self::truncate_list(urls, urls.len(), 100),
|
||||
Self::truncate_files(files, 100),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn overwrite(url: &UrlBuf) -> Self {
|
||||
pub fn overwrite(file: &File) -> Self {
|
||||
Self::new(
|
||||
YAZI.confirm.overwrite_title.clone(),
|
||||
YAZI.confirm.overwrite_position(),
|
||||
Some(Text::raw(&YAZI.confirm.overwrite_body)),
|
||||
Some(url.to_strand().into_string_lossy().into()),
|
||||
Self::truncate_files(slice::from_ref(file), 1),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +77,7 @@ impl ConfirmCfg {
|
|||
Self::replace_number(&YAZI.confirm.quit_title, len),
|
||||
YAZI.confirm.quit_position(),
|
||||
Some(Text::raw(&YAZI.confirm.quit_body)),
|
||||
Self::truncate_list(names, len, 10),
|
||||
Self::truncate_lines(names, len, 10),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +85,7 @@ impl ConfirmCfg {
|
|||
tpl.replace("{n}", &n.to_string()).replace("{s}", if n > 1 { "s" } else { "" })
|
||||
}
|
||||
|
||||
fn truncate_list<I>(it: I, len: usize, max: usize) -> Option<Text<'static>>
|
||||
fn truncate_lines<I>(it: I, len: usize, max: usize) -> Option<Text<'static>>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: ToStrand,
|
||||
|
|
@ -97,4 +100,22 @@ impl ConfirmCfg {
|
|||
}
|
||||
Some(Text::from_iter(lines))
|
||||
}
|
||||
|
||||
fn truncate_files(files: &[File], max: usize) -> Option<Text<'static>> {
|
||||
let mut lines = Vec::with_capacity(files.len().min(max + 1));
|
||||
for (i, f) in files.iter().enumerate() {
|
||||
if i >= max {
|
||||
lines.push(Line::raw(format!("... and {} more", files.len() - max)));
|
||||
break;
|
||||
}
|
||||
|
||||
lines.push(Line::default());
|
||||
if let Some(icon) = THEME.icon.matches(f, false) {
|
||||
lines[i].push_span(Span::styled(icon.text, icon.style));
|
||||
lines[i].push_span(" ");
|
||||
}
|
||||
lines[i].push_span(f.url.to_strand().into_string_lossy());
|
||||
}
|
||||
Some(lines.into())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use yazi_fs::{Xdg, ok_or_not_found, path::sanitize_path};
|
|||
use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
|
||||
|
||||
use super::{Custom, Filetype, Flavor, Icon};
|
||||
use crate::YAZI;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)]
|
||||
pub struct Theme {
|
||||
|
|
@ -232,7 +233,11 @@ pub struct Input {
|
|||
|
||||
impl From<&Input> for yazi_widgets::input::InputStyles {
|
||||
fn from(input: &Input) -> Self {
|
||||
Self { normal: Some(input.value.get().into()), selected: Some(input.selected.get().into()) }
|
||||
Self {
|
||||
normal: Some(input.value.get().into()),
|
||||
selected: Some(input.selected.get().into()),
|
||||
blink: Some(YAZI.input.cursor_blink),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,12 +264,10 @@ pub struct Tasks {
|
|||
// --- Help
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
|
||||
pub struct Help {
|
||||
pub on: SyncCell<StyleFlat>,
|
||||
pub run: SyncCell<StyleFlat>,
|
||||
pub desc: SyncCell<StyleFlat>,
|
||||
|
||||
pub border: SyncCell<StyleFlat>,
|
||||
pub chord: SyncCell<StyleFlat>,
|
||||
pub action: SyncCell<StyleFlat>,
|
||||
pub hovered: SyncCell<StyleFlat>,
|
||||
pub footer: SyncCell<StyleFlat>,
|
||||
}
|
||||
|
||||
fn deserialize_syntect_theme<'de, D>(deserializer: D) -> Result<ArcSwap<PathBuf>, D::Error>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ impl Core {
|
|||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<(Position, SetCursorStyle)> {
|
||||
if let Some(cursor) = self.help.cursor() {
|
||||
let Rect { x, y, .. } = self.mgr.area(self.help.position).padding(self.help.padding());
|
||||
return Some((Position { x: x + cursor, y }, self.help.cursor_shape()?));
|
||||
}
|
||||
|
||||
if let Some(guard) = self.input.lock() {
|
||||
let Rect { x, y, .. } = match &guard {
|
||||
InputGuard::Main(_) => self.mgr.area(self.input.position()?).padding(self.input.padding()),
|
||||
|
|
@ -41,10 +46,6 @@ impl Core {
|
|||
return Some((Position { x: x + guard.cursor(), y }, guard.cursor_shape()));
|
||||
}
|
||||
|
||||
if let Some((x, y)) = self.help.cursor() {
|
||||
return Some((Position { x, y }, self.help.cursor_shape()?));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use ratatui_widgets::block::Padding;
|
||||
use yazi_binding::position::Position;
|
||||
use yazi_config::{KEYMAP, keymap::ChordArc};
|
||||
use yazi_macro::{act, render, render_and};
|
||||
use yazi_macro::render;
|
||||
use yazi_shared::Layer;
|
||||
use yazi_term::{TERM, event::{KeyCode, KeyEvent}};
|
||||
use yazi_term::event::KeyEvent;
|
||||
use yazi_tty::sequence::SetCursorStyle;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
use crate::help::HELP_MARGIN;
|
||||
use yazi_widgets::{Scrollable, input::Input};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Help {
|
||||
pub visible: bool,
|
||||
pub layer: Layer,
|
||||
pub(super) bindings: Vec<ChordArc>,
|
||||
pub visible: bool,
|
||||
pub layer: Layer,
|
||||
pub position: Position,
|
||||
pub bindings: Vec<ChordArc>,
|
||||
|
||||
// Filter
|
||||
pub keyword: String,
|
||||
pub in_filter: Option<yazi_widgets::input::Input>,
|
||||
pub input: Input,
|
||||
pub keyword: String,
|
||||
|
||||
pub offset: usize,
|
||||
pub cursor: usize,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Help {
|
||||
pub fn r#type(&mut self, key: KeyEvent) -> Result<bool> {
|
||||
let Some(input) = &mut self.in_filter else { return Ok(false) };
|
||||
match key {
|
||||
KeyEvent { code: KeyCode::Escape, modifiers, .. } if modifiers.is_empty() => {
|
||||
self.in_filter = None;
|
||||
render!();
|
||||
}
|
||||
KeyEvent { code: KeyCode::Enter, modifiers, .. } if modifiers.is_empty() => {
|
||||
self.in_filter = None;
|
||||
return Ok(render_and!(true)); // Don't do the `filter_apply` below, since we already have the filtered results.
|
||||
}
|
||||
KeyEvent { code: KeyCode::Backspace, modifiers, .. } if modifiers.is_empty() => {
|
||||
act!(backspace, input)?;
|
||||
}
|
||||
_ => {
|
||||
input.r#type(key)?;
|
||||
}
|
||||
if !self.input.r#type(key)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.filter_apply();
|
||||
|
|
@ -48,31 +35,48 @@ impl Help {
|
|||
}
|
||||
|
||||
pub fn filter_apply(&mut self) {
|
||||
let kw = self.in_filter.as_ref().map_or("", |i| i.value());
|
||||
let kw = self.input.value();
|
||||
|
||||
if kw.is_empty() {
|
||||
self.keyword = String::new();
|
||||
self.keyword.clear();
|
||||
self.bindings = KEYMAP.chords(self.layer).iter().cloned().collect();
|
||||
} else if self.keyword != kw {
|
||||
self.keyword = kw.to_owned();
|
||||
self.bindings =
|
||||
KEYMAP.chords(self.layer).iter().filter(|&c| c.contains(kw)).cloned().collect();
|
||||
self.bindings = Self::filter_chords(&KEYMAP.chords(self.layer), kw);
|
||||
}
|
||||
|
||||
render!(self.scroll(0));
|
||||
}
|
||||
|
||||
fn filter_chords(chords: &[ChordArc], kw: &str) -> Vec<ChordArc> {
|
||||
let lowercased = kw.to_lowercase();
|
||||
let mut exact = vec![];
|
||||
let mut icase = vec![];
|
||||
let mut contains = vec![];
|
||||
let mut desc = vec![];
|
||||
|
||||
for c in chords {
|
||||
let on = c.on();
|
||||
if on.starts_with(kw) {
|
||||
exact.push(c.clone());
|
||||
} else if on.to_lowercase().starts_with(&lowercased) {
|
||||
icase.push(c.clone());
|
||||
} else if on.to_lowercase().contains(&lowercased) {
|
||||
contains.push(c.clone());
|
||||
} else if c.desc_or_run().to_lowercase().contains(&lowercased) {
|
||||
desc.push(c.clone());
|
||||
}
|
||||
}
|
||||
|
||||
exact.extend(icase);
|
||||
exact.extend(contains);
|
||||
exact.extend(desc);
|
||||
exact
|
||||
}
|
||||
}
|
||||
|
||||
impl Help {
|
||||
// --- Keyword
|
||||
pub fn keyword(&self) -> Option<String> {
|
||||
self
|
||||
.in_filter
|
||||
.as_ref()
|
||||
.map(|i| i.value())
|
||||
.or(Some(self.keyword.as_str()).filter(|&s| !s.is_empty()))
|
||||
.map(|s| format!("Filter: {s}"))
|
||||
}
|
||||
pub fn padding(&self) -> Padding { Padding::new(1, 1, 1, 1) }
|
||||
|
||||
// --- Bindings
|
||||
pub fn window(&self) -> &[ChordArc] {
|
||||
|
|
@ -81,27 +85,22 @@ impl Help {
|
|||
}
|
||||
|
||||
// --- Cursor
|
||||
pub fn cursor(&self) -> Option<(u16, u16)> {
|
||||
if !self.visible || self.in_filter.is_none() {
|
||||
return None;
|
||||
}
|
||||
if let Some(kw) = self.keyword() {
|
||||
return Some((kw.width() as u16, TERM.dimension().rows));
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn cursor(&self) -> Option<u16> { self.visible.then_some(self.input.cursor()) }
|
||||
|
||||
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
||||
|
||||
pub fn cursor_shape(&self) -> Option<SetCursorStyle> {
|
||||
Some(self.in_filter.as_ref()?.cursor_shape())
|
||||
self.visible.then_some(self.input.cursor_shape())
|
||||
}
|
||||
}
|
||||
|
||||
impl Scrollable for Help {
|
||||
fn total(&self) -> usize { self.bindings.len() }
|
||||
|
||||
fn limit(&self) -> usize { TERM.dimension().rows.saturating_sub(HELP_MARGIN) as usize }
|
||||
fn limit(&self) -> usize {
|
||||
let p = self.padding();
|
||||
self.height.saturating_sub(p.top + /* input */ 1 + /* divider */ 1 + p.bottom) as usize
|
||||
}
|
||||
|
||||
fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
yazi_macro::mod_flat!(help);
|
||||
|
||||
const HELP_MARGIN: u16 = 1;
|
||||
|
|
|
|||
|
|
@ -52,9 +52,10 @@ impl Input {
|
|||
#[derive(Default)]
|
||||
pub struct InputMain {
|
||||
inner: yazi_widgets::input::Input,
|
||||
pub visible: bool,
|
||||
pub name: String,
|
||||
pub title: String,
|
||||
pub position: Position,
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Deref for InputMain {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ impl Splatable for Mgr {
|
|||
tab
|
||||
.checked_sub(1)
|
||||
.and_then(|tab| self.tabs.get(tab))
|
||||
.map(|tab| tab.selected_or_hovered())
|
||||
.map(|tab| tab.selected_or_hovered_urls())
|
||||
.unwrap_or_else(|| Box::new(iter::empty()))
|
||||
.skip(idx.unwrap_or(0))
|
||||
.take(if idx.is_some() { 1 } else { usize::MAX })
|
||||
|
|
@ -82,5 +82,5 @@ impl Splatable for Mgr {
|
|||
.map(|h| h.url.as_url())
|
||||
}
|
||||
|
||||
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { self.yanked.iter().map(|u| u.as_url()) }
|
||||
fn yanked(&self) -> impl Iterator<Item = Url<'_>> { self.yanked.iter().map(|f| f.url.as_url()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,34 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use indexmap::IndexSet;
|
||||
use indexmap::{IndexSet, set::MutableValues};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::FilesOp;
|
||||
use yazi_fs::{FilesOp, file::FileCov};
|
||||
use yazi_macro::err;
|
||||
use yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlLike};
|
||||
use yazi_shared::url::{Url, UrlBuf, UrlCov, UrlLike};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Yanked {
|
||||
pub cut: bool,
|
||||
urls: IndexSet<UrlBufCov>,
|
||||
files: IndexSet<FileCov>,
|
||||
|
||||
version: u64,
|
||||
revision: u64,
|
||||
}
|
||||
|
||||
impl Deref for Yanked {
|
||||
type Target = IndexSet<UrlBufCov>;
|
||||
type Target = IndexSet<FileCov>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.urls }
|
||||
fn deref(&self) -> &Self::Target { &self.files }
|
||||
}
|
||||
|
||||
impl Yanked {
|
||||
pub fn new(cut: bool, urls: IndexSet<UrlBufCov>) -> Self {
|
||||
Self { cut, urls, ..Default::default() }
|
||||
pub fn new(cut: bool, files: IndexSet<FileCov>) -> Self {
|
||||
Self { cut, files, ..Default::default() }
|
||||
}
|
||||
|
||||
pub fn urls(&self) -> impl Iterator<Item = &UrlBuf> { self.files.iter().map(|f| &f.url) }
|
||||
|
||||
pub fn remove_many<'a, I, T>(&mut self, urls: I)
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
|
|
@ -37,45 +39,47 @@ impl Yanked {
|
|||
return;
|
||||
}
|
||||
|
||||
let old = self.urls.len();
|
||||
self.urls.retain(|u| !urls.contains(&UrlCov::new(u)));
|
||||
self.revision += (old != self.urls.len()) as u64;
|
||||
let old = self.files.len();
|
||||
self.files.retain(|f| !urls.contains(&UrlCov::new(&f.url)));
|
||||
self.revision += (old != self.files.len()) as u64;
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
if self.urls.is_empty() {
|
||||
if self.files.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.urls.clear();
|
||||
self.files.clear();
|
||||
self.revision += 1;
|
||||
}
|
||||
|
||||
pub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> bool {
|
||||
self.urls.contains(&UrlCov::new(url))
|
||||
self.files.contains(&UrlCov::new(url))
|
||||
}
|
||||
|
||||
pub fn contains_in(&self, dir: &UrlBuf) -> bool {
|
||||
self.urls.iter().any(|u| {
|
||||
let mut it = u.components();
|
||||
self.files.iter().any(|f| {
|
||||
let mut it = f.url.components();
|
||||
it.next_back().is_some()
|
||||
&& it.covariant(&dir.components())
|
||||
&& u.parent().is_some_and(|p| p == *dir)
|
||||
&& f.url.parent().is_some_and(|p| p == *dir)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||
let (removal, addition) = op.diff_recoverable(|u| self.contains(u));
|
||||
if !removal.is_empty() {
|
||||
let old = self.urls.len();
|
||||
self.urls.retain(|u| !removal.contains(u));
|
||||
self.revision += (old != self.urls.len()) as u64;
|
||||
let old = self.files.len();
|
||||
self.files.retain(|f| !removal.iter().any(|u| f.url.covariant(u)));
|
||||
self.revision += (old != self.files.len()) as u64;
|
||||
}
|
||||
|
||||
if !addition.is_empty() {
|
||||
let old = self.urls.len();
|
||||
self.urls.extend(addition.into_iter().map(UrlBufCov));
|
||||
self.revision += (old != self.urls.len()) as u64;
|
||||
let old = self.files.len();
|
||||
self.files.extend(addition.into_iter().map(FileCov));
|
||||
self.revision += (old != self.files.len()) as u64;
|
||||
}
|
||||
for f in op.files() {
|
||||
self.files.get_full_mut2(&UrlCov::new(&f.url)).map(|(_, v)| *v = f.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +89,7 @@ impl Yanked {
|
|||
}
|
||||
|
||||
self.version = self.revision;
|
||||
err!(Pubsub::pub_after_yank(self.cut, &self.urls));
|
||||
err!(Pubsub::pub_after_yank(self.cut, &self.files));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,16 @@ impl TryFrom<Table> for SpotLock {
|
|||
|
||||
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
||||
let file: FileRef = t.raw_get("file")?;
|
||||
Ok(Self {
|
||||
url: file.url_owned(),
|
||||
cha: file.cha,
|
||||
mime: t.raw_get("mime")?,
|
||||
file.borrow(|f| {
|
||||
Ok(Self {
|
||||
url: f.url_owned(),
|
||||
cha: f.cha,
|
||||
mime: t.raw_get("mime")?,
|
||||
|
||||
id: t.raw_get("id")?,
|
||||
skip: t.raw_get("skip")?,
|
||||
data: Default::default(),
|
||||
id: t.raw_get("id")?,
|
||||
skip: t.raw_get("skip")?,
|
||||
data: Default::default(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use hashbrown::HashMap;
|
||||
use yazi_fs::{Files, Filter, FilterCase};
|
||||
use yazi_fs::{Entries, Filter, FilterCase};
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, url::UrlBuf};
|
||||
|
||||
use crate::tab::Folder;
|
||||
|
|
@ -26,10 +26,10 @@ impl Finder {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn prev(&self, files: &Files, cursor: usize, include: bool) -> Option<isize> {
|
||||
for i in !include as usize..files.len() {
|
||||
let idx = (cursor + files.len() - i) % files.len();
|
||||
if let Some(s) = files[idx].name()
|
||||
pub fn prev(&self, entries: &Entries, cursor: usize, include: bool) -> Option<isize> {
|
||||
for i in !include as usize..entries.len() {
|
||||
let idx = (cursor + entries.len() - i) % entries.len();
|
||||
if let Some(s) = entries[idx].name()
|
||||
&& self.filter.matches(s)
|
||||
{
|
||||
return Some(idx as isize - cursor as isize);
|
||||
|
|
@ -38,10 +38,10 @@ impl Finder {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn next(&self, files: &Files, cursor: usize, include: bool) -> Option<isize> {
|
||||
for i in !include as usize..files.len() {
|
||||
let idx = (cursor + i) % files.len();
|
||||
if let Some(s) = files[idx].name()
|
||||
pub fn next(&self, entries: &Entries, cursor: usize, include: bool) -> Option<isize> {
|
||||
for i in !include as usize..entries.len() {
|
||||
let idx = (cursor + i) % entries.len();
|
||||
if let Some(s) = entries[idx].name()
|
||||
&& self.filter.matches(s)
|
||||
{
|
||||
return Some(idx as isize - cursor as isize);
|
||||
|
|
@ -57,7 +57,7 @@ impl Finder {
|
|||
self.matched.clear();
|
||||
|
||||
let mut i = 0u8;
|
||||
for file in folder.files.iter() {
|
||||
for file in folder.entries.iter() {
|
||||
if file.name().is_none_or(|s| !self.filter.matches(s)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -87,12 +87,12 @@ impl Finder {
|
|||
// --- Lock
|
||||
impl From<&Folder> for FinderLock {
|
||||
fn from(value: &Folder) -> Self {
|
||||
Self { cwd: value.url.clone(), revision: value.files.revision }
|
||||
Self { cwd: value.url.clone(), revision: value.entries.revision }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Folder> for FinderLock {
|
||||
fn eq(&self, other: &Folder) -> bool {
|
||||
self.revision == other.files.revision && self.cwd == other.url
|
||||
self.revision == other.entries.revision && self.cwd == other.url
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::mem;
|
|||
|
||||
use yazi_config::{LAYOUT, YAZI};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{Files, FilesOp, FolderStage, cha::Cha, file::File};
|
||||
use yazi_fs::{Entries, FilesOp, FolderStage, cha::Cha, file::File};
|
||||
use yazi_macro::err;
|
||||
use yazi_shared::{id::Id, path::{AsPath, PathBufDyn, PathDyn}, url::UrlBuf};
|
||||
use yazi_widgets::{Scrollable, Step};
|
||||
|
|
@ -10,10 +10,10 @@ use yazi_widgets::{Scrollable, Step};
|
|||
use crate::MgrProxy;
|
||||
|
||||
pub struct Folder {
|
||||
pub url: UrlBuf,
|
||||
pub cha: Cha,
|
||||
pub files: Files,
|
||||
pub stage: FolderStage,
|
||||
pub url: UrlBuf,
|
||||
pub cha: Cha,
|
||||
pub entries: Entries,
|
||||
pub stage: FolderStage,
|
||||
|
||||
pub offset: usize,
|
||||
pub cursor: usize,
|
||||
|
|
@ -25,14 +25,14 @@ pub struct Folder {
|
|||
impl Default for Folder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: Default::default(),
|
||||
cha: Default::default(),
|
||||
files: Files::new(YAZI.mgr.show_hidden.get()),
|
||||
stage: Default::default(),
|
||||
offset: Default::default(),
|
||||
cursor: Default::default(),
|
||||
page: Default::default(),
|
||||
trace: Default::default(),
|
||||
url: Default::default(),
|
||||
cha: Default::default(),
|
||||
entries: Entries::new(YAZI.mgr.show_hidden.get()),
|
||||
stage: Default::default(),
|
||||
offset: Default::default(),
|
||||
cursor: Default::default(),
|
||||
page: Default::default(),
|
||||
trace: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ impl<T: Into<UrlBuf>> From<T> for Folder {
|
|||
|
||||
impl Folder {
|
||||
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||
let (stage, revision) = (self.stage.clone(), self.files.revision);
|
||||
let (stage, revision) = (self.stage.clone(), self.entries.revision);
|
||||
match op {
|
||||
FilesOp::Full(_, _, cha) => {
|
||||
(self.cha, self.stage) = (cha, FolderStage::Loaded);
|
||||
|
|
@ -51,10 +51,10 @@ impl Folder {
|
|||
FilesOp::Part(_, ref files, _) if files.is_empty() => {
|
||||
(self.cha, self.stage) = (Cha::default(), FolderStage::Loading);
|
||||
}
|
||||
FilesOp::Part(_, _, ticket) if ticket == self.files.ticket() => {
|
||||
FilesOp::Part(_, _, ticket) if ticket == self.entries.ticket() => {
|
||||
self.stage = FolderStage::Loading;
|
||||
}
|
||||
FilesOp::Done(_, cha, ticket) if ticket == self.files.ticket() => {
|
||||
FilesOp::Done(_, cha, ticket) if ticket == self.entries.ticket() => {
|
||||
(self.cha, self.stage) = (cha, FolderStage::Loaded);
|
||||
}
|
||||
FilesOp::IOErr(_, ref err) => {
|
||||
|
|
@ -65,23 +65,23 @@ impl Folder {
|
|||
|
||||
let mut deleted = vec![];
|
||||
match op {
|
||||
FilesOp::Full(_, files, _) => self.files.update_full(files),
|
||||
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
|
||||
FilesOp::Full(_, files, _) => self.entries.update_full(files),
|
||||
FilesOp::Part(_, files, ticket) => self.entries.update_part(files, ticket),
|
||||
FilesOp::Done(..) => {}
|
||||
FilesOp::Size(_, sizes) => self.files.update_size(sizes),
|
||||
FilesOp::IOErr(..) => self.files.update_ioerr(),
|
||||
FilesOp::Size(_, sizes) => self.entries.update_size(sizes),
|
||||
FilesOp::IOErr(..) => self.entries.update_ioerr(),
|
||||
|
||||
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
||||
FilesOp::Deleting(_, urns) => deleted = self.files.update_deleting(urns),
|
||||
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
|
||||
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
||||
FilesOp::Creating(_, files) => self.entries.update_creating(files),
|
||||
FilesOp::Deleting(_, urns) => deleted = self.entries.update_deleting(urns),
|
||||
FilesOp::Updating(_, files) => _ = self.entries.update_updating(files),
|
||||
FilesOp::Upserting(_, files) => self.entries.update_upserting(files),
|
||||
};
|
||||
|
||||
self.trace.take_if(|_| self.files.is_empty() && !self.stage.is_loading());
|
||||
self.trace.take_if(|_| self.entries.is_empty() && !self.stage.is_loading());
|
||||
self.arrow(-(deleted.into_iter().filter(|&i| i < self.cursor).count() as isize));
|
||||
self.repos(None);
|
||||
|
||||
(&stage, revision) != (&self.stage, self.files.revision)
|
||||
(&stage, revision) != (&self.stage, self.entries.revision)
|
||||
}
|
||||
|
||||
pub fn update_pub(&mut self, tab: Id, op: FilesOp) -> bool {
|
||||
|
|
@ -93,7 +93,7 @@ impl Folder {
|
|||
}
|
||||
|
||||
pub fn arrow(&mut self, step: impl Into<Step>) -> bool {
|
||||
let mut b = if self.files.is_empty() {
|
||||
let mut b = if self.entries.is_empty() {
|
||||
(mem::take(&mut self.cursor), mem::take(&mut self.offset)) != (0, 0)
|
||||
} else {
|
||||
self.scroll(step)
|
||||
|
|
@ -109,7 +109,7 @@ impl Folder {
|
|||
return self.arrow(0);
|
||||
}
|
||||
|
||||
let new = self.files.position(urn).unwrap_or(self.cursor) as isize;
|
||||
let new = self.entries.position(urn).unwrap_or(self.cursor) as isize;
|
||||
let b = self.arrow(new - self.cursor as isize);
|
||||
|
||||
self.retrace();
|
||||
|
|
@ -146,7 +146,7 @@ impl Folder {
|
|||
|
||||
fn squeeze_offset(&mut self) -> bool {
|
||||
let old = self.offset;
|
||||
let len = self.files.len();
|
||||
let len = self.entries.len();
|
||||
|
||||
let limit = LAYOUT.get().folder_limit();
|
||||
let scrolloff = (limit / 2).min(YAZI.mgr.scrolloff.get() as usize);
|
||||
|
|
@ -164,23 +164,23 @@ impl Folder {
|
|||
|
||||
impl Folder {
|
||||
#[inline]
|
||||
pub fn hovered(&self) -> Option<&File> { self.files.get(self.cursor) }
|
||||
pub fn hovered(&self) -> Option<&File> { self.entries.get(self.cursor) }
|
||||
|
||||
#[inline]
|
||||
pub fn hovered_mut(&mut self) -> Option<&mut File> { self.files.get_mut(self.cursor) }
|
||||
pub fn hovered_mut(&mut self) -> Option<&mut File> { self.entries.get_mut(self.cursor) }
|
||||
|
||||
pub fn paginate(&self, page: usize) -> &[File] {
|
||||
let len = self.files.len();
|
||||
let len = self.entries.len();
|
||||
let limit = LAYOUT.get().folder_limit();
|
||||
|
||||
let start = (page.saturating_sub(1) * limit).min(len.saturating_sub(1));
|
||||
let end = ((page + 2) * limit).min(len);
|
||||
&self.files[start..end]
|
||||
&self.entries[start..end]
|
||||
}
|
||||
}
|
||||
|
||||
impl Scrollable for Folder {
|
||||
fn total(&self) -> usize { self.files.len() }
|
||||
fn total(&self) -> usize { self.entries.len() }
|
||||
|
||||
fn limit(&self) -> usize { LAYOUT.get().folder_limit() }
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(backstack finder folder history mode preference preview preview_lock selected tab);
|
||||
yazi_macro::mod_flat!(backstack finder folder history visual mode preference preview preview_lock selected tab);
|
||||
|
|
|
|||
|
|
@ -1,41 +1,43 @@
|
|||
use std::{collections::BTreeSet, fmt::Display, mem};
|
||||
use std::{fmt::{self, Display}, mem};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
use strum::EnumIs;
|
||||
|
||||
use super::Visual;
|
||||
use crate::tab::VisualIndices;
|
||||
|
||||
#[derive(Clone, Debug, Default, EnumIs, Eq, PartialEq)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Normal,
|
||||
Select(usize, BTreeSet<usize>),
|
||||
Unset(usize, BTreeSet<usize>),
|
||||
Select(Visual),
|
||||
Unset(Visual),
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
||||
pub fn visual(&self) -> Option<Visual> {
|
||||
match self {
|
||||
Self::Normal => None,
|
||||
Self::Select(start, indices) => Some((*start, indices)),
|
||||
Self::Unset(start, indices) => Some((*start, indices)),
|
||||
Self::Select(visual) | Self::Unset(visual) => Some(*visual),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
|
||||
pub fn visual_mut(&mut self) -> Option<&mut Visual> {
|
||||
match self {
|
||||
Self::Normal => None,
|
||||
Self::Select(visual) | Self::Unset(visual) => Some(visual),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_visual(&mut self, end: usize, len: usize) -> Option<VisualIndices> {
|
||||
match mem::take(self) {
|
||||
Self::Normal => None,
|
||||
Self::Select(start, indices) => Some((start, indices)),
|
||||
Self::Unset(start, indices) => Some((start, indices)),
|
||||
Self::Select(visual) | Self::Unset(visual) => Some(visual.indices(end, len)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn is_select(&self) -> bool { matches!(self, Self::Select(..)) }
|
||||
|
||||
pub fn is_unset(&self) -> bool { matches!(self, Self::Unset(..)) }
|
||||
|
||||
pub fn is_visual(&self) -> bool { matches!(self, Self::Select(..) | Self::Unset(..)) }
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Normal => "normal",
|
||||
Self::Select(..) => "select",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use tokio::{pin, task::JoinHandle};
|
|||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_adapter::ADAPTOR;
|
||||
use yazi_config::{LAYOUT, YAZI};
|
||||
use yazi_fs::{Files, FilesOp, cha::Cha, file::File};
|
||||
use yazi_fs::{Entries, FilesOp, cha::Cha, file::File};
|
||||
use yazi_macro::render;
|
||||
use yazi_runner::{
|
||||
RUNNER,
|
||||
|
|
@ -14,7 +14,7 @@ use yazi_shared::{
|
|||
pool::Symbol,
|
||||
url::{UrlBuf, UrlLike},
|
||||
};
|
||||
use yazi_vfs::{VfsFiles, VfsFilesOp};
|
||||
use yazi_vfs::{VfsEntries, VfsFilesOp};
|
||||
|
||||
use crate::{AppProxy, Highlighter, MgrProxy, tab::PreviewLock};
|
||||
|
||||
|
|
@ -68,9 +68,9 @@ impl Preview {
|
|||
self.folder_lock = Some(wd.clone());
|
||||
self.folder_loader.take().map(|h| h.abort());
|
||||
self.folder_loader = Some(tokio::spawn(async move {
|
||||
let Some(new) = Files::assert_stale(&wd, dir.unwrap_or_default()).await else { return };
|
||||
let Some(new) = Entries::assert_stale(&wd, dir.unwrap_or_default()).await else { return };
|
||||
|
||||
let rx = match Files::from_dir(&wd).await {
|
||||
let rx = match Entries::from_dir(&wd).await {
|
||||
Ok(rx) => rx,
|
||||
Err(e) => return FilesOp::issue_error(&wd, e).await,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,15 +24,17 @@ impl TryFrom<Table> for PreviewLock {
|
|||
|
||||
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
||||
let file: FileRef = t.raw_get("file")?;
|
||||
Ok(Self {
|
||||
url: file.url_owned(),
|
||||
cha: file.cha,
|
||||
mime: t.raw_get::<mlua::String>("mime")?.to_str()?.intern(),
|
||||
file.borrow(|f| {
|
||||
Ok(Self {
|
||||
url: f.url_owned(),
|
||||
cha: f.cha,
|
||||
mime: t.raw_get::<mlua::String>("mime")?.to_str()?.intern(),
|
||||
|
||||
skip: t.raw_get("skip")?,
|
||||
search_idx: t.raw_get("search_idx")?,
|
||||
area: t.raw_get("area")?,
|
||||
data: Default::default(),
|
||||
skip: t.raw_get("skip")?,
|
||||
search_idx: t.raw_get("search_idx")?,
|
||||
area: t.raw_get("area")?,
|
||||
data: Default::default(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use indexmap::IndexMap;
|
||||
use yazi_fs::FilesOp;
|
||||
use yazi_shared::{timestamp_us, url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlCovMapExt}};
|
||||
use indexmap::{IndexMap, map::MutableKeys};
|
||||
use yazi_fs::{FilesOp, file::{File, FileCov}};
|
||||
use yazi_shared::{timestamp_us, url::{AsUrl, Url, UrlBuf, UrlBufCov, UrlCov, UrlCovMapExt, UrlLike, UrlMapExt}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Selected {
|
||||
inner: IndexMap<UrlBufCov, u64>,
|
||||
inner: IndexMap<FileCov, u64>,
|
||||
parents: HashMap<UrlBufCov, usize>,
|
||||
}
|
||||
|
||||
|
|
@ -16,64 +14,70 @@ impl Selected {
|
|||
|
||||
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &UrlBuf> { self.inner.keys().map(Deref::deref) }
|
||||
pub fn files(&self) -> impl Iterator<Item = &File> { self.inner.keys().map(|f| &f.0) }
|
||||
|
||||
pub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> bool {
|
||||
self.inner.contains_key(&UrlCov::new(url))
|
||||
pub fn urls(&self) -> impl Iterator<Item = &UrlBuf> { self.inner.keys().map(|f| &f.url) }
|
||||
|
||||
pub fn contains(&self, url: impl AsUrl) -> bool {
|
||||
self.inner.contains_key(&UrlCov::new(url.as_url()))
|
||||
}
|
||||
|
||||
pub fn add<'a>(&mut self, url: impl Into<Url<'a>>) -> bool { self.add_same([url]) == 1 }
|
||||
pub fn add(&mut self, file: impl AsUrl + Into<File>) -> bool { self.add_same([file]) == 1 }
|
||||
|
||||
pub fn add_many<'a, I, T>(&mut self, urls: I) -> usize
|
||||
pub fn add_many<I, T>(&mut self, files: I) -> usize
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<Url<'a>>,
|
||||
T: AsUrl + Into<File>,
|
||||
{
|
||||
let mut grouped: IndexMap<_, Vec<_>> = Default::default();
|
||||
for url in urls.into_iter().map(Into::into) {
|
||||
if let Some(p) = url.parent() {
|
||||
grouped.entry(p).or_default().push(url);
|
||||
for file in files {
|
||||
if let Some(p) = file.as_url().parent() {
|
||||
grouped.get_or_insert_default(p).push(file);
|
||||
}
|
||||
}
|
||||
grouped.into_values().map(|v| self.add_same(v)).sum()
|
||||
}
|
||||
|
||||
fn add_same<'a, I, T>(&mut self, urls: I) -> usize
|
||||
fn add_same<I, T>(&mut self, files: I) -> usize
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<Url<'a>>,
|
||||
T: AsUrl + Into<File>,
|
||||
{
|
||||
// If it has appeared as a parent
|
||||
let urls: Vec<_> =
|
||||
urls.into_iter().map(UrlCov::new).filter(|u| !self.parents.contains_key(u)).collect();
|
||||
if urls.is_empty() {
|
||||
let files: Vec<_> =
|
||||
files.into_iter().filter(|f| !self.parents.contains_key(&UrlCov::new(f.as_url()))).collect();
|
||||
if files.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If it has appeared as a child
|
||||
let mut parent = urls[0].parent();
|
||||
let mut parents = vec![];
|
||||
let mut parent = files[0].as_url().parent();
|
||||
while let Some(u) = parent {
|
||||
if self.inner.contains_key(&UrlCov::new(u)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
parent = u.parent();
|
||||
parents.push(u);
|
||||
}
|
||||
|
||||
let (now, len) = (timestamp_us(), self.inner.len());
|
||||
self.inner.extend(urls.iter().enumerate().map(|(i, u)| (u.into(), now + i as u64)));
|
||||
// Mark the files as selected children with a timestamp
|
||||
let (now, fl, il) = (timestamp_us(), files.len(), self.inner.len());
|
||||
self
|
||||
.inner
|
||||
.extend(files.into_iter().enumerate().map(|(i, f)| (FileCov(f.into()), now + i as u64)));
|
||||
|
||||
for u in parents {
|
||||
*self.parents.get_or_insert_default(UrlCov::new(u)) += self.inner.len() - len;
|
||||
// Update the parent counts
|
||||
if let Some((first, _)) = self.inner.get_index(il) {
|
||||
let mut parent = first.url.parent();
|
||||
while let Some(u) = parent {
|
||||
*self.parents.get_or_insert_default(UrlCov::new(u)) += self.inner.len() - il;
|
||||
parent = u.parent();
|
||||
}
|
||||
}
|
||||
urls.len()
|
||||
|
||||
fl
|
||||
}
|
||||
|
||||
pub fn remove<'a>(&mut self, url: impl Into<Url<'a>> + Clone) -> bool {
|
||||
self.remove_same([url]) == 1
|
||||
}
|
||||
pub fn remove(&mut self, url: impl AsUrl) -> bool { self.remove_same([url]) == 1 }
|
||||
|
||||
pub fn remove_many<'a, I, T>(&mut self, urls: I) -> usize
|
||||
where
|
||||
|
|
@ -95,21 +99,21 @@ impl Selected {
|
|||
affected
|
||||
}
|
||||
|
||||
fn remove_same<'a, I, T>(&mut self, urls: I) -> usize
|
||||
fn remove_same<I, T>(&mut self, urls: I) -> usize
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<Url<'a>> + Clone,
|
||||
T: AsUrl,
|
||||
{
|
||||
let mut it = urls.into_iter().peekable();
|
||||
let Some(first) = it.peek().cloned().map(UrlCov::new) else { return 0 };
|
||||
let mut it = urls.into_iter();
|
||||
let Some(first) = it.next() else { return 0 };
|
||||
|
||||
let count = it.filter_map(|u| self.inner.swap_remove(&UrlCov::new(u))).count();
|
||||
let count = self.inner.swap_remove(&UrlCov::new(first.as_url())).is_some() as usize
|
||||
+ it.filter_map(|u| self.inner.swap_remove(&UrlCov::new(u.as_url()))).count();
|
||||
if count == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FIXME: use UrlCov::parent() instead
|
||||
let mut parent = first.parent();
|
||||
let mut parent = first.as_url().parent();
|
||||
while let Some(u) = parent {
|
||||
let n = self.parents.get_mut(&UrlCov::new(u)).unwrap();
|
||||
|
||||
|
|
@ -134,23 +138,28 @@ impl Selected {
|
|||
self.remove_many(&removal);
|
||||
}
|
||||
if !addition.is_empty() {
|
||||
self.add_many(&addition);
|
||||
self.add_many(addition);
|
||||
}
|
||||
for f in op.files() {
|
||||
self.inner.get_full_mut2(&UrlCov::new(&f.url)).map(|(_, k, _)| *k = f.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn f<S: AsRef<OsStr> + ?Sized>(s: &S) -> File { File::from_dummy(Path::new(s), None) }
|
||||
|
||||
#[test]
|
||||
fn test_insert_non_conflicting() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert!(s.add(Path::new("/a/b")));
|
||||
assert!(s.add(Path::new("/c/d")));
|
||||
assert!(s.add(f("/a/b")));
|
||||
assert!(s.add(f("/c/d")));
|
||||
assert_eq!(s.inner.len(), 2);
|
||||
}
|
||||
|
||||
|
|
@ -158,24 +167,24 @@ mod tests {
|
|||
fn test_insert_conflicting_parent() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert!(s.add(Path::new("/a")));
|
||||
assert!(!s.add(Path::new("/a/b")));
|
||||
assert!(s.add(f("/a")));
|
||||
assert!(!s.add(f("/a/b")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_conflicting_child() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert!(s.add(Path::new("/a/b/c")));
|
||||
assert!(!s.add(Path::new("/a/b")));
|
||||
assert!(s.add(Path::new("/a/b/d")));
|
||||
assert!(s.add(f("/a/b/c")));
|
||||
assert!(!s.add(f("/a/b")));
|
||||
assert!(s.add(f("/a/b/d")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert!(s.add(Path::new("/a/b")));
|
||||
assert!(s.add(f("/a/b")));
|
||||
assert!(!s.remove(Path::new("/a/c")));
|
||||
assert!(s.remove(Path::new("/a/b")));
|
||||
assert!(!s.remove(Path::new("/a/b")));
|
||||
|
|
@ -184,92 +193,83 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_success() {
|
||||
fn add_many_success() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert_eq!(
|
||||
3,
|
||||
s.add_same([
|
||||
Path::new("/parent/child1"),
|
||||
Path::new("/parent/child2"),
|
||||
Path::new("/parent/child3")
|
||||
])
|
||||
);
|
||||
assert_eq!(3, s.add_same([f("/parent/child1"), f("/parent/child2"), f("/parent/child3")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_with_existing_parent_fails() {
|
||||
fn add_many_with_existing_parent_fails() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(Path::new("/parent"));
|
||||
assert_eq!(0, s.add_same([Path::new("/parent/child1"), Path::new("/parent/child2")]));
|
||||
s.add(f("/parent"));
|
||||
assert_eq!(0, s.add_same([f("/parent/child1"), f("/parent/child2")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_with_existing_child_fails() {
|
||||
fn add_many_with_existing_child_fails() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(Path::new("/parent/child1"));
|
||||
assert_eq!(2, s.add_same([Path::new("/parent/child1"), Path::new("/parent/child2")]));
|
||||
s.add(f("/parent/child1"));
|
||||
assert_eq!(2, s.add_same([f("/parent/child1"), f("/parent/child2")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_empty_urls_list() {
|
||||
fn add_many_empty_files_list() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert_eq!(0, s.add_same([] as [Url; 0]));
|
||||
assert_eq!(0, s.add_same([] as [File; 0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_with_parent_as_child_of_another_url() {
|
||||
fn add_many_with_parent_as_child_of_another_url() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(Path::new("/parent/child"));
|
||||
assert_eq!(
|
||||
0,
|
||||
s.add_same([Path::new("/parent/child/child1"), Path::new("/parent/child/child2")])
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn insert_many_with_direct_parent_fails() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(Path::new("/a"));
|
||||
assert_eq!(0, s.add_same([Path::new("/a/b")]));
|
||||
s.add(f("/parent/child"));
|
||||
assert_eq!(0, s.add_same([f("/parent/child/child1"), f("/parent/child/child2")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_with_nested_child_fails() {
|
||||
fn add_many_with_direct_parent_fails() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(Path::new("/a/b"));
|
||||
assert_eq!(0, s.add_same([Path::new("/a")]));
|
||||
assert_eq!(1, s.add_same([Path::new("/b"), Path::new("/a")]));
|
||||
s.add(f("/a"));
|
||||
assert_eq!(0, s.add_same([f("/a/b")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_sibling_directories_success() {
|
||||
fn add_many_with_nested_child_fails() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
assert_eq!(2, s.add_same([Path::new("/a/b"), Path::new("/a/c")]));
|
||||
s.add(f("/a/b"));
|
||||
assert_eq!(0, s.add_same([f("/a")]));
|
||||
assert_eq!(1, s.add_same([f("/b"), f("/a")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_many_with_grandchild_fails() {
|
||||
fn add_many_sibling_directories_success() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(Path::new("/a/b"));
|
||||
assert_eq!(0, s.add_same([Path::new("/a/b/c")]));
|
||||
assert_eq!(2, s.add_same([f("/a/b"), f("/a/c")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_many_with_remove() {
|
||||
fn add_many_with_grandchild_fails() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(f("/a/b"));
|
||||
assert_eq!(0, s.add_same([f("/a/b/c")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_many_with_remove() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
let child1 = Path::new("/parent/child1");
|
||||
let child2 = Path::new("/parent/child2");
|
||||
let child3 = Path::new("/parent/child3");
|
||||
assert_eq!(3, s.add_same([child1, child2, child3]));
|
||||
assert_eq!(3, s.add_same([f(child1), f(child2), f(child3)]));
|
||||
|
||||
assert!(s.remove(child1));
|
||||
assert_eq!(s.inner.len(), 2);
|
||||
|
|
@ -282,4 +282,13 @@ mod tests {
|
|||
assert!(s.inner.is_empty());
|
||||
assert!(s.parents.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_same_all_duplicates() {
|
||||
let mut s = Selected::default();
|
||||
|
||||
s.add(f("/a/b"));
|
||||
assert_eq!(1, s.add_same([f("/a/b")]));
|
||||
assert_eq!(s.inner.len(), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ impl Tab {
|
|||
pub fn hovered_mut(&mut self) -> Option<&mut File> { self.current.hovered_mut() }
|
||||
|
||||
pub fn hovered_rect(&self) -> Option<Rect> {
|
||||
let y = self.current.files.position(self.hovered()?.urn())? - self.current.offset;
|
||||
let y = self.current.entries.position(self.hovered()?.urn())? - self.current.offset;
|
||||
|
||||
let mut rect = LAYOUT.get().current;
|
||||
rect.y = rect.y.saturating_sub(1) + y as u16;
|
||||
|
|
@ -99,22 +99,30 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn selected_or_hovered(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> {
|
||||
pub fn selected_or_hovered_files(&self) -> Box<dyn Iterator<Item = &File> + '_> {
|
||||
if self.selected.is_empty() {
|
||||
Box::new(self.hovered().into_iter())
|
||||
} else {
|
||||
Box::new(self.selected.files())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_or_hovered_urls(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> {
|
||||
if self.selected.is_empty() {
|
||||
Box::new(self.hovered().map(|h| &h.url).into_iter())
|
||||
} else {
|
||||
Box::new(self.selected.values())
|
||||
Box::new(self.selected.urls())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hovered_and_selected(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> {
|
||||
let Some(h) = self.hovered() else {
|
||||
return Box::new([UrlBuf::new()].into_iter().chain(self.selected.values()));
|
||||
return Box::new([UrlBuf::new()].into_iter().chain(self.selected.urls()));
|
||||
};
|
||||
if self.selected.is_empty() {
|
||||
Box::new([&h.url, &h.url].into_iter())
|
||||
} else {
|
||||
Box::new([&h.url].into_iter().chain(self.selected.values()))
|
||||
Box::new([&h.url].into_iter().chain(self.selected.urls()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
49
yazi-core/src/tab/visual.rs
Normal file
49
yazi-core/src/tab/visual.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use std::{iter::Chain, ops::Range};
|
||||
|
||||
use yazi_widgets::Step;
|
||||
|
||||
pub type VisualIndices = Chain<Range<usize>, Range<usize>>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Visual {
|
||||
start: usize,
|
||||
wraps: isize,
|
||||
}
|
||||
|
||||
impl Visual {
|
||||
pub fn new(start: usize) -> Self { Self { start, wraps: 0 } }
|
||||
|
||||
pub fn arrow(&mut self, step: Step, old: usize, new: usize) {
|
||||
self.wraps += match step {
|
||||
Step::Prev if new > old => -1,
|
||||
Step::Next if new < old => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, index: usize, end: usize, len: usize) -> bool {
|
||||
let (first, second) = self.ranges(end, len);
|
||||
first.contains(&index) || second.contains(&index)
|
||||
}
|
||||
|
||||
pub fn indices(&self, end: usize, len: usize) -> VisualIndices {
|
||||
let (first, second) = self.ranges(end, len);
|
||||
first.chain(second)
|
||||
}
|
||||
|
||||
fn ranges(&self, end: usize, len: usize) -> (Range<usize>, Range<usize>) {
|
||||
if len == 0 {
|
||||
return (0..0, 0..0);
|
||||
}
|
||||
|
||||
let start = self.start.min(len - 1);
|
||||
let end = end.min(len - 1);
|
||||
|
||||
match self.wraps {
|
||||
0 => (start.min(end)..start.max(end) + 1, 0..0),
|
||||
1 if start > end + 1 => (0..end + 1, start..len),
|
||||
-1 if end > start + 1 => (0..start + 1, end..len),
|
||||
_ => (0..len, 0..0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use indexmap::IndexSet;
|
||||
use tracing::debug;
|
||||
use yazi_scheduler::file::FileInCut;
|
||||
use yazi_shared::url::{UrlBuf, UrlBufCov, UrlLike};
|
||||
use yazi_shared::url::{UrlBuf, UrlLike};
|
||||
|
||||
use super::Tasks;
|
||||
use crate::mgr::Yanked;
|
||||
|
|
@ -10,15 +9,15 @@ impl Tasks {
|
|||
pub fn file_cut(&self, src: &Yanked, dest: &UrlBuf, force: bool) {
|
||||
self.scheduler.behavior.reset();
|
||||
|
||||
for u in src.iter() {
|
||||
for u in src.urls() {
|
||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||
debug!("file_cut: cannot join {u:?} with {dest:?}");
|
||||
continue;
|
||||
};
|
||||
if force && *u == to {
|
||||
if force && u == to {
|
||||
debug!("file_cut: same file, skip {to:?}");
|
||||
} else {
|
||||
self.scheduler.file_cut(FileInCut::new(u.0.clone(), to, force));
|
||||
self.scheduler.file_cut(FileInCut::new(u.clone(), to, force));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,47 +25,47 @@ impl Tasks {
|
|||
pub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: bool) {
|
||||
self.scheduler.behavior.reset();
|
||||
|
||||
for u in src.iter() {
|
||||
for u in src.urls() {
|
||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||
debug!("file_copy: cannot join {u:?} with {dest:?}");
|
||||
continue;
|
||||
};
|
||||
if force && *u == to {
|
||||
if force && u == to {
|
||||
debug!("file_copy: same file, skip {to:?}");
|
||||
} else {
|
||||
self.scheduler.file_copy(u.0.clone(), to, force, follow);
|
||||
self.scheduler.file_copy(u.clone(), to, force, follow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_link(&self, src: &IndexSet<UrlBufCov>, dest: &UrlBuf, relative: bool, force: bool) {
|
||||
pub fn file_link(&self, src: &Yanked, dest: &UrlBuf, relative: bool, force: bool) {
|
||||
self.scheduler.behavior.reset();
|
||||
|
||||
for u in src {
|
||||
for u in src.urls() {
|
||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||
debug!("file_link: cannot join {u:?} with {dest:?}");
|
||||
continue;
|
||||
};
|
||||
if force && *u == to {
|
||||
if force && u == to {
|
||||
debug!("file_link: same file, skip {to:?}");
|
||||
} else {
|
||||
self.scheduler.file_link(u.0.clone(), to, relative, force);
|
||||
self.scheduler.file_link(u.clone(), to, relative, force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_hardlink(&self, src: &IndexSet<UrlBufCov>, dest: &UrlBuf, force: bool, follow: bool) {
|
||||
pub fn file_hardlink(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: bool) {
|
||||
self.scheduler.behavior.reset();
|
||||
|
||||
for u in src {
|
||||
for u in src.urls() {
|
||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||
debug!("file_hardlink: cannot join {u:?} with {dest:?}");
|
||||
continue;
|
||||
};
|
||||
if force && *u == to {
|
||||
if force && u == to {
|
||||
debug!("file_hardlink: same file, skip {to:?}");
|
||||
} else {
|
||||
self.scheduler.file_hardlink(u.0.clone(), to, force, follow);
|
||||
self.scheduler.file_hardlink(u.clone(), to, force, follow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use yazi_config::{YAZI, plugin::MAX_FETCHERS};
|
||||
use yazi_fs::{Files, FsHash64, SortBy, file::File};
|
||||
use yazi_fs::{Entries, FsHash64, SortBy, file::File};
|
||||
|
||||
use super::Tasks;
|
||||
use crate::mgr::Mimetype;
|
||||
|
|
@ -44,7 +44,7 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn prework_sorted(&self, targets: &Files) {
|
||||
pub fn prework_sorted(&self, targets: &Entries) {
|
||||
if targets.sorter().by != SortBy::Size {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ use yazi_shared::{Layer, event::ActionCow};
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct WhichOpt {
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<ChordArc>>>,
|
||||
pub layer: Layer,
|
||||
pub cands: Vec<ChordArc>,
|
||||
pub silent: bool,
|
||||
pub times: usize,
|
||||
pub silent: bool,
|
||||
}
|
||||
|
||||
impl_data_any!(WhichOpt);
|
||||
|
|
@ -24,19 +25,21 @@ impl TryFrom<ActionCow> for WhichOpt {
|
|||
|
||||
Ok(Self {
|
||||
tx: a.take_any2("tx").transpose()?,
|
||||
layer: a.str("layer").parse()?,
|
||||
cands: a.take_any_iter().collect(),
|
||||
silent: a.bool("silent"),
|
||||
times: a.get("times").unwrap_or(0),
|
||||
silent: a.bool("silent"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Layer, Key)> for WhichOpt {
|
||||
fn from((layer, key): (Layer, Key)) -> Self {
|
||||
impl WhichOpt {
|
||||
pub fn new(src: Layer, dist: Layer, key: Key) -> Self {
|
||||
Self {
|
||||
tx: None,
|
||||
layer: dist,
|
||||
cands: KEYMAP
|
||||
.chords(layer)
|
||||
.chords(src)
|
||||
.iter()
|
||||
.filter(|&c| c.on.len() > 1 && c.on[0] == key)
|
||||
.cloned()
|
||||
|
|
@ -55,6 +58,7 @@ impl FromLua for WhichOpt {
|
|||
|
||||
Ok(Self {
|
||||
tx: t.raw_get::<yazi_binding::MpscUnboundedTx<_>>("tx").ok().map(|t| t.0),
|
||||
layer: t.raw_get("layer")?,
|
||||
cands: t.raw_get::<Table>("cands")?.sequence_values().collect::<mlua::Result<Vec<_>>>()?,
|
||||
times: t.raw_get("times").unwrap_or_default(),
|
||||
silent: t.raw_get("silent")?,
|
||||
|
|
@ -68,6 +72,7 @@ impl IntoLua for WhichOpt {
|
|||
lua
|
||||
.create_table_from([
|
||||
("tx", self.tx.map(yazi_binding::MpscUnboundedTx).into_lua(lua)?),
|
||||
("layer", self.layer.into_lua(lua)?),
|
||||
("cands", lua.create_sequence_from(self.cands)?.into_lua(lua)?),
|
||||
("times", self.times.into_lua(lua)?),
|
||||
("silent", self.silent.into_lua(lua)?),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_config::keymap::{ChordArc, Key};
|
||||
use yazi_macro::{emit, render_and};
|
||||
use yazi_shared::Layer;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Which {
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<ChordArc>>>,
|
||||
pub times: usize,
|
||||
pub layer: Layer,
|
||||
pub cands: Vec<ChordArc>,
|
||||
pub times: usize,
|
||||
|
||||
// Active state
|
||||
pub active: bool,
|
||||
|
|
@ -32,8 +34,8 @@ impl Which {
|
|||
}
|
||||
|
||||
pub fn dismiss(&mut self, chord: Option<ChordArc>) {
|
||||
self.times = 0;
|
||||
self.cands.clear();
|
||||
self.times = 0;
|
||||
|
||||
self.active = false;
|
||||
self.silent = false;
|
||||
|
|
@ -42,7 +44,7 @@ impl Which {
|
|||
_ = tx.send(chord.as_ref().map(Into::into));
|
||||
}
|
||||
if let Some(chord) = chord {
|
||||
emit!(Seq(chord.into_seq()));
|
||||
emit!(Seq(chord.into_seq(self.layer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,34 +3,31 @@ use std::borrow::Cow;
|
|||
use indexmap::IndexSet;
|
||||
use mlua::{AnyUserData, FromLua, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_fs::file::FileCov;
|
||||
use yazi_macro::impl_data_any;
|
||||
use yazi_shared::url::{UrlBuf, UrlBufCov};
|
||||
use yazi_shim::mlua::get_metatable;
|
||||
|
||||
use super::Ember;
|
||||
|
||||
type Iter = yazi_binding::Iter<
|
||||
std::iter::Map<indexmap::set::IntoIter<UrlBufCov>, fn(UrlBufCov) -> UrlBuf>,
|
||||
UrlBuf,
|
||||
>;
|
||||
type Iter = yazi_binding::Iter<indexmap::set::IntoIter<FileCov>, FileCov>;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct EmberYank<'a> {
|
||||
pub cut: bool,
|
||||
pub urls: Cow<'a, IndexSet<UrlBufCov>>,
|
||||
pub cut: bool,
|
||||
pub files: Cow<'a, IndexSet<FileCov>>,
|
||||
}
|
||||
|
||||
impl_data_any!(EmberYank<'static>, from_into_lua = inherit);
|
||||
|
||||
impl<'a> EmberYank<'a> {
|
||||
pub fn borrowed(cut: bool, urls: &'a IndexSet<UrlBufCov>) -> Ember<'a> {
|
||||
Self { cut, urls: Cow::Borrowed(urls) }.into()
|
||||
pub fn borrowed(cut: bool, files: &'a IndexSet<FileCov>) -> Ember<'a> {
|
||||
Self { cut, files: Cow::Borrowed(files) }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl EmberYank<'static> {
|
||||
pub fn owned(cut: bool, _: &IndexSet<UrlBufCov>) -> Ember<'static> {
|
||||
Self { cut, urls: Default::default() }.into()
|
||||
pub fn owned(cut: bool, _: &IndexSet<FileCov>) -> Ember<'static> {
|
||||
Self { cut, files: Default::default() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,8 +50,8 @@ impl FromLua for EmberYank<'static> {
|
|||
|
||||
impl IntoLua for EmberYank<'_> {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
let len = self.urls.len();
|
||||
let iter = Iter::new(self.urls.into_owned().into_iter().map(UrlBuf::from), Some(len));
|
||||
let len = self.files.len();
|
||||
let iter = Iter::new(self.files.into_owned().into_iter(), Some(len));
|
||||
EmberYankIter { cut: self.cut, len, inner: lua.create_userdata(iter)? }.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
@ -69,15 +66,8 @@ pub struct EmberYankIter {
|
|||
impl EmberYankIter {
|
||||
pub fn collect(self, lua: &Lua) -> mlua::Result<EmberYank<'static>> {
|
||||
Ok(EmberYank {
|
||||
cut: self.cut,
|
||||
urls: Cow::Owned(
|
||||
self
|
||||
.inner
|
||||
.take::<Iter>()?
|
||||
.into_iter(lua)
|
||||
.map(|result| result.map(Into::into))
|
||||
.collect::<mlua::Result<_>>()?,
|
||||
),
|
||||
cut: self.cut,
|
||||
files: Cow::Owned(self.inner.take::<Iter>()?.into_iter(lua).collect::<mlua::Result<_>>()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use indexmap::IndexSet;
|
|||
use mlua::Function;
|
||||
use parking_lot::RwLock;
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_fs::FolderStage;
|
||||
use yazi_shared::{id::Id, url::{Url, UrlBuf, UrlBufCov}};
|
||||
use yazi_fs::{FolderStage, file::FileCov};
|
||||
use yazi_shared::{id::Id, url::{Url, UrlBuf}};
|
||||
use yazi_shim::cell::RoCell;
|
||||
|
||||
use crate::{Client, ID, PEERS, ember::{Ember, EmberBulkRename, EmberDuplicateItem, EmberHi, EmberMoveItem}};
|
||||
|
|
@ -162,7 +162,7 @@ impl Pubsub {
|
|||
|
||||
pub_after!(rename(tab: Id, from: &UrlBuf, to: &UrlBuf), (tab, from, to));
|
||||
|
||||
pub_after!(@yank(cut: bool, urls: &IndexSet<UrlBufCov>), (cut, urls));
|
||||
pub_after!(@yank(cut: bool, files: &IndexSet<FileCov>), (cut, files));
|
||||
|
||||
pub_after!(duplicate(items: Vec<EmberDuplicateItem>), (&items), (items));
|
||||
|
||||
|
|
|
|||
|
|
@ -321,16 +321,18 @@ impl<'a> Executor<'a> {
|
|||
|
||||
on!(escape);
|
||||
on!(arrow);
|
||||
on!(filter);
|
||||
on!(close);
|
||||
|
||||
match action.name.as_ref() {
|
||||
// Help
|
||||
"close" => act!(help:toggle, cx, Layer::Help),
|
||||
// Plugin
|
||||
"plugin" => act!(app:plugin, cx, action),
|
||||
// Lua
|
||||
"lua" => act!(app:lua, cx, action),
|
||||
_ => succ!(),
|
||||
_ => {
|
||||
cx.help.input.execute(action)?;
|
||||
cx.help.filter_apply();
|
||||
succ!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue