mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-22 07:11:03 +00:00
Compare commits
154 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9accf929f4 | ||
|
|
6aa75594db | ||
|
|
60d94608cb | ||
|
|
52640fcece | ||
|
|
b25ae9f82d | ||
|
|
4dab480347 | ||
|
|
d39637f40c | ||
|
|
ddc6632505 | ||
|
|
6d84921e70 | ||
|
|
dbb0cc0d55 | ||
|
|
aa850f9a3b | ||
|
|
c92c4aba88 | ||
|
|
6e0aaee822 | ||
|
|
b8ceaea940 | ||
|
|
21550a7eb5 | ||
|
|
39527f488f | ||
|
|
de611b0e6b | ||
|
|
ef654f4420 | ||
|
|
2a106aa231 | ||
|
|
b1ee2e72ff | ||
|
|
581886a355 | ||
|
|
4f45ddb514 | ||
|
|
0ba1811af3 | ||
|
|
feeea673f1 | ||
|
|
ef5db6e257 | ||
|
|
0daa5df644 | ||
|
|
6144d2748e | ||
|
|
84a5be0b6a | ||
|
|
ab8d634f8e | ||
|
|
85132ff343 | ||
|
|
53d36b5186 | ||
|
|
446a5721bf | ||
|
|
f1e93d7f52 | ||
|
|
b37be0529a | ||
|
|
9b920ed2e3 | ||
|
|
07a5deb109 | ||
|
|
4086fbb166 | ||
|
|
9cfeb67db7 | ||
|
|
f6113d3865 | ||
|
|
ea7fa2127c | ||
|
|
8a4b0b208e | ||
|
|
9bdd6777e4 | ||
|
|
8e80798984 | ||
|
|
e892bf7d90 | ||
|
|
601cb10b32 | ||
|
|
6c30430666 | ||
|
|
7a51626505 | ||
|
|
cf8b54179e | ||
|
|
f7fea2b19a | ||
|
|
d9cd1907d9 | ||
|
|
5d643ee01a | ||
|
|
c037855c4f | ||
|
|
3f5cc47a48 | ||
|
|
fde563380b | ||
|
|
247f925e53 | ||
|
|
22fb9e0d09 | ||
|
|
92b9ea3794 | ||
|
|
3968c4799c | ||
|
|
aa526434f0 | ||
|
|
10fc76db07 | ||
|
|
5ad1e003f2 | ||
|
|
62dd22b8da | ||
|
|
740d891989 | ||
|
|
1caa5807c6 | ||
|
|
c4c533e3ef | ||
|
|
88848932fc | ||
|
|
a2996908de | ||
|
|
4a2e5addcd | ||
|
|
369c47539c | ||
|
|
d1a27bca1f | ||
|
|
ae4c138f49 | ||
|
|
463150848d | ||
|
|
80a54c3dd5 | ||
|
|
9abbc12212 | ||
|
|
74d903a49a | ||
|
|
f6408656b4 | ||
|
|
354cdef907 | ||
|
|
ec178fdb52 | ||
|
|
4bb4f37555 | ||
|
|
0cedbd9c7b | ||
|
|
ad655eda52 | ||
|
|
4857d46918 | ||
|
|
6b143c1f4d | ||
|
|
4225c34fea | ||
|
|
607bc0dd2b | ||
|
|
2514725e56 | ||
|
|
935eb6b298 | ||
|
|
b67798ea19 | ||
|
|
5b2dd41e64 | ||
|
|
e2fae151a5 | ||
|
|
5c05350d52 | ||
|
|
d4924ebcad | ||
|
|
c703332b4b | ||
|
|
7b61eb1595 | ||
|
|
a246e9c7c0 | ||
|
|
cc2414728c | ||
|
|
6a1ddb71e0 | ||
|
|
da96795303 | ||
|
|
de01a56ac5 | ||
|
|
d22c96b69c | ||
|
|
fa1ee46edc | ||
|
|
3534ba0705 | ||
|
|
e4829372db | ||
|
|
1be991ec72 | ||
|
|
dee27a2377 | ||
|
|
8932ddd66d | ||
|
|
741f84e22b | ||
|
|
f10b8910ad | ||
|
|
dbf81deeab | ||
|
|
be91b4111c | ||
|
|
9648d8a43b | ||
|
|
b92b576ce1 | ||
|
|
0efeaf5f64 | ||
|
|
0a83f0bd27 | ||
|
|
3cdc3ecb70 | ||
|
|
884c265de1 | ||
|
|
05aeae3b09 | ||
|
|
9eb4d03a44 | ||
|
|
0bb6379a2a | ||
|
|
8b773a62a5 | ||
|
|
16aa3cd0fc | ||
|
|
d84f0b1265 | ||
|
|
e49cdfe220 | ||
|
|
9af792ef69 | ||
|
|
578016e00f | ||
|
|
7f339381f1 | ||
|
|
31cfc58188 | ||
|
|
c0f96f553b | ||
|
|
13bdb164a4 | ||
|
|
c3bc4fecad | ||
|
|
98b1614016 | ||
|
|
a8a4b684a1 | ||
|
|
30ec603441 | ||
|
|
1a121bbfb0 | ||
|
|
0aeeb3b0f7 | ||
|
|
dc66f79cef | ||
|
|
6757fed5aa | ||
|
|
339002ee48 | ||
|
|
a015d4a7ef | ||
|
|
dfdb235d74 | ||
|
|
1ea0ea072c | ||
|
|
eaeda8b4fa | ||
|
|
cfd9a22125 | ||
|
|
6d942ea011 | ||
|
|
583345296f | ||
|
|
24c60419bb | ||
|
|
e0911f2507 | ||
|
|
6150571738 | ||
|
|
592e70a1c9 | ||
|
|
06c665a086 | ||
|
|
92880b844b | ||
|
|
face6aed40 | ||
|
|
afc694c122 | ||
|
|
49b8324bbf |
1269 changed files with 37016 additions and 20708 deletions
6
.github/DISCUSSION_TEMPLATE/1-q-a.yml
vendored
6
.github/DISCUSSION_TEMPLATE/1-q-a.yml
vendored
|
|
@ -22,10 +22,10 @@ body:
|
|||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: debug
|
||||
id: env
|
||||
attributes:
|
||||
label: "`yazi --debug` output"
|
||||
description: Please run `yazi --debug` and paste the debug information here.
|
||||
label: "`ya env` output"
|
||||
description: Run `ya env` and paste the environment information here.
|
||||
render: Shell
|
||||
validations:
|
||||
required: true
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/bug.yml
vendored
8
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -26,10 +26,10 @@ body:
|
|||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: debug
|
||||
id: env
|
||||
attributes:
|
||||
label: "`yazi --debug` output"
|
||||
description: Please run `yazi --debug` and paste the debug information here.
|
||||
label: "`ya env` output"
|
||||
description: Run `ya env` and paste the environment information here.
|
||||
render: Shell
|
||||
validations:
|
||||
required: true
|
||||
|
|
@ -65,6 +65,6 @@ body:
|
|||
options:
|
||||
- label: I tried the [latest nightly build](https://yazi-rs.github.io/docs/installation#binaries), and the issue is still reproducible
|
||||
required: true
|
||||
- label: I updated the debug information (`yazi --debug`) input box to the nightly that I tried
|
||||
- label: I updated the environment information (`ya env`) field to the nightly that I tried
|
||||
required: true
|
||||
- label: I can reproduce it after disabling all custom configs/plugins (`mv ~/.config/yazi ~/.config/yazi-backup`)
|
||||
|
|
|
|||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,5 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🚧 Build Issues
|
||||
url: https://github.com/sxyazi/yazi/discussions/new?category=3-build-issues
|
||||
about: If you have issues building Yazi from source code
|
||||
- name: 📝 Documentation Improvement
|
||||
url: https://github.com/yazi-rs/yazi-rs.github.io
|
||||
about: If you'd like to help improve the documentation
|
||||
|
|
|
|||
6
.github/ISSUE_TEMPLATE/feature.yml
vendored
6
.github/ISSUE_TEMPLATE/feature.yml
vendored
|
|
@ -4,10 +4,10 @@ labels: [feature]
|
|||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: debug
|
||||
id: env
|
||||
attributes:
|
||||
label: "`yazi --debug` output"
|
||||
description: Please run `yazi --debug` and paste the debug information here.
|
||||
label: "`ya env` output"
|
||||
description: Run `ya env` and paste the environment information here.
|
||||
render: Shell
|
||||
validations:
|
||||
required: true
|
||||
|
|
|
|||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
|
|
@ -15,3 +15,10 @@ A clear and concise description of the rationale of the changes, to help our rev
|
|||
|
||||
If it has already been detailed in the associated issue, please skip this section.
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have read [CONTRIBUTING.md](https://github.com/sxyazi/yazi/blob/main/CONTRIBUTING.md)
|
||||
- [ ] I confirm this PR follows the [AI Policy](https://github.com/sxyazi/yazi/blob/main/CONTRIBUTING.md#ai-policy)
|
||||
|
||||
<!-- AI bots are not allowed to open PRs in this repository. All PRs must be made by humans and comply with the AI policy. -->
|
||||
|
|
|
|||
4
.github/workflows/cachix.yml
vendored
4
.github/workflows/cachix.yml
vendored
|
|
@ -15,13 +15,13 @@ 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
|
||||
|
||||
- name: Authenticate with Cachix
|
||||
uses: cachix/cachix-action@v16
|
||||
uses: cachix/cachix-action@v17
|
||||
with:
|
||||
name: yazi
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
|
|
|||
14
.github/workflows/check.yml
vendored
14
.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: |
|
||||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
rustup component add clippy
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --all
|
||||
|
|
@ -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: |
|
||||
|
|
@ -42,16 +42,16 @@ jobs:
|
|||
rustup component add rustfmt --toolchain nightly
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
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: JohnnyMorganz/stylua-action@v4
|
||||
- uses: actions/checkout@v7
|
||||
- uses: JohnnyMorganz/stylua-action@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: latest
|
||||
|
|
|
|||
39
.github/workflows/draft.yml
vendored
39
.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 != ''
|
||||
|
|
@ -53,13 +53,13 @@ jobs:
|
|||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Build
|
||||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
|
@ -81,14 +81,14 @@ 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:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Build
|
||||
run: cargo build --profile release-windows --locked --target ${{ matrix.target }}
|
||||
|
|
@ -97,15 +97,16 @@ 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"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: yazi-${{ matrix.target }}.zip
|
||||
|
|
@ -120,20 +121,20 @@ 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:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Build
|
||||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
|
@ -150,7 +151,7 @@ jobs:
|
|||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
@ -167,7 +168,7 @@ jobs:
|
|||
run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: snap-${{ matrix.arch }}
|
||||
path: yazi-${{ matrix.arch }}.snap
|
||||
|
|
@ -176,7 +177,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [build-snap]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: snap-*
|
||||
merge-multiple: true
|
||||
|
|
@ -205,12 +206,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [build-unix, build-windows, build-musl, build-snap]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Draft
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
|
|
@ -233,9 +234,9 @@ 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@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
@ -246,7 +247,7 @@ jobs:
|
|||
git tag --force nightly && git push --force origin tag nightly
|
||||
|
||||
- name: Nightly
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: nightly
|
||||
prerelease: true
|
||||
|
|
|
|||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
winget:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
snapcraft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
|
|||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -21,13 +21,13 @@ 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
|
||||
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: Validate Form
|
||||
name: Validate Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
|
|
@ -12,10 +12,10 @@ jobs:
|
|||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
|
@ -24,8 +24,8 @@ jobs:
|
|||
cd scripts/validate-form
|
||||
npm ci
|
||||
|
||||
- name: Validate Form
|
||||
uses: actions/github-script@v8
|
||||
- name: Validate Issue
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const script = require('./scripts/validate-form/main.js')
|
||||
36
.github/workflows/validate-pr.yml
vendored
Normal file
36
.github/workflows/validate-pr.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
name: Validate PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-list:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
cd scripts/validate-form
|
||||
npm ci
|
||||
|
||||
- name: Validate PR
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const script = require('./scripts/validate-form/main.js')
|
||||
await script({github, context, core})
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||
"runtime.version": "Lua 5.4",
|
||||
"runtime.version": "Lua 5.5",
|
||||
"runtime.special": {
|
||||
"fail": "error"
|
||||
},
|
||||
"workspace.library": ["~/.config/yazi/plugins/types.yazi/"],
|
||||
"diagnostics.disable": ["redefined-local"]
|
||||
}
|
||||
|
|
|
|||
293
CHANGELOG.md
293
CHANGELOG.md
|
|
@ -12,6 +12,112 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Drag and drop ([#4005])
|
||||
- Trash bin ([#4144])
|
||||
- Bulk create ([#3793])
|
||||
- Make help menu a command palette ([#4074])
|
||||
- Input history ([#4104])
|
||||
- Experimental `%y`, `%Y`, `%t`, `%T`, `%yN`, `%YN`, `%tN`, `%TN` shell formatting parameters ([#4108])
|
||||
- Custom VFS provider ([#4118])
|
||||
- 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])
|
||||
- New gait for input `backward` and `forward` actions ([#4012])
|
||||
|
||||
### Changed
|
||||
|
||||
- Rename SFTP sections in `vfs.toml` from `[services.domain]` to `[sftp.domain]` ([#4120]).
|
||||
- Rename `<BackTab>` to `<S-Tab>` ([#3989])
|
||||
- Remove `Url.is_archive` - `archive://` is no longer built in and can now be registered by plugins ([#4118])
|
||||
- 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])
|
||||
- Deprecate `Url.is_regular`, `Url.is_search`, and `Url.domain` in favor of `Url.spec.is_regular`, `Url.spec.is_search`, and `Url.spec.domain`, respectively ([#4118])
|
||||
|
||||
### Fixed
|
||||
|
||||
- Normalize `\\?\`-prefixed Verbatim paths when creating relative symlinks on Windows ([#4067])
|
||||
- Keep package hashes indifferent to line endings when `ya pkg` pulls packages ([#4064])
|
||||
- Use WebP as `magick` preset preloader cache format to keep image transparency ([#4065])
|
||||
- Use cleaned and normalized `$PWD` as startup CWD to avoid MSYS2/git-bash path quirks ([#4068])
|
||||
|
||||
### Improved
|
||||
|
||||
- Eliminate hacks in image and UI conflict resolution ([#4022])
|
||||
|
||||
## [v26.5.6]
|
||||
|
||||
### Added
|
||||
|
||||
- Custom tab name ([#3666])
|
||||
- New `--in` for `search` action to set search directory ([#3696])
|
||||
- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854])
|
||||
- Drag-resize panes with mouse ([#3890])
|
||||
- Multi-file spotter ([#3733])
|
||||
- New `app:theme` action that hot-reload user themes/flavors ([#3906])
|
||||
- Dynamic open/opener Lua API ([#3901])
|
||||
- Dynamic previewer Lua API ([#3891])
|
||||
- Custom styles for plugins ([#3934])
|
||||
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
||||
- Certificate authentication for SFTP VFS provider ([#3716])
|
||||
- New `hovered` condition specifying different icons for hovered files ([#3728])
|
||||
- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638])
|
||||
- New `ya.exec()` API and `ya exec` subcommand to execute an action and await its result ([#3780])
|
||||
- New `sort_fallback` option to control fallback sorting behavior ([#3077])
|
||||
- New `fs.access()` API to access the filesystem ([#3668])
|
||||
- New `relay-notify-push` DDS event to customize the notification handler ([#3642])
|
||||
- New `ind-app-title` DDS event to customize the app title ([#3684])
|
||||
- New `ind-hidden` and `key-hidden` DDS events to change hidden status in Lua ([#3748])
|
||||
- New `marker_symbol` option to specify the symbol used for marking files ([#3689])
|
||||
- New `--discard` for `ya pkg` that discard local changes made to packages ([#3781])
|
||||
- New `bulk_exit` action that customizes the prompt for bulk operations ([#3792])
|
||||
- New `fs.unique()` creates a unique file or directory ([#3677])
|
||||
- New `download` DDS event fires when remote files are downloaded ([#3687])
|
||||
- New `ind-which-activate` DDS event to change the which component behavior ([#3608])
|
||||
- New `hey` DDS event fires when static messages are restored from persistence ([#3725])
|
||||
- New `cx.which` API to access the which component state ([#3617])
|
||||
- New experimental `ya.co()` API that creates a coroutine ([#3757])
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgrade Lua to 5.5 ([#3633])
|
||||
- Change preset <kbd>t</kbd> for creating tabs to <kbd>t</kbd> ⇒ <kbd>t</kbd> to avoid conflict with new <kbd>t</kbd> ⇒ <kbd>r</kbd> for renaming tabs ([#3666])
|
||||
- Make `ui.Style` immutable ([#3862])
|
||||
- Remove `title_format` in favor of new `ind-app-title` DDS event for flexible title customization ([#3684])
|
||||
- Remove `micro_workers` and `macro_workers` in favor of finer control over concurrent workers ([#3661])
|
||||
- Rename `id` of the fetcher rule to `group` ([#3943])
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `fs.unique_name()` in favor of `fs.unique()` to fix a TOCTOU race condition ([#3677])
|
||||
|
||||
### Fixed
|
||||
|
||||
- Chafa v1.18.1 causes random ghost keypresses when previewing images ([#3678])
|
||||
- `paste --force` doesn't work on existing read-only files with the same name ([#3894])
|
||||
- Be a little defensive while parsing the output of `7zz -ba` ([#3744])
|
||||
- Make `ya pkg` ignore default remote name in user Git config ([#3648])
|
||||
- Archive extraction fails for target paths with non-ASCII characters on Windows ([#3607])
|
||||
- Escape backslashes in ImageMagick font path parameter ([#3708])
|
||||
|
||||
### Improved
|
||||
|
||||
- Reduce memory allocations by using Lua 5.5 external strings ([#3634])
|
||||
- Reuse previewed and spotted widgets when possible ([#3765])
|
||||
- Avoid unnecessary allocations in code highlighting ([#3804])
|
||||
|
||||
## [v26.1.22]
|
||||
|
||||
### Added
|
||||
|
|
@ -57,11 +163,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- New experimental `ya.async()` API ([#3422])
|
||||
- New `overall` option to set the overall background color ([#3317])
|
||||
- Rounded corners for indicator bar ([#3419])
|
||||
- New `bulk_rename` command always renames files with the editor ([#2984])
|
||||
- New `bulk_rename` action always renames files with the editor ([#2984])
|
||||
- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037])
|
||||
- New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])
|
||||
- `filter` by full path (prefix + filename) in search view instead of just filename ([#2915])
|
||||
- New `casefy` command for case conversion of the input content ([#3235])
|
||||
- New `casefy` action for case conversion of the input content ([#3235])
|
||||
- Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964])
|
||||
- Support `.deb` packages ([#2807], [#3128], [#3209])
|
||||
- Port several widespread GUI keys to the input component ([#2849])
|
||||
|
|
@ -82,7 +188,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Rename `name` to `url` for open, fetchers, spotters, preloaders, previewers, filetype, and `globs` icon rules to support virtual file system ([#3034])
|
||||
- Rename `mime` fetcher to `mime.local`, and introduce `mime.dir` fetcher to support folder MIME types ([#3222])
|
||||
- Reclassify `hovered` and `preview_hovered` under `[mgr]` of `theme.toml` into `[indicator]` as `current` and `preview`, respectively ([#3419])
|
||||
- Remove `$0` parameter in opener rules to make the `open` command work under empty directories ([#3226])
|
||||
- Remove `$0` parameter in opener rules to make the `open` action work under empty directories ([#3226])
|
||||
- Return `Path` instead of `Url` from `Url:strip_prefix()` and `File.link_to` to enforce type safety ([#3361], [#3385])
|
||||
- Use `body` instead of the term `content` in confirmations ([#2921])
|
||||
- Use `u16` instead of `u32` as the type of `max_width` and `max_height` options to avoid memory exhaustion ([#3313])
|
||||
|
|
@ -90,7 +196,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `$n`, `$@` (\*nix) and `%n`, `%*` (Windows) in `shell` command and opener rules in favor of new shell formatting ([#3232])
|
||||
- Deprecate `$n`, `$@` (\*nix) and `%n`, `%*` (Windows) in `shell` action and opener rules in favor of new shell formatting ([#3232])
|
||||
- Deprecate `ya.hide`, `ya.render`, and `ya.truncate` in favor of `ui.hide`, `ui.render`, and `ui.truncate` ([#2939])
|
||||
- Deprecate `position` property of `ya.input()` in favor of `pos` to align with `ya.confirm()` and its type `ui.Pos` ([#2921])
|
||||
- Deprecate `cx.tasks.progress` in favor of `cx.tasks.summary` ([#3131])
|
||||
|
|
@ -101,7 +207,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- User-prepended open rules do not override presets ([#3360])
|
||||
- Respect user's system media opener instead of hardcoding `mpv` ([#2959])
|
||||
- Incorrect `$0` and `$@` parameters in `shell` command under empty directories ([#3225])
|
||||
- Incorrect `$0` and `$@` parameters in `shell` action under empty directories ([#3225])
|
||||
- Avoid appending a newline when reading clipboard contents ([#3059])
|
||||
- Renew package `rev` only when it's empty ([#3200])
|
||||
- Suspend only when there is a parent process ([#3008])
|
||||
|
|
@ -155,15 +261,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- New `ya.emit()` API ([#2653])
|
||||
- New `fs.calc_size()` API ([#2691])
|
||||
- Allow custom exit code with `quit --code` ([#2609])
|
||||
- New `--hovered` for the `copy` command ([#2709])
|
||||
- New `--hovered` for the `copy` action ([#2709])
|
||||
- `s` and `S` keybinds in the input component ([#2678])
|
||||
- Limit memory usage for previewing large images ([#2602])
|
||||
- Show error when image preview fails ([#2706])
|
||||
- New `ui.Align`, `ui.Wrap`, and `ui.Edge` ([#2802])
|
||||
- Make `ui.Line` renderable ([#2743])
|
||||
- Checks in `ya pub` and `ya emit` subcommands to verify receiver exists and has necessary abilities ([#2696])
|
||||
- Make the hover state for `reveal`, `sort`, and `hidden` commands stable ([#2657])
|
||||
- New `--no-dummy` option for `reveal` command ([#2664])
|
||||
- Make the hover state for `reveal`, `sort`, and `hidden` actions stable ([#2657])
|
||||
- New `--no-dummy` option for `reveal` action ([#2664])
|
||||
- Fall back to `CSI 16 t` when PowerShell OpenSSH returns a fake terminal size ([#2636])
|
||||
|
||||
### Changed
|
||||
|
|
@ -207,7 +313,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Enhance `fzf` integration ([#2553])
|
||||
- Platform-specific key binding ([#2526])
|
||||
- Custom search engine Lua API ([#2452])
|
||||
- New `follow` command to follow files pointed to by symlinks ([#2543])
|
||||
- New `follow` action to follow files pointed to by symlinks ([#2543])
|
||||
- Allow `tab_swap` to cycle tabs ([#2456])
|
||||
- Show error message when directory fails to load ([#2527])
|
||||
- New `symlink_target` to style the target of symbolic links ([#2522])
|
||||
|
|
@ -215,7 +321,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- New `<C-A>` and `<C-E>` keybindings to select entire line for the input component ([#2439])
|
||||
- New `fs.expand_url()` API ([#2476])
|
||||
- New `ui.Text:scroll()` API for setting text to scroll horizontally or vertically ([#2589])
|
||||
- Allow initializing input when opening it with commands like `rename`, `create`, `find`, `filter`, etc. ([#2578])
|
||||
- Allow initializing input when opening it with actions like `rename`, `create`, `find`, `filter`, etc. ([#2578])
|
||||
- New `@sync peek` annotation for sync previewers ([#2487])
|
||||
- New `ya.id("app")` to get `YAZI_ID` in plugins ([#2503])
|
||||
- New `base` field for the `Url` ([#2492])
|
||||
|
|
@ -225,7 +331,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Changed
|
||||
|
||||
- Navigation wraparound with new `arrow prev` and `arrow next` commands ([#2485], [#2540])
|
||||
- Navigation wraparound with new `arrow prev` and `arrow next` actions ([#2485], [#2540])
|
||||
- Swap default key bindings for fzf and zoxide ([#2546])
|
||||
- Switch to `resvg` as the SVG renderer ([#2581])
|
||||
- Make `frag`, `name`, `stem`, `ext`, and `parent` on `Url`, `name` on `tab::Tab`, and `is_hovered` on `fs::File` properties ([#2572])
|
||||
|
|
@ -267,11 +373,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Added
|
||||
|
||||
- Allow to specify layer for keymap commands ([#2399])
|
||||
- Allow to specify layer for keymap actions ([#2399])
|
||||
- New `rt` and `th` allow to access user configuration and theme scheme in sync/async plugins consistently ([#2389], [#2392], [#2393], [#2397])
|
||||
- New `tbl_col` and `tbl_cell` in theme system for spotter table styling ([#2391])
|
||||
- Allow different separators to be applied individually to the left and right sides of the status bar ([#2313])
|
||||
- `ripgrep-all` support for the `search` command ([#2383])
|
||||
- `ripgrep-all` support for the `search` action ([#2383])
|
||||
- Respect the user's `max_width` setting in the preset PDF preloader ([#2331])
|
||||
- Respect the user's `wrap` setting in the preset JSON previewer ([#2337])
|
||||
- Respect the user's `image_alloc` setting in the preset ImageMagick previewer ([#2403])
|
||||
|
|
@ -312,15 +418,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- Mount manager ([#2199])
|
||||
- New `ya.confirm()` API ([#2095])
|
||||
- New `arrow top` and `arrow bot` commands to jump to the top and bottom ([#2294])
|
||||
- Support end of options (`--`) marker for all commands ([#2298])
|
||||
- New `arrow top` and `arrow bot` actions to jump to the top and bottom ([#2294])
|
||||
- Support end of options (`--`) marker for all actions ([#2298])
|
||||
- Replace mode and Vim motions (`W`, `E`, `B`, `^`, `_`) for inputs ([#2143])
|
||||
- New `ya pack -d` subcommand to delete packages ([#2181])
|
||||
- `ya pack` supports adding and deleting multiple packages at once ([#2257])
|
||||
- Theme support for the spotter border and title ([#2002])
|
||||
- Use positional argument instead of `--args` for the `plugin` command ([#2299])
|
||||
- Use positional argument instead of `--args` for the `plugin` action ([#2299])
|
||||
- Support and hide Windows system files by default ([#2149])
|
||||
- New `--no-cwd-file` option for the `close` command ([#2185])
|
||||
- New `--no-cwd-file` option for the `close` action ([#2185])
|
||||
- Prompt users missing fzf in the zoxide plugin ([#2122])
|
||||
- More decent package locking mechanism ([#2168])
|
||||
- Custom modal component API ([#2205])
|
||||
|
|
@ -336,7 +442,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `--args` in the `plugin` command in favor of a 2nd positional parameter ([#2299])
|
||||
- Deprecate `--args` in the `plugin` action in favor of a 2nd positional parameter ([#2299])
|
||||
- Deprecate plugin entry file `init.lua` in favor of `main.lua` ([#2168])
|
||||
- Deprecate `arrow -99999999` and `arrow 99999999` in favor of `arrow top` and `arrow bot` ([#2294])
|
||||
- Deprecate the numeric return value of preloaders in favor of a boolean return value ([#2253])
|
||||
|
|
@ -363,7 +469,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- More supported archive formats to the preset config ([#1926])
|
||||
- New `fs.create()` Lua API ([#2068])
|
||||
- New `--cwd` parameter for the `shell` command and `fs.cwd()` API ([#2060])
|
||||
- New `--cwd` parameter for the `shell` action and `fs.cwd()` API ([#2060])
|
||||
- Allow `noop` for single-key chords by removing the mixing length limit ([#2064])
|
||||
- Support for Android platform in the `for` qualifier of opener ([#2041])
|
||||
|
||||
|
|
@ -392,8 +498,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Spotter ([#1802])
|
||||
- Support transparent image preview ([#1556])
|
||||
- Auto switch between dark and light icons/flavors based on terminal backgrounds ([#1946])
|
||||
- Allow disabling certain preset keybinds with the new `noop` virtual command ([#1882])
|
||||
- New `ya emit` and `ya emit-to` subcommands to emit commands to a specified instance for execution ([#1979])
|
||||
- Allow disabling certain preset keybinds with the new `noop` virtual action ([#1882])
|
||||
- New `ya emit` and `ya emit-to` subcommands to emit actions to a specified instance for execution ([#1979])
|
||||
- Custom styles for the `confirm` component ([#1789])
|
||||
- Make the builtin `extract` plugin support compressed tarballs (`*.tar.gz`, `*.tar.bz2`, etc.) ([#1583])
|
||||
- Launch from preset settings if the user's config cannot be parsed ([#1832])
|
||||
|
|
@ -427,15 +533,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Use an `Error` userdata instead of a plain error code for I/O errors ([#1939])
|
||||
- Remove `ui.ListItem` since it's no longer necessary ([#1772])
|
||||
- Decouple coordinates from `ui.List`, `ui.Bar`, `ui.Border`, and `ui.Gauge` ([#1782])
|
||||
- Make `backspace` command not close the input even when value is empty ([#1680])
|
||||
- Remove the meaningless `--confirm` option to simplify the `shell` command ([#1982])
|
||||
- Make `backspace` action not close the input even when value is empty ([#1680])
|
||||
- Remove the meaningless `--confirm` option to simplify the `shell` action ([#1982])
|
||||
- Use `dark` and `light` instead of `use` under `[flavor]` to support auto-switching between light and dark modes ([#1946])
|
||||
- Unify the `fg_dark` and `fg_light` into one `fg` since `fg_light` is redundant and never used ([#1946])
|
||||
- Extend the available styles for `mode` by separating `mode` from the `separator` styles ([#1953])
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `--sync` option for the `plugin` command ([#1891])
|
||||
- Deprecate `--sync` option for the `plugin` action ([#1891])
|
||||
- Deprecate `ui.Paragraph` in favor of `ui.Text` ([#1776])
|
||||
- Deprecate the task info of `peek()`, `seek()`, and `preload()` from `self` in favor of a `job` parameter ([#1966])
|
||||
- Deprecate parameter list of `entry()` from its first argument in favor of a `job` parameter ([#1966])
|
||||
|
|
@ -492,7 +598,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- New confirm component ([#1167])
|
||||
- Word wrapping in `code` previewer ([#1159])
|
||||
- New `--dir` option for `create` command ([#1505])
|
||||
- New `--dir` option for `create` action ([#1505])
|
||||
- New `ext()` method for `Url` ([#1528])
|
||||
- Make the builtin `code` previewer handle invalid carriage return chars and binary streams better ([#1550])
|
||||
|
||||
|
|
@ -515,10 +621,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Start with multiple tabs with different paths ([#1443])
|
||||
- Key notion shorthands such as `<C-S-x>` as `<C-X>` ([#1448])
|
||||
- Support `F13` - `F19` keys ([#1446])
|
||||
- New `--cursor` for the `shell` command ([#1422])
|
||||
- New `search_do` command to make it easier to achieve a flat view ([#1431])
|
||||
- New `--cursor` for the `shell` action ([#1422])
|
||||
- New `search_do` action to make it easier to achieve a flat view ([#1431])
|
||||
- Portrait orientation preview for EXIF image ([#1412])
|
||||
- Keybinding for the `hardlink` command ([#1461])
|
||||
- Keybinding for the `hardlink` action ([#1461])
|
||||
- New `empty` previewer for empty and `/proc/*` files ([#1482])
|
||||
- Note about filtering in the help menu ([#1361])
|
||||
- New `tab` DDS event on tab switch ([#1474])
|
||||
|
|
@ -546,12 +652,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Font preview ([#1048])
|
||||
- SVG, HEIC, AVIF, and JPEG XL preview support ([#1050], [#1249])
|
||||
- Simplify keybindings ([#1241])
|
||||
- New command `hardlink` to create hard links ([#1268])
|
||||
- New `hardlink` action to create hard links ([#1268])
|
||||
- Keep file creation time on macOS and Windows ([#1169])
|
||||
- Sort randomly ([#1291])
|
||||
- New linemode to show file ownership ([#1238])
|
||||
- New linemode to show file ctime ([#1295])
|
||||
- New `--hovered` option for the `rename` and `remove` commands ([#1227])
|
||||
- New `--hovered` option for the `rename` and `remove` actions ([#1227])
|
||||
- Support Super/Command/Windows key with `D-` notation ([#1069])
|
||||
- Interactive `cd` path auto-completion supports `~` expansion ([#1081])
|
||||
- Preview files containing non-UTF-8 characters ([#958])
|
||||
|
|
@ -576,7 +682,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Use Ctrl+c instead of Ctrl+q as the universal close key to follow the conventions
|
||||
- Replace Alt+k/Alt+j with K/J as the `seek` keybindings to avoid Alt key not working in certain terminals
|
||||
- Replace Ctrl+Enter with Shift+Enter as the alternative key for Shift+o so that it corresponds to Enter being `o` (without Shift)
|
||||
- keep original state of `sort` command in favor of specifying `yes` or `no` to explicitly apply a new state to its `--reverse`, `--dir-first`, and `--translit`
|
||||
- keep original state of `sort` action in favor of specifying `yes` or `no` to explicitly apply a new state to its `--reverse`, `--dir-first`, and `--translit`
|
||||
- Move `mime` plugin from `[plugin.preloaders]` to `[plugin.fetchers]` of yazi.toml
|
||||
- Turn `success` and `code` into properties of `Status` instead of methods
|
||||
- Remove `fs.cha_follow(url)` in favor of `fs.cha(url, true)`
|
||||
|
|
@ -603,7 +709,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Data distribution service ([#826], [#855], [#861], [#867], [#868], [#871], [#880], [#895], [#913], [#928], [#933], [#940])
|
||||
- Re-implement fzf and zoxide as built-in plugins ([#884], [#881])
|
||||
- Preserve files' modified at timestamp while copying ([#926])
|
||||
- New `--orphan` option for `shell` command ([#887])
|
||||
- New `--orphan` option for `shell` action ([#887])
|
||||
- Smart-case for completion of interactive `cd` paths ([#910])
|
||||
- Allow creating a tab with the startup directory through `tab_create` without specifying a path ([#917])
|
||||
- Bunch of new debugging information to `yazi --debug` ([#824])
|
||||
|
|
@ -616,7 +722,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate the `jump` command in favor of `plugin fzf` and `plugin zoxide` ([#884], [#881])
|
||||
- Deprecate the `jump` action in favor of `plugin fzf` and `plugin zoxide` ([#884], [#881])
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -648,7 +754,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- New `<Home>`, `<End>`, and `<Delete>` keybindings for inputs ([#665])
|
||||
- New `<C-p>` and `<C-n>` for the select component to move the cursor up/down ([#779])
|
||||
- New `Ctrl+[` as an alternative to the escape key ([#763])
|
||||
- New option `--hovered` for the `open` command allows only to open the currently hovered file ([#687])
|
||||
- New option `--hovered` for the `open` action allows only to open the currently hovered file ([#687])
|
||||
- Support musl builds for Linux ([#759])
|
||||
- New `--debug` flag to print debug information ([#794])
|
||||
- Send a foreground notification to the user when the process fails to run ([#775])
|
||||
|
|
@ -659,7 +765,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Ability to sort candidates in the which component ([#662])
|
||||
- Expose selected/yanked files as Lua API ([#674])
|
||||
- New `cx.yanked` API to access yanked files ([#788])
|
||||
- New `$0` (Unix) / `%0` (Windows) to access the hovered file in `shell` command ([#738])
|
||||
- New `$0` (Unix) / `%0` (Windows) to access the hovered file in `shell` action ([#738])
|
||||
- New `ya.hide()` API to hide the UI temporarily ([#792])
|
||||
- Allow both `/` and `\` for folder creation on Windows ([#751])
|
||||
- New `parse()` method for the line elements to parse ANSI sequences
|
||||
|
|
@ -686,13 +792,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Added
|
||||
|
||||
- Preview image over SSH ([#585])
|
||||
- New `unyank` command ([#313])
|
||||
- New `unyank` action ([#313])
|
||||
- Customize number of columns of the which component ([#571])
|
||||
- Support passing arguments to plugin ([#587])
|
||||
- New `image_quality` and `sixel_fraction` options to configure the image preview quality ([#576])
|
||||
- New `ya.which()` API for custom key events ([#617])
|
||||
- New `ya.quote()` API to quote strings safely
|
||||
- `plugin` command for each layer
|
||||
- `plugin` action for each layer
|
||||
- Plugin-specific state persistence ([#590])
|
||||
- Allow to configure image filter ([#586])
|
||||
- Shorten unit names and add more units to `ya.readable_size()`
|
||||
|
|
@ -730,7 +836,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Improved
|
||||
|
||||
- New event system allows multiple commands to reuse a single render ([#561])
|
||||
- New event system allows multiple actions to reuse a single render ([#561])
|
||||
|
||||
## [v0.2.1]
|
||||
|
||||
|
|
@ -742,17 +848,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Added
|
||||
|
||||
- New `filter` command to filter files on the fly ([#454])
|
||||
- New `filter` action to filter files on the fly ([#454])
|
||||
- Sort by file extension ([#405])
|
||||
- Custom preloader and previewer ([#401])
|
||||
- New `plugin` command to run Lua plugins
|
||||
- New `plugin` action to run Lua plugins
|
||||
- Auto-completion for input component ([#324], [#353], [#352])
|
||||
- Start with the specified file hovers over ([#358])
|
||||
- Emacs readline keybindings for inputs ([#345], [#382])
|
||||
- New `--empty` and `--cursor` options for the `rename` command ([#513])
|
||||
- New `--follow` option for `paste` command ([#436])
|
||||
- Make `copy` command work over SSH with OSC 52 ([#447])
|
||||
- New `reveal` command ([#341])
|
||||
- New `--empty` and `--cursor` options for the `rename` action ([#513])
|
||||
- New `--follow` option for `paste` action ([#436])
|
||||
- Make `copy` action work over SSH with OSC 52 ([#447])
|
||||
- New `reveal` action ([#341])
|
||||
- Support colored icons ([#503])
|
||||
- Support highlighting specific file types ([#510])
|
||||
- Make the position of input and select components customizable ([#361])
|
||||
|
|
@ -774,8 +880,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Changed
|
||||
|
||||
- Rename the option `layout` to `ratio` to make it more self-explanatory
|
||||
- Rename the `peek` command to `seek` to better convey the action of "seeking for" content to preview
|
||||
- Rename the `--dir_first` option of `sort` command to `--dir-first` to make it consistent with the style of other commands
|
||||
- Rename the `peek` action to `seek` to better convey the action of "seeking for" content to preview
|
||||
- Rename the `--dir_first` option of `sort` action to `--dir-first` to make it consistent with the style of other actions
|
||||
- Replace `[plugins.preload]` with the `init.lua` entry file
|
||||
|
||||
### Fixed
|
||||
|
|
@ -800,22 +906,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Added
|
||||
|
||||
- New `find` command to find files ([#104])
|
||||
- New `find` action to find files ([#104])
|
||||
- Linemode to show extra file info ([#291])
|
||||
- New `sort_sensitive` option ([#155])
|
||||
- Cross-platform opener rules ([#289])
|
||||
- Multiple openers for a single open rule ([#154])
|
||||
- Vim-like `gg`, `G` in the preset key mappings for boundary jumps
|
||||
- Theme system ([#161])
|
||||
- New `--force` option for `remove`, `create`, `rename` commands ([#179], [#208])
|
||||
- New `--force` option for `remove`, `create`, `rename` actions ([#179], [#208])
|
||||
- Image preview within tmux ([#147])
|
||||
- New `link` command that creates symlinks to the yanked files ([#167])
|
||||
- New `link` action creates symlinks to the yanked files ([#167])
|
||||
- New `orphan` option for opener rules to detach processes from the task scheduler ([#216])
|
||||
- New `backward` and `forward` commands
|
||||
- New `--smart` option for the `find` command to support smart case ([#240])
|
||||
- New `backward` and `forward` actions
|
||||
- New `--smart` option for the `find` action to support smart case ([#240])
|
||||
- Sorting for each tab individually ([#131])
|
||||
- Suspend process with `Ctrl+z` ([#120])
|
||||
- Percentage values for the `arrow` command to scroll half/full page (with newly added Vi-like `<C-u>`, `<C-d>`, `<C-b>`, and `<C-f>` keybindings) ([#213])
|
||||
- Percentage values for the `arrow` action to scroll half/full page (with newly added Vi-like `<C-u>`, `<C-d>`, `<C-b>`, and `<C-f>` keybindings) ([#213])
|
||||
- Show keywords when in search mode ([#152])
|
||||
- Tab switch wraparound ([#160])
|
||||
- Highlight matched keywords in find mode ([#211])
|
||||
|
|
@ -824,7 +930,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Use of environment variables in `cd` paths ([#241])
|
||||
- Nix Flakes package ([#205])
|
||||
- New `V`, `D`, `C` Vim-like keybindings for the input component
|
||||
- New `--no-cwd-file` option for the `quit` command to exit without writing the CWD file ([#245])
|
||||
- New `--no-cwd-file` option for the `quit` action to exit without writing the CWD file ([#245])
|
||||
- Fallback to built-in code highlighting if `jq` is not installed ([#151])
|
||||
- New `realtime` option for the input to support real-time input feedback ([#127])
|
||||
- RGBA-16 image preview ([#250])
|
||||
|
|
@ -861,7 +967,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Scrollable preview ([#86])
|
||||
- Natural sorting ([#82])
|
||||
- Windows support
|
||||
- New `copy` command to copy file paths to clipboard ([#72])
|
||||
- New `copy` action to copy file paths to clipboard ([#72])
|
||||
- File chooser mode ([#69])
|
||||
- Show symlink path ([#67])
|
||||
- Respect `$EDITOR` environment variable when opening text files ([#91])
|
||||
|
|
@ -894,8 +1000,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Code highlighting supports more languages ([#22])
|
||||
- Change the shell CWD on exit with the shell wrapper ([#40])
|
||||
- Allow customizing the display name of openers ([#31])
|
||||
- New `shell` command ([#24])
|
||||
- Command template support for the `shell` command ([#48])
|
||||
- New `shell` action ([#24])
|
||||
- Command template support for the `shell` action ([#48])
|
||||
- Interactive `cd` ([#43])
|
||||
- Show the output of running tasks in real time ([#17])
|
||||
- Allow using the current directory name as tab name ([#41])
|
||||
|
|
@ -910,7 +1016,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- PDF cache cannot be generated with a large `max_width` value ([#28])
|
||||
- `show_hidden` option not working ([#47])
|
||||
- Wrong task name when `shell` command has no arguments
|
||||
- Wrong task name when `shell` action has no arguments
|
||||
|
||||
### Improved
|
||||
|
||||
|
|
@ -923,7 +1029,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Added
|
||||
|
||||
- New `sort` command to change sorting method on the fly ([#7])
|
||||
- New `sort` action to change sorting method on the fly ([#7])
|
||||
- Which-key component to support multi-key chords ([#4])
|
||||
- Hover the cursor over newly created files automatically ([#10])
|
||||
- Make folders openable ([#9])
|
||||
|
|
@ -944,8 +1050,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Arrow keys are now bound for navigation by default (along with existing Vim-style bindings)
|
||||
- Horizontal scrolling support for the `input` component
|
||||
- Visual mode for the input component
|
||||
- New `yank` and `paste` commands for the input component
|
||||
- New `undo` and `redo` commands for the `input` component
|
||||
- New `yank` and `paste` actions for the input component
|
||||
- New `undo` and `redo` actions for the `input` component
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -960,7 +1066,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Added
|
||||
|
||||
- Preset configurations
|
||||
- New `open` command
|
||||
- New `open` action
|
||||
- Select component for interactive `open`
|
||||
- Plain text and archive preview
|
||||
- Search files with `fd` and `rg`
|
||||
|
|
@ -1008,6 +1114,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[v25.12.29]: https://github.com/sxyazi/yazi/compare/v25.5.31...v25.12.29
|
||||
[v26.1.4]: https://github.com/sxyazi/yazi/compare/v25.12.29...v26.1.4
|
||||
[v26.1.22]: https://github.com/sxyazi/yazi/compare/v26.1.4...v26.1.22
|
||||
[v26.5.6]: https://github.com/sxyazi/yazi/compare/v26.1.22...v26.5.6
|
||||
[#4]: https://github.com/sxyazi/yazi/pull/4
|
||||
[#5]: https://github.com/sxyazi/yazi/pull/5
|
||||
[#6]: https://github.com/sxyazi/yazi/pull/6
|
||||
|
|
@ -1545,6 +1652,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3038]: https://github.com/sxyazi/yazi/pull/3038
|
||||
[#3059]: https://github.com/sxyazi/yazi/pull/3059
|
||||
[#3067]: https://github.com/sxyazi/yazi/pull/3067
|
||||
[#3077]: https://github.com/sxyazi/yazi/pull/3077
|
||||
[#3083]: https://github.com/sxyazi/yazi/pull/3083
|
||||
[#3084]: https://github.com/sxyazi/yazi/pull/3084
|
||||
[#3091]: https://github.com/sxyazi/yazi/pull/3091
|
||||
|
|
@ -1612,3 +1720,66 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3566]: https://github.com/sxyazi/yazi/pull/3566
|
||||
[#3582]: https://github.com/sxyazi/yazi/pull/3582
|
||||
[#3594]: https://github.com/sxyazi/yazi/pull/3594
|
||||
[#3607]: https://github.com/sxyazi/yazi/pull/3607
|
||||
[#3608]: https://github.com/sxyazi/yazi/pull/3608
|
||||
[#3617]: https://github.com/sxyazi/yazi/pull/3617
|
||||
[#3633]: https://github.com/sxyazi/yazi/pull/3633
|
||||
[#3634]: https://github.com/sxyazi/yazi/pull/3634
|
||||
[#3638]: https://github.com/sxyazi/yazi/pull/3638
|
||||
[#3642]: https://github.com/sxyazi/yazi/pull/3642
|
||||
[#3648]: https://github.com/sxyazi/yazi/pull/3648
|
||||
[#3661]: https://github.com/sxyazi/yazi/pull/3661
|
||||
[#3666]: https://github.com/sxyazi/yazi/pull/3666
|
||||
[#3668]: https://github.com/sxyazi/yazi/pull/3668
|
||||
[#3677]: https://github.com/sxyazi/yazi/pull/3677
|
||||
[#3678]: https://github.com/sxyazi/yazi/pull/3678
|
||||
[#3684]: https://github.com/sxyazi/yazi/pull/3684
|
||||
[#3687]: https://github.com/sxyazi/yazi/pull/3687
|
||||
[#3689]: https://github.com/sxyazi/yazi/pull/3689
|
||||
[#3696]: https://github.com/sxyazi/yazi/pull/3696
|
||||
[#3708]: https://github.com/sxyazi/yazi/pull/3708
|
||||
[#3716]: https://github.com/sxyazi/yazi/pull/3716
|
||||
[#3725]: https://github.com/sxyazi/yazi/pull/3725
|
||||
[#3728]: https://github.com/sxyazi/yazi/pull/3728
|
||||
[#3733]: https://github.com/sxyazi/yazi/pull/3733
|
||||
[#3744]: https://github.com/sxyazi/yazi/pull/3744
|
||||
[#3748]: https://github.com/sxyazi/yazi/pull/3748
|
||||
[#3757]: https://github.com/sxyazi/yazi/pull/3757
|
||||
[#3765]: https://github.com/sxyazi/yazi/pull/3765
|
||||
[#3780]: https://github.com/sxyazi/yazi/pull/3780
|
||||
[#3781]: https://github.com/sxyazi/yazi/pull/3781
|
||||
[#3792]: https://github.com/sxyazi/yazi/pull/3792
|
||||
[#3793]: https://github.com/sxyazi/yazi/pull/3793
|
||||
[#3804]: https://github.com/sxyazi/yazi/pull/3804
|
||||
[#3813]: https://github.com/sxyazi/yazi/pull/3813
|
||||
[#3846]: https://github.com/sxyazi/yazi/pull/3846
|
||||
[#3854]: https://github.com/sxyazi/yazi/pull/3854
|
||||
[#3862]: https://github.com/sxyazi/yazi/pull/3862
|
||||
[#3890]: https://github.com/sxyazi/yazi/pull/3890
|
||||
[#3891]: https://github.com/sxyazi/yazi/pull/3891
|
||||
[#3894]: https://github.com/sxyazi/yazi/pull/3894
|
||||
[#3901]: https://github.com/sxyazi/yazi/pull/3901
|
||||
[#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
|
||||
[#4012]: https://github.com/sxyazi/yazi/pull/4012
|
||||
[#4022]: https://github.com/sxyazi/yazi/pull/4022
|
||||
[#4031]: https://github.com/sxyazi/yazi/pull/4031
|
||||
[#4040]: https://github.com/sxyazi/yazi/pull/4040
|
||||
[#4064]: https://github.com/sxyazi/yazi/pull/4064
|
||||
[#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
|
||||
[#4104]: https://github.com/sxyazi/yazi/pull/4104
|
||||
[#4108]: https://github.com/sxyazi/yazi/pull/4108
|
||||
[#4118]: https://github.com/sxyazi/yazi/pull/4118
|
||||
[#4120]: https://github.com/sxyazi/yazi/pull/4120
|
||||
[#4144]: https://github.com/sxyazi/yazi/pull/4144
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ This guide will help you understand how to contribute to the project.
|
|||
3. [Development Setup](#development-setup)
|
||||
4. [How to Contribute](#how-to-contribute)
|
||||
5. [Pull Requests](#pull-requests)
|
||||
6. [AI Policy](#ai-policy)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
@ -164,3 +165,10 @@ We want you to succeed, and it can be discouraging to find that a lot of re-work
|
|||
- Your code passes all tests and lints.
|
||||
- Your pull request description clearly explains the changes and why they are needed.
|
||||
4. Address any review comments. Make sure to push updates to the same branch on your fork.
|
||||
|
||||
## AI Policy
|
||||
|
||||
1. All issue, PR, and discussion descriptions must be written by humans, not AI.
|
||||
2. Any use of AI must be disclosed. You must declare which model you used and the extent of AI assistance.
|
||||
3. Any AI-generated code must be reviewed, tested, and simplified by a human before publishing. This requires you to fully understand how it interacts with the greater system without AI assistance.
|
||||
4. Any AI tools used must explicitly state they do not assert copyright over the work.
|
||||
|
|
|
|||
3403
Cargo.lock
generated
3403
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
69
Cargo.toml
69
Cargo.toml
|
|
@ -1,8 +1,17 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
resolver = "3"
|
||||
members = [ "yazi-*" ]
|
||||
default-members = [ "yazi-fm", "yazi-cli" ]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
version = "26.5.6"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
rust-version = "1.95.0"
|
||||
|
||||
[profile.dev]
|
||||
debug = "line-tables-only"
|
||||
|
||||
|
|
@ -27,42 +36,52 @@ debug = false
|
|||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "8.0.1"
|
||||
anyhow = "1.0.100"
|
||||
anyhow = "1.0.104"
|
||||
arc-swap = { version = "1.9.2", features = [ "serde" ] }
|
||||
base64 = "0.22.1"
|
||||
bitflags = { version = "2.10.0", features = [ "serde" ] }
|
||||
clap = { version = "4.5.54", features = [ "derive" ] }
|
||||
bitflags = { version = "2.13.1", features = [ "serde" ] }
|
||||
chrono = "0.4.45"
|
||||
clap = { version = "4.6.2", features = [ "derive" ] }
|
||||
compact_str = { version = "0.10.0", features = [ "serde" ] }
|
||||
core-foundation-sys = "0.8.7"
|
||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
||||
data-encoding = "2.11.0"
|
||||
dirs = "6.0.0"
|
||||
dyn-clone = "1.0.20"
|
||||
either = { version = "1.16.0" }
|
||||
foldhash = "0.2.0"
|
||||
futures = "0.3.31"
|
||||
globset = "0.4.18"
|
||||
hashbrown = { version = "0.16.1", features = [ "serde" ] }
|
||||
indexmap = { version = "2.13.0", features = [ "serde" ] }
|
||||
libc = "0.2.180"
|
||||
lru = "0.16.3"
|
||||
mlua = { version = "0.11.5", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
|
||||
objc2 = "0.6.3"
|
||||
ordered-float = { version = "5.1.0", features = [ "serde" ] }
|
||||
futures = "0.3.33"
|
||||
globset = "0.4.19"
|
||||
hashbrown = { version = "0.17.1", features = [ "serde" ] }
|
||||
image = { version = "0.25.10", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp" ] }
|
||||
indexmap = { version = "2.14.0", features = [ "serde" ] }
|
||||
inventory = "0.3.24"
|
||||
libc = "0.2.186"
|
||||
lru = "0.18.1"
|
||||
mlua = { version = "0.12.0", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] }
|
||||
objc2 = "0.6.4"
|
||||
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.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] }
|
||||
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
regex = "1.12.2"
|
||||
russh = { version = "0.56.0", default-features = false, features = [ "ring", "rsa" ] }
|
||||
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.13.1"
|
||||
russh = { version = "0.62.2", default-features = false, features = [ "ring", "rsa" ] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.149"
|
||||
serde = { version = "1.0.229", features = [ "derive" ] }
|
||||
serde_json = "1.0.151"
|
||||
serde_with = "3.21.0"
|
||||
strum = { version = "0.28.0", features = [ "derive" ] }
|
||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.49.0", features = [ "full" ] }
|
||||
thiserror = "2.0.19"
|
||||
tokio = { version = "1.53.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.18"
|
||||
tokio-util = "0.7.18"
|
||||
toml = { version = "0.9.11" }
|
||||
toml = { version = "1.1.3" }
|
||||
tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
typed-path = "0.12.0"
|
||||
twox-hash = { version = "2.1.3", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
typed-path = "0.12.3"
|
||||
unicode-width = { version = "0.2.2", default-features = false }
|
||||
uzers = "0.12.2"
|
||||
|
||||
|
|
|
|||
56
README.md
56
README.md
|
|
@ -1,16 +1,10 @@
|
|||
<div align="center">
|
||||
<a href="https://go.warp.dev/yazi" target="_blank">
|
||||
<sup>Special thanks to:</sup>
|
||||
<br>
|
||||
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png">
|
||||
<br>
|
||||
<h>Warp, built for coding with multiple AI agents</b>
|
||||
<br>
|
||||
<sup>Available for macOS, Linux and Windows</sup>
|
||||
</a>
|
||||
</div>
|
||||
<sup>Special thanks to:</sup><br>
|
||||
|
||||
<br>
|
||||
| <a href="https://go.warp.dev/yazi" target="_blank"><img alt="Warp sponsorship" width=350 src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png"><br><b>Warp, built for coding with multiple AI agents</b><br><sup>Available for macOS, Linux and Windows</sup></a> |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
</div>
|
||||
|
||||
## Yazi - ⚡️ Blazing Fast Terminal File Manager
|
||||
|
||||
|
|
@ -29,8 +23,8 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
|
|||
- 🧰 Integration with ripgrep, fd, fzf, zoxide
|
||||
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
|
||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
|
||||
- 🔄 Bulk Renaming, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
|
||||
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
|
||||
- 🔄 Bulk Rename/Create, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
|
||||
- 🎨 Theme System, Mouse Support, Drag and Drop, Trash Bin, Custom Layouts, CSI u, OSC 52
|
||||
- ... and more!
|
||||
|
||||
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7
|
||||
|
|
@ -53,24 +47,24 @@ Yazi is currently in heavy development, expect breaking changes.
|
|||
|
||||
## Image Preview
|
||||
|
||||
| Platform | Protocol | Support |
|
||||
| ---------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |
|
||||
| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
|
||||
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
|
||||
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
|
||||
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio renders images at incorrect sizes |
|
||||
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
|
||||
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required |
|
||||
| Platform | Protocol | Support |
|
||||
| ---------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------- |
|
||||
| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
|
||||
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
|
||||
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
|
||||
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| [Rio](https://github.com/raphamorim/rio) (>= 0.3.9) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
|
||||
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
|
||||
| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
|
||||
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
|
||||
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required (>= 1.16.0) |
|
||||
|
||||
See https://yazi-rs.github.io/docs/image-preview for details.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Name=Yazi
|
||||
Name=Yazi File Manager
|
||||
Icon=yazi
|
||||
Comment=Blazing fast terminal file manager written in Rust, based on async I/O
|
||||
Terminal=true
|
||||
TryExec=yazi
|
||||
Exec=yazi %u
|
||||
Exec=yazi %f
|
||||
Type=Application
|
||||
MimeType=inode/directory
|
||||
Categories=Utility;Core;System;FileTools;FileManager;ConsoleOnly
|
||||
Categories=System;FileManager;FileTools;ConsoleOnly
|
||||
Keywords=File;Manager;Explorer;Browser;Launcher
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize"],"version":"0.2"}
|
||||
{"flagWords":[],"version":"0.2","language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU","fellback","watchee","Textlike","sstr","pointee","writef","wakeup","nonblocking","sigwinch","timespec","termios","tcgetattr","tcsetattr","tcgetwinsize","rustix","codepoint","codepoints","Raterm","mimetypes","Mimelist","renderables","redrawer","Padable"]}
|
||||
12
flake.lock
generated
12
flake.lock
generated
|
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1768875095,
|
||||
"narHash": "sha256-dYP3DjiL7oIiiq3H65tGIXXIT1Waiadmv93JS0sS+8A=",
|
||||
"lastModified": 1783279667,
|
||||
"narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ed142ab1b3a092c4d149245d0c4126a5d7ea00b0",
|
||||
"rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -48,11 +48,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769091129,
|
||||
"narHash": "sha256-Jj/vIHjiu4OdDIrDXZ3xOPCJrMZZKzhE2UIVXV/NYzY=",
|
||||
"lastModified": 1783320166,
|
||||
"narHash": "sha256-l7C/OsjcnWDOk2K3ssj+SBduwL67LashjBqis9+t468=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "131e22d6a6d54ab72aeef6a5a661ab7005b4c596",
|
||||
"rev": "20ee15370c9256669d66968b89ee20a4b0a4e673",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
rev = self.shortRev or self.dirtyShortRev or "dirty";
|
||||
date = self.lastModifiedDate or self.lastModified or "19700101";
|
||||
version =
|
||||
(builtins.fromTOML (builtins.readFile ./yazi-fm/Cargo.toml)).package.version
|
||||
(builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version
|
||||
+ "pre${builtins.substring 0 8 date}_${rev}";
|
||||
in
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
mkShell,
|
||||
yazi,
|
||||
toolchain,
|
||||
nodePackages,
|
||||
cspell,
|
||||
yazi-unwrapped,
|
||||
}:
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ mkShell {
|
|||
"clippy"
|
||||
];
|
||||
})
|
||||
nodePackages.cspell
|
||||
cspell
|
||||
];
|
||||
|
||||
inputsFrom = [ yazi-unwrapped ];
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
|||
|
||||
cargoLock = {
|
||||
lockFile = "${src}/Cargo.lock";
|
||||
#outputHashes = {
|
||||
# "mlua-0.10.0" = "sha256-Xg6/jc+UP8tbJJ6x1sbAgt8ZHt051xEBBcjmikQqYlw=";
|
||||
#};
|
||||
};
|
||||
|
||||
env = {
|
||||
|
|
@ -54,6 +51,11 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
|||
--fish ./yazi-boot/completions/yazi.fish \
|
||||
--zsh ./yazi-boot/completions/_yazi
|
||||
|
||||
installShellCompletion --cmd ya \
|
||||
--bash ./yazi-cli/completions/ya.bash \
|
||||
--fish ./yazi-cli/completions/ya.fish \
|
||||
--zsh ./yazi-cli/completions/_ya
|
||||
|
||||
# Resize logo
|
||||
for RES in 16 24 32 48 64 128 256; do
|
||||
mkdir -p $out/share/icons/hicolor/"$RES"x"$RES"/apps
|
||||
|
|
@ -75,7 +77,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
|||
hash = "sha256-kEVXejDg4ChFoMNBvKlwdFEyUuTcY2VuK9j0PdafKus=";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
meta = {
|
||||
description = "Blazing fast terminal file manager written in Rust, based on async I/O";
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ export YAZI_GEN_COMPLETIONS=1
|
|||
git config --global --add safe.directory "*"
|
||||
cargo build --release --locked --target "$1"
|
||||
|
||||
# Copy the binaries to a known location
|
||||
mkdir -p "target/release"
|
||||
cp "target/$1/release/ya" "target/release/ya"
|
||||
cp "target/$1/release/yazi" "target/release/yazi"
|
||||
|
||||
# Package deb
|
||||
if [[ "$ARTIFACT_NAME" == *-linux-* ]] && { [[ "$ARTIFACT_NAME" == *-aarch64-* ]] || [[ "$ARTIFACT_NAME" == *-x86_64-* ]]; }; then
|
||||
cargo install cargo-deb
|
||||
|
|
@ -16,8 +21,8 @@ fi
|
|||
|
||||
# Create the artifact
|
||||
mkdir -p "$ARTIFACT_NAME/completions"
|
||||
cp "target/$1/release/ya" "$ARTIFACT_NAME"
|
||||
cp "target/$1/release/yazi" "$ARTIFACT_NAME"
|
||||
cp "target/release/ya" "$ARTIFACT_NAME"
|
||||
cp "target/release/yazi" "$ARTIFACT_NAME"
|
||||
cp yazi-cli/completions/* "$ARTIFACT_NAME/completions"
|
||||
cp yazi-boot/completions/* "$ARTIFACT_NAME/completions"
|
||||
cp README.md LICENSE "$ARTIFACT_NAME"
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ prev_ver=$(grep -oE '^\[v[0-9][^]]+\]' CHANGELOG.md | tail -n1 | tr -d '[]')
|
|||
link="\[v$1\]: https://github.com/sxyazi/yazi/compare/$prev_ver...v$1"
|
||||
perl -pi -e 's{(\['"$prev_ver"'\]:[^\n]+\n)}{$1'"$link"'\n}s' CHANGELOG.md
|
||||
|
||||
ESLINT_USE_FLAT_CONFIG=true eslint -c ~/.config/rules/eslint/eslint.config.cjs --fix -- $TOML_FILES
|
||||
eslint -c ~/.config/rules/eslint/eslint.config.js --fix -- $TOML_FILES
|
||||
|
|
|
|||
|
|
@ -2,19 +2,32 @@ const LABEL_NAME = "needs info"
|
|||
const RE_VERSION = /Yazi\s+Version\s*:\s\d+\.\d+\.\d+\s\(/gm
|
||||
const RE_DEPENDENCIES = /Dependencies\s+[/a-z]+\s*:\s/gm
|
||||
const RE_CHECKLIST = /#{3}\s+Checklist\s+(?:^-\s+\[x]\s+.+?(?:\n|\r\n|$)){2}/gm
|
||||
const RE_PR_CHECKLIST = /#{2}\s+Checklist\s+(?:^-\s+\[x]\s+.+?(?:\n|\r\n|$)){2}/gm
|
||||
|
||||
function pullRequestBody(content) {
|
||||
if (RE_PR_CHECKLIST.test(content)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return "All required checklist items must be checked in the PR description."
|
||||
}
|
||||
|
||||
function bugReportBody(creator, content, hash) {
|
||||
if (RE_DEPENDENCIES.test(content) && RE_CHECKLIST.test(content) && new RegExp(` \\(${hash}[a-f0-9]? `).test(content)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return `Hey @${creator}, I noticed that you did not correctly follow the issue template. Please ensure that:
|
||||
return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!
|
||||
|
||||
I noticed that you did not correctly follow the issue template. Please ensure that:
|
||||
|
||||
- The bug can still be reproduced on the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).
|
||||
- The debug information (\`yazi --debug\`) is updated for the newest nightly.
|
||||
- The environment info (\`ya env\`) is updated for the newest nightly.
|
||||
- The non-optional items in the checklist are checked.
|
||||
|
||||
Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
|
||||
|
||||
Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏
|
||||
`
|
||||
}
|
||||
|
||||
|
|
@ -23,16 +36,26 @@ function featureRequestBody(creator, content) {
|
|||
return null
|
||||
}
|
||||
|
||||
return `Hey @${creator}, I noticed that you did not correctly follow the issue template. Please ensure that:
|
||||
return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!
|
||||
|
||||
I noticed that you did not correctly follow the issue template. Please ensure that:
|
||||
|
||||
- The requested feature does not exist in the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).
|
||||
- The debug information (\`yazi --debug\`) is updated for the newest nightly.
|
||||
- The environment info (\`ya env\`) is updated for the newest nightly.
|
||||
- The non-optional items in the checklist are checked.
|
||||
|
||||
Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
|
||||
|
||||
Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏
|
||||
`
|
||||
}
|
||||
|
||||
function skipValidation(context) {
|
||||
const login = context.payload.issue?.user?.login || context.payload.pull_request?.user?.login
|
||||
const owner = context.payload.repository?.owner?.login || context.repo.owner
|
||||
return !!login && login === owner
|
||||
}
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
async function nightlyHash() {
|
||||
try {
|
||||
|
|
@ -66,7 +89,7 @@ module.exports = async ({ github, context, core }) => {
|
|||
const { data: events } = await github.rest.issues.listEvents({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
per_page : 100,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
const all = events.filter(v => v.event === "labeled" && v.label?.name === LABEL_NAME)
|
||||
|
|
@ -82,7 +105,7 @@ module.exports = async ({ github, context, core }) => {
|
|||
const { data: events } = await github.rest.issues.listEvents({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
per_page : 100,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
const all = events.filter(v => v.event === "unlabeled" && v.label?.name === LABEL_NAME)
|
||||
|
|
@ -101,14 +124,14 @@ module.exports = async ({ github, context, core }) => {
|
|||
await github.rest.issues.removeLabel({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
name : LABEL_NAME,
|
||||
name: LABEL_NAME,
|
||||
})
|
||||
await hideOldComments(id)
|
||||
} else if (mark && !marked && !await removedLabelManually(id)) {
|
||||
await github.rest.issues.addLabels({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
labels : [LABEL_NAME],
|
||||
labels: [LABEL_NAME],
|
||||
})
|
||||
await hideOldComments(id)
|
||||
await github.rest.issues.createComment({
|
||||
|
|
@ -127,7 +150,7 @@ module.exports = async ({ github, context, core }) => {
|
|||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
per_page : 100,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
for (const c of comments) {
|
||||
|
|
@ -157,7 +180,7 @@ module.exports = async ({ github, context, core }) => {
|
|||
try {
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
...context.repo,
|
||||
state : "open",
|
||||
state: "open",
|
||||
labels: LABEL_NAME,
|
||||
})
|
||||
|
||||
|
|
@ -170,13 +193,13 @@ module.exports = async ({ github, context, core }) => {
|
|||
await github.rest.issues.update({
|
||||
...context.repo,
|
||||
issue_number: issue.number,
|
||||
state : "closed",
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
})
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: issue.number,
|
||||
body : `This issue has been automatically closed because it was marked as \`${LABEL_NAME}\` for more than 2 days without updates.
|
||||
body: `This issue has been automatically closed because it was marked as \`${LABEL_NAME}\` for more than 2 days without updates.
|
||||
If the problem persists, please file a new issue and complete the issue template so we can capture all the details necessary to investigate further.`,
|
||||
})
|
||||
}
|
||||
|
|
@ -191,13 +214,13 @@ If the problem persists, please file a new issue and complete the issue template
|
|||
await github.rest.issues.update({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
state : "closed",
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
})
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: id,
|
||||
body : `Unsupported issue template.
|
||||
body: `Unsupported issue template.
|
||||
Either the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.yml) or [Feature Request](https://github.com/sxyazi/yazi/issues/new?template=feature.yml) template should be used.`,
|
||||
})
|
||||
} catch (e) {
|
||||
|
|
@ -206,19 +229,23 @@ Either the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.y
|
|||
}
|
||||
|
||||
async function main() {
|
||||
const hash = await nightlyHash()
|
||||
if (!hash) return
|
||||
|
||||
if (context.eventName === "schedule") {
|
||||
await closeOldIssues()
|
||||
return
|
||||
}
|
||||
|
||||
if (skipValidation(context)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (context.eventName === "issues") {
|
||||
const id = context.payload.issue.number
|
||||
const content = context.payload.issue.body || ""
|
||||
const creator = context.payload.issue.user.login
|
||||
|
||||
const hash = await nightlyHash()
|
||||
if (!hash) return
|
||||
|
||||
if (await hasLabel(id, "bug")) {
|
||||
const body = bugReportBody(creator, content, hash)
|
||||
await updateLabels(id, !!body, body)
|
||||
|
|
@ -228,6 +255,13 @@ Either the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.y
|
|||
} else if (context.payload.action === "opened") {
|
||||
await closeUnsupportedIssue(id)
|
||||
}
|
||||
} else if (context.eventName === "pull_request" || context.eventName === "pull_request_target") {
|
||||
const content = context.payload.pull_request.body || ""
|
||||
|
||||
const body = pullRequestBody(content)
|
||||
if (body) {
|
||||
core.setFailed(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
293
scripts/validate-form/package-lock.json
generated
293
scripts/validate-form/package-lock.json
generated
|
|
@ -8,247 +8,225 @@
|
|||
"name": "validate-form",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.2",
|
||||
"@actions/github": "^7.0.0"
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/github": "^9.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.2.tgz",
|
||||
"integrity": "sha512-Ast1V7yHbGAhplAsuVlnb/5J8Mtr/Zl6byPPL+Qjq3lmfIgWF1ak1iYfF/079cRERiuTALTXkSuEUdZeDCfGtA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz",
|
||||
"integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/http-client": "^3.0.1"
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/http-client": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz",
|
||||
"integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
||||
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/io": "^2.0.0"
|
||||
"@actions/io": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-7.0.0.tgz",
|
||||
"integrity": "sha512-PyGODO938aoBTZd/IfN/+e+Pd5hUcVpyf+thm4CPESLeqhdSkq5QwMTGX9v84XHE1ifmHWBQ60KB8kIgm96opw==",
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-9.1.1.tgz",
|
||||
"integrity": "sha512-tL5JbYOBZHc0ngEnCsaDcryUizIUIlQyIMwy1Wkx93H5HzbBJ7TbiPx2PnFjBwZW0Vh05JmfFZhecE6gglYegA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@octokit/core": "^5.0.1",
|
||||
"@octokit/plugin-paginate-rest": "^9.2.2",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.4.0",
|
||||
"@octokit/request": "^8.4.1",
|
||||
"@octokit/request-error": "^5.1.1",
|
||||
"undici": "^5.28.5"
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/plugin-paginate-rest": "^14.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
||||
"@octokit/request": "^10.0.7",
|
||||
"@octokit/request-error": "^7.1.0",
|
||||
"undici": "^6.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.1.tgz",
|
||||
"integrity": "sha512-SbGS8c/vySbNO3kjFgSW77n83C4MQx/Yoe+b1hAdpuvfHxnkHzDq2pWljUpAA56Si1Gae/7zjeZsV0CYjmLo/w==",
|
||||
"node_modules/@actions/github/node_modules/@actions/http-client": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz",
|
||||
"integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.28.5"
|
||||
"undici": "^6.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz",
|
||||
"integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^6.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz",
|
||||
"integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
||||
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz",
|
||||
"integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
|
||||
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz",
|
||||
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.1.0",
|
||||
"@octokit/request": "^8.4.1",
|
||||
"@octokit/request-error": "^5.1.1",
|
||||
"@octokit/types": "^13.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
"@octokit/auth-token": "^6.0.0",
|
||||
"@octokit/graphql": "^9.0.3",
|
||||
"@octokit/request": "^10.0.6",
|
||||
"@octokit/request-error": "^7.0.2",
|
||||
"@octokit/types": "^16.0.0",
|
||||
"before-after-hook": "^4.0.0",
|
||||
"universal-user-agent": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
|
||||
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz",
|
||||
"integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.1.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
"@octokit/types": "^16.0.0",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
|
||||
"integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz",
|
||||
"integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^8.4.1",
|
||||
"@octokit/types": "^13.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
"@octokit/request": "^10.0.6",
|
||||
"@octokit/types": "^16.0.0",
|
||||
"universal-user-agent": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||
"version": "27.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz",
|
||||
"integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
|
||||
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz",
|
||||
"integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.6.0"
|
||||
"@octokit/types": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": "5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
||||
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
|
||||
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^20.0.0"
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
|
||||
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz",
|
||||
"integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^12.6.0"
|
||||
"@octokit/types": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": "5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
|
||||
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
|
||||
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^20.0.0"
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
|
||||
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
|
||||
"version": "10.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz",
|
||||
"integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^9.0.6",
|
||||
"@octokit/request-error": "^5.1.1",
|
||||
"@octokit/types": "^13.1.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
"@octokit/endpoint": "^11.0.3",
|
||||
"@octokit/request-error": "^7.0.2",
|
||||
"@octokit/types": "^16.0.0",
|
||||
"fast-content-type-parse": "^3.0.0",
|
||||
"json-with-bigint": "^3.5.3",
|
||||
"universal-user-agent": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
|
||||
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz",
|
||||
"integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^13.1.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
"@octokit/types": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "13.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz",
|
||||
"integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
"@octokit/openapi-types": "^27.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz",
|
||||
"integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
|
||||
"license": "ISC"
|
||||
"node_modules/fast-content-type-parse": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz",
|
||||
"integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
"node_modules/json-with-bigint": {
|
||||
"version": "3.5.8",
|
||||
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz",
|
||||
"integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
|
|
@ -260,27 +238,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
|
||||
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.2",
|
||||
"@actions/github": "^7.0.0"
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/github": "^9.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,9 +133,16 @@ parts:
|
|||
- --prefix=/usr
|
||||
build-attributes:
|
||||
- enable-patchelf
|
||||
organize:
|
||||
# `libgomp1` installs libgomp into the multiarch directory, but the
|
||||
# `magick` binary's RPATH only searches `$ORIGIN/../lib` (i.e. usr/lib).
|
||||
# Move it alongside the Magick libraries so the bundled binary can load
|
||||
# it at runtime; otherwise magick fails with:
|
||||
# "libgomp.so.1: cannot open shared object file: No such file or directory"
|
||||
usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so*: usr/lib/
|
||||
prime:
|
||||
- usr/bin/magick
|
||||
- usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so.1
|
||||
- usr/lib/libgomp.so*
|
||||
- usr/lib/libMagickCore-7.Q16HDRI.so*
|
||||
- usr/lib/libMagickWand-7.Q16HDRI.so*
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
[package]
|
||||
name = "yazi-actor"
|
||||
version = "26.1.22"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
description = "Yazi actor model"
|
||||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
name = "yazi-actor"
|
||||
description = "Yazi actor model"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -16,37 +17,42 @@ default = [ "vendored-lua" ]
|
|||
vendored-lua = [ "mlua/vendored" ]
|
||||
|
||||
[dependencies]
|
||||
yazi-binding = { path = "../yazi-binding", version = "26.1.22" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "26.1.22" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.1.22" }
|
||||
yazi-core = { path = "../yazi-core", version = "26.1.22" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "26.1.22" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.1.22" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.22" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "26.1.22" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "26.1.22" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "26.1.22" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.1.22" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.22" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.1.22" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.1.22" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.1.22" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.1.22" }
|
||||
yazi-binding = { path = "../yazi-binding", version = "26.5.6" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "26.5.6" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
||||
yazi-core = { path = "../yazi-core", version = "26.5.6" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
|
||||
yazi-emulator = { path = "../yazi-emulator", version = "26.5.6" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "26.5.6" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "26.5.6" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "26.5.6" }
|
||||
yazi-runner = { path = "../yazi-runner", version = "26.5.6" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
yazi-tui = { path = "../yazi-tui", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
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 }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use anyhow::Result;
|
||||
use yazi_dds::spark::SparkKind;
|
||||
use yazi_parser::spark::SparkKind;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
pub trait Actor {
|
||||
type Options;
|
||||
type Form;
|
||||
|
||||
const NAME: &str;
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data>;
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data>;
|
||||
|
||||
fn hook(_cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> { None }
|
||||
fn hook(_cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> { None }
|
||||
}
|
||||
|
|
|
|||
42
yazi-actor/src/app/accept_payload.rs
Normal file
42
yazi-actor/src/app/accept_payload.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use anyhow::Result;
|
||||
use mlua::IntoLua;
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_dds::{LOCAL, Payload, REMOTE};
|
||||
use yazi_macro::succ;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct AcceptPayload;
|
||||
|
||||
impl Actor for AcceptPayload {
|
||||
type Form = Payload<'static>;
|
||||
|
||||
const NAME: &str = "accept_payload";
|
||||
|
||||
fn act(cx: &mut Ctx, payload: Payload) -> Result<Data> {
|
||||
let kind = payload.body.kind();
|
||||
let lock = if payload.receiver == 0 || payload.receiver != payload.sender {
|
||||
REMOTE.read()
|
||||
} else {
|
||||
LOCAL.read()
|
||||
};
|
||||
|
||||
let Some(handlers) = lock.get(kind).filter(|&m| !m.is_empty()).cloned() else { succ!() };
|
||||
drop(lock);
|
||||
|
||||
let kind = kind.to_owned();
|
||||
succ!(Lives::scope(cx.core, |_| {
|
||||
let body = payload.body.into_lua(&LUA)?;
|
||||
for (id, cb) in handlers {
|
||||
if let Err(e) = runtime_scope!(LUA, &id, cb.call::<()>(body.clone())) {
|
||||
error!("Failed to run `{kind}` event handler in your `{id}` plugin: {e}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
32
yazi-actor/src/app/bootstrap.rs
Normal file
32
yazi-actor/src/app/bootstrap.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_core::mgr::CdSource;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::{data::Data, strand::StrandLike, url::UrlLike};
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Bootstrap;
|
||||
|
||||
impl Actor for Bootstrap {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "bootstrap";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
cx.mgr.tabs.resize_with(BOOT.files.len(), Default::default);
|
||||
|
||||
for (i, file) in BOOT.files.iter().enumerate().rev() {
|
||||
cx.tab = i;
|
||||
if file.is_empty() {
|
||||
act!(mgr:cd, cx, (BOOT.cwds[i].clone(), CdSource::Tab))?;
|
||||
} else if let Ok(u) = BOOT.cwds[i].try_join(file) {
|
||||
act!(mgr:reveal, cx, (u, CdSource::Tab))?;
|
||||
}
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
24
yazi-actor/src/app/deprecate.rs
Normal file
24
yazi-actor/src/app/deprecate.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::notify::{MessageLevel, MessageOpt};
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::app::DeprecateForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Deprecate;
|
||||
|
||||
impl Actor for Deprecate {
|
||||
type Form = DeprecateForm;
|
||||
|
||||
const NAME: &str = "deprecate";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(notify:push, cx, MessageOpt {
|
||||
title: "Deprecated API".to_owned(),
|
||||
content: form.content.into_owned(),
|
||||
level: MessageLevel::Warn,
|
||||
timeout: std::time::Duration::from_secs(20),
|
||||
})
|
||||
}
|
||||
}
|
||||
43
yazi-actor/src/app/dnd.rs
Normal file
43
yazi-actor/src/app/dnd.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use anyhow::Result;
|
||||
use mlua::{ObjectLike, Table};
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::DndForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Dnd;
|
||||
|
||||
impl Actor for Dnd {
|
||||
type Form = DndForm;
|
||||
|
||||
const NAME: &str = "dnd";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
let area = yazi_binding::elements::Rect::from(size);
|
||||
|
||||
let result = Lives::scope(cx.core, move |_| {
|
||||
runtime_scope!(LUA, "root", {
|
||||
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
|
||||
|
||||
if form.event.is_drag() {
|
||||
root.call_method::<()>("drag", form.event)?;
|
||||
} else {
|
||||
root.call_method::<()>("drop", form.event)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
if let Err(ref e) = result {
|
||||
error!("{e}");
|
||||
}
|
||||
succ!(result?);
|
||||
}
|
||||
}
|
||||
17
yazi-actor/src/app/focus.rs
Normal file
17
yazi-actor/src/app/focus.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Focus;
|
||||
|
||||
impl Actor for Focus {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "focus";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> { act!(mgr:refresh, cx) }
|
||||
}
|
||||
24
yazi-actor/src/app/lua.rs
Normal file
24
yazi-actor/src/app/lua.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::LuaForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::{Data, Sendable};
|
||||
|
||||
use crate::{Actor, Ctx, lives::Lives};
|
||||
|
||||
pub struct Lua;
|
||||
|
||||
impl Actor for Lua {
|
||||
type Form = LuaForm;
|
||||
|
||||
const NAME: &str = "lua";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let chunk = LUA.load(&*form.code).set_name("anonymous");
|
||||
let result = Lives::scope(cx.core, |_| {
|
||||
runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
|
||||
});
|
||||
succ!(result?);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,18 @@ yazi_macro::mod_flat!(
|
|||
accept_payload
|
||||
bootstrap
|
||||
deprecate
|
||||
dnd
|
||||
focus
|
||||
lua
|
||||
mouse
|
||||
notify
|
||||
plugin
|
||||
plugin_do
|
||||
quit
|
||||
reflow
|
||||
render
|
||||
resize
|
||||
resume
|
||||
stop
|
||||
update_notify
|
||||
theme
|
||||
title
|
||||
update_progress
|
||||
);
|
||||
52
yazi-actor/src/app/mouse.rs
Normal file
52
yazi-actor/src/app/mouse.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use anyhow::Result;
|
||||
use mlua::{ObjectLike, Table};
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::MouseForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::event::MouseEventKind;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Mouse;
|
||||
|
||||
impl Actor for Mouse {
|
||||
type Form = MouseForm;
|
||||
|
||||
const NAME: &str = "mouse";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
let area = yazi_binding::elements::Rect::from(size);
|
||||
|
||||
let result = Lives::scope(cx.core, move |_| {
|
||||
runtime_scope!(LUA, "root", {
|
||||
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
|
||||
|
||||
match form.event.kind {
|
||||
MouseEventKind::Down(_) => root.call_method("click", (form.event, false))?,
|
||||
MouseEventKind::Up(_) => root.call_method("click", (form.event, true))?,
|
||||
|
||||
MouseEventKind::ScrollDown => root.call_method("scroll", (form.event, 1))?,
|
||||
MouseEventKind::ScrollUp => root.call_method("scroll", (form.event, -1))?,
|
||||
|
||||
MouseEventKind::ScrollRight => root.call_method("touch", (form.event, 1))?,
|
||||
MouseEventKind::ScrollLeft => root.call_method("touch", (form.event, -1))?,
|
||||
|
||||
MouseEventKind::Moved => root.call_method("move", form.event)?,
|
||||
MouseEventKind::Drag(_) => root.call_method("drag", form.event)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
if let Err(ref e) = result {
|
||||
error!("{e}");
|
||||
}
|
||||
succ!(result?);
|
||||
}
|
||||
}
|
||||
40
yazi-actor/src/app/plugin.rs
Normal file
40
yazi-actor/src/app/plugin.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::app::PluginMode;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::app::PluginForm;
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_runner::loader::LOADER;
|
||||
use yazi_scheduler::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Plugin;
|
||||
|
||||
impl Actor for Plugin {
|
||||
type Form = PluginForm;
|
||||
|
||||
const NAME: &str = "plugin";
|
||||
|
||||
fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
|
||||
let mut hits = false;
|
||||
if let Some(chunk) = LOADER.read().get(&*opt.id) {
|
||||
hits = true;
|
||||
opt.mode = opt.mode.auto_then(chunk.sync_entry);
|
||||
}
|
||||
|
||||
if opt.mode == PluginMode::Async {
|
||||
succ!(cx.core.tasks.scheduler.plugin_entry(opt.into()));
|
||||
} else if opt.mode == PluginMode::Sync && hits {
|
||||
return act!(app:plugin_do, cx, opt);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
match LOADER.ensure(&opt.id, |_| ()).await {
|
||||
Ok(()) => AppProxy::plugin_do(opt),
|
||||
Err(e) => NotifyProxy::push_error("Plugin load failed", e.to_string()),
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
58
yazi-actor/src/app/plugin_do.rs
Normal file
58
yazi-actor/src/app/plugin_do.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use anyhow::Result;
|
||||
use mlua::ObjectLike;
|
||||
use scopeguard::defer;
|
||||
use tracing::{error, warn};
|
||||
use yazi_binding::runtime_mut;
|
||||
use yazi_core::app::PluginMode;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::PluginForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_runner::{entry::EntryJob, loader::{LOADER, Loader}};
|
||||
use yazi_scheduler::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx, lives::Lives};
|
||||
|
||||
pub struct PluginDo;
|
||||
|
||||
impl Actor for PluginDo {
|
||||
type Form = PluginForm;
|
||||
|
||||
const NAME: &str = "plugin_do";
|
||||
|
||||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
let loader = LOADER.read();
|
||||
let Some(chunk) = loader.get(&*opt.id) else {
|
||||
succ!(warn!("plugin `{}` not found", opt.id));
|
||||
};
|
||||
|
||||
if let Err(e) = Loader::compatible_or_error(&opt.id, chunk) {
|
||||
succ!(NotifyProxy::push_error("Incompatible plugin", e.to_string()));
|
||||
}
|
||||
|
||||
if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync {
|
||||
succ!(cx.core.tasks.scheduler.plugin_entry(opt.into()));
|
||||
}
|
||||
|
||||
let blocking = runtime_mut!(LUA)?.critical_push(&opt.id, true);
|
||||
defer! { _ = runtime_mut!(LUA).map(|mut r| r.critical_pop(blocking)) }
|
||||
|
||||
let plugin = match LOADER.load_chunk(&LUA, &opt.id, chunk) {
|
||||
Ok(t) => t,
|
||||
Err(e) => succ!(warn!("{e}")),
|
||||
};
|
||||
drop(loader);
|
||||
|
||||
let result = Lives::scope(cx.core, |_| {
|
||||
if let Some(cb) = opt.callback {
|
||||
cb(&LUA, plugin)
|
||||
} else {
|
||||
plugin.call_method("entry", EntryJob { args: opt.args, ..Default::default() })
|
||||
}
|
||||
});
|
||||
if let Err(ref e) = result {
|
||||
error!("Sync plugin `{}` failed: {e}", opt.id);
|
||||
}
|
||||
succ!(result?);
|
||||
}
|
||||
}
|
||||
50
yazi-actor/src/app/quit.rs
Normal file
50
yazi-actor/src/app/quit.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use std::process;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_fs::engine::{Engine, local::Local};
|
||||
use yazi_parser::app::QuitForm;
|
||||
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Quit;
|
||||
|
||||
impl Actor for Quit {
|
||||
type Form = QuitForm;
|
||||
|
||||
const NAME: &str = "quit";
|
||||
|
||||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
cx.tasks.shutdown();
|
||||
cx.mgr.shutdown();
|
||||
|
||||
futures::executor::block_on(async {
|
||||
_ = futures::join!(
|
||||
yazi_dds::shutdown(),
|
||||
yazi_dds::STATE.drain(),
|
||||
Self::cwd_to_file(cx, opt.no_cwd_file),
|
||||
Self::selected_to_file(opt.selected)
|
||||
);
|
||||
});
|
||||
|
||||
Raterm::stop();
|
||||
process::exit(opt.code);
|
||||
}
|
||||
}
|
||||
|
||||
impl Quit {
|
||||
async fn cwd_to_file(cx: &Ctx<'_>, no: bool) {
|
||||
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) {
|
||||
let cwd = cx.mgr.cwd().to_strand();
|
||||
Local::regular(p).write(cwd.encoded_bytes()).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
async fn selected_to_file(selected: Option<StrandBuf>) {
|
||||
if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) {
|
||||
Local::regular(p).write(s.encoded_bytes()).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use mlua::Value;
|
||||
use ratatui::layout::Position;
|
||||
use mlua::{LuaString, Value};
|
||||
use ratatui_core::layout::Position;
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_parser::app::ReflowForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Root, app::App};
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl App {
|
||||
pub fn reflow(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
pub struct Reflow;
|
||||
|
||||
impl Actor for Reflow {
|
||||
type Form = ReflowForm;
|
||||
|
||||
const NAME: &str = "reflow";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
let mut layout = LAYOUT.get();
|
||||
|
||||
let result = Lives::scope(&self.core, || {
|
||||
let comps = Root::reflow((Position::ORIGIN, size).into())?;
|
||||
let result = Lives::scope(cx.core, |_| {
|
||||
let comps = (form.reflow)((Position::ORIGIN, size).into())?;
|
||||
|
||||
for v in comps.sequence_values::<Value>() {
|
||||
let Value::Table(t) = v? else {
|
||||
|
|
@ -24,7 +30,7 @@ impl App {
|
|||
continue;
|
||||
};
|
||||
|
||||
let id: mlua::String = t.get("_id")?;
|
||||
let id: LuaString = t.get("_id")?;
|
||||
match &*id.as_bytes() {
|
||||
b"current" => layout.current = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
|
||||
b"preview" => layout.preview = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
|
||||
25
yazi-actor/src/app/resize.rs
Normal file
25
yazi-actor/src/app/resize.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::app::ReflowForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Resize;
|
||||
|
||||
impl Actor for Resize {
|
||||
type Form = ReflowForm;
|
||||
|
||||
const NAME: &str = "resize";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(app:reflow, cx, form)?;
|
||||
|
||||
cx.current_mut().arrow(0);
|
||||
cx.parent_mut().map(|f| f.arrow(0));
|
||||
cx.current_mut().sync_page(true);
|
||||
|
||||
act!(mgr:peek, cx)
|
||||
}
|
||||
}
|
||||
29
yazi-actor/src/app/resume.rs
Normal file
29
yazi-actor/src/app/resume.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::app::ReflowForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Resume;
|
||||
|
||||
impl Actor for Resume {
|
||||
type Form = ReflowForm;
|
||||
|
||||
const NAME: &str = "resume";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
cx.active_mut().preview.reset();
|
||||
|
||||
drop(cx.term.take());
|
||||
*cx.term = Some(Raterm::start()?);
|
||||
|
||||
// While the app resumes, it's possible that the terminal size has changed.
|
||||
// We need to trigger a resize, and render the UI based on the resized area.
|
||||
act!(app:resize, cx, form.reflow)?;
|
||||
|
||||
act!(app:title, cx).ok();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
22
yazi-actor/src/app/stop.rs
Normal file
22
yazi-actor/src/app/stop.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Stop;
|
||||
|
||||
impl Actor for Stop {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "stop";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
cx.active_mut().preview.reset_image();
|
||||
|
||||
*cx.term = None;
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
25
yazi-actor/src/app/theme.rs
Normal file
25
yazi-actor/src/app/theme.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_config::{THEME, build_flavor};
|
||||
use yazi_emulator::EMULATOR;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_shim::serde::Overlay;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Theme;
|
||||
|
||||
impl Actor for Theme {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "theme";
|
||||
|
||||
fn act(_cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
THEME.overlay(build_flavor(EMULATOR.light, true)?);
|
||||
yazi_plugin::theme::reset()?;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
32
yazi-actor/src/app/title.rs
Normal file
32
yazi-actor/src/app/title.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_parser::{app::TitleForm, spark::SparkKind};
|
||||
use yazi_shared::{Source, data::Data};
|
||||
use yazi_tty::{TTY, sequence::SetTitle};
|
||||
use yazi_tui::RatermState;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Title;
|
||||
|
||||
impl Actor for Title {
|
||||
type Form = TitleForm;
|
||||
|
||||
const NAME: &str = "title";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let s = form.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
|
||||
writef!(TTY.writer(), "{}", SetTitle(&s))?;
|
||||
|
||||
yazi_tui::STATE.set(RatermState { title: !s.is_empty(), ..yazi_tui::STATE.get() });
|
||||
succ!()
|
||||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
match cx.source() {
|
||||
Source::Ind => Some(SparkKind::IndAppTitle),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,29 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::app::UpdateProgressOpt;
|
||||
use yazi_macro::{act, render, render_partial, succ};
|
||||
use yazi_parser::app::UpdateProgressForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::Actor;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn update_progress(&mut self, opt: UpdateProgressOpt) -> Result<Data> {
|
||||
pub struct UpdateProgress;
|
||||
|
||||
impl Actor for UpdateProgress {
|
||||
type Form = UpdateProgressForm;
|
||||
|
||||
const NAME: &str = "update_progress";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
// Update the progress of all tasks.
|
||||
let tasks = &mut self.core.tasks;
|
||||
let progressed = tasks.summary != opt.summary;
|
||||
tasks.summary = opt.summary;
|
||||
let tasks = &mut cx.tasks;
|
||||
let progressed = tasks.summary != form.summary;
|
||||
tasks.summary = form.summary;
|
||||
|
||||
// If the task manager is visible, update the snaps with a full render.
|
||||
if tasks.visible {
|
||||
let new = tasks.paginate();
|
||||
if tasks.snaps != new {
|
||||
tasks.snaps = new;
|
||||
let cx = &mut Ctx::active(&mut self.core);
|
||||
act!(tasks:arrow, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
@ -29,7 +34,7 @@ impl App {
|
|||
} else if tasks.summary.total == 0 {
|
||||
succ!(render!())
|
||||
} else {
|
||||
act!(render_partially, self)
|
||||
succ!(render_partial!())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_parser::ArrowForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
|
|
@ -9,11 +9,11 @@ use crate::{Actor, Ctx};
|
|||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
type Form = ArrowForm;
|
||||
|
||||
const NAME: &str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(render!(cx.cmp.scroll(opt.step)));
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
succ!(render!(cx.cmp.scroll(form.step)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,46 @@ use std::mem;
|
|||
|
||||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::{cmp::CloseOpt, input::CompleteOpt};
|
||||
use yazi_parser::cmp::CloseForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::parser::CompleteOpt;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
type Form = CloseForm;
|
||||
|
||||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cmp = &mut cx.core.cmp;
|
||||
if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {
|
||||
return act!(input:complete, cx, CompleteOpt { item, ticket: cmp.ticket });
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
if form.submit && cx.cmp.visible {
|
||||
Self::flush_last_input(cx)?;
|
||||
}
|
||||
|
||||
let cmp = &mut cx.cmp;
|
||||
if let Some(item) = cmp.selected().filter(|_| form.submit).cloned() {
|
||||
return act!(input:complete, cx, CompleteOpt { name: item.name, is_dir: item.is_dir, ticket: cmp.ticket });
|
||||
}
|
||||
|
||||
cmp.caches.clear();
|
||||
cmp.ticket = Default::default();
|
||||
cmp.handle.take().map(|h| h.abort());
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
}
|
||||
|
||||
impl Close {
|
||||
fn flush_last_input(cx: &mut Ctx) -> Result<Data> {
|
||||
let Some(guard) = cx.input.lock() else { succ!() };
|
||||
if cx.cmp.ticket == guard.ticket.current() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let before = guard.partition().0.to_owned();
|
||||
drop(guard);
|
||||
|
||||
act!(cmp:trigger, cx, before)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::{mem, ops::ControlFlow};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_core::cmp::CmpItem;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::cmp::{CmpItem, ShowOpt};
|
||||
use yazi_shared::{data::Data, path::{AsPath, PathDyn}, strand::StrandLike};
|
||||
use yazi_parser::cmp::ShowForm;
|
||||
use yazi_shared::{data::Data, path::{DynPath, PathDyn}, strand::StrandLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -12,11 +13,11 @@ const LIMIT: usize = 30;
|
|||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
type Form = ShowForm;
|
||||
|
||||
const NAME: &str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
let cmp = &mut cx.cmp;
|
||||
if cmp.ticket != opt.ticket {
|
||||
succ!();
|
||||
|
|
@ -29,8 +30,8 @@ impl Actor for Show {
|
|||
succ!();
|
||||
};
|
||||
|
||||
cmp.cands = Self::match_candidates(opt.word.as_path(), cache);
|
||||
if cmp.cands.is_empty() {
|
||||
cmp.matches = Self::match_candidates(opt.word.dyn_path(), cache);
|
||||
if cmp.matches.is_empty() {
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,42 @@
|
|||
use std::mem;
|
||||
use std::{io, mem};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_fs::{path::clean_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_core::cmp::{CmpItem, CmpOpt};
|
||||
use yazi_fs::{engine::{DirReader, FileHolder}, path::clean_url};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
|
||||
use yazi_parser::cmp::TriggerForm;
|
||||
use yazi_proxy::CmpProxy;
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{AsPath, PathBufDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{DynPath, PathBufDyn, PathLike}, spec::Spec, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::engine;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Trigger;
|
||||
|
||||
impl Actor for Trigger {
|
||||
type Options = TriggerOpt;
|
||||
type Form = TriggerForm;
|
||||
|
||||
const NAME: &str = "trigger";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cmp = &mut cx.cmp;
|
||||
if let Some(t) = opt.ticket {
|
||||
if t < cmp.ticket {
|
||||
succ!();
|
||||
}
|
||||
cmp.ticket = t;
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
|
||||
succ!();
|
||||
} else if form.ticket.is_none() {
|
||||
cx.cmp.ticket = cx.input.lock().map(|g| g.ticket.current()).unwrap_or_default();
|
||||
}
|
||||
|
||||
let Some((parent, word)) = Self::split_url(&opt.word) else {
|
||||
cx.cmp.handle.take().map(|h| h.abort());
|
||||
let Some((parent, word)) = Self::split_url(&form.word) else {
|
||||
return act!(cmp:close, cx, false);
|
||||
};
|
||||
|
||||
if cmp.caches.contains_key(&parent) {
|
||||
let ticket = cmp.ticket;
|
||||
return act!(cmp:show, cx, ShowOpt { cache: vec![], cache_name: parent, word, ticket });
|
||||
let ticket = cx.cmp.ticket;
|
||||
if cx.cmp.caches.contains_key(&parent) {
|
||||
return act!(cmp:show, cx, CmpOpt { cache: vec![], cache_name: parent, word, ticket });
|
||||
}
|
||||
|
||||
let ticket = cmp.ticket;
|
||||
tokio::spawn(async move {
|
||||
let mut dir = provider::read_dir(&parent).await?;
|
||||
cx.cmp.handle = Some(tokio::spawn(async move {
|
||||
let mut dir = engine::read_dir(&parent).await?;
|
||||
let mut cache = vec![];
|
||||
|
||||
// "/" is both a directory separator and the root directory per se
|
||||
|
|
@ -54,45 +53,39 @@ impl Actor for Trigger {
|
|||
|
||||
if !cache.is_empty() {
|
||||
cache
|
||||
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false));
|
||||
CmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket });
|
||||
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), true));
|
||||
CmpProxy::show(CmpOpt { cache, cache_name: parent, word, ticket });
|
||||
}
|
||||
|
||||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
Ok::<_, io::Error>(())
|
||||
}));
|
||||
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
succ!(render!(mem::replace(&mut cx.cmp.visible, false)));
|
||||
}
|
||||
}
|
||||
|
||||
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()?) {
|
||||
let (spec, path) = Spec::parse(s.as_bytes()).ok()?;
|
||||
if path.is_empty() && !AnyAsciiChar::SEP.predicate(s.bytes().last()?) {
|
||||
return None; // We don't complete a `sftp://test`, but `sftp://test/`
|
||||
}
|
||||
|
||||
// Scheme
|
||||
let scheme = scheme.zeroed();
|
||||
if scheme.is_local() && path.as_strand() == "~" {
|
||||
// Spec
|
||||
let spec = spec.zeroed();
|
||||
if spec.kind.is_local() && path.as_strand() == "~" {
|
||||
return None; // We don't complete a `~`, but `~/`
|
||||
}
|
||||
|
||||
// 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.dyn_path(), |(_, c)| c).to_owned();
|
||||
|
||||
// Parent
|
||||
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
||||
let abs = if let Some(u) = provider::try_absolute(&url) { u } else { url };
|
||||
let url = UrlCow::try_from((spec.clone().zeroed(), path)).ok()?;
|
||||
let abs = if let Some(u) = engine::try_absolute(&url) { u } else { url };
|
||||
let parent = abs.loc().try_strip_suffix(&child).ok()?;
|
||||
|
||||
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child))
|
||||
Some((clean_url(UrlCow::try_from((spec, parent)).ok()?), child))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,10 +108,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_split() {
|
||||
yazi_shared::init_tests();
|
||||
yazi_config::init_tests();
|
||||
yazi_fs::init();
|
||||
|
||||
assert_eq!(Trigger::split_url(""), None);
|
||||
assert_eq!(Trigger::split_url("sftp://test"), None);
|
||||
assert_eq!(Trigger::split_url("sftp://vps"), None);
|
||||
compare(" ", "", " ");
|
||||
|
||||
compare("/", "/", "");
|
||||
|
|
@ -134,16 +128,18 @@ mod tests {
|
|||
compare("/foo/bar", "/foo/", "bar");
|
||||
compare("///foo/bar", "/foo/", "bar");
|
||||
|
||||
CWD.set(&"sftp://test".parse::<UrlBuf>().unwrap(), || {});
|
||||
compare("sftp://test/a", "sftp://test/.", "a");
|
||||
compare("sftp://test//a", "sftp://test//", "a");
|
||||
compare("sftp://test2/a", "sftp://test2/.", "a");
|
||||
compare("sftp://test2//a", "sftp://test2//", "a");
|
||||
CWD.set(&"sftp://vps".parse::<UrlBuf>().unwrap(), || {});
|
||||
compare("sftp://vps/a", "sftp://vps/.", "a");
|
||||
compare("sftp://vps//a", "sftp://vps//", "a");
|
||||
compare("test-scope://aws/a", "test-scope://aws/.", "a");
|
||||
compare("test-scope://aws//a", "test-scope://aws//", "a");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_split() {
|
||||
yazi_shared::init_tests();
|
||||
yazi_config::init_tests();
|
||||
yazi_fs::init();
|
||||
compare("foo", "", "foo");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_parser::ArrowForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,18 +8,18 @@ use crate::{Actor, Ctx};
|
|||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
type Form = ArrowForm;
|
||||
|
||||
const NAME: &str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let confirm = &mut cx.core.confirm;
|
||||
|
||||
let area = cx.core.mgr.area(confirm.position);
|
||||
let len = confirm.list.line_count(area.width);
|
||||
|
||||
let old = confirm.offset;
|
||||
confirm.offset = opt.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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::confirm::CloseOpt;
|
||||
use yazi_parser::confirm::CloseForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,15 +8,12 @@ use crate::{Actor, Ctx};
|
|||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
type Form = CloseForm;
|
||||
|
||||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if let Some(cb) = cx.confirm.callback.take() {
|
||||
_ = cb.send(opt.submit);
|
||||
}
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
cx.confirm.token.complete(form.submit);
|
||||
cx.confirm.visible = false;
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::confirm::ShowOpt;
|
||||
use yazi_parser::confirm::ShowForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,22 +8,22 @@ use crate::{Actor, Ctx};
|
|||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
type Form = ShowForm;
|
||||
|
||||
const NAME: &str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(confirm:close, cx)?;
|
||||
|
||||
let confirm = &mut cx.confirm;
|
||||
confirm.title = opt.cfg.title;
|
||||
confirm.body = opt.cfg.body;
|
||||
confirm.list = opt.cfg.list;
|
||||
confirm.title = form.cfg.title;
|
||||
confirm.body = form.cfg.body;
|
||||
confirm.list = form.cfg.list;
|
||||
|
||||
confirm.position = opt.cfg.position;
|
||||
confirm.position = form.cfg.position;
|
||||
confirm.offset = 0;
|
||||
|
||||
confirm.callback = Some(opt.tx);
|
||||
confirm.token = form.token;
|
||||
confirm.visible = true;
|
||||
|
||||
succ!(render!());
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks};
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, Source, event::Cmd, url::UrlBuf};
|
||||
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}};
|
||||
use yazi_fs::file::File;
|
||||
use yazi_shared::{Source, event::Action, id::Id, url::UrlBuf};
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
pub core: &'a mut Core,
|
||||
pub term: &'a mut Option<Raterm>,
|
||||
pub tab: usize,
|
||||
pub level: usize,
|
||||
pub source: Source,
|
||||
|
|
@ -25,9 +27,8 @@ impl DerefMut for Ctx<'_> {
|
|||
}
|
||||
|
||||
impl<'a> Ctx<'a> {
|
||||
#[inline]
|
||||
pub fn new(core: &'a mut Core, cmd: &Cmd) -> Result<Self> {
|
||||
let tab = if let Ok(id) = cmd.get::<Id>("tab") {
|
||||
pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Raterm>) -> Result<Self> {
|
||||
let tab = if let Ok(id) = action.get::<Id>("tab") {
|
||||
core
|
||||
.mgr
|
||||
.tabs
|
||||
|
|
@ -40,19 +41,20 @@ impl<'a> Ctx<'a> {
|
|||
|
||||
Ok(Self {
|
||||
core,
|
||||
term,
|
||||
tab,
|
||||
level: 0,
|
||||
source: cmd.source,
|
||||
source: action.source,
|
||||
#[cfg(debug_assertions)]
|
||||
backtrace: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn renew<'b>(cx: &'a mut Ctx<'b>) -> Self {
|
||||
let tab = cx.core.mgr.tabs.cursor;
|
||||
Self {
|
||||
core: cx.core,
|
||||
term: cx.term,
|
||||
tab,
|
||||
level: cx.level,
|
||||
source: cx.source,
|
||||
|
|
@ -61,11 +63,11 @@ impl<'a> Ctx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn active(core: &'a mut Core) -> Self {
|
||||
pub fn active(core: &'a mut Core, term: &'a mut Option<Raterm>) -> Self {
|
||||
let tab = core.mgr.tabs.cursor;
|
||||
Self {
|
||||
core,
|
||||
term,
|
||||
tab,
|
||||
level: 0,
|
||||
source: Source::Unknown,
|
||||
|
|
@ -106,6 +108,9 @@ impl<'a> Ctx<'a> {
|
|||
#[inline]
|
||||
pub fn hovered(&self) -> Option<&File> { self.tab().hovered() }
|
||||
|
||||
#[inline]
|
||||
pub fn hovered_url(&self) -> Option<&UrlBuf> { self.tab().hovered_url() }
|
||||
|
||||
#[inline]
|
||||
pub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() }
|
||||
|
||||
|
|
@ -114,9 +119,5 @@ impl<'a> Ctx<'a> {
|
|||
self.tab_mut().hovered_folder_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tasks(&self) -> &Tasks { &self.tasks }
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source { if self.level != 1 { Source::Ind } else { self.source } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use mlua::{ErrorContext, ExternalError, IntoLua, Value};
|
||||
use yazi_binding::runtime_mut;
|
||||
use yazi_dds::{LOCAL, spark::{Spark, SparkKind}};
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_dds::LOCAL;
|
||||
use yazi_parser::spark::{Spark, SparkKind};
|
||||
use yazi_plugin::LUA;
|
||||
|
||||
use crate::{Ctx, lives::Lives};
|
||||
|
|
@ -11,18 +12,14 @@ pub struct Preflight;
|
|||
impl Preflight {
|
||||
pub fn act<'a>(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> {
|
||||
let kind = opt.0;
|
||||
let Some(handlers) = LOCAL.read().get(kind.as_ref()).filter(|&m| !m.is_empty()).cloned() else {
|
||||
let Some(handlers) = LOCAL.read().get(kind.into()).filter(|&m| !m.is_empty()).cloned() else {
|
||||
return Ok(opt.1);
|
||||
};
|
||||
|
||||
Ok(Lives::scope(cx.core, || {
|
||||
Ok(Lives::scope(cx.core, |_| {
|
||||
let mut body = opt.1.into_lua(&LUA)?;
|
||||
for (id, cb) in handlers {
|
||||
runtime_mut!(LUA)?.push(&id);
|
||||
let result = cb.call::<Value>(&body);
|
||||
runtime_mut!(LUA)?.pop();
|
||||
|
||||
match result {
|
||||
match runtime_scope!(LUA, &id, cb.call::<Value>(&body)) {
|
||||
Ok(Value::Nil) => {
|
||||
Err(format!("`{kind}` event cancelled by `{id}` plugin on preflight").into_lua_err())?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_parser::ArrowForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
|
|
@ -9,11 +9,11 @@ use crate::{Actor, Ctx};
|
|||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
type Form = ArrowForm;
|
||||
|
||||
const NAME: &str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(render!(cx.help.scroll(opt.step)));
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
succ!(render!(cx.help.scroll(form.step)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::InputMode;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Escape;
|
||||
|
||||
impl Actor for Escape {
|
||||
type Options = VoidOpt;
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
if cx.help.keyword().is_none() {
|
||||
return act!(help:toggle, cx, cx.help.layer);
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
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::VoidOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Filter;
|
||||
|
||||
impl Actor for Filter {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &str = "filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> 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,29 +1,39 @@
|
|||
use anyhow::Result;
|
||||
use ratatui_core::layout::Margin;
|
||||
use yazi_config::popup::Help;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::help::ToggleOpt;
|
||||
use yazi_parser::help::ToggleForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::Input;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Toggle;
|
||||
|
||||
impl Actor for Toggle {
|
||||
type Options = ToggleOpt;
|
||||
type Form = ToggleForm;
|
||||
|
||||
const NAME: &str = "toggle";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
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 = true;
|
||||
help.layer = form.layer;
|
||||
help.position = position;
|
||||
help.height = area.height;
|
||||
|
||||
help.visible = !help.visible;
|
||||
help.layer = opt.layer;
|
||||
|
||||
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!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,47 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::input::InputMutGuard;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::input::CloseOpt;
|
||||
use yazi_shared::{data::Data, errors::InputError};
|
||||
use yazi_parser::{input::CloseForm, spark::SparkKind};
|
||||
use yazi_shared::{Source, data::Data};
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
type Form = CloseForm;
|
||||
|
||||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let input = &mut cx.input;
|
||||
input.visible = false;
|
||||
input.ticket.next();
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(mut guard) = cx.input.lock_mut() else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
if let Some(tx) = input.tx.take() {
|
||||
let value = input.snap().value.clone();
|
||||
_ = tx.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
|
||||
guard.ticket.next();
|
||||
if let Some(cb) = guard.cb.take() {
|
||||
let value = guard.value().to_owned();
|
||||
cb(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
|
||||
}
|
||||
|
||||
if form.submit
|
||||
&& let InputMutGuard::Main(input) = guard
|
||||
{
|
||||
input.histories.remember(&input.main.history.name, input.main.value());
|
||||
}
|
||||
|
||||
cx.input.main.visible = false;
|
||||
|
||||
act!(cmp:close, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
match cx.source() {
|
||||
Source::Key => Some(SparkKind::KeyInputClose),
|
||||
Source::Ind => Some(SparkKind::IndInputClose),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::input::CompleteOpt;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::parser::CompleteOpt;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Complete;
|
||||
|
||||
impl Actor for Complete {
|
||||
type Options = CompleteOpt;
|
||||
type Form = CompleteOpt;
|
||||
|
||||
const NAME: &str = "complete";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let input = &mut cx.input;
|
||||
if !input.visible || input.ticket.current() != opt.ticket {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(mut guard) = cx.input.lock_mut() else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
if guard.ticket.current() != form.ticket {
|
||||
succ!();
|
||||
}
|
||||
|
||||
act!(complete, input, opt)
|
||||
act!(complete, guard, form)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::InputOp;
|
||||
|
||||
|
|
@ -9,22 +9,26 @@ use crate::{Actor, Ctx};
|
|||
pub struct Escape;
|
||||
|
||||
impl Actor for Escape {
|
||||
type Options = VoidOpt;
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
use yazi_widgets::input::InputMode as M;
|
||||
let input = &mut cx.input;
|
||||
let Some(mut guard) = cx.input.lock_mut() else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
let mode = input.snap().mode;
|
||||
let (mode, op) = (guard.snap().mode, guard.snap().op);
|
||||
act!(escape, guard)?;
|
||||
|
||||
drop(guard);
|
||||
match mode {
|
||||
M::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx),
|
||||
M::Normal if op == InputOp::None => act!(input:close, cx),
|
||||
M::Insert => act!(cmp:close, cx),
|
||||
M::Normal | M::Replace => Ok(().into()),
|
||||
}?;
|
||||
|
||||
act!(escape, cx.input)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(close complete escape show);
|
||||
yazi_macro::mod_flat!(close complete escape recall remember show);
|
||||
|
|
|
|||
32
yazi-actor/src/input/recall.rs
Normal file
32
yazi-actor/src/input/recall.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::input::InputMutGuard;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::input::RecallForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Recall;
|
||||
|
||||
impl Actor for Recall {
|
||||
type Form = RecallForm;
|
||||
|
||||
const NAME: &str = "recall";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(input) = cx.input.lock_mut() else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
match input {
|
||||
InputMutGuard::Main(input) => {
|
||||
let entries = input.histories.get(&input.main.history.name);
|
||||
input.main.recall(entries, form.step)
|
||||
}
|
||||
InputMutGuard::Alt(input, mut guard) => {
|
||||
let entries = input.histories.get(&guard.history.name);
|
||||
guard.recall(entries, form.step)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
yazi-actor/src/input/remember.rs
Normal file
33
yazi-actor/src/input/remember.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::input::InputMutGuard;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Remember;
|
||||
|
||||
impl Actor for Remember {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "remember";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let Some(mut input) = cx.input.lock_mut() else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
match &mut input {
|
||||
InputMutGuard::Main(input) => {
|
||||
input.histories.remember(&input.main.history.name, input.main.value());
|
||||
}
|
||||
InputMutGuard::Alt(input, guard) => {
|
||||
input.histories.remember(&guard.history.name, guard.value());
|
||||
}
|
||||
}
|
||||
|
||||
input.history.take();
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +1,35 @@
|
|||
use std::ops::DerefMut;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_config::{THEME, YAZI};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::input::ShowOpt;
|
||||
use yazi_shared::{data::Data, errors::InputError};
|
||||
use yazi_widgets::input::InputCallback;
|
||||
use yazi_parser::input::ShowForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_shim::ratatui::Padable;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
type Form = ShowForm;
|
||||
|
||||
const NAME: &str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
|
||||
act!(input:close, cx)?;
|
||||
|
||||
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
||||
let input = &mut cx.input;
|
||||
input.visible = true;
|
||||
input.title = opt.cfg.title;
|
||||
input.position = opt.cfg.position;
|
||||
input.main.name = opt.name.clone();
|
||||
input.main.title = opt.title.clone();
|
||||
input.main.position = opt.position;
|
||||
input.main.visible = true;
|
||||
|
||||
// Typing
|
||||
input.tx = Some(opt.tx.clone());
|
||||
let ticket = input.ticket.clone();
|
||||
|
||||
// Reset input
|
||||
let cb: InputCallback = Box::new(move |before, after| {
|
||||
if opt.cfg.realtime {
|
||||
opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok();
|
||||
} else if opt.cfg.completion {
|
||||
opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok();
|
||||
}
|
||||
});
|
||||
*input.deref_mut() = yazi_widgets::input::Input::new(
|
||||
opt.cfg.value,
|
||||
opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize,
|
||||
opt.cfg.obscure,
|
||||
cb,
|
||||
);
|
||||
|
||||
// Set cursor after reset
|
||||
// TODO: remove this
|
||||
if let Some(cursor) = opt.cfg.cursor {
|
||||
input.snap_mut().cursor = cursor;
|
||||
act!(r#move, input)?;
|
||||
}
|
||||
opt.styles = (&THEME.input).into();
|
||||
opt.blinking = YAZI.input.cursor_blink;
|
||||
*input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?;
|
||||
input.main.repos(area);
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
extern crate self as yazi_actor;
|
||||
|
||||
yazi_macro::mod_pub!(cmp confirm core help input lives mgr pick spot tasks which);
|
||||
yazi_macro::mod_pub!(app cmp confirm core help input lives mgr notify pick spot tasks which);
|
||||
|
||||
yazi_macro::mod_flat!(actor context);
|
||||
|
||||
pub fn init() { lives::init(); }
|
||||
|
|
|
|||
29
yazi-actor/src/lives/behavior.rs
Normal file
29
yazi-actor/src/lives/behavior.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataMethods};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct Behavior {
|
||||
inner: PtrCell<yazi_scheduler::Behavior>,
|
||||
}
|
||||
|
||||
impl Deref for Behavior {
|
||||
type Target = yazi_scheduler::Behavior;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl Behavior {
|
||||
pub(super) fn make(inner: &yazi_scheduler::Behavior) -> mlua::Result<AnyUserData> {
|
||||
let inner = PtrCell::from(inner);
|
||||
|
||||
Lives::scoped_userdata(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Behavior {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("reset", |_, me, ()| Ok(me.reset()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, Value};
|
||||
use mlua::{AnyUserData, IntoLua, LuaString, MetaMethod, UserData, UserDataMethods, UserDataRef, Value};
|
||||
use paste::paste;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) type CoreRef = UserDataRef<Core>;
|
||||
|
||||
pub(super) struct Core {
|
||||
inner: PtrCell<yazi_core::Core>,
|
||||
|
||||
|
|
@ -12,6 +14,8 @@ pub(super) struct Core {
|
|||
c_tabs: Option<Value>,
|
||||
c_tasks: Option<Value>,
|
||||
c_yanked: Option<Value>,
|
||||
c_input: Option<Value>,
|
||||
c_which: Option<Value>,
|
||||
c_layer: Option<Value>,
|
||||
}
|
||||
|
||||
|
|
@ -30,14 +34,16 @@ impl Core {
|
|||
c_tabs: None,
|
||||
c_tasks: None,
|
||||
c_yanked: None,
|
||||
c_input: None,
|
||||
c_which: None,
|
||||
c_layer: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Core {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: mlua::String| {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: LuaString| {
|
||||
macro_rules! reuse {
|
||||
($key:ident, $value:expr) => {
|
||||
match paste! { &me.[<c_ $key>] } {
|
||||
|
|
@ -55,9 +61,9 @@ impl UserData for Core {
|
|||
b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
|
||||
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),
|
||||
b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)),
|
||||
b"layer" => {
|
||||
reuse!(layer, Ok::<_, mlua::Error>(yazi_plugin::bindings::Layer::from(me.layer())))
|
||||
}
|
||||
b"input" => reuse!(input, super::Input::make(&me.input)),
|
||||
b"which" => reuse!(which, super::Which::make(&me.which)),
|
||||
b"layer" => reuse!(layer, Ok::<_, mlua::Error>(me.layer())),
|
||||
_ => Value::Nil,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +1,35 @@
|
|||
use std::ops::{Deref, Range};
|
||||
|
||||
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_binding::cached_field;
|
||||
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
|
||||
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>,
|
||||
|
||||
v_filter: Option<Value>,
|
||||
}
|
||||
|
||||
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,
|
||||
tab: &yazi_core::tab::Tab,
|
||||
) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into(), v_filter: None })
|
||||
Lives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Files {
|
||||
impl UserData for Entries {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
cached_field!(fields, filter, |_, me| me.filter().map(Filter::make).transpose());
|
||||
fields.add_static_field("filter", |_, me| me.filter().map(Filter::make).transpose());
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
|
|
@ -1,38 +1,28 @@
|
|||
use std::{ops::Deref, ptr};
|
||||
|
||||
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_binding::{Style, cached_field};
|
||||
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_binding::{Range, style::Style};
|
||||
use yazi_config::THEME;
|
||||
use yazi_plugin::bindings::Range;
|
||||
use yazi_shared::{path::AsPath, url::UrlLike};
|
||||
use yazi_fs::file::FileInventory;
|
||||
use yazi_shared::{path::DynPath, url::UrlLike};
|
||||
|
||||
use super::Lives;
|
||||
use crate::lives::PtrCell;
|
||||
use super::{FILE_CACHE, Lives};
|
||||
use crate::lives::{CoreRef, PtrCell};
|
||||
|
||||
pub(super) struct File {
|
||||
idx: usize,
|
||||
folder: PtrCell<yazi_core::tab::Folder>,
|
||||
tab: PtrCell<yazi_core::tab::Tab>,
|
||||
|
||||
v_cha: Option<Value>,
|
||||
v_url: Option<Value>,
|
||||
v_link_to: Option<Value>,
|
||||
|
||||
v_name: Option<Value>,
|
||||
v_path: Option<Value>,
|
||||
v_cache: Option<Value>,
|
||||
|
||||
v_bare: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for File {
|
||||
type Target = yazi_fs::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> for File {
|
||||
fn as_ref(&self) -> &yazi_fs::File { self }
|
||||
impl AsRef<yazi_fs::file::File> for File {
|
||||
fn as_ref(&self) -> &yazi_fs::file::File { self }
|
||||
}
|
||||
|
||||
impl File {
|
||||
|
|
@ -43,38 +33,28 @@ impl File {
|
|||
) -> mlua::Result<AnyUserData> {
|
||||
use hashbrown::hash_map::Entry;
|
||||
|
||||
Ok(match super::FILE_CACHE.borrow_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(),
|
||||
|
||||
v_cha: None,
|
||||
v_url: None,
|
||||
v_link_to: None,
|
||||
|
||||
v_name: None,
|
||||
v_path: None,
|
||||
v_cache: None,
|
||||
|
||||
v_bare: None,
|
||||
})?;
|
||||
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]
|
||||
fn is_hovered(&self) -> bool { self.idx == self.folder.cursor }
|
||||
}
|
||||
|
||||
impl UserData for File {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
yazi_binding::impl_file_fields!(fields);
|
||||
cached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me)));
|
||||
|
||||
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
||||
fields.add_field_method_get("is_hovered", |_, me| Ok(me.idx == me.folder.cursor));
|
||||
fields.add_field_method_get("is_hovered", |_, me| Ok(me.is_hovered()));
|
||||
fields.add_field_method_get("in_current", |_, me| Ok(ptr::eq(&*me.folder, &me.tab.current)));
|
||||
fields.add_field_method_get("in_preview", |_, me| {
|
||||
Ok(me.idx == me.folder.cursor && me.tab.hovered().is_some_and(|f| f.url == me.folder.url))
|
||||
|
|
@ -84,13 +64,24 @@ impl UserData for File {
|
|||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
yazi_binding::impl_file_methods!(methods);
|
||||
|
||||
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.entry_key()).copied()
|
||||
} else {
|
||||
Some(me.len)
|
||||
})
|
||||
});
|
||||
methods.add_method("mime", |lua, me, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
|
||||
core.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose()
|
||||
})?
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
core.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose()
|
||||
});
|
||||
methods.add_method("prefix", |lua, me, ()| {
|
||||
if !me.url.has_trail() {
|
||||
|
|
@ -99,64 +90,68 @@ impl UserData for File {
|
|||
|
||||
let mut comp = me.url.try_strip_prefix(me.url.trail()).unwrap_or(me.url.loc()).components();
|
||||
comp.next_back();
|
||||
Some(lua.create_string(comp.as_path().encoded_bytes())).transpose()
|
||||
Some(lua.create_string(comp.dyn_path().encoded_bytes())).transpose()
|
||||
});
|
||||
methods.add_method("style", |lua, me, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
|
||||
let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();
|
||||
THEME.filetype.iter().find(|&x| x.matches(me, mime)).map(|x| Style::from(x.style))
|
||||
})
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();
|
||||
Ok(THEME.filetype.match_style(me, mime).map(Style::from))
|
||||
});
|
||||
methods.add_method("is_yanked", |lua, me, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
|
||||
if !core.mgr.yanked.contains(&me.url) {
|
||||
0u8
|
||||
} else if core.mgr.yanked.cut {
|
||||
2u8
|
||||
} else {
|
||||
1u8
|
||||
}
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
Ok(if !core.mgr.yanked.contains(&me.url) {
|
||||
0u8
|
||||
} else if core.mgr.yanked.cut {
|
||||
2u8
|
||||
} else {
|
||||
1u8
|
||||
})
|
||||
});
|
||||
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, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
|
||||
let Some(finder) = &core.active().finder else {
|
||||
return Ok(None);
|
||||
};
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
let Some(finder) = &core.active().finder else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(idx) = finder.matched_idx(&me.folder, me.urn()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(idx) = finder.matched_idx(&me.folder, me.entry_key()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Some(lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?]))
|
||||
.transpose()
|
||||
})
|
||||
lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?]).map(Some)
|
||||
});
|
||||
methods.add_method("highlights", |lua, me, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
|
||||
let Some(finder) = &core.active().finder else {
|
||||
return None;
|
||||
};
|
||||
if me.folder.url != me.tab.current.url {
|
||||
return None;
|
||||
}
|
||||
let core: CoreRef = lua.named_registry_value("cx")?;
|
||||
let Some(finder) = &core.active().finder else {
|
||||
return Ok(None);
|
||||
};
|
||||
if me.folder.url != me.tab.current.url {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(Some(h)) = me.url.name().map(|s| finder.filter.highlighted(s)) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let h = finder.filter.highlighted(me.url.name()?)?;
|
||||
Some(h.into_iter().map(Range::from).collect::<Vec<_>>())
|
||||
})
|
||||
lua.create_sequence_from(h.into_iter().map(Range::from)).map(Some)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit! {
|
||||
FileInventory {
|
||||
register: |_| {},
|
||||
borrow: |ud, f| f(&*ud.borrow::<File>()?),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
use std::ops::{Deref, Range};
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields, Value};
|
||||
use yazi_binding::{FolderStage, Url, cached_field};
|
||||
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>,
|
||||
inner: PtrCell<yazi_core::tab::Folder>,
|
||||
tab: PtrCell<yazi_core::tab::Tab>,
|
||||
|
||||
v_cwd: Option<Value>,
|
||||
v_files: Option<Value>,
|
||||
v_stage: Option<Value>,
|
||||
v_window: Option<Value>,
|
||||
v_hovered: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Folder {
|
||||
|
|
@ -34,34 +28,24 @@ 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)
|
||||
}
|
||||
};
|
||||
|
||||
Lives::scoped_userdata(Self {
|
||||
window,
|
||||
inner: inner.into(),
|
||||
tab: tab.into(),
|
||||
|
||||
v_cwd: None,
|
||||
v_files: None,
|
||||
v_stage: None,
|
||||
v_window: None,
|
||||
v_hovered: None,
|
||||
})
|
||||
Lives::scoped_userdata(Self { window, inner: inner.into(), tab: tab.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Folder {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
cached_field!(fields, cwd, |_, me| Ok(Url::new(&me.url)));
|
||||
cached_field!(fields, files, |_, me| Files::make(0..me.files.len(), me, &me.tab));
|
||||
cached_field!(fields, stage, |_, me| Ok(FolderStage::new(me.stage.clone())));
|
||||
cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab));
|
||||
fields.add_cached_field("cwd", |_, me| Ok(me.url.clone()));
|
||||
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| 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));
|
||||
cached_field!(fields, hovered, |_, me| {
|
||||
fields.add_static_field("hovered", |_, me| {
|
||||
me.hovered().map(|_| File::make(me.cursor, me, &me.tab)).transpose()
|
||||
});
|
||||
}
|
||||
|
|
|
|||
29
yazi-actor/src/lives/input.rs
Normal file
29
yazi-actor/src/lives/input.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
use crate::lives::InputAlt;
|
||||
|
||||
pub(super) struct Input {
|
||||
inner: PtrCell<yazi_core::input::Input>,
|
||||
}
|
||||
|
||||
impl Deref for Input {
|
||||
type Target = yazi_core::input::Input;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub(super) fn make(inner: &yazi_core::input::Input) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Input {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("alt", |_, me| me.alt.as_ref().map(InputAlt::make).transpose());
|
||||
}
|
||||
}
|
||||
26
yazi-actor/src/lives/input_alt.rs
Normal file
26
yazi-actor/src/lives/input_alt.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct InputAlt(PtrCell<yazi_core::input::InputAlt>);
|
||||
|
||||
impl Deref for InputAlt {
|
||||
type Target = yazi_core::input::InputAlt;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl InputAlt {
|
||||
pub(super) fn make(inner: &yazi_core::input::InputAlt) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self(inner.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for InputAlt {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("value", |lua, me| lua.create_string(me.lock().value()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,38 @@
|
|||
use std::cell::RefCell;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use mlua::{AnyUserData, UserData};
|
||||
use scopeguard::defer;
|
||||
use tracing::error;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::RoCell;
|
||||
|
||||
use super::{Core, PtrCell};
|
||||
use crate::lives::MutCell;
|
||||
|
||||
static TO_DESTROY: RoCell<RefCell<Vec<AnyUserData>>> = RoCell::new_const(RefCell::new(Vec::new()));
|
||||
pub(super) static FILE_CACHE: RoCell<RefCell<HashMap<PtrCell<yazi_fs::File>, AnyUserData>>> =
|
||||
RoCell::new();
|
||||
pub(super) static TO_DESTROY: MutCell<Vec<AnyUserData>> = MutCell::new(Vec::new());
|
||||
pub(super) static FILE_CACHE: MutCell<
|
||||
MaybeUninit<HashMap<PtrCell<yazi_fs::file::File>, AnyUserData>>,
|
||||
> = MutCell::new(MaybeUninit::uninit());
|
||||
|
||||
pub struct Lives;
|
||||
|
||||
impl Lives {
|
||||
pub fn scope<T>(core: &yazi_core::Core, f: impl FnOnce() -> mlua::Result<T>) -> mlua::Result<T> {
|
||||
FILE_CACHE.init(Default::default());
|
||||
defer! { FILE_CACHE.drop(); }
|
||||
|
||||
let result = LUA.scope(|scope| {
|
||||
scope.add_destructor(|| {
|
||||
for ud in TO_DESTROY.borrow_mut().drain(..) {
|
||||
pub fn scope<T, F>(core: &mut yazi_core::Core, f: F) -> mlua::Result<T>
|
||||
where
|
||||
F: FnOnce(&mut yazi_core::Core) -> mlua::Result<T>,
|
||||
{
|
||||
defer! {
|
||||
unsafe {
|
||||
(*FILE_CACHE.get()).assume_init_mut().clear();
|
||||
for ud in (*TO_DESTROY.get()).drain(..) {
|
||||
ud.destroy().expect("failed to destruct scoped userdata");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
LUA.set_named_registry_value("cx", scope.create_any_userdata_ref(core)?)?;
|
||||
LUA.globals().raw_set("cx", Core::make(core)?)?;
|
||||
f()
|
||||
});
|
||||
LUA.set_named_registry_value("cx", Core::make(core)?)?;
|
||||
LUA.globals().raw_set("cx", Core::make(core)?)?;
|
||||
let result = f(core);
|
||||
|
||||
if let Err(ref e) = result {
|
||||
error!("{e}");
|
||||
|
|
@ -43,7 +45,7 @@ impl Lives {
|
|||
T: UserData + 'static,
|
||||
{
|
||||
let ud = LUA.create_userdata(data)?;
|
||||
TO_DESTROY.borrow_mut().push(ud.clone());
|
||||
unsafe { &mut *TO_DESTROY.get() }.push(ud.clone());
|
||||
Ok(ud)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,5 @@
|
|||
yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks 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) {
|
||||
|
|
|
|||
11
yazi-actor/src/lives/mut_cell.rs
Normal file
11
yazi-actor/src/lives/mut_cell.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use std::cell::UnsafeCell;
|
||||
|
||||
pub(super) struct MutCell<T>(UnsafeCell<T>);
|
||||
|
||||
unsafe impl<T> Sync for MutCell<T> {}
|
||||
|
||||
impl<T> MutCell<T> {
|
||||
pub(super) const fn new(value: T) -> Self { Self(UnsafeCell::new(value)) }
|
||||
|
||||
pub(super) fn get(&self) -> *mut T { self.0.get() }
|
||||
}
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields, Value};
|
||||
use yazi_binding::cached_field;
|
||||
use mlua::{AnyUserData, UserData, UserDataFields};
|
||||
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct Preference {
|
||||
inner: PtrCell<yazi_core::tab::Preference>,
|
||||
|
||||
v_sort_by: Option<Value>,
|
||||
v_linemode: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Preference {
|
||||
|
|
@ -20,19 +17,23 @@ impl Deref for Preference {
|
|||
|
||||
impl Preference {
|
||||
pub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { inner: inner.into(), v_sort_by: None, v_linemode: None })
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Preference {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
cached_field!(fields, sort_by, |_, me| Ok(me.sort_by.to_string()));
|
||||
// Display
|
||||
fields.add_cached_field("name", |lua, me| lua.create_string(&me.name));
|
||||
fields.add_cached_field("linemode", |lua, me| lua.create_string(&*me.linemode));
|
||||
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
|
||||
|
||||
// Sorting
|
||||
fields.add_cached_field("sort_by", |_, me| Ok(me.sort_by.into_str()));
|
||||
fields.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
|
||||
fields.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
|
||||
fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
|
||||
fields.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit));
|
||||
|
||||
cached_field!(fields, linemode, |lua, me| lua.create_string(&me.linemode));
|
||||
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
|
||||
fields.add_field_method_get("sort_fallback", |_, me| Ok(me.sort_fallback.into_str()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields, Value};
|
||||
use yazi_binding::cached_field;
|
||||
use mlua::{AnyUserData, UserData, UserDataFields};
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use super::{Folder, Lives, PtrCell};
|
||||
|
||||
pub(super) struct Preview {
|
||||
tab: PtrCell<yazi_core::tab::Tab>,
|
||||
|
||||
v_folder: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Preview {
|
||||
|
|
@ -20,19 +18,19 @@ impl Deref for Preview {
|
|||
|
||||
impl Preview {
|
||||
pub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { tab: tab.into(), v_folder: None })
|
||||
Lives::scoped_userdata(Self { tab: tab.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Preview {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("skip", |_, me| Ok(me.skip));
|
||||
cached_field!(fields, folder, |_, me| {
|
||||
fields.add_static_field("folder", |_, me| {
|
||||
me.tab
|
||||
.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,7 +1,6 @@
|
|||
use mlua::AnyUserData;
|
||||
|
||||
use super::Lives;
|
||||
use crate::lives::PtrCell;
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct Selected;
|
||||
|
|
@ -11,7 +10,7 @@ impl Selected {
|
|||
let inner = PtrCell::from(inner);
|
||||
|
||||
Lives::scoped_userdata(yazi_binding::Iter::new(
|
||||
inner.as_static().values().map(yazi_binding::Url::new),
|
||||
inner.as_static().files().cloned(),
|
||||
Some(inner.len()),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,13 @@
|
|||
use std::ops::Deref;
|
||||
use std::{borrow::Cow, ops::Deref};
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_binding::{Id, UrlRef, cached_field};
|
||||
use yazi_shared::url::UrlLike;
|
||||
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_shared::url::UrlRef;
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};
|
||||
|
||||
pub(super) struct Tab {
|
||||
inner: PtrCell<yazi_core::tab::Tab>,
|
||||
|
||||
v_name: Option<Value>,
|
||||
v_mode: Option<Value>,
|
||||
v_pref: Option<Value>,
|
||||
v_current: Option<Value>,
|
||||
v_parent: Option<Value>,
|
||||
v_selected: Option<Value>,
|
||||
v_preview: Option<Value>,
|
||||
v_finder: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Tab {
|
||||
|
|
@ -27,40 +18,29 @@ impl Deref for Tab {
|
|||
|
||||
impl Tab {
|
||||
pub(super) fn make(inner: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self {
|
||||
inner: inner.into(),
|
||||
|
||||
v_name: None,
|
||||
v_mode: None,
|
||||
v_pref: None,
|
||||
v_current: None,
|
||||
v_parent: None,
|
||||
v_selected: None,
|
||||
v_preview: None,
|
||||
v_finder: None,
|
||||
})
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Tab {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
|
||||
cached_field!(fields, name, |lua, me| {
|
||||
let url = &me.current.url;
|
||||
lua.create_string(url.name().map_or_else(|| url.loc().encoded_bytes(), |n| n.encoded_bytes()))
|
||||
fields.add_field_method_get("id", |_, me| Ok(me.id));
|
||||
fields.add_cached_field("name", |lua, me| match me.name() {
|
||||
Cow::Borrowed(s) => lua.create_string(s),
|
||||
Cow::Owned(s) => lua.create_external_string(s),
|
||||
});
|
||||
|
||||
cached_field!(fields, mode, |_, me| Mode::make(&me.mode));
|
||||
cached_field!(fields, pref, |_, me| Preference::make(&me.pref));
|
||||
cached_field!(fields, current, |_, me| Folder::make(None, &me.current, me));
|
||||
cached_field!(fields, parent, |_, me| {
|
||||
fields.add_static_field("mode", |_, me| Mode::make(&me.mode));
|
||||
fields.add_static_field("pref", |_, me| Preference::make(&me.pref));
|
||||
fields.add_static_field("current", |_, me| Folder::make(None, &me.current, me));
|
||||
fields.add_static_field("parent", |_, me| {
|
||||
me.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose()
|
||||
});
|
||||
|
||||
cached_field!(fields, selected, |_, me| Selected::make(&me.selected));
|
||||
fields.add_static_field("selected", |_, me| Selected::make(&me.selected));
|
||||
|
||||
cached_field!(fields, preview, |_, me| Preview::make(me));
|
||||
cached_field!(fields, finder, |_, me| me.finder.as_ref().map(Finder::make).transpose());
|
||||
fields.add_static_field("preview", |_, me| Preview::make(me));
|
||||
fields.add_static_field("finder", |_, me| me.finder.as_ref().map(Finder::make).transpose());
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
|
||||
use yazi_binding::{SER_OPT, cached_field};
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
|
||||
use yazi_scheduler::Progress;
|
||||
use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct TaskSnap {
|
||||
inner: PtrCell<yazi_scheduler::TaskSnap>,
|
||||
|
||||
v_name: Option<Value>,
|
||||
v_prog: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for TaskSnap {
|
||||
|
|
@ -20,17 +18,17 @@ impl Deref for TaskSnap {
|
|||
|
||||
impl TaskSnap {
|
||||
pub(super) fn make(inner: &yazi_scheduler::TaskSnap) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { inner: inner.into(), v_name: None, v_prog: None })
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for TaskSnap {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
cached_field!(fields, name, |lua, me| lua.create_string(&me.name));
|
||||
cached_field!(fields, prog, |lua, me| lua.to_value_with(&me.prog, SER_OPT));
|
||||
fields.add_cached_field("title", |lua, me| lua.create_string(&me.title));
|
||||
fields.add_cached_field("prog", |lua, me| lua.to_value_with(&me.prog, SER_OPT));
|
||||
|
||||
fields.add_field_method_get("cooked", |_, me| Ok(me.prog.cooked()));
|
||||
fields.add_field_method_get("running", |_, me| Ok(me.prog.running()));
|
||||
fields.add_field_method_get("cooked", |_, me| Ok(me.prog.cooked()));
|
||||
fields.add_field_method_get("success", |_, me| Ok(me.prog.success()));
|
||||
fields.add_field_method_get("failed", |_, me| Ok(me.prog.failed()));
|
||||
fields.add_field_method_get("percent", |_, me| Ok(me.prog.percent()));
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
|
||||
use yazi_binding::{SER_OPT, cached_field, deprecate};
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
|
||||
use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
use crate::lives::TaskSnap;
|
||||
use crate::lives::{Behavior, TaskSnap};
|
||||
|
||||
pub(super) struct Tasks {
|
||||
inner: PtrCell<yazi_core::tasks::Tasks>,
|
||||
|
||||
v_snaps: Option<Value>,
|
||||
v_summary: Option<Value>,
|
||||
v_progress: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Tasks {
|
||||
|
|
@ -22,13 +18,7 @@ impl Deref for Tasks {
|
|||
|
||||
impl Tasks {
|
||||
pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self {
|
||||
inner: inner.into(),
|
||||
|
||||
v_snaps: None,
|
||||
v_summary: None,
|
||||
v_progress: None,
|
||||
})
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +26,9 @@ impl UserData for Tasks {
|
|||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
|
||||
|
||||
cached_field!(fields, snaps, |lua, me| {
|
||||
fields.add_static_field("behavior", |_, me| Behavior::make(&me.scheduler.behavior));
|
||||
|
||||
fields.add_static_field("snaps", |lua, me| {
|
||||
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
|
||||
for snap in &me.snaps {
|
||||
tbl.raw_push(TaskSnap::make(snap)?)?;
|
||||
|
|
@ -44,20 +36,6 @@ impl UserData for Tasks {
|
|||
Ok(tbl)
|
||||
});
|
||||
|
||||
cached_field!(fields, summary, |lua, me| lua.to_value_with(&me.summary, SER_OPT));
|
||||
|
||||
cached_field!(fields, progress, |lua, me| {
|
||||
deprecate!(
|
||||
lua,
|
||||
"`cx.tasks.progress` is deprecated, use `cx.tasks.summary` instead, in your {}"
|
||||
);
|
||||
lua.create_table_from([
|
||||
("total", me.summary.total),
|
||||
("succ", me.summary.success),
|
||||
("fail", me.summary.failed),
|
||||
("found", 0),
|
||||
("processed", 0),
|
||||
])
|
||||
});
|
||||
fields.add_cached_field("summary", |lua, me| lua.to_value_with(&me.summary, SER_OPT));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
yazi-actor/src/lives/which.rs
Normal file
35
yazi-actor/src/lives/which.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct Which {
|
||||
inner: PtrCell<yazi_core::which::Which>,
|
||||
}
|
||||
|
||||
impl Deref for Which {
|
||||
type Target = yazi_core::which::Which;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl Which {
|
||||
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
}
|
||||
}
|
||||
|
||||
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_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));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_binding::{Iter, get_metatable};
|
||||
use yazi_binding::Iter;
|
||||
use yazi_fs::file::File;
|
||||
use yazi_shim::mlua::get_metatable;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
|
|
@ -23,7 +25,7 @@ impl Yanked {
|
|||
Lives::scoped_userdata(Self {
|
||||
inner,
|
||||
iter: Lives::scoped_userdata(Iter::new(
|
||||
inner.as_static().iter().map(yazi_binding::Url::new),
|
||||
inner.as_static().iter().map(File::from),
|
||||
Some(inner.len()),
|
||||
))?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_parser::ArrowForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,26 +8,30 @@ use crate::{Actor, Ctx};
|
|||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
type Form = ArrowForm;
|
||||
|
||||
const NAME: &str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
if !tab.current.arrow(opt.step) {
|
||||
let old = tab.current.cursor;
|
||||
if !tab.current.arrow(form.step) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
// Retrace
|
||||
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)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
|
||||
cx.tasks.scheduler.behavior.reset();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::mgr::CdSource;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, mgr::CdSource};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,11 +9,11 @@ use crate::{Actor, Ctx};
|
|||
pub struct Back;
|
||||
|
||||
impl Actor for Back {
|
||||
type Options = VoidOpt;
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "back";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
if let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() {
|
||||
act!(mgr:cd, cx, (u, CdSource::Back))?;
|
||||
}
|
||||
|
|
|
|||
175
yazi-actor/src/mgr/bulk_create.rs
Normal file
175
yazi-actor/src/mgr/bulk_create.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
use std::{fmt::{self, Display}, io::{self, Read, Write}, path::{MAIN_SEPARATOR, Path}};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use scopeguard::defer;
|
||||
use yazi_binding::Permit;
|
||||
use yazi_config::{YAZI, opener::OpenerRuleArc};
|
||||
use yazi_fs::{FilesOp, Splatter, engine::{Engine, local::Local}};
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::TasksProxy;
|
||||
use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
|
||||
use yazi_shared::{data::Data, strand::Strand, url::{UrlBuf, UrlLike}};
|
||||
use yazi_shim::path::CROSS_SEPARATOR;
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_tty::{TTY, sequence::EraseScreen};
|
||||
use yazi_vfs::engine;
|
||||
use yazi_watcher::WATCHER;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
pub struct BulkCreate;
|
||||
impl Actor for BulkCreate {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "bulk_create";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let Some(opener) = Self::opener() else {
|
||||
succ!(NotifyProxy::push_warn("Bulk create", "No text opener found"));
|
||||
};
|
||||
|
||||
let cwd = cx.cwd().clone();
|
||||
tokio::spawn(async move {
|
||||
let tmp = YAZI.preview.tmpfile("bulk-create");
|
||||
let file = engine::create_new(&tmp).await?.file().await?;
|
||||
|
||||
defer! {
|
||||
let tmp = tmp.clone();
|
||||
tokio::spawn(async move {
|
||||
Local::regular(&tmp).remove_file().await
|
||||
});
|
||||
}
|
||||
|
||||
TasksProxy::process_exec(ShellOpt {
|
||||
cwd: cwd.clone(),
|
||||
cmd: Splatter::new(&[file]).splat(&opener.run),
|
||||
block: opener.block,
|
||||
orphan: opener.orphan,
|
||||
})
|
||||
.await;
|
||||
|
||||
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());
|
||||
AppProxy::stop().await;
|
||||
|
||||
let content = Local::regular(&tmp).read_to_string().await?;
|
||||
Self::r#do(cwd, content.lines().filter_map(Entry::parse).collect()).await
|
||||
});
|
||||
succ!()
|
||||
}
|
||||
}
|
||||
|
||||
impl BulkCreate {
|
||||
async fn r#do(cwd: UrlBuf, todo: Vec<Entry<'_>>) -> Result<()> {
|
||||
writef!(TTY.writer(), "{EraseScreen}\n")?;
|
||||
if todo.is_empty() {
|
||||
return Ok(());
|
||||
} else if !Self::ask_continue(&todo, None)? {
|
||||
return Ok(()); // TODO: support `bulk_exit`?
|
||||
}
|
||||
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
let (mut failed, mut succeeded) = (vec![], Vec::with_capacity(todo.len()));
|
||||
for entry in todo {
|
||||
let Ok(dist) = cwd.try_join(entry.path) else {
|
||||
failed.push((entry, anyhow!("Invalid path")));
|
||||
continue;
|
||||
};
|
||||
|
||||
let result: io::Result<()> = if entry.is_dir {
|
||||
engine::create_dir_all(&dist).await
|
||||
} else if let Some(parent) = dist.parent() {
|
||||
engine::create_dir_all(parent).await.ok();
|
||||
engine::create_new(&dist).await.map(|_| ())
|
||||
} else {
|
||||
Err(io::Error::other("No parent directory"))
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
failed.push((entry, e.into()));
|
||||
} else if let Ok(f) = engine::file(dist).await {
|
||||
succeeded.push(f);
|
||||
} else {
|
||||
failed.push((entry, anyhow!("Failed to retrieve file info")));
|
||||
}
|
||||
}
|
||||
|
||||
if !succeeded.is_empty() {
|
||||
// err!(Pubsub::pub_after_bulk_create(it)); // FIXME
|
||||
FilesOp::create(succeeded);
|
||||
}
|
||||
drop(_permit);
|
||||
|
||||
if !failed.is_empty() {
|
||||
Self::output_failed(failed).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn opener() -> Option<OpenerRuleArc> {
|
||||
YAZI
|
||||
.open
|
||||
.match_dummy(Path::new("bulk-create.txt"), "text/plain")
|
||||
.and_then(|r| YAZI.opener.block(&r))
|
||||
}
|
||||
|
||||
fn ask_continue(todo: &[Entry], decision: Option<bool>) -> Result<bool> {
|
||||
if let Some(decision) = decision {
|
||||
return Ok(decision);
|
||||
}
|
||||
|
||||
{
|
||||
let mut w = TTY.lockout();
|
||||
for entry in todo {
|
||||
writeln!(w, "{entry}")?;
|
||||
}
|
||||
write!(w, "Continue to create? (y/N): ")?;
|
||||
w.flush()?;
|
||||
}
|
||||
|
||||
let mut buf = [0; 10];
|
||||
_ = TTY.reader().read(&mut buf)?;
|
||||
Ok(buf[0] == b'y' || buf[0] == b'Y')
|
||||
}
|
||||
|
||||
async fn output_failed(failed: Vec<(Entry<'_>, anyhow::Error)>) -> Result<()> {
|
||||
let mut stdout = TTY.lockout();
|
||||
writeln!(stdout, "{EraseScreen}")?;
|
||||
|
||||
writeln!(stdout, "Failed to create:")?;
|
||||
for (entry, err) in failed {
|
||||
writeln!(stdout, "{entry}: {err}")?;
|
||||
}
|
||||
writeln!(stdout, "\nPress ENTER to exit")?;
|
||||
|
||||
stdout.flush()?;
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// --- Entry
|
||||
struct Entry<'a> {
|
||||
path: Strand<'a>,
|
||||
is_dir: bool,
|
||||
}
|
||||
|
||||
impl<'a> Entry<'a> {
|
||||
fn parse(s: &'a str) -> Option<Self> {
|
||||
let (path, is_dir) = match s.strip_suffix(CROSS_SEPARATOR) {
|
||||
Some(p) => (p, true),
|
||||
None => (s, false),
|
||||
};
|
||||
|
||||
(!path.is_empty()).then_some(Self { path: path.into(), is_dir })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Entry<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_dir {
|
||||
write!(f, "{}{MAIN_SEPARATOR}", self.path.display())
|
||||
} else {
|
||||
self.path.display().fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
yazi-actor/src/mgr/bulk_exit.rs
Normal file
19
yazi-actor/src/mgr/bulk_exit.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::BulkExitForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct BulkExit;
|
||||
|
||||
impl Actor for BulkExit {
|
||||
type Form = BulkExitForm;
|
||||
|
||||
const NAME: &str = "bulk_exit";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
cx.mgr.batcher.decide(form.target, form.accept);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,21 @@
|
|||
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use crossterm::{execute, style::Print};
|
||||
use hashbrown::HashMap;
|
||||
use scopeguard::defer;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use yazi_binding::Permit;
|
||||
use yazi_config::{YAZI, opener::OpenerRule};
|
||||
use yazi_config::{YAZI, opener::OpenerRuleArc};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_macro::{err, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER, TasksProxy};
|
||||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_term::tty::TTY;
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_fs::{FilesOp, Splatter, engine::{Engine, FileBuilder, local::Local}, max_common_root, path::skip_url};
|
||||
use yazi_macro::{err, succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::TasksProxy;
|
||||
use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
|
||||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_tty::{TTY, sequence::EraseScreen};
|
||||
use yazi_vfs::{engine::{self, Demand}, maybe_exists};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -22,18 +23,18 @@ use crate::{Actor, Ctx};
|
|||
pub struct BulkRename;
|
||||
|
||||
impl Actor for BulkRename {
|
||||
type Options = VoidOpt;
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "bulk_rename";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let Some(opener) = Self::opener() else {
|
||||
succ!(AppProxy::notify_warn("Bulk rename", "No text opener found"));
|
||||
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!(AppProxy::notify_warn("Bulk rename", "No files selected"));
|
||||
succ!(NotifyProxy::push_warn("Bulk rename", "No files selected"));
|
||||
}
|
||||
|
||||
let root = max_common_root(&selected);
|
||||
|
|
@ -41,32 +42,31 @@ impl Actor for BulkRename {
|
|||
selected.iter().enumerate().map(|(i, u)| Tuple::new(i, skip_url(u, root))).collect();
|
||||
|
||||
let cwd = cx.cwd().clone();
|
||||
let batcher = cx.core.mgr.batcher.clone();
|
||||
tokio::spawn(async move {
|
||||
let tmp = YAZI.preview.tmpfile("bulk");
|
||||
Gate::default()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&tmp)
|
||||
.await?
|
||||
.write_all(old.join(Strand::Utf8("\n")).encoded_bytes())
|
||||
.await?;
|
||||
let tmp = YAZI.preview.tmpfile("bulk-rename");
|
||||
|
||||
let mut rw = Demand::default().write(true).create_new(true).open(&tmp).await?;
|
||||
rw.write_all(old.join(Strand::Utf8("\n")).encoded_bytes()).await?;
|
||||
|
||||
defer! {
|
||||
let tmp = tmp.clone();
|
||||
batcher.drain(&tmp);
|
||||
tokio::spawn(async move {
|
||||
Local::regular(&tmp).remove_file().await
|
||||
});
|
||||
}
|
||||
TasksProxy::process_exec(
|
||||
cwd.into(),
|
||||
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
|
||||
vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
|
||||
opener.block,
|
||||
opener.orphan,
|
||||
)
|
||||
|
||||
batcher.prime(&tmp);
|
||||
TasksProxy::process_exec(ShellOpt {
|
||||
cwd,
|
||||
cmd: Splatter::new(&[rw.into_file().await?]).splat(&opener.run),
|
||||
block: opener.block,
|
||||
orphan: opener.orphan,
|
||||
})
|
||||
.await;
|
||||
|
||||
let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
|
||||
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());
|
||||
AppProxy::stop().await;
|
||||
|
||||
let new: Vec<_> = Local::regular(&tmp)
|
||||
|
|
@ -78,7 +78,8 @@ impl Actor for BulkRename {
|
|||
.map(|(i, s)| Tuple::new(i, s))
|
||||
.collect();
|
||||
|
||||
Self::r#do(root, old, new, selected).await
|
||||
let decision = batcher.drain(&tmp);
|
||||
Self::r#do(root, old, new, selected, decision).await
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -90,12 +91,12 @@ impl BulkRename {
|
|||
old: Vec<Tuple>,
|
||||
new: Vec<Tuple>,
|
||||
selected: Vec<UrlBuf>,
|
||||
decision: Option<bool>,
|
||||
) -> Result<()> {
|
||||
terminal_clear(TTY.writer())?;
|
||||
writef!(TTY.writer(), "{EraseScreen}\n")?;
|
||||
if old.len() != new.len() {
|
||||
#[rustfmt::skip]
|
||||
let s = format!("Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len());
|
||||
execute!(TTY.writer(), Print(s))?;
|
||||
writef!(TTY.writer(), "Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len())?;
|
||||
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
return Ok(());
|
||||
|
|
@ -107,18 +108,7 @@ impl BulkRename {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
{
|
||||
let mut w = TTY.lockout();
|
||||
for (old, new) in &todo {
|
||||
writeln!(w, "{} -> {}", old.display(), new.display())?;
|
||||
}
|
||||
write!(w, "Continue to rename? (y/N): ")?;
|
||||
w.flush()?;
|
||||
}
|
||||
|
||||
let mut buf = [0; 10];
|
||||
_ = TTY.reader().read(&mut buf)?;
|
||||
if buf[0] != b'y' && buf[0] != b'Y' {
|
||||
if !Self::ask_continue(&todo, decision)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -132,11 +122,11 @@ impl BulkRename {
|
|||
continue;
|
||||
};
|
||||
|
||||
if maybe_exists(&new).await && !provider::must_identical(&old, &new).await {
|
||||
if maybe_exists(&new).await && !engine::must_identical(&old, &new).await {
|
||||
failed.push((o, n, anyhow!("Destination already exists")));
|
||||
} else if let Err(e) = provider::rename(&old, &new).await {
|
||||
} else if let Err(e) = engine::rename(&old, &new).await {
|
||||
failed.push((o, n, e.into()));
|
||||
} else if let Ok(f) = File::new(new).await {
|
||||
} else if let Ok(f) = engine::file(new).await {
|
||||
succeeded.insert(old, f);
|
||||
} else {
|
||||
failed.push((o, n, anyhow!("Failed to retrieve file info")));
|
||||
|
|
@ -145,7 +135,7 @@ impl BulkRename {
|
|||
|
||||
if !succeeded.is_empty() {
|
||||
let it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));
|
||||
err!(Pubsub::pub_after_bulk(it));
|
||||
err!(Pubsub::pub_after_bulk_rename(it));
|
||||
FilesOp::rename(succeeded);
|
||||
}
|
||||
drop(permit);
|
||||
|
|
@ -156,17 +146,39 @@ impl BulkRename {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn opener() -> Option<&'static OpenerRule> {
|
||||
YAZI.opener.block(YAZI.open.all(Path::new("bulk-rename.txt"), "text/plain"))
|
||||
fn opener() -> Option<OpenerRuleArc> {
|
||||
YAZI
|
||||
.open
|
||||
.match_dummy(Path::new("bulk-rename.txt"), "text/plain")
|
||||
.and_then(|r| YAZI.opener.block(&r))
|
||||
}
|
||||
|
||||
fn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result<UrlBuf> {
|
||||
Ok(url.try_replace(take, PathDyn::with(url.kind(), rep)?)?.into_owned())
|
||||
}
|
||||
|
||||
fn ask_continue(todo: &[(Tuple, Tuple)], decision: Option<bool>) -> Result<bool> {
|
||||
if let Some(decision) = decision {
|
||||
return Ok(decision);
|
||||
}
|
||||
|
||||
{
|
||||
let mut w = TTY.lockout();
|
||||
for (old, new) in todo {
|
||||
writeln!(w, "{} -> {}", old.display(), new.display())?;
|
||||
}
|
||||
write!(w, "Continue to rename? (y/N): ")?;
|
||||
w.flush()?;
|
||||
}
|
||||
|
||||
let mut buf = [0; 10];
|
||||
_ = TTY.reader().read(&mut buf)?;
|
||||
Ok(buf[0] == b'y' || buf[0] == b'Y')
|
||||
}
|
||||
|
||||
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {
|
||||
let mut stdout = TTY.lockout();
|
||||
terminal_clear(&mut *stdout)?;
|
||||
writeln!(stdout, "{EraseScreen}")?;
|
||||
|
||||
writeln!(stdout, "Failed to rename:")?;
|
||||
for (old, new, err) in failed {
|
||||
|
|
|
|||
|
|
@ -3,64 +3,67 @@ use std::{mem, time::Duration};
|
|||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_core::mgr::CdSource;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
|
||||
use yazi_macro::{act, err, render, succ};
|
||||
use yazi_parser::mgr::CdOpt;
|
||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_fs::{FilesOp, path::{clean_url, expand_url}};
|
||||
use yazi_macro::{act, err, input, render, succ};
|
||||
use yazi_parser::mgr::CdForm;
|
||||
use yazi_proxy::{CmpProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::engine;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Cd;
|
||||
|
||||
impl Actor for Cd {
|
||||
type Options = CdOpt;
|
||||
type Form = CdForm;
|
||||
|
||||
const NAME: &str = "cd";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
if opt.interactive {
|
||||
if form.interactive {
|
||||
return Self::cd_interactive(cx);
|
||||
}
|
||||
|
||||
let tab = cx.tab_mut();
|
||||
if opt.target == *tab.cwd() {
|
||||
} else if form.target == *cx.cwd() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
// Stash first so it's possible to access the original cwd in hooks
|
||||
act!(mgr:stash, cx, &form).ok();
|
||||
|
||||
// Take parent to history
|
||||
let tab = cx.tab_mut();
|
||||
if let Some(t) = tab.parent.take() {
|
||||
tab.history.insert(t.url.clone(), t);
|
||||
}
|
||||
|
||||
// Current
|
||||
let rep = tab.history.remove_or(&opt.target);
|
||||
let rep = tab.history.remove_or(&form.target);
|
||||
let rep = mem::replace(&mut tab.current, rep);
|
||||
tab.history.insert(rep.url.clone(), rep);
|
||||
|
||||
// Parent
|
||||
if let Some(parent) = opt.target.parent() {
|
||||
if let Some(parent) = form.target.parent() {
|
||||
tab.parent = Some(tab.history.remove_or(parent));
|
||||
}
|
||||
|
||||
err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
|
||||
act!(mgr:displace, cx)?;
|
||||
act!(mgr:hidden, cx)?;
|
||||
act!(mgr:hidden, cx).ok();
|
||||
act!(mgr:sort, cx).ok();
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:refresh, cx)?;
|
||||
act!(mgr:stash, cx, opt).ok();
|
||||
act!(app:title, cx).ok();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
||||
impl Cd {
|
||||
fn cd_interactive(cx: &Ctx) -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::cd(cx.cwd().as_url()));
|
||||
fn cd_interactive(cx: &mut Ctx) -> Result<Data> {
|
||||
let input = input!(cx, YAZI.input.cd(cx.cwd().as_url()))?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
|
||||
|
|
@ -68,22 +71,22 @@ impl Cd {
|
|||
|
||||
while let Some(result) = rx.next().await {
|
||||
match result {
|
||||
Ok(s) => {
|
||||
InputEvent::Submit(s) => {
|
||||
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
|
||||
let Ok(url) = provider::absolute(&url).await else { return };
|
||||
let Ok(url) = engine::absolute(&url).await else { return };
|
||||
let url = clean_url(url);
|
||||
|
||||
let Ok(file) = File::new(&url).await else { return };
|
||||
let Ok(file) = engine::file(&url).await else { return };
|
||||
if file.is_dir() {
|
||||
return MgrProxy::cd(&url);
|
||||
return MgrProxy::cd(&url, CdSource::Cd);
|
||||
}
|
||||
|
||||
if let Some(p) = url.parent() {
|
||||
FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
|
||||
if let Some((p, k)) = url.pair2() {
|
||||
FilesOp::Upserting(p.into(), [(k.into(), file)].into()).emit();
|
||||
}
|
||||
MgrProxy::reveal(url);
|
||||
}
|
||||
Err(InputError::Completed(before, ticket)) => {
|
||||
InputEvent::Trigger(before, ticket) => {
|
||||
CmpProxy::trigger(before, ticket);
|
||||
}
|
||||
_ => break,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::mgr::CloseOpt;
|
||||
use yazi_parser::{mgr::CloseForm, spark::SparkKind};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,15 +8,19 @@ use crate::{Actor, Ctx};
|
|||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
type Form = CloseForm;
|
||||
|
||||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
if cx.tabs().len() > 1 {
|
||||
act!(mgr:tab_close, cx, cx.tabs().cursor)
|
||||
} else {
|
||||
act!(mgr:quit, cx, opt.0)
|
||||
act!(mgr:quit, cx, opt)
|
||||
}
|
||||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
cx.source().is_key().then_some(SparkKind::KeyClose)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Result, bail};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::CopyOpt;
|
||||
use yazi_parser::mgr::CopyForm;
|
||||
use yazi_shared::{data::Data, strand::ToStrand, url::UrlLike};
|
||||
use yazi_widgets::CLIPBOARD;
|
||||
|
||||
|
|
@ -9,39 +9,39 @@ use crate::{Actor, Ctx};
|
|||
pub struct Copy;
|
||||
|
||||
impl Actor for Copy {
|
||||
type Options = CopyOpt;
|
||||
type Form = CopyForm;
|
||||
|
||||
const NAME: &str = "copy";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
let mut s = Vec::<u8>::new();
|
||||
let mut it = if opt.hovered {
|
||||
Box::new(cx.hovered().map(|h| &h.url).into_iter())
|
||||
let mut it = if form.hovered {
|
||||
Box::new(cx.hovered_url().into_iter())
|
||||
} else {
|
||||
cx.tab().selected_or_hovered()
|
||||
cx.tab().selected_or_hovered_urls()
|
||||
}
|
||||
.peekable();
|
||||
|
||||
while let Some(u) = it.next() {
|
||||
match opt.r#type.as_ref() {
|
||||
match form.r#type.as_ref() {
|
||||
// TODO: rename to "url"
|
||||
"path" => {
|
||||
s.extend_from_slice(&opt.separator.transform(&u.to_strand()));
|
||||
s.extend_from_slice(&form.separator.transform(&u.to_strand()));
|
||||
}
|
||||
"dirname" => {
|
||||
if let Some(p) = u.parent() {
|
||||
s.extend_from_slice(&opt.separator.transform(&p.to_strand()));
|
||||
s.extend_from_slice(&form.separator.transform(&p.to_strand()));
|
||||
}
|
||||
}
|
||||
"filename" => {
|
||||
s.extend_from_slice(&opt.separator.transform(&u.name().unwrap_or_default()));
|
||||
s.extend_from_slice(&form.separator.transform(&u.name().unwrap_or_default()));
|
||||
}
|
||||
"name_without_ext" => {
|
||||
s.extend_from_slice(&opt.separator.transform(&u.stem().unwrap_or_default()));
|
||||
s.extend_from_slice(&form.separator.transform(&u.stem().unwrap_or_default()));
|
||||
}
|
||||
_ => bail!("Unknown copy type: {}", opt.r#type),
|
||||
_ => bail!("Unknown copy type: {}", form.r#type),
|
||||
};
|
||||
if it.peek().is_some() {
|
||||
s.push(b'\n');
|
||||
|
|
@ -49,8 +49,8 @@ impl Actor for Copy {
|
|||
}
|
||||
|
||||
// Copy the CWD path regardless even if the directory is empty
|
||||
if s.is_empty() && opt.r#type == "dirname" {
|
||||
s.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand()));
|
||||
if s.is_empty() && form.r#type == "dirname" {
|
||||
s.extend_from_slice(&form.separator.transform(&cx.cwd().to_strand()));
|
||||
}
|
||||
|
||||
futures::executor::block_on(CLIPBOARD.set(s));
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue