Merge branch 'main' into ignores

This commit is contained in:
Carlos de Paula 2026-01-04 18:40:38 -03:00
commit 712823cf5b
No known key found for this signature in database
470 changed files with 13228 additions and 7108 deletions

View file

@ -4,6 +4,9 @@ on:
push: push:
branches: [main] branches: [main]
permissions:
contents: read
jobs: jobs:
publish: publish:
name: Publish Flake name: Publish Flake
@ -12,7 +15,7 @@ jobs:
os: [ubuntu-latest, macos-latest] os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v31 uses: cachix/install-nix-action@v31

View file

@ -10,11 +10,14 @@ env:
SCCACHE_GHA_ENABLED: true SCCACHE_GHA_ENABLED: true
RUSTC_WRAPPER: sccache RUSTC_WRAPPER: sccache
permissions:
contents: read
jobs: jobs:
clippy: clippy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
run: | run: |
@ -31,7 +34,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
run: | run: |
@ -47,7 +50,7 @@ jobs:
stylua: stylua:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: JohnnyMorganz/stylua-action@v4 - uses: JohnnyMorganz/stylua-action@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -9,6 +9,9 @@ on:
env: env:
SCCACHE_GHA_ENABLED: true SCCACHE_GHA_ENABLED: true
permissions:
contents: read
jobs: jobs:
build-unix: build-unix:
strategy: strategy:
@ -39,7 +42,7 @@ jobs:
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- name: Install gcc - name: Install gcc
if: matrix.gcc != '' if: matrix.gcc != ''
@ -56,7 +59,7 @@ jobs:
run: ./scripts/build.sh ${{ matrix.target }} run: ./scripts/build.sh ${{ matrix.target }}
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: ${{ matrix.target }} name: ${{ matrix.target }}
path: | path: |
@ -78,7 +81,7 @@ jobs:
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
@ -102,7 +105,7 @@ jobs:
Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath "${env:TARGET_NAME}.zip" Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath "${env:TARGET_NAME}.zip"
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: ${{ matrix.target }} name: ${{ matrix.target }}
path: yazi-${{ matrix.target }}.zip path: yazi-${{ matrix.target }}.zip
@ -117,7 +120,7 @@ jobs:
container: container:
image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
@ -130,7 +133,7 @@ jobs:
run: ./scripts/build.sh ${{ matrix.target }} run: ./scripts/build.sh ${{ matrix.target }}
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: ${{ matrix.target }} name: ${{ matrix.target }}
path: | path: |
@ -147,12 +150,12 @@ jobs:
arch: arm64 arch: arm64
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup LXD - name: Setup LXD
uses: canonical/setup-lxd@v0.1.3 uses: canonical/setup-lxd@v1
- name: Setup snapcraft - name: Setup snapcraft
run: sudo snap install --classic snapcraft run: sudo snap install --classic snapcraft
@ -164,7 +167,7 @@ jobs:
run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: snap-${{ matrix.arch }} name: snap-${{ matrix.arch }}
path: yazi-${{ matrix.arch }}.snap path: yazi-${{ matrix.arch }}.snap
@ -173,7 +176,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build-snap] needs: [build-snap]
steps: steps:
- uses: actions/download-artifact@v6 - uses: actions/download-artifact@v7
with: with:
pattern: snap-* pattern: snap-*
merge-multiple: true merge-multiple: true
@ -202,7 +205,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build-unix, build-windows, build-musl, build-snap] needs: [build-unix, build-windows, build-musl, build-snap]
steps: steps:
- uses: actions/download-artifact@v6 - uses: actions/download-artifact@v7
with: with:
merge-multiple: true merge-multiple: true
@ -227,11 +230,12 @@ jobs:
echo 'NIGHTLY_BODY<<EOF' >> $GITHUB_ENV echo 'NIGHTLY_BODY<<EOF' >> $GITHUB_ENV
echo "From commit: ${GITHUB_SHA:0:8}" >> $GITHUB_ENV echo "From commit: ${GITHUB_SHA:0:8}" >> $GITHUB_ENV
echo "Generated on: $(date -u +"%Y-%m-%d %H:%M") UTC" >> $GITHUB_ENV echo "Generated on: $(date -u +"%Y-%m-%d %H:%M") UTC" >> $GITHUB_ENV
echo "Nightly changelog: https://github.com/sxyazi/yazi/blob/main/CHANGELOG.md#unreleased" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions/download-artifact@v6 - uses: actions/download-artifact@v7
with: with:
merge-multiple: true merge-multiple: true

View file

@ -16,7 +16,7 @@ jobs:
pull-requests: write pull-requests: write
discussions: write discussions: write
steps: steps:
- uses: dessant/lock-threads@v5 - uses: dessant/lock-threads@v6
with: with:
issue-inactive-days: "30" issue-inactive-days: "30"
issue-comment: > issue-comment: >

View file

@ -4,11 +4,14 @@ on:
release: release:
types: [published] types: [published]
permissions:
contents: read
jobs: jobs:
winget: winget:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/download-artifact@v6 - uses: actions/download-artifact@v7
with: with:
merge-multiple: true merge-multiple: true
@ -19,6 +22,13 @@ jobs:
installers-regex: 'yazi-(x86_64|aarch64)-pc-windows-msvc\.zip$' installers-regex: 'yazi-(x86_64|aarch64)-pc-windows-msvc\.zip$'
token: ${{ secrets.WINGET_TOKEN }} token: ${{ secrets.WINGET_TOKEN }}
snapcraft:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v7
with:
merge-multiple: true
- name: Promote snap to stable - name: Promote snap to stable
env: env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }} SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}

View file

@ -11,6 +11,9 @@ env:
RUSTC_WRAPPER: sccache RUSTC_WRAPPER: sccache
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
permissions:
contents: read
jobs: jobs:
test: test:
strategy: strategy:
@ -18,7 +21,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
run: rustup toolchain install stable --profile minimal run: rustup toolchain install stable --profile minimal

View file

@ -12,7 +12,7 @@ jobs:
permissions: permissions:
issues: write issues: write
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6

View file

@ -12,16 +12,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
## [Unreleased] ## [Unreleased]
## [v26.1.4]
### Added ### Added
- Remote file management ([#3166], [#3170], [#3172], [#3198], [#3201], [#3243], [#3264], [#3268]) - Support VFS for preset previewers that rely on external commands ([#3477])
- Support 8-bit images in RGB, CIELAB, and GRAY color spaces ([#3358])
### Fixed
- `ya pkg` fails to write `package.toml` when the config directory does not exist ([#3482])
- A race condition generating unique filenames for concurrent file operations ([#3494])
## [v25.12.29]
### Added
- Remote file management ([#3396])
- Virtual file system ([#3034], [#3035], [#3094], [#3108], [#3187], [#3203]) - Virtual file system ([#3034], [#3035], [#3094], [#3108], [#3187], [#3203])
- Shell formatting ([#3232]) - Shell formatting ([#3232])
- Multi-entry support for plugin system ([#3154]) - Multi-entry support for plugin system ([#3154])
- Zoom in or out of the preview image ([#2864]) - Zoom in or out of the preview image ([#2864])
- Improve the UX of the pick and input components ([#2906], [#2935]) - Improve the UX of the pick and input components ([#2906], [#2935])
- Show progress of each task in task manager ([#3121], [#3131], [#3134]) - Show progress of each task in task manager ([#3121], [#3131], [#3134])
- New `fs.copy()` and `fs.rename()` APIs ([#3467])
- New experimental `ya.async()` API ([#3422])
- New `overall` option to set the overall background color ([#3317]) - 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` command always renames files with the editor ([#2984])
- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037]) - `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]) - New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])
@ -30,12 +47,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964]) - Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964])
- Support `.deb` packages ([#2807], [#3128], [#3209]) - Support `.deb` packages ([#2807], [#3128], [#3209])
- Port several widespread GUI keys to the input component ([#2849]) - Port several widespread GUI keys to the input component ([#2849])
- Support invalid UTF-8 paths throughout the codebase ([#2884], [#2889], [#2890], [#2895], [#3023], [#3290]) - Support invalid UTF-8 paths throughout the codebase ([#2884], [#2889], [#2890], [#2895], [#3023], [#3290], [#3369])
- Allow upgrading only specific packages with `ya pkg` ([#2841]) - Allow upgrading only specific packages with `ya pkg` ([#2841])
- Respect the user's `image_filter` setting in the preset ImageMagick previewer ([#3286]) - Respect the user's `image_filter` setting in the preset ImageMagick previewer ([#3286])
- New `duplicate` DDS event for copying files ([#3456])
- New `ind-sort` and `key-sort` DDS events to change sorting in Lua ([#3391])
- Allow custom mouse click behavior for individual files ([#2925]) - Allow custom mouse click behavior for individual files ([#2925])
- Display newlines in input as spaces to improve readability ([#2932]) - Display newlines in input as spaces to improve readability ([#2932])
- Fill in error messages if preview fails ([#2917]) - Fill in error messages if preview fails ([#2917], [#3383], [#3387])
- Search view shares file selection and yank state ([#2855]) - Search view shares file selection and yank state ([#2855])
- Offload mimetype fetching on opening files to the task scheduler ([#3141]) - Offload mimetype fetching on opening files to the task scheduler ([#3141])
- Increase terminal response timeout to better tolerate slow SSH network environments ([#2843]) - Increase terminal response timeout to better tolerate slow SSH network environments ([#2843])
@ -44,7 +63,9 @@ 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 `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]) - 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` command 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 `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]) - Use `u16` instead of `u32` as the type of `max_width` and `max_height` options to avoid memory exhaustion ([#3313])
- Implement `__pairs` metamethod instead of `__index` for the callback argument of the `@yank` DDS event ([#2997]) - Implement `__pairs` metamethod instead of `__index` for the callback argument of the `@yank` DDS event ([#2997])
@ -60,6 +81,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Fixed ### Fixed
- User-prepended open rules do not override presets ([#3360])
- Respect user's system media opener instead of hardcoding `mpv` ([#2959]) - 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` command under empty directories ([#3225])
- Avoid appending a newline when reading clipboard contents ([#3059]) - Avoid appending a newline when reading clipboard contents ([#3059])
@ -71,14 +93,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Force Git checkout for plugin cache repositories ([#3169]) - Force Git checkout for plugin cache repositories ([#3169])
- Check compatibility when reusing previewer bytecode cache ([#3190]) - Check compatibility when reusing previewer bytecode cache ([#3190])
- Disable kitty keyboard protocol on Windows due to `crossterm` inability to handle it ([#3250]) - Disable kitty keyboard protocol on Windows due to `crossterm` inability to handle it ([#3250])
- Prevent quotes in file(1) arguments from being stripped under MSYS2 ([#3364])
- Expose `ya` CLI in the Snap build ([#2904]) - Expose `ya` CLI in the Snap build ([#2904])
- Fallback to `PollWatcher` for file changes watching on NetBSD ([#2941]) - Fallback to `PollWatcher` for file changes watching on NetBSD ([#2941])
- Generate unique image IDs for Kgp to tolerate tmux ([#3038]) - Generate unique image IDs for Kgp to tolerate tmux ([#3038])
### Improved ### Improved
- Make copy, cut, delete, link, hardlink, download, and upload tasks immediately cancellable ([#3429])
- Make preload tasks discardable ([#2875]) - Make preload tasks discardable ([#2875])
- Reduce file change event frequency ([#2820]) - Reduce file change event frequency ([#2820])
- Upload and download of a single file over SFTP in chunks concurrently ([#3393])
- Do not listen for file changes in inactive tabs ([#2958]) - Do not listen for file changes in inactive tabs ([#2958])
- Switch to a higher-performance hash algorithm ([#3083]) - Switch to a higher-performance hash algorithm ([#3083])
- Sequence-based rendering merge strategy ([#2861]) - Sequence-based rendering merge strategy ([#2861])
@ -776,7 +801,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Show keywords when in search mode ([#152]) - Show keywords when in search mode ([#152])
- Tab switch wraparound ([#160]) - Tab switch wraparound ([#160])
- Highlight matched keywords in find mode ([#211]) - Highlight matched keywords in find mode ([#211])
- Customable main UI border styles ([#278]) - Customizable main UI border styles ([#278])
- `<BackTab>` key notion ([#209]) - `<BackTab>` key notion ([#209])
- Use of environment variables in `cd` paths ([#241]) - Use of environment variables in `cd` paths ([#241])
- Nix Flakes package ([#205]) - Nix Flakes package ([#205])
@ -962,6 +987,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[v25.4.8]: https://github.com/sxyazi/yazi/compare/v25.3.2...v25.4.8 [v25.4.8]: https://github.com/sxyazi/yazi/compare/v25.3.2...v25.4.8
[v25.5.28]: https://github.com/sxyazi/yazi/compare/v25.4.8...v25.5.28 [v25.5.28]: https://github.com/sxyazi/yazi/compare/v25.4.8...v25.5.28
[v25.5.31]: https://github.com/sxyazi/yazi/compare/v25.5.28...v25.5.31 [v25.5.31]: https://github.com/sxyazi/yazi/compare/v25.5.28...v25.5.31
[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
[#4]: https://github.com/sxyazi/yazi/pull/4 [#4]: https://github.com/sxyazi/yazi/pull/4
[#5]: https://github.com/sxyazi/yazi/pull/5 [#5]: https://github.com/sxyazi/yazi/pull/5
[#6]: https://github.com/sxyazi/yazi/pull/6 [#6]: https://github.com/sxyazi/yazi/pull/6
@ -1537,3 +1564,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3290]: https://github.com/sxyazi/yazi/pull/3290 [#3290]: https://github.com/sxyazi/yazi/pull/3290
[#3313]: https://github.com/sxyazi/yazi/pull/3313 [#3313]: https://github.com/sxyazi/yazi/pull/3313
[#3317]: https://github.com/sxyazi/yazi/pull/3317 [#3317]: https://github.com/sxyazi/yazi/pull/3317
[#3358]: https://github.com/sxyazi/yazi/pull/3358
[#3360]: https://github.com/sxyazi/yazi/pull/3360
[#3361]: https://github.com/sxyazi/yazi/pull/3361
[#3364]: https://github.com/sxyazi/yazi/pull/3364
[#3369]: https://github.com/sxyazi/yazi/pull/3369
[#3383]: https://github.com/sxyazi/yazi/pull/3383
[#3385]: https://github.com/sxyazi/yazi/pull/3385
[#3387]: https://github.com/sxyazi/yazi/pull/3387
[#3391]: https://github.com/sxyazi/yazi/pull/3391
[#3393]: https://github.com/sxyazi/yazi/pull/3393
[#3396]: https://github.com/sxyazi/yazi/pull/3396
[#3419]: https://github.com/sxyazi/yazi/pull/3419
[#3422]: https://github.com/sxyazi/yazi/pull/3422
[#3429]: https://github.com/sxyazi/yazi/pull/3429
[#3456]: https://github.com/sxyazi/yazi/pull/3456
[#3467]: https://github.com/sxyazi/yazi/pull/3467
[#3477]: https://github.com/sxyazi/yazi/pull/3477
[#3482]: https://github.com/sxyazi/yazi/pull/3482
[#3494]: https://github.com/sxyazi/yazi/pull/3494

1953
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,9 @@ resolver = "2"
members = [ "yazi-*" ] members = [ "yazi-*" ]
default-members = [ "yazi-fm", "yazi-cli" ] default-members = [ "yazi-fm", "yazi-cli" ]
[profile.dev]
debug = "line-tables-only"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
lto = true lto = true
@ -19,46 +22,57 @@ codegen-units = 256
incremental = true incremental = true
lto = false lto = false
[profile.dev.package."*"]
debug = false
[workspace.dependencies] [workspace.dependencies]
ansi-to-tui = "7.0.0" ansi-to-tui = "8.0.0"
anyhow = "1.0.100" anyhow = "1.0.100"
base64 = "0.22.1" base64 = "0.22.1"
bitflags = { version = "2.10.0", features = [ "serde" ] } bitflags = { version = "2.10.0", features = [ "serde" ] }
clap = { version = "4.5.51", features = [ "derive" ] } clap = { version = "4.5.54", features = [ "derive" ] }
core-foundation-sys = "0.8.7" core-foundation-sys = "0.8.7"
crossterm = { version = "0.29.0", features = [ "event-stream" ] } crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0" dirs = "6.0.0"
foldhash = "0.2.0" foldhash = "0.2.0"
futures = "0.3.31" futures = "0.3.31"
globset = "0.4.18" globset = "0.4.18"
hashbrown = { version = "0.16.0", features = [ "serde" ] } hashbrown = { version = "0.16.1", features = [ "serde" ] }
indexmap = { version = "2.12.0", features = [ "serde" ] } indexmap = { version = "2.12.1", features = [ "serde" ] }
libc = "0.2.177" libc = "0.2.179"
lru = "0.16.2" lru = "0.16.2"
mlua = { version = "0.11.4", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] } mlua = { version = "0.11.5", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
objc2 = "0.6.3" objc2 = "0.6.3"
ordered-float = { version = "5.1.0", features = [ "serde" ] } ordered-float = { version = "5.1.0", features = [ "serde" ] }
parking_lot = "0.12.5" parking_lot = "0.12.5"
paste = "1.0.15" paste = "1.0.15"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } 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" regex = "1.12.2"
russh = { version = "0.54.6", default-features = false, features = [ "ring", "rsa" ] } russh = { version = "0.56.0", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] } serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.145" serde_json = "1.0.148"
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.48.0", features = [ "full" ] } thiserror = "2.0.17"
tokio = { version = "1.49.0", features = [ "full" ] }
tokio-stream = "0.1.17" tokio-stream = "0.1.17"
tokio-util = "0.7.17" tokio-util = "0.7.17"
toml = { version = "0.9.8" } toml = { version = "0.9.10" }
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] } 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" ] } twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
unicode-width = { version = "0.2.0", default-features = false } typed-path = "0.12.0"
uzers = "0.12.1" unicode-width = { version = "0.2.2", default-features = false }
uzers = "0.12.2"
[workspace.lints.clippy] [workspace.lints.clippy]
format_push_string = "warn" format_push_string = "warn"
implicit_clone = "warn" if_same_then_else = "allow"
module_inception = "allow" implicit_clone = "warn"
use_self = "warn" len_without_is_empty = "allow"
missing_safety_doc = "allow"
module_inception = "allow"
option_map_unit_fn = "allow"
unit_arg = "allow"
use_self = "warn"

View file

@ -23,13 +23,14 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++ and Chafa, covering almost all terminals. - 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++ and Chafa, covering almost all terminals.
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading. - 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer/preloader/spotter/fetcher; Just some pieces of Lua. - 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer/preloader/spotter/fetcher; Just some pieces of Lua.
- ☁️ **Virtual Filesystem**: Remote file management, custom search engines.
- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence. - 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.
- 📦 **Package Manager**: Install plugins and themes with one command, keeping them up-to-date, or pin them to a specific version. - 📦 **Package Manager**: Install plugins and themes with one command, keeping them up-to-date, or pin them to a specific version.
- 🧰 Integration with ripgrep, fd, fzf, zoxide - 🧰 Integration with ripgrep, fd, fzf, zoxide
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths - 💫 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.) - 🏷️ 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) - 🔄 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, Virtual Filesystem, CSI u, OSC 52 - 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
- ... and more! - ... and more!
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7 https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7

View file

@ -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"],"version":"0.2"} {"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"],"version":"0.2","language":"en"}

12
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1762286042, "lastModified": 1763806073,
"narHash": "sha256-OD5HsZ+sN7VvNucbrjiCz7CHF5zf9gP51YVJvPwYIH8=", "narHash": "sha256-FHsEKDvfWpzdADWj99z7vBk4D716Ujdyveo5+A048aI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "12c1f0253aa9a54fdf8ec8aecaafada64a111e24", "rev": "878e468e02bfabeda08c79250f7ad583037f2227",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -48,11 +48,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1762396738, "lastModified": 1763952169,
"narHash": "sha256-BarSecuxtzp1boERdABLkkoxQTi6s/V33lJwUbWLrLY=", "narHash": "sha256-+PeDBD8P+NKauH+w7eO/QWCIp8Cx4mCfWnh9sJmy9CM=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "c63598992afd54d215d54f2b764adc0484c2b159", "rev": "ab726555a9a72e6dc80649809147823a813fa95b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -52,10 +52,13 @@
}; };
devShells = { devShells = {
default = pkgs.callPackage ./nix/shell.nix { }; default = pkgs.callPackage ./nix/shell.nix {
inherit toolchain;
inherit (self.packages.${system}) yazi yazi-unwrapped;
};
}; };
formatter = pkgs.nixfmt-rfc-style; formatter = pkgs.nixfmt-tree;
} }
) )
// { // {

View file

@ -1,14 +1,14 @@
{ {
callPackage, mkShell,
rust-bin, yazi,
toolchain,
nodePackages, nodePackages,
yazi-unwrapped,
}: }:
let
mainPkg = callPackage ./yazi.nix { }; mkShell {
in packages = yazi.passthru.runtimePaths ++ [
mainPkg.overrideAttrs (oa: { (toolchain.override {
nativeBuildInputs = [
(rust-bin.nightly.latest.default.override {
extensions = [ extensions = [
"rust-src" "rust-src"
"rustfmt" "rustfmt"
@ -16,10 +16,10 @@ mainPkg.overrideAttrs (oa: {
"clippy" "clippy"
]; ];
}) })
nodePackages.cspell nodePackages.cspell
] ];
++ (oa.nativeBuildInputs or [ ]);
inputsFrom = [ yazi-unwrapped ];
env.RUST_BACKTRACE = "1"; env.RUST_BACKTRACE = "1";
}) }

View file

@ -6,7 +6,7 @@
lib, lib,
installShellFiles, installShellFiles,
stdenv, fetchFromGitHub,
rust-jemalloc-sys, rust-jemalloc-sys,
imagemagick, imagemagick,
@ -22,7 +22,7 @@ let
]; ];
}; };
in in
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage (finalAttrs: {
pname = "yazi"; pname = "yazi";
inherit version src; inherit version src;
@ -60,14 +60,27 @@ rustPlatform.buildRustPackage rec {
magick assets/logo.png -resize "$RES"x"$RES" $out/share/icons/hicolor/"$RES"x"$RES"/apps/yazi.png magick assets/logo.png -resize "$RES"x"$RES" $out/share/icons/hicolor/"$RES"x"$RES"/apps/yazi.png
done done
installManPage ${finalAttrs.passthru.srcs.man_src}/yazi{.1,-config.5}
mkdir -p $out/share/applications mkdir -p $out/share/applications
install -m644 assets/yazi.desktop $out/share/applications/ install -m644 assets/yazi.desktop $out/share/applications/
''; '';
passthru.srcs = {
man_src = fetchFromGitHub {
name = "manpages"; # needed to ensure name is unique
owner = "yazi-rs";
repo = "manpages";
rev = "8950e968f4a1ad0b83d5836ec54a070855068dbf";
hash = "sha256-kEVXejDg4ChFoMNBvKlwdFEyUuTcY2VuK9j0PdafKus=";
};
};
meta = { meta = {
description = "Blazing fast terminal file manager written in Rust, based on async I/O"; description = "Blazing fast terminal file manager written in Rust, based on async I/O";
homepage = "https://github.com/sxyazi/yazi"; homepage = "https://github.com/sxyazi/yazi";
license = lib.licenses.mit; license = lib.licenses.mit;
mainProgram = "yazi"; mainProgram = "yazi";
}; };
} })

View file

@ -96,6 +96,8 @@ runCommand yazi-unwrapped.name
inherit (yazi-unwrapped) pname version meta; inherit (yazi-unwrapped) pname version meta;
nativeBuildInputs = [ makeWrapper ]; nativeBuildInputs = [ makeWrapper ];
passthru.runtimePaths = runtimePaths;
} }
'' ''
mkdir -p $out/bin mkdir -p $out/bin

View file

@ -7,7 +7,14 @@ cd "$SCRIPT_DIR/.."
echo "Bumping version: $1" echo "Bumping version: $1"
TOML_FILES="$(git ls-files '*Cargo.toml')" TOML_FILES="$(git ls-files '*Cargo.toml')"
perl -pi -e "s/^version .*= .*\$/version = \"$1\"/" -- $TOML_FILES perl -pi -e 's/^version .*= .*$/version = "'"$1"'"/' -- $TOML_FILES
perl -pi -e "s/^(yazi-[a-z]+)\\s*=\\s*{.*\$/\\1 = { path = \"..\/\\1\", version = \"$1\" }/" -- $TOML_FILES perl -pi -e 's/^(yazi-[a-z]+)\s*=\s*{.*$/\1 = { path = "..\/\1", version = "'"$1"'" }/' -- $TOML_FILES
# Insert "## [v$1]" after "## [Unreleased]"
perl -0777 -pe "s/^(## \[Unreleased\]\s*)/\\1## [v$1]\n\n/m" -i CHANGELOG.md
# Determine previous version and append compare link
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_USE_FLAT_CONFIG=true eslint -c ~/.config/rules/eslint/eslint.config.cjs --fix -- $TOML_FILES

View file

@ -1,20 +0,0 @@
#!/usr/bin/env bash
cargo publish -p yazi-macro && sleep 30
cargo publish -p yazi-codegen && sleep 30
cargo publish -p yazi-shared && sleep 30
cargo publish -p yazi-ffi && sleep 30
cargo publish -p yazi-fs && sleep 30
cargo publish -p yazi-term && sleep 30
cargo publish -p yazi-config && sleep 30
cargo publish -p yazi-proxy && sleep 30
cargo publish -p yazi-adapter && sleep 30
cargo publish -p yazi-boot && sleep 30
cargo publish -p yazi-binding && sleep 30
cargo publish -p yazi-dds && sleep 30
cargo publish -p yazi-scheduler && sleep 30
cargo publish -p yazi-plugin && sleep 30
cargo publish -p yazi-widgets && sleep 30
cargo publish -p yazi-core && sleep 30
cargo publish -p yazi-fm && sleep 30
cargo publish -p yazi-cli

View file

@ -8,27 +8,37 @@
"name": "validate-form", "name": "validate-form",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^2.0.1",
"@actions/github": "^6.0.0" "@actions/github": "^6.0.0"
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
"version": "1.11.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", "integrity": "sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1", "@actions/exec": "^2.0.0",
"@actions/http-client": "^2.0.1" "@actions/http-client": "^3.0.0"
}
},
"node_modules/@actions/core/node_modules/@actions/http-client": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.0.tgz",
"integrity": "sha512-1s3tXAfVMSz9a4ZEBkXXRQD4QhY3+GAsWSbaYpeknPOKEeyRiU3lH+bHiLMZdo2x/fIeQ/hscL1wCkDLVM2DZQ==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.28.5"
} }
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
"version": "1.1.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", "integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/io": "^1.0.1" "@actions/io": "^2.0.0"
} }
}, },
"node_modules/@actions/github": { "node_modules/@actions/github": {
@ -57,9 +67,9 @@
} }
}, },
"node_modules/@actions/io": { "node_modules/@actions/io": {
"version": "1.1.3", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", "integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@fastify/busboy": { "node_modules/@fastify/busboy": {
@ -85,6 +95,7 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz",
"integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@octokit/auth-token": "^4.0.0", "@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0", "@octokit/graphql": "^7.1.0",

View file

@ -6,7 +6,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^2.0.1",
"@actions/github": "^6.0.0" "@actions/github": "^6.0.0"
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "yazi-actor" name = "yazi-actor"
version = "25.9.15" version = "26.1.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ] authors = [ "sxyazi <sxyazi@gmail.com>" ]
@ -8,27 +8,30 @@ description = "Yazi actor model"
homepage = "https://yazi-rs.github.io" homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[lints]
workspace = true
[features] [features]
default = [ "vendored-lua" ] default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ] vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-binding = { path = "../yazi-binding", version = "25.9.15" } yazi-binding = { path = "../yazi-binding", version = "26.1.4" }
yazi-boot = { path = "../yazi-boot", version = "25.9.15" } yazi-boot = { path = "../yazi-boot", version = "26.1.4" }
yazi-config = { path = "../yazi-config", version = "25.9.15" } yazi-config = { path = "../yazi-config", version = "26.1.4" }
yazi-core = { path = "../yazi-core", version = "25.9.15" } yazi-core = { path = "../yazi-core", version = "26.1.4" }
yazi-dds = { path = "../yazi-dds", version = "25.9.15" } yazi-dds = { path = "../yazi-dds", version = "26.1.4" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
yazi-parser = { path = "../yazi-parser", version = "25.9.15" } yazi-parser = { path = "../yazi-parser", version = "26.1.4" }
yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" } yazi-plugin = { path = "../yazi-plugin", version = "26.1.4" }
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" } yazi-proxy = { path = "../yazi-proxy", version = "26.1.4" }
yazi-scheduler = { path = "../yazi-scheduler", version = "25.9.15" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.1.4" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
yazi-term = { path = "../yazi-term", version = "25.9.15" } yazi-term = { path = "../yazi-term", version = "26.1.4" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } yazi-vfs = { path = "../yazi-vfs", version = "26.1.4" }
yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" } yazi-watcher = { path = "../yazi-watcher", version = "26.1.4" }
yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" } yazi-widgets = { path = "../yazi-widgets", version = "26.1.4" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

5
yazi-actor/README.md Normal file
View file

@ -0,0 +1,5 @@
# yazi-actor
This crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.
[source]: https://github.com/sxyazi/yazi

View file

@ -17,10 +17,11 @@ impl Actor for Close {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let cmp = &mut cx.core.cmp; let cmp = &mut cx.core.cmp;
if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() { if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {
return act!(complete, cx.core.input, CompleteOpt { item, _ticket: cmp.ticket }); return act!(input:complete, cx, CompleteOpt { item, ticket: cmp.ticket });
} }
cmp.caches.clear(); cmp.caches.clear();
cmp.ticket = Default::default();
succ!(render!(mem::replace(&mut cmp.visible, false))); succ!(render!(mem::replace(&mut cmp.visible, false)));
} }
} }

View file

@ -1,9 +1,9 @@
use std::{ffi::OsStr, mem, ops::ControlFlow}; use std::{mem, ops::ControlFlow};
use anyhow::Result; use anyhow::Result;
use yazi_macro::{render, succ}; use yazi_macro::{render, succ};
use yazi_parser::cmp::{CmpItem, ShowOpt}; use yazi_parser::cmp::{CmpItem, ShowOpt};
use yazi_shared::{data::Data, osstr_contains, osstr_starts_with}; use yazi_shared::{data::Data, path::{AsPath, PathDyn}, strand::StrandLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -29,8 +29,7 @@ impl Actor for Show {
succ!(); succ!();
}; };
cmp.ticket = opt.ticket; cmp.cands = Self::match_candidates(opt.word.as_path(), cache);
cmp.cands = Self::match_candidates(opt.word.as_os_str(), cache);
if cmp.cands.is_empty() { if cmp.cands.is_empty() {
succ!(render!(mem::replace(&mut cmp.visible, false))); succ!(render!(mem::replace(&mut cmp.visible, false)));
} }
@ -43,16 +42,19 @@ impl Actor for Show {
} }
impl Show { impl Show {
fn match_candidates(word: &OsStr, cache: &[CmpItem]) -> Vec<CmpItem> { fn match_candidates(word: PathDyn, cache: &[CmpItem]) -> Vec<CmpItem> {
let smart = !word.as_encoded_bytes().iter().any(|c| c.is_ascii_uppercase()); let smart = !word.encoded_bytes().iter().any(|&b| b.is_ascii_uppercase());
let flow = cache.iter().try_fold((Vec::new(), Vec::new()), |(mut exact, mut fuzzy), item| { let flow = cache.iter().try_fold((Vec::new(), Vec::new()), |(mut exact, mut fuzzy), item| {
if osstr_starts_with(&item.name, word, smart) { let starts_with =
if smart { item.name.eq_ignore_ascii_case(word) } else { item.name.starts_with(word) };
if starts_with {
exact.push(item); exact.push(item);
if exact.len() >= LIMIT { if exact.len() >= LIMIT {
return ControlFlow::Break((exact, fuzzy)); return ControlFlow::Break((exact, fuzzy));
} }
} else if fuzzy.len() < LIMIT - exact.len() && osstr_contains(&item.name, word) { } else if fuzzy.len() < LIMIT - exact.len() && item.name.contains(word) {
// Here we don't break the control flow, since we want more exact matching. // Here we don't break the control flow, since we want more exact matching.
fuzzy.push(item) fuzzy.push(item)
} }

View file

@ -1,11 +1,11 @@
use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, PathBuf}}; use std::mem;
use anyhow::Result; use anyhow::Result;
use yazi_fs::{CWD, path::expand_url, provider::{DirReader, FileHolder}}; use yazi_fs::{path::clean_url, provider::{DirReader, FileHolder}};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt}; use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
use yazi_proxy::CmpProxy; use yazi_proxy::CmpProxy;
use yazi_shared::{OsStrSplit, data::Data, natsort, scheme::SchemeLike, url::{UrlBuf, UrlCow}}; 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_vfs::provider;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -32,7 +32,7 @@ impl Actor for Trigger {
if cmp.caches.contains_key(&parent) { if cmp.caches.contains_key(&parent) {
let ticket = cmp.ticket; let ticket = cmp.ticket;
return act!(cmp:show, cx, ShowOpt { cache_name: parent, word, ticket, ..Default::default() }); return act!(cmp:show, cx, ShowOpt { cache: vec![], cache_name: parent, word, ticket });
} }
let ticket = cmp.ticket; let ticket = cmp.ticket;
@ -42,8 +42,8 @@ impl Actor for Trigger {
// "/" is both a directory separator and the root directory per se // "/" is both a directory separator and the root directory per se
// As there's no parent directory for the FS root, it is a special case // As there's no parent directory for the FS root, it is a special case
if parent.loc.as_os_str() == "/" { if parent.loc() == "/" {
cache.push(CmpItem { name: OsString::new(), is_dir: true }); cache.push(CmpItem { name: Default::default(), is_dir: true });
} }
while let Ok(Some(ent)) = dir.next().await { while let Ok(Some(ent)) = dir.next().await {
@ -53,9 +53,8 @@ impl Actor for Trigger {
} }
if !cache.is_empty() { if !cache.is_empty() {
cache.sort_unstable_by(|a, b| { cache
natsort(a.name.as_encoded_bytes(), b.name.as_encoded_bytes(), false) .sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false));
});
CmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket }); CmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket });
} }
@ -67,38 +66,47 @@ impl Actor for Trigger {
} }
impl Trigger { impl Trigger {
fn split_url(s: &str) -> Option<(UrlBuf, PathBuf)> { fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
let (scheme, path, ..) = UrlCow::parse(s.as_bytes()).ok()?; let sep = if cfg!(windows) {
AnyAsciiChar::new(b"/\\").unwrap()
} else {
AnyAsciiChar::new(b"/").unwrap()
};
if scheme.is_local() && path.as_os_str() == "~" { let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
return None; // We don't autocomplete a `~`, but `~/` if path.is_empty() && !sep.predicate(s.bytes().last()?) {
return None; // We don't complete a `sftp://test`, but `sftp://test/`
} }
#[cfg(windows)] // Scheme
const SEP: &[char] = &['/', '\\']; let scheme = scheme.zeroed();
#[cfg(not(windows))] if scheme.is_local() && path.as_strand() == "~" {
const SEP: char = std::path::MAIN_SEPARATOR; return None; // We don't complete a `~`, but `~/`
}
Some(match path.as_os_str().rsplit_once(SEP) { // Child
Some((p, c)) if p.is_empty() => { let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned();
(UrlBuf { loc: MAIN_SEPARATOR_STR.into(), scheme: scheme.into() }, c.into())
} // Parent
Some((p, c)) => (expand_url(UrlBuf { loc: p.into(), scheme: scheme.into() }), c.into()), let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
None => (CWD.load().as_ref().clone(), path.into()), let abs = if let Some(u) = provider::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))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use yazi_fs::CWD;
use yazi_shared::url::UrlLike; use yazi_shared::url::UrlLike;
use super::*; use super::*;
fn compare(s: &str, parent: &str, child: &str) { fn compare(s: &str, parent: &str, child: &str) {
let (mut p, c) = Trigger::split_url(s).unwrap(); let (mut p, c) = Trigger::split_url(s).unwrap();
if let Some(u) = p.strip_prefix(yazi_fs::CWD.load().as_ref()) { if let Ok(u) = p.try_strip_prefix(yazi_fs::CWD.load().as_ref()) {
p = UrlBuf::from(u); p = UrlBuf::Regular(u.as_os().unwrap().into());
} }
assert_eq!((p, c.to_str().unwrap()), (parent.parse().unwrap(), child)); assert_eq!((p, c.to_str().unwrap()), (parent.parse().unwrap(), child));
} }
@ -106,14 +114,31 @@ mod tests {
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn test_split() { fn test_split() {
yazi_shared::init_tests();
yazi_fs::init(); yazi_fs::init();
compare("", "", "");
assert_eq!(Trigger::split_url(""), None);
assert_eq!(Trigger::split_url("sftp://test"), None);
compare(" ", "", " "); compare(" ", "", " ");
compare("/", "/", ""); compare("/", "/", "");
compare("//", "//", ""); compare("//", "/", "");
compare("///", "/", "");
compare("/foo", "/", "foo"); compare("/foo", "/", "foo");
compare("//foo", "/", "foo");
compare("///foo", "/", "foo");
compare("/foo/", "/foo/", ""); compare("/foo/", "/foo/", "");
compare("//foo/", "/foo/", "");
compare("/foo/bar", "/foo/", "bar"); 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");
} }
#[cfg(windows)] #[cfg(windows)]
@ -121,9 +146,11 @@ mod tests {
fn test_split() { fn test_split() {
yazi_fs::init(); yazi_fs::init();
compare("foo", "", "foo"); compare("foo", "", "foo");
compare(r"foo\", r"foo\", ""); compare(r"foo\", r"foo\", "");
compare(r"foo\bar", r"foo\", "bar"); compare(r"foo\bar", r"foo\", "bar");
compare(r"foo\bar\", r"foo\bar\", ""); compare(r"foo\bar\", r"foo\bar\", "");
compare(r"C:\", r"C:\", ""); compare(r"C:\", r"C:\", "");
compare(r"C:\foo", r"C:\", "foo"); compare(r"C:\foo", r"C:\", "foo");
compare(r"C:\foo\", r"C:\foo\", ""); compare(r"C:\foo\", r"C:\foo\", "");

View file

@ -118,13 +118,5 @@ impl<'a> Ctx<'a> {
pub fn tasks(&self) -> &Tasks { &self.tasks } pub fn tasks(&self) -> &Tasks { &self.tasks }
#[inline] #[inline]
pub fn source(&self) -> Source { pub fn source(&self) -> Source { if self.level != 1 { Source::Ind } else { self.source } }
use Source::*;
match self.source {
Key if self.level != 1 => Ind,
Emit if self.level != 1 => EmitInd,
Relay if self.level != 1 => RelayInd,
s => s,
}
}
} }

View file

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use anyhow::Result; use anyhow::Result;
use mlua::{ErrorContext, ExternalError, IntoLua, Value}; use mlua::{ErrorContext, ExternalError, IntoLua, Value};
use yazi_binding::runtime_mut; use yazi_binding::runtime_mut;
@ -8,12 +6,10 @@ use yazi_plugin::LUA;
use crate::{Ctx, lives::Lives}; use crate::{Ctx, lives::Lives};
pub struct Preflight<'a> { pub struct Preflight;
_lifetime: PhantomData<&'a ()>,
}
impl<'a> Preflight<'a> { impl Preflight {
pub fn act(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> { pub fn act<'a>(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> {
let kind = opt.0; 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.as_ref()).filter(|&m| !m.is_empty()).cloned() else {
return Ok(opt.1); return Ok(opt.1);
@ -27,10 +23,9 @@ impl<'a> Preflight<'a> {
runtime_mut!(LUA)?.pop(); runtime_mut!(LUA)?.pop();
match result { match result {
Ok(Value::Nil) => Err( Ok(Value::Nil) => {
format!("Cancelled by `{kind}` event handler in `{id}` plugin on preflight") Err(format!("`{kind}` event cancelled by `{id}` plugin on preflight").into_lua_err())?
.into_lua_err(), }
)?,
Ok(v) => body = v, Ok(v) => body = v,
Err(e) => Err( Err(e) => Err(
format!("Failed to run `{kind}` event handler in `{id}` plugin: {e}").into_lua_err(), format!("Failed to run `{kind}` event handler in `{id}` plugin: {e}").into_lua_err(),

View file

@ -0,0 +1,23 @@
use anyhow::Result;
use yazi_macro::{act, succ};
use yazi_parser::input::CompleteOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Complete;
impl Actor for Complete {
type Options = 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 {
succ!();
}
act!(complete, input, opt)
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(close escape show); yazi_macro::mod_flat!(close complete escape show);

View file

@ -1,5 +1,3 @@
#![allow(clippy::if_same_then_else, clippy::option_map_unit_fn)]
extern crate self as yazi_actor; 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!(cmp confirm core help input lives mgr pick spot tasks which);

View file

@ -50,7 +50,7 @@ impl UserData for Core {
} }
}; };
} }
Ok(match key.as_bytes().as_ref() { Ok(match &*key.as_bytes() {
b"active" => reuse!(active, super::Tab::make(me.active())), b"active" => reuse!(active, super::Tab::make(me.active())),
b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)), b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)), b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),

View file

@ -1,10 +1,10 @@
use std::ops::Deref; use std::{ops::Deref, ptr};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::Style; use yazi_binding::{Style, cached_field};
use yazi_config::THEME; use yazi_config::THEME;
use yazi_plugin::bindings::Range; use yazi_plugin::bindings::Range;
use yazi_shared::url::UrlLike; use yazi_shared::{path::AsPath, url::UrlLike};
use super::Lives; use super::Lives;
use crate::lives::PtrCell; use crate::lives::PtrCell;
@ -19,7 +19,10 @@ pub(super) struct File {
v_link_to: Option<Value>, v_link_to: Option<Value>,
v_name: Option<Value>, v_name: Option<Value>,
v_path: Option<Value>,
v_cache: Option<Value>, v_cache: Option<Value>,
v_bare: Option<Value>,
} }
impl Deref for File { impl Deref for File {
@ -53,7 +56,10 @@ impl File {
v_link_to: None, v_link_to: None,
v_name: None, v_name: None,
v_path: None,
v_cache: None, v_cache: None,
v_bare: None,
})?; })?;
ve.insert(ud.clone()); ve.insert(ud.clone());
ud ud
@ -65,11 +71,13 @@ impl File {
impl UserData for File { impl UserData for File {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
yazi_binding::impl_file_fields!(fields); 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("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.idx == me.folder.cursor));
fields.add_field_method_get("in_current", |_, me| Ok(ptr::eq(&*me.folder, &me.tab.current)));
fields.add_field_method_get("in_preview", |_, me| { fields.add_field_method_get("in_preview", |_, me| {
Ok(me.tab.hovered().is_some_and(|f| f.url == me.folder.url)) Ok(me.idx == me.folder.cursor && me.tab.hovered().is_some_and(|f| f.url == me.folder.url))
}); });
} }
@ -88,13 +96,10 @@ impl UserData for File {
if !me.url.has_trail() { if !me.url.has_trail() {
return Ok(None); return Ok(None);
} }
let Some(path) = me.url.as_path() else {
return Ok(None);
};
let mut comp = path.strip_prefix(me.url.loc.trail()).unwrap_or(path).components(); let mut comp = me.url.try_strip_prefix(me.url.trail()).unwrap_or(me.url.loc()).components();
comp.next_back(); comp.next_back();
Some(lua.create_string(comp.as_path().as_os_str().as_encoded_bytes())).transpose() Some(lua.create_string(comp.as_path().encoded_bytes())).transpose()
}); });
methods.add_method("style", |lua, me, ()| { methods.add_method("style", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {

View file

@ -54,7 +54,7 @@ impl Folder {
impl UserData for Folder { impl UserData for Folder {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cwd, |_, me| Ok(Url::new(me.url.to_owned()))); 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, 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, stage, |_, me| Ok(FolderStage::new(me.stage.clone())));
cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab)); cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab));

View file

@ -32,7 +32,7 @@ impl UserData for Preference {
fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first)); fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
fields.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit)); fields.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit));
cached_field!(fields, linemode, |_, me| Ok(me.linemode.to_string())); 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("show_hidden", |_, me| Ok(me.show_hidden));
} }
} }

View file

@ -11,7 +11,7 @@ impl Selected {
let inner = PtrCell::from(inner); let inner = PtrCell::from(inner);
Lives::scoped_userdata(yazi_binding::Iter::new( Lives::scoped_userdata(yazi_binding::Iter::new(
inner.as_static().values().cloned().map(yazi_binding::Url::new), inner.as_static().values().map(yazi_binding::Url::new),
Some(inner.len()), Some(inner.len()),
)) ))
} }

View file

@ -47,7 +47,7 @@ impl UserData for Tab {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id))); fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| { cached_field!(fields, name, |lua, me| {
let url = &me.current.url; let url = &me.current.url;
lua.create_string(url.name().unwrap_or(url.loc.as_os_str()).as_encoded_bytes()) lua.create_string(url.name().map_or_else(|| url.loc().encoded_bytes(), |n| n.encoded_bytes()))
}); });
cached_field!(fields, mode, |_, me| Mode::make(&me.mode)); cached_field!(fields, mode, |_, me| Mode::make(&me.mode));

View file

@ -1,8 +1,7 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value}; use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::cached_field; use yazi_binding::{SER_OPT, cached_field};
use yazi_plugin::runtime::SER_OPT;
use super::{Lives, PtrCell}; use super::{Lives, PtrCell};
@ -30,6 +29,7 @@ impl UserData for TaskSnap {
cached_field!(fields, name, |lua, me| lua.create_string(&me.name)); 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)); cached_field!(fields, 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("running", |_, me| Ok(me.prog.running()));
fields.add_field_method_get("success", |_, me| Ok(me.prog.success())); 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("failed", |_, me| Ok(me.prog.failed()));

View file

@ -1,8 +1,7 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value}; use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{cached_field, deprecate}; use yazi_binding::{SER_OPT, cached_field, deprecate};
use yazi_plugin::runtime::SER_OPT;
use super::{Lives, PtrCell}; use super::{Lives, PtrCell};
use crate::lives::TaskSnap; use crate::lives::TaskSnap;

View file

@ -23,7 +23,7 @@ impl Yanked {
Lives::scoped_userdata(Self { Lives::scoped_userdata(Self {
inner, inner,
iter: Lives::scoped_userdata(Iter::new( iter: Lives::scoped_userdata(Iter::new(
inner.as_static().iter().cloned().map(yazi_binding::Url::new), inner.as_static().iter().map(yazi_binding::Url::new),
Some(inner.len()), Some(inner.len()),
))?, ))?,
}) })

View file

@ -14,7 +14,7 @@ impl Actor for Back {
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
if let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() { if let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() {
return act!(mgr:cd, cx, (u, CdSource::Back)); act!(mgr:cd, cx, (u, CdSource::Back))?;
} }
succ!(); succ!();
} }

View file

@ -1,17 +1,18 @@
use std::{ffi::{OsStr, OsString}, hash::Hash, io::{Read, Write}, ops::Deref, path::Path}; use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use crossterm::{execute, style::Print}; use crossterm::{execute, style::Print};
use hashbrown::HashMap; use hashbrown::HashMap;
use scopeguard::defer; use scopeguard::defer;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule}; use yazi_config::{YAZI, opener::OpenerRule};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}}; 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_macro::{err, succ};
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER, TasksProxy}; use yazi_proxy::{AppProxy, HIDER, TasksProxy};
use yazi_shared::{OsStrJoin, data::Data, terminal_clear, url::{AsUrl, Component, UrlBuf, UrlCow, UrlLike}}; 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_term::tty::TTY;
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -47,10 +48,15 @@ impl Actor for BulkRename {
.create_new(true) .create_new(true)
.open(&tmp) .open(&tmp)
.await? .await?
.write_all(old.join(OsStr::new("\n")).as_encoded_bytes()) .write_all(old.join(Strand::Utf8("\n")).encoded_bytes())
.await?; .await?;
defer! { tokio::spawn(Local.remove_file(tmp.clone())); } defer! {
let tmp = tmp.clone();
tokio::spawn(async move {
Local::regular(&tmp).remove_file().await
});
}
TasksProxy::process_exec( TasksProxy::process_exec(
cwd.into(), cwd.into(),
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run), Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
@ -60,12 +66,11 @@ impl Actor for BulkRename {
) )
.await; .await;
let _permit = HIDER.acquire().await.unwrap(); let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
defer!(AppProxy::resume());
AppProxy::stop().await; AppProxy::stop().await;
let new: Vec<_> = Local let new: Vec<_> = Local::regular(&tmp)
.read_to_string(&tmp) .read_to_string()
.await? .await?
.lines() .lines()
.take(old.len()) .take(old.len())
@ -120,10 +125,12 @@ impl BulkRename {
let permit = WATCHER.acquire().await.unwrap(); let permit = WATCHER.acquire().await.unwrap();
let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len())); let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));
for (o, n) in todo { for (o, n) in todo {
let (old, new): (UrlBuf, UrlBuf) = ( let (Ok(old), Ok(new)) =
selected[o.0].components().take(root).chain([Component::Normal(&o)]).collect(), (Self::replace_url(&selected[o.0], root, &o), Self::replace_url(&selected[n.0], root, &n))
selected[n.0].components().take(root).chain([Component::Normal(&n)]).collect(), else {
); failed.push((o, n, anyhow!("Invalid new or old file name")));
continue;
};
if maybe_exists(&new).await && !provider::must_identical(&old, &new).await { if maybe_exists(&new).await && !provider::must_identical(&old, &new).await {
failed.push((o, n, anyhow!("Destination already exists"))); failed.push((o, n, anyhow!("Destination already exists")));
@ -137,7 +144,7 @@ impl BulkRename {
} }
if !succeeded.is_empty() { if !succeeded.is_empty() {
let it = succeeded.iter().map(|(o, n)| (o, &n.url)); 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(it));
FilesOp::rename(succeeded); FilesOp::rename(succeeded);
} }
@ -153,6 +160,10 @@ impl BulkRename {
YAZI.opener.block(YAZI.open.all(Path::new("bulk-rename.txt"), "text/plain")) YAZI.opener.block(YAZI.open.all(Path::new("bulk-rename.txt"), "text/plain"))
} }
fn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result<UrlBuf> {
Ok(url.try_replace(take, PathDyn::with(url.kind(), rep)?)?.into_owned())
}
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> { async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {
let mut stdout = TTY.lockout(); let mut stdout = TTY.lockout();
terminal_clear(&mut *stdout)?; terminal_clear(&mut *stdout)?;
@ -209,10 +220,10 @@ impl BulkRename {
// --- Tuple // --- Tuple
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Tuple(usize, OsString); struct Tuple(usize, StrandBuf);
impl Deref for Tuple { impl Deref for Tuple {
type Target = OsStr; type Target = StrandBuf;
fn deref(&self) -> &Self::Target { &self.1 } fn deref(&self) -> &Self::Target { &self.1 }
} }
@ -227,12 +238,12 @@ impl Hash for Tuple {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.1.hash(state); } fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.1.hash(state); }
} }
impl AsRef<OsStr> for Tuple { impl AsStrand for &Tuple {
fn as_ref(&self) -> &OsStr { &self.1 } fn as_strand(&self) -> Strand<'_> { self.1.as_strand() }
} }
impl Tuple { impl Tuple {
fn new(index: usize, inner: impl Into<OsString>) -> Self { Self(index, inner.into()) } fn new(index: usize, inner: impl Into<StrandBuf>) -> Self { Self(index, inner.into()) }
} }
// --- Tests // --- Tests

View file

@ -5,12 +5,12 @@ use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, path::expand_url}; use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
use yazi_macro::{act, err, render, succ}; use yazi_macro::{act, err, render, succ};
use yazi_parser::mgr::CdOpt; use yazi_parser::mgr::CdOpt;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy}; use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
use yazi_shared::{Debounce, data::Data, errors::InputError, path::PathLike, url::{AsUrl, UrlBuf, UrlLike}}; use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_vfs::VfsFile; use yazi_vfs::{VfsFile, provider};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -24,7 +24,7 @@ impl Actor for Cd {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?; act!(mgr:escape_visual, cx)?;
if opt.interactive { if opt.interactive {
return Self::cd_interactive(); return Self::cd_interactive(cx);
} }
let tab = cx.tab_mut(); let tab = cx.tab_mut();
@ -33,18 +33,8 @@ impl Actor for Cd {
} }
// Take parent to history // Take parent to history
if let Some(rep) = tab.parent.take() { if let Some(t) = tab.parent.take() {
tab.history.insert(rep.url.to_owned(), rep); tab.history.insert(t.url.clone(), t);
}
// Backstack
if opt.source.big_jump() {
if tab.current.url.is_regular() {
tab.backstack.push(tab.current.url.as_url());
}
if opt.target.is_regular() {
tab.backstack.push(opt.target.as_url());
}
} }
// Current // Current
@ -60,7 +50,7 @@ impl Actor for Cd {
rep.stage = Default::default(); rep.stage = Default::default();
} }
let rep = mem::replace(&mut tab.current, rep); let rep = mem::replace(&mut tab.current, rep);
tab.history.insert(rep.url.to_owned(), rep); tab.history.insert(rep.url.clone(), rep);
// Parent // Parent
if let Some(parent) = opt.target.parent() { if let Some(parent) = opt.target.parent() {
@ -74,8 +64,9 @@ impl Actor for Cd {
tab.parent = Some(parent_folder); tab.parent = Some(parent_folder);
} }
err!(Pubsub::pub_after_cd(tab.id, tab.cwd())); err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
act!(mgr:displace, cx)?;
act!(mgr:hidden, cx)?; act!(mgr:hidden, cx)?;
act!(mgr:sort, cx)?; act!(mgr:sort, cx).ok();
// Apply config excludes if no plugin patterns are set // Apply config excludes if no plugin patterns are set
// This ensures config patterns work when gitignore plugin is disabled // This ensures config patterns work when gitignore plugin is disabled
@ -86,13 +77,14 @@ impl Actor for Cd {
act!(mgr:hover, cx)?; act!(mgr:hover, cx)?;
act!(mgr:refresh, cx)?; act!(mgr:refresh, cx)?;
act!(mgr:stash, cx, opt).ok();
succ!(render!()); succ!(render!());
} }
} }
impl Cd { impl Cd {
fn cd_interactive() -> Result<Data> { fn cd_interactive(cx: &Ctx) -> Result<Data> {
let input = InputProxy::show(InputCfg::cd()); let input = InputProxy::show(InputCfg::cd(cx.cwd().as_url()));
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
@ -102,6 +94,8 @@ impl Cd {
match result { match result {
Ok(s) => { Ok(s) => {
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return }; let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
let Ok(url) = provider::absolute(&url).await else { return };
let url = clean_url(url);
let Ok(file) = File::new(&url).await else { return }; let Ok(file) = File::new(&url).await else { return };
if file.is_dir() { if file.is_dir() {
@ -109,12 +103,12 @@ impl Cd {
} }
if let Some(p) = url.parent() { if let Some(p) = url.parent() {
FilesOp::Upserting(p.into(), [(url.urn().owned(), file)].into()).emit(); FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
} }
MgrProxy::reveal(&url); MgrProxy::reveal(url);
} }
Err(InputError::Completed(before, ticket)) => { Err(InputError::Completed(before, ticket)) => {
CmpProxy::trigger(&before, ticket); CmpProxy::trigger(before, ticket);
} }
_ => break, _ => break,
} }

View file

@ -1,9 +1,7 @@
use std::ffi::OsString;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::mgr::CopyOpt; use yazi_parser::mgr::CopyOpt;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, strand::ToStrand, url::UrlLike};
use yazi_widgets::CLIPBOARD; use yazi_widgets::CLIPBOARD;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -18,7 +16,7 @@ impl Actor for Copy {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?; act!(mgr:escape_visual, cx)?;
let mut s = OsString::new(); let mut s = Vec::<u8>::new();
let mut it = if opt.hovered { let mut it = if opt.hovered {
Box::new(cx.hovered().map(|h| &h.url).into_iter()) Box::new(cx.hovered().map(|h| &h.url).into_iter())
} else { } else {
@ -30,29 +28,29 @@ impl Actor for Copy {
match opt.r#type.as_ref() { match opt.r#type.as_ref() {
// TODO: rename to "url" // TODO: rename to "url"
"path" => { "path" => {
s.push(opt.separator.transform(&u.os_str())); s.extend_from_slice(&opt.separator.transform(&u.to_strand()));
} }
"dirname" => { "dirname" => {
if let Some(p) = u.parent() { if let Some(p) = u.parent() {
s.push(opt.separator.transform(&p.os_str())); s.extend_from_slice(&opt.separator.transform(&p.to_strand()));
} }
} }
"filename" => { "filename" => {
s.push(opt.separator.transform(u.name().unwrap_or_default())); s.extend_from_slice(&opt.separator.transform(&u.name().unwrap_or_default()));
} }
"name_without_ext" => { "name_without_ext" => {
s.push(opt.separator.transform(u.stem().unwrap_or_default())); s.extend_from_slice(&opt.separator.transform(&u.stem().unwrap_or_default()));
} }
_ => bail!("Unknown copy type: {}", opt.r#type), _ => bail!("Unknown copy type: {}", opt.r#type),
}; };
if it.peek().is_some() { if it.peek().is_some() {
s.push("\n"); s.push(b'\n');
} }
} }
// Copy the CWD path regardless even if the directory is empty // Copy the CWD path regardless even if the directory is empty
if s.is_empty() && opt.r#type == "dirname" { if s.is_empty() && opt.r#type == "dirname" {
s.push(opt.separator.transform(&cx.cwd().os_str())); s.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand()));
} }
futures::executor::block_on(CLIPBOARD.set(s)); futures::executor::block_on(CLIPBOARD.set(s));

View file

@ -4,7 +4,7 @@ use yazi_fs::{File, FilesOp};
use yazi_macro::{ok_or_not_found, succ}; use yazi_macro::{ok_or_not_found, succ};
use yazi_parser::mgr::CreateOpt; use yazi_parser::mgr::CreateOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
use yazi_shared::{data::Data, path::PathLike, url::{UrlBuf, UrlLike}}; use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -27,7 +27,10 @@ impl Actor for Create {
return; return;
} }
let new = cwd.join(&name); let Ok(new) = cwd.try_join(&name) else {
return;
};
if !opt.force if !opt.force
&& maybe_exists(&new).await && maybe_exists(&new).await
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await && !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
@ -51,7 +54,7 @@ impl Create {
&& let Some((parent, urn)) = real.pair() && let Some((parent, urn)) = real.pair()
{ {
ok_or_not_found!(provider::remove_file(&new).await); ok_or_not_found!(provider::remove_file(&new).await);
FilesOp::Deleting(parent.into(), [urn.owned()].into()).emit(); FilesOp::Deleting(parent.into(), [urn.into()].into()).emit();
provider::create(&new).await?; provider::create(&new).await?;
} else if let Some(parent) = new.parent() { } else if let Some(parent) = new.parent() {
provider::create_dir_all(parent).await.ok(); provider::create_dir_all(parent).await.ok();
@ -65,7 +68,7 @@ impl Create {
&& let Some((parent, urn)) = real.pair() && let Some((parent, urn)) = real.pair()
{ {
let file = File::new(&real).await?; let file = File::new(&real).await?;
FilesOp::Upserting(parent.into(), [(urn.owned(), file)].into()).emit(); FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()).emit();
MgrProxy::reveal(&real); MgrProxy::reveal(&real);
} }

View file

@ -0,0 +1,30 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::{VoidOpt, mgr::DisplaceDoOpt};
use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, url::UrlLike};
use yazi_vfs::provider;
use crate::{Actor, Ctx};
pub struct Displace;
impl Actor for Displace {
type Options = VoidOpt;
const NAME: &str = "displace";
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
if cx.cwd().is_absolute() {
succ!();
}
let tab = cx.tab().id;
let from = cx.cwd().to_owned();
tokio::spawn(async move {
MgrProxy::displace_do(tab, DisplaceDoOpt { to: provider::canonicalize(&from).await, from });
});
succ!();
}
}

View file

@ -0,0 +1,36 @@
use anyhow::{Result, bail};
use yazi_fs::FilesOp;
use yazi_macro::{act, succ};
use yazi_parser::mgr::{CdSource, DisplaceDoOpt};
use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx};
pub struct DisplaceDo;
impl Actor for DisplaceDo {
type Options = DisplaceDoOpt;
const NAME: &str = "displace_do";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
if cx.cwd() != opt.from {
succ!()
}
let to = match opt.to {
Ok(url) => url,
Err(e) => return act!(mgr:update_files, cx, FilesOp::IOErr(opt.from, e.into())),
};
if !to.is_absolute() {
bail!("Target URL must be absolute");
} else if let Some(hovered) = cx.hovered()
&& let Ok(url) = to.try_join(hovered.urn())
{
act!(mgr:reveal, cx, (url, CdSource::Displace))
} else {
act!(mgr:cd, cx, (to, CdSource::Displace))
}
}
}

View file

@ -3,12 +3,11 @@ use std::{mem, time::{Duration, Instant}};
use anyhow::Result; use anyhow::Result;
use futures::{StreamExt, stream::FuturesUnordered}; use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::HashSet; use hashbrown::HashSet;
use tokio::sync::oneshot;
use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}}; use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}};
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::{DownloadOpt, OpenOpt}; use yazi_parser::mgr::{DownloadOpt, OpenOpt};
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, url::UrlCow}; use yazi_shared::{data::Data, url::{UrlCow, UrlLike}};
use yazi_vfs::VfsFile; use yazi_vfs::VfsFile;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -29,9 +28,8 @@ impl Actor for Download {
let mut wg1 = FuturesUnordered::new(); let mut wg1 = FuturesUnordered::new();
for url in opt.urls { for url in opt.urls {
let (tx, rx) = oneshot::channel(); let done = scheduler.file_download(url.to_owned());
scheduler.file_download(url.to_owned(), Some(tx)); wg1.push(async move { (done.future().await, url) });
wg1.push(async move { (rx.await == Ok(false), url) });
} }
let mut wg2 = vec![]; let mut wg2 = vec![];
@ -78,7 +76,7 @@ impl Download {
let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect(); let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect();
for mut root in roots { for mut root in roots {
root.push("%lock"); root.push("%lock");
Local.create_dir_all(root).await.ok(); Local::regular(&root).create_dir_all().await.ok();
} }
} }
} }

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidOpt, mgr::CdSource}; use yazi_parser::{VoidOpt, mgr::CdSource};
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -15,7 +15,7 @@ impl Actor for Enter {
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
let Some(h) = cx.hovered().filter(|h| h.is_dir()) else { succ!() }; let Some(h) = cx.hovered().filter(|h| h.is_dir()) else { succ!() };
let url = if h.url.is_search() { h.url.to_regular() } else { h.url.clone() }; let url = if h.url.is_search() { h.url.to_regular()? } else { h.url.clone() };
act!(mgr:cd, cx, (url, CdSource::Enter)) act!(mgr:cd, cx, (url, CdSource::Enter))
} }

View file

@ -2,7 +2,7 @@ use anyhow::{Result, bail};
use yazi_macro::{act, render, render_and, succ}; use yazi_macro::{act, render, render_and, succ};
use yazi_parser::{VoidOpt, mgr::EscapeOpt}; use yazi_parser::{VoidOpt, mgr::EscapeOpt};
use yazi_proxy::AppProxy; use yazi_proxy::AppProxy;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use yazi_fs::Filter; use yazi_fs::Filter;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::FilterOpt; use yazi_parser::mgr::FilterOpt;
use yazi_shared::{data::Data, path::PathLike}; use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -16,7 +16,7 @@ impl Actor for FilterDo {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) }; let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) };
let hovered = cx.hovered().map(|f| f.urn().owned()); let hovered = cx.hovered().map(|f| f.urn().into());
cx.current_mut().files.set_filter(filter); cx.current_mut().files.set_filter(filter);
if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) { if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_fs::path::clean_url; use yazi_fs::path::clean_url;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::VoidOpt; use yazi_parser::{VoidOpt, mgr::CdSource};
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -16,11 +16,8 @@ impl Actor for Follow {
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
let Some(file) = cx.hovered() else { succ!() }; let Some(file) = cx.hovered() else { succ!() };
let Some(link_to) = &file.link_to else { succ!() }; let Some(link_to) = &file.link_to else { succ!() };
let Some(parent) = file.url.parent() else { succ!() };
if let Some(p) = file.url.parent() { let Ok(joined) = parent.try_join(link_to) else { succ!() };
act!(mgr:reveal, cx, clean_url(p.join(link_to))) act!(mgr:reveal, cx, (clean_url(joined), CdSource::Follow))
} else {
succ!()
}
} }
} }

View file

@ -13,9 +13,9 @@ impl Actor for Forward {
const NAME: &str = "forward"; const NAME: &str = "forward";
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
match cx.tab_mut().backstack.shift_forward().cloned() { if let Some(u) = cx.tab_mut().backstack.shift_forward().cloned() {
Some(u) => act!(mgr:cd, cx, (u, CdSource::Forward)), act!(mgr:cd, cx, (u, CdSource::Forward))?;
None => succ!(),
} }
succ!()
} }
} }

View file

@ -3,7 +3,7 @@ use yazi_core::tab::Folder;
use yazi_fs::FolderStage; use yazi_fs::FolderStage;
use yazi_macro::{act, render, render_and, succ}; use yazi_macro::{act, render, render_and, succ};
use yazi_parser::mgr::HiddenOpt; use yazi_parser::mgr::HiddenOpt;
use yazi_shared::{data::Data, path::PathLike}; use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -18,7 +18,7 @@ impl Actor for Hidden {
let state = opt.state.bool(cx.tab().pref.show_hidden); let state = opt.state.bool(cx.tab().pref.show_hidden);
cx.tab_mut().pref.show_hidden = state; cx.tab_mut().pref.show_hidden = state;
let hovered = cx.hovered().map(|f| f.urn().owned()); let hovered = cx.hovered().map(|f| f.urn().to_owned());
let apply = |f: &mut Folder| { let apply = |f: &mut Folder| {
if f.stage == FolderStage::Loading { if f.stage == FolderStage::Loading {
render!(); render!();

View file

@ -18,7 +18,7 @@ impl Actor for Hover {
// Parent should always track CWD // Parent should always track CWD
if let Some(p) = &mut tab.parent { if let Some(p) = &mut tab.parent {
render!(p.repos(tab.current.url.strip_prefix(&p.url))); render!(p.repos(tab.current.url.try_strip_prefix(&p.url).ok()));
} }
// Repos CWD // Repos CWD
@ -30,7 +30,7 @@ impl Actor for Hover {
{ {
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc. // `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
// At this point, it's intuitive to track the location of the file regardless. // At this point, it's intuitive to track the location of the file regardless.
tab.current.trace = Some(u.to_owned()); tab.current.trace = Some(u.clone());
} }
// Publish through DDS // Publish through DDS

View file

@ -21,7 +21,7 @@ impl Actor for Leave {
let Some(mut url) = url else { succ!() }; let Some(mut url) = url else { succ!() };
if url.is_search() { if url.is_search() {
url = url.into_regular(); url = url.as_regular()?;
} }
act!(mgr:cd, cx, (url, CdSource::Leave)) act!(mgr:cd, cx, (url, CdSource::Leave))

View file

@ -6,6 +6,8 @@ yazi_macro::mod_flat!(
close close
copy copy
create create
displace
displace_do
download download
enter enter
escape escape
@ -39,6 +41,7 @@ yazi_macro::mod_flat!(
shell shell
sort sort
spot spot
stash
suspend suspend
tab_close tab_close
tab_create tab_create

View file

@ -1,4 +1,4 @@
use std::{ffi::OsString, time::Duration}; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use tokio::{select, time}; use tokio::{select, time};
@ -7,7 +7,7 @@ use yazi_dds::spark::SparkKind;
use yazi_macro::{emit, succ}; use yazi_macro::{emit, succ};
use yazi_parser::mgr::QuitOpt; use yazi_parser::mgr::QuitOpt;
use yazi_proxy::ConfirmProxy; use yazi_proxy::ConfirmProxy;
use yazi_shared::{data::Data, event::EventQuit, url::{AsUrl, UrlCow}}; use yazi_shared::{data::Data, event::EventQuit, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -66,17 +66,12 @@ impl Actor for Quit {
} }
impl Quit { impl Quit {
pub(super) fn with_selected<'a, I, T>(selected: I) pub(super) fn with_selected<I>(selected: I)
where where
I: IntoIterator<Item = T>, I: IntoIterator,
T: Into<UrlCow<'a>>, I::Item: AsUrl,
{ {
let paths = selected.into_iter().fold(OsString::new(), |mut s, u| { let paths = selected.into_iter().join(Strand::Utf8("\n"));
s.push(u.into().as_url().os_str());
s.push("\n");
s
});
if !paths.is_empty() { if !paths.is_empty() {
emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() })); emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() }));
} }

View file

@ -6,7 +6,7 @@ use yazi_fs::{CWD, Files, FilesOp, cha::Cha};
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, scheme::SchemeLike, url::UrlBuf}; use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
use yazi_term::tty::TTY; use yazi_term::tty::TTY;
use yazi_vfs::{VfsFiles, VfsFilesOp}; use yazi_vfs::{VfsFiles, VfsFilesOp};
@ -41,7 +41,7 @@ impl Actor for Refresh {
impl Refresh { impl Refresh {
fn cwd_changed() { fn cwd_changed() {
if CWD.load().scheme.is_virtual() { if CWD.load().kind().is_virtual() {
MgrProxy::watch(); MgrProxy::watch();
} }
} }
@ -58,8 +58,8 @@ impl Refresh {
let futs: Vec<_> = folders let futs: Vec<_> = folders
.iter() .iter()
.filter(|&f| f.url.is_internal()) .filter(|&f| f.url.is_absolute() && f.url.is_internal())
.map(|&f| go(f.url.to_owned(), f.cha)) .map(|&f| go(f.url.clone(), f.cha))
.collect(); .collect();
if !futs.is_empty() { if !futs.is_empty() {
tokio::spawn(futures::future::join_all(futs)); tokio::spawn(futures::future::join_all(futs));

View file

@ -5,7 +5,7 @@ use yazi_fs::{File, FilesOp};
use yazi_macro::{act, err, ok_or_not_found, succ}; use yazi_macro::{act, err, ok_or_not_found, succ};
use yazi_parser::mgr::RenameOpt; use yazi_parser::mgr::RenameOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
use yazi_shared::{Id, data::Data, path::PathLike, url::{UrlBuf, UrlLike}}; use yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -49,7 +49,10 @@ impl Actor for Rename {
return; return;
} }
let new = old.parent().unwrap().join(name); let Some(Ok(new)) = old.parent().map(|u| u.try_join(name)) else {
return;
};
if opt.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await { if opt.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await {
Self::r#do(tab, old, new).await.ok(); Self::r#do(tab, old, new).await.ok();
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await { } else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
@ -70,10 +73,11 @@ impl Rename {
provider::rename(&old, &new).await?; provider::rename(&old, &new).await?;
if let Ok(u) = overwritten if let Ok(u) = overwritten
&& u != new
&& let Some((parent, urn)) = u.pair() && let Some((parent, urn)) = u.pair()
{ {
ok_or_not_found!(provider::rename(&u, &new).await); ok_or_not_found!(provider::rename(&u, &new).await);
FilesOp::Deleting(parent.to_owned(), [urn.owned()].into()).emit(); FilesOp::Deleting(parent.to_owned(), [urn.into()].into()).emit();
} }
let new = provider::casefold(&new).await?; let new = provider::casefold(&new).await?;
@ -81,10 +85,10 @@ impl Rename {
let file = File::new(&new).await?; let file = File::new(&new).await?;
if new_p == old_p { if new_p == old_p {
FilesOp::Upserting(old_p.into(), [(old_n.owned(), file)].into()).emit(); FilesOp::Upserting(old_p.into(), [(old_n.into(), file)].into()).emit();
} else { } else {
FilesOp::Deleting(old_p.into(), [old_n.owned()].into()).emit(); FilesOp::Deleting(old_p.into(), [old_n.into()].into()).emit();
FilesOp::Upserting(new_p.into(), [(new_n.owned(), file)].into()).emit(); FilesOp::Upserting(new_p.into(), [(new_n.into(), file)].into()).emit();
} }
MgrProxy::reveal(&new); MgrProxy::reveal(&new);

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use yazi_fs::{File, FilesOp}; use yazi_fs::{File, FilesOp};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::RevealOpt; use yazi_parser::mgr::RevealOpt;
use yazi_shared::{data::Data, path::PathLike, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -19,7 +19,7 @@ impl Actor for Reveal {
// Cd to the parent directory // Cd to the parent directory
act!(mgr:cd, cx, (parent, opt.source))?; act!(mgr:cd, cx, (parent, opt.source))?;
// Try to hover on the child file // Try to hover over the child file
let tab = cx.tab_mut(); let tab = cx.tab_mut();
render!(tab.current.hover(child)); render!(tab.current.hover(child));
@ -30,8 +30,8 @@ impl Actor for Reveal {
tab.current.update_pub(tab.id, op); tab.current.update_pub(tab.id, op);
} }
// Now, we can safely hover on the target // Now, we can safely hover over the target
act!(mgr:hover, cx, Some(child.owned()))?; act!(mgr:hover, cx, Some(child.into()))?;
act!(mgr:peek, cx)?; act!(mgr:peek, cx)?;
act!(mgr:watch, cx)?; act!(mgr:watch, cx)?;

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, mem, time::Duration}; use std::{borrow::Cow, time::Duration};
use anyhow::Result; use anyhow::Result;
use tokio::pin; use tokio::pin;
@ -6,10 +6,10 @@ use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_fs::{FilesOp, cha::Cha}; use yazi_fs::{FilesOp, cha::Cha};
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::{VoidOpt, mgr::{SearchOpt, SearchOptVia}}; use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}};
use yazi_plugin::external; use yazi_plugin::external;
use yazi_proxy::{InputProxy, MgrProxy}; use yazi_proxy::{AppProxy, InputProxy, MgrProxy};
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -52,8 +52,10 @@ impl Actor for SearchDo {
handle.abort(); handle.abort();
} }
let cwd = tab.cwd().to_search(opt.subject.as_ref());
let hidden = tab.pref.show_hidden; let hidden = tab.pref.show_hidden;
let Ok(cwd) = tab.cwd().to_search(&opt.subject) else {
succ!(AppProxy::notify_warn("Search", "Only local filesystem searches are supported"));
};
tab.search = Some(tokio::spawn(async move { tab.search = Some(tokio::spawn(async move {
let rx = match opt.via { let rx = match opt.via {
@ -107,15 +109,10 @@ impl Actor for SearchStop {
handle.abort(); handle.abort();
} }
if !tab.cwd().is_search() { if tab.cwd().is_search() {
succ!(); act!(mgr:cd, cx, (tab.cwd().to_regular()?, CdSource::Escape))?;
} }
let rep = tab.history.remove_or(tab.cwd().to_regular()); succ!();
drop(mem::replace(&mut tab.current, rep));
act!(mgr:hidden, cx)?;
act!(mgr:sort, cx)?;
act!(mgr:refresh, cx)
} }
} }

View file

@ -40,7 +40,7 @@ impl Actor for Shell {
TasksProxy::open_shell_compat(ProcessOpenOpt { TasksProxy::open_shell_compat(ProcessOpenOpt {
cwd: cwd.into(), cwd: cwd.into(),
cmd: Splatter::new(&selected).splat(opt.run.as_ref()), cmd: Splatter::new(&selected).splat(&*opt.run),
args: selected, args: selected,
block: opt.block, block: opt.block,
orphan: opt.orphan, orphan: opt.orphan,

View file

@ -1,9 +1,10 @@
use anyhow::Result; use anyhow::Result;
use yazi_core::tab::Folder; use yazi_core::tab::Folder;
use yazi_dds::spark::SparkKind;
use yazi_fs::{FilesSorter, FolderStage}; use yazi_fs::{FilesSorter, FolderStage};
use yazi_macro::{act, render, render_and, succ}; use yazi_macro::{act, render, render_and, succ};
use yazi_parser::mgr::SortOpt; use yazi_parser::mgr::SortOpt;
use yazi_shared::{data::Data, path::PathLike}; use yazi_shared::{Source, data::Data};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -23,7 +24,7 @@ impl Actor for Sort {
pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit); pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit);
let sorter = FilesSorter::from(&*pref); let sorter = FilesSorter::from(&*pref);
let hovered = cx.hovered().map(|f| f.urn().owned()); let hovered = cx.hovered().map(|f| f.urn().to_owned());
let apply = |f: &mut Folder| { let apply = |f: &mut Folder| {
if f.stage == FolderStage::Loading { if f.stage == FolderStage::Loading {
render!(); render!();
@ -56,4 +57,12 @@ impl Actor for Sort {
succ!(); succ!();
} }
fn hook(cx: &Ctx, _: &Self::Options) -> Option<SparkKind> {
match cx.source() {
Source::Ind => Some(SparkKind::IndSort),
Source::Key => Some(SparkKind::KeySort),
_ => None,
}
}
} }

View file

@ -0,0 +1,31 @@
use anyhow::Result;
use yazi_dds::spark::SparkKind;
use yazi_macro::succ;
use yazi_parser::mgr::StashOpt;
use yazi_shared::{Source, data::Data, url::{AsUrl, UrlLike}};
use crate::{Actor, Ctx};
pub struct Stash;
impl Actor for Stash {
type Options = StashOpt;
const NAME: &str = "stash";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
if opt.target.is_absolute() && opt.target.is_internal() {
cx.tab_mut().backstack.push(opt.target.as_url());
}
succ!()
}
fn hook(cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> {
match cx.source() {
Source::Ind => Some(SparkKind::IndStash),
Source::Relay => Some(SparkKind::RelayStash),
_ => None,
}
}
}

View file

@ -3,7 +3,7 @@ use yazi_core::tab::Tab;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::{CdSource, TabCreateOpt}; use yazi_parser::mgr::{CdSource, TabCreateOpt};
use yazi_proxy::AppProxy; use yazi_proxy::AppProxy;
use yazi_shared::data::Data; use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -25,14 +25,17 @@ impl Actor for TabCreate {
} }
let mut tab = Tab::default(); let mut tab = Tab::default();
let (cd, url) = if let Some(wd) = opt.wd { let (cd, url) = if let Some(wd) = opt.url {
(true, wd.into_owned()) (true, wd.into_owned())
} else if let Some(h) = cx.hovered() { } else if let Some(h) = cx.hovered() {
tab.pref = cx.tab().pref.clone(); tab.pref = cx.tab().pref.clone();
(false, h.url.to_regular()) (false, h.url.clone())
} else if cx.cwd().is_search() {
tab.pref = cx.tab().pref.clone();
(true, cx.cwd().to_regular()?)
} else { } else {
tab.pref = cx.tab().pref.clone(); tab.pref = cx.tab().pref.clone();
(true, cx.cwd().to_regular()) (true, cx.cwd().clone())
}; };
let tabs = &mut cx.mgr.tabs; let tabs = &mut cx.mgr.tabs;

View file

@ -18,6 +18,7 @@ impl Actor for UpdateFiles {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let revision = cx.current().files.revision; let revision = cx.current().files.revision;
let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.chdir(u)).collect(); let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.chdir(u)).collect();
for op in [opt.op].into_iter().chain(linked) { for op in [opt.op].into_iter().chain(linked) {
cx.mgr.yanked.apply_op(&op); cx.mgr.yanked.apply_op(&op);
Self::update_tab(cx, op).ok(); Self::update_tab(cx, op).ok();
@ -25,7 +26,7 @@ impl Actor for UpdateFiles {
render!(cx.mgr.yanked.catchup_revision(false)); render!(cx.mgr.yanked.catchup_revision(false));
act!(mgr:hidden, cx)?; act!(mgr:hidden, cx)?;
act!(mgr:sort, cx)?; act!(mgr:sort, cx).ok();
if revision != cx.current().files.revision { if revision != cx.current().files.revision {
act!(mgr:hover, cx)?; act!(mgr:hover, cx)?;

View file

@ -4,6 +4,7 @@ use anyhow::Result;
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}}; use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
use scopeguard::defer; use scopeguard::defer;
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time}; use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
use yazi_binding::Permit;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::VoidOpt; use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER}; use yazi_proxy::{AppProxy, HIDER};
@ -26,7 +27,7 @@ impl Actor for Inspect {
}; };
tokio::spawn(async move { tokio::spawn(async move {
let _permit = HIDER.acquire().await.unwrap(); let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
let buffered = { let buffered = {
@ -37,9 +38,7 @@ impl Actor for Inspect {
task.logs.clone() task.logs.clone()
}; };
defer!(AppProxy::resume());
AppProxy::stop().await; AppProxy::stop().await;
terminal_clear(TTY.writer()).ok(); terminal_clear(TTY.writer()).ok();
TTY.writer().write_all(buffered.as_bytes()).ok(); TTY.writer().write_all(buffered.as_bytes()).ok();
TTY.writer().flush().ok(); TTY.writer().flush().ok();

View file

@ -1,6 +1,6 @@
[package] [package]
name = "yazi-adapter" name = "yazi-adapter"
version = "25.9.15" version = "26.1.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ] authors = [ "sxyazi <sxyazi@gmail.com>" ]
@ -8,21 +8,25 @@ description = "Yazi image adapter"
homepage = "https://yazi-rs.github.io" homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[lints]
workspace = true
[dependencies] [dependencies]
yazi-config = { path = "../yazi-config", version = "25.9.15" } yazi-config = { path = "../yazi-config", version = "26.1.4" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
yazi-term = { path = "../yazi-term", version = "25.9.15" } yazi-term = { path = "../yazi-term", version = "26.1.4" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
image = { version = "0.25.8", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] } image = { version = "0.25.9", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
moxcms = "0.7.11"
palette = { version = "0.7.6", default-features = false } palette = { version = "0.7.6", default-features = false }
quantette = { version = "0.3.0", default-features = false } quantette = { version = "0.5.1", default-features = false }
ratatui = { workspace = true } ratatui = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }

5
yazi-adapter/README.md Normal file
View file

@ -0,0 +1,5 @@
# yazi-adapter
This crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.
[source]: https://github.com/sxyazi/yazi

View file

@ -84,7 +84,7 @@ impl Adapter {
} }
impl Adapter { impl Adapter {
pub fn matches(emulator: Emulator) -> Self { pub fn matches(emulator: &Emulator) -> Self {
let mut protocols = emulator.adapters().to_owned(); let mut protocols = emulator.adapters().to_owned();
if env_exists("ZELLIJ_SESSION_NAME") { if env_exists("ZELLIJ_SESSION_NAME") {
protocols.retain(|p| *p == Self::Sixel); protocols.retain(|p| *p == Self::Sixel);

View file

@ -44,42 +44,40 @@ impl Brand {
} }
pub fn from_env() -> Option<Self> { pub fn from_env() -> Option<Self> {
use Brand as B; let (term, program) = Self::env();
let (term, program) = B::env();
let vars = [ let vars = [
("KITTY_WINDOW_ID", B::Kitty), ("KITTY_WINDOW_ID", Self::Kitty),
("KONSOLE_VERSION", B::Konsole), ("KONSOLE_VERSION", Self::Konsole),
("ITERM_SESSION_ID", B::Iterm2), ("ITERM_SESSION_ID", Self::Iterm2),
("WEZTERM_EXECUTABLE", B::WezTerm), ("WEZTERM_EXECUTABLE", Self::WezTerm),
("GHOSTTY_RESOURCES_DIR", B::Ghostty), ("GHOSTTY_RESOURCES_DIR", Self::Ghostty),
("WT_Session", B::Microsoft), ("WT_Session", Self::Microsoft),
("WARP_HONOR_PS1", B::Warp), ("WARP_HONOR_PS1", Self::Warp),
("VSCODE_INJECTION", B::VSCode), ("VSCODE_INJECTION", Self::VSCode),
("TABBY_CONFIG_DIRECTORY", B::Tabby), ("TABBY_CONFIG_DIRECTORY", Self::Tabby),
]; ];
match term.as_str() { match term.as_str() {
"xterm-kitty" => return Some(B::Kitty), "xterm-kitty" => return Some(Self::Kitty),
"foot" => return Some(B::Foot), "foot" => return Some(Self::Foot),
"foot-extra" => return Some(B::Foot), "foot-extra" => return Some(Self::Foot),
"xterm-ghostty" => return Some(B::Ghostty), "xterm-ghostty" => return Some(Self::Ghostty),
"rio" => return Some(B::Rio), "rio" => return Some(Self::Rio),
"rxvt-unicode-256color" => return Some(B::Urxvt), "rxvt-unicode-256color" => return Some(Self::Urxvt),
_ => {} _ => {}
} }
match program.as_str() { match program.as_str() {
"iTerm.app" => return Some(B::Iterm2), "iTerm.app" => return Some(Self::Iterm2),
"WezTerm" => return Some(B::WezTerm), "WezTerm" => return Some(Self::WezTerm),
"ghostty" => return Some(B::Ghostty), "ghostty" => return Some(Self::Ghostty),
"WarpTerminal" => return Some(B::Warp), "WarpTerminal" => return Some(Self::Warp),
"rio" => return Some(B::Rio), "rio" => return Some(Self::Rio),
"BlackBox" => return Some(B::BlackBox), "BlackBox" => return Some(Self::BlackBox),
"vscode" => return Some(B::VSCode), "vscode" => return Some(Self::VSCode),
"Tabby" => return Some(B::Tabby), "Tabby" => return Some(Self::Tabby),
"Hyper" => return Some(B::Hyper), "Hyper" => return Some(Self::Hyper),
"mintty" => return Some(B::Mintty), "mintty" => return Some(Self::Mintty),
"Apple_Terminal" => return Some(B::Apple), "Apple_Terminal" => return Some(Self::Apple),
_ => {} _ => {}
} }
if let Some((var, brand)) = vars.into_iter().find(|&(s, _)| env_exists(s)) { if let Some((var, brand)) = vars.into_iter().find(|&(s, _)| env_exists(s)) {
@ -91,30 +89,28 @@ impl Brand {
} }
pub(super) fn adapters(self) -> &'static [crate::Adapter] { pub(super) fn adapters(self) -> &'static [crate::Adapter] {
use Brand as B;
use crate::Adapter as A; use crate::Adapter as A;
match self { match self {
B::Kitty => &[A::Kgp], Self::Kitty => &[A::Kgp],
B::Konsole => &[A::KgpOld], Self::Konsole => &[A::KgpOld],
B::Iterm2 => &[A::Iip, A::Sixel], Self::Iterm2 => &[A::Iip, A::Sixel],
B::WezTerm => &[A::Iip, A::Sixel], Self::WezTerm => &[A::Iip, A::Sixel],
B::Foot => &[A::Sixel], Self::Foot => &[A::Sixel],
B::Ghostty => &[A::Kgp], Self::Ghostty => &[A::Kgp],
B::Microsoft => &[A::Sixel], Self::Microsoft => &[A::Sixel],
B::Warp => &[A::Iip, A::KgpOld], Self::Warp => &[A::Iip, A::KgpOld],
B::Rio => &[A::Iip, A::Sixel], Self::Rio => &[A::Iip, A::Sixel],
B::BlackBox => &[A::Sixel], Self::BlackBox => &[A::Sixel],
B::VSCode => &[A::Iip, A::Sixel], Self::VSCode => &[A::Iip, A::Sixel],
B::Tabby => &[A::Iip, A::Sixel], Self::Tabby => &[A::Iip, A::Sixel],
B::Hyper => &[A::Iip, A::Sixel], Self::Hyper => &[A::Iip, A::Sixel],
B::Mintty => &[A::Iip], Self::Mintty => &[A::Iip],
B::Tmux => &[], Self::Tmux => &[],
B::VTerm => &[], Self::VTerm => &[],
B::Apple => &[], Self::Apple => &[],
B::Urxvt => &[], Self::Urxvt => &[],
B::Bobcat => &[A::Iip, A::Sixel], Self::Bobcat => &[A::Iip, A::Sixel],
} }
} }

View file

@ -50,7 +50,7 @@ impl Dimension {
} }
pub fn cell_size() -> Option<(f64, f64)> { pub fn cell_size() -> Option<(f64, f64)> {
let emu = EMULATOR.get(); let emu = &*EMULATOR;
Some(if emu.force_16t { Some(if emu.force_16t {
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64) (emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
} else if let Some(r) = Self::available().ratio() { } else if let Some(r) = Self::available().ratio() {

View file

@ -4,13 +4,18 @@ use anyhow::{Result, bail};
use crossterm::{cursor::MoveTo, queue}; use crossterm::{cursor::MoveTo, queue};
use image::{DynamicImage, GenericImageView, RgbImage}; use image::{DynamicImage, GenericImageView, RgbImage};
use palette::{Srgb, cast::ComponentsAs}; use palette::{Srgb, cast::ComponentsAs};
use quantette::{ColorSlice, PaletteSize, QuantizeOutput, wu::UIntBinner}; use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct Sixel; pub(crate) struct Sixel;
struct QuantizeOutput<T> {
indices: Vec<u8>,
palette: Vec<T>,
}
impl Sixel { impl Sixel {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(path, max).await?;
@ -50,7 +55,7 @@ impl Sixel {
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut buf = vec![]; let mut buf = vec![];
write!(buf, "{START}P0;1;8q\"1;1;{};{}", img.width(), img.height())?; write!(buf, "{START}P9;1q\"1;1;{};{}", img.width(), img.height())?;
// Palette // Palette
for (i, c) in qo.palette.iter().enumerate() { for (i, c) in qo.palette.iter().enumerate() {
@ -110,12 +115,14 @@ impl Sixel {
fn quantify(rgb: &RgbImage, alpha: bool) -> Result<QuantizeOutput<Srgb<u8>>> { fn quantify(rgb: &RgbImage, alpha: bool) -> Result<QuantizeOutput<Srgb<u8>>> {
let buf = &rgb.as_raw()[..(rgb.pixels().len() * 3)]; let buf = &rgb.as_raw()[..(rgb.pixels().len() * 3)];
let slice: ColorSlice<Srgb<u8>> = buf.components_as().try_into()?; let colors: &[Srgb<u8>] = buf.components_as();
Ok(quantette::wu::indexed_palette( let wu = WuU8x3::run_slice(colors, BinnerU8x3::rgb())?;
&slice, let color_map = wu.color_map(PaletteSize::try_from(256u16 - alpha as u16)?);
PaletteSize::try_from(256u16 - alpha as u16)?,
&UIntBinner::<32>, Ok(QuantizeOutput {
)) indices: color_map.map_to_indices(colors),
palette: color_map.into_palette().into_vec(),
})
} }
} }

View file

@ -10,16 +10,25 @@ use yazi_term::tty::{Handle, TTY};
use crate::{Adapter, Brand, Dimension, Mux, TMUX, Unknown}; use crate::{Adapter, Brand, Dimension, Mux, TMUX, Unknown};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct Emulator { pub struct Emulator {
pub kind: Either<Brand, Unknown>, pub kind: Either<Brand, Unknown>,
pub version: String,
pub light: bool, pub light: bool,
pub csi_16t: (u16, u16), pub csi_16t: (u16, u16),
pub force_16t: bool, pub force_16t: bool,
} }
impl Default for Emulator { impl Default for Emulator {
fn default() -> Self { Self::unknown() } fn default() -> Self {
Self {
kind: Either::Right(Unknown::default()),
version: String::new(),
light: false,
csi_16t: (0, 0),
force_16t: false,
}
}
} }
impl Emulator { impl Emulator {
@ -60,22 +69,14 @@ impl Emulator {
let csi_16t = Self::csi_16t(&resp).unwrap_or_default(); let csi_16t = Self::csi_16t(&resp).unwrap_or_default();
Ok(Self { Ok(Self {
kind, kind,
version: Self::csi_gt_q(&resp).unwrap_or_default(),
light: Self::light_bg(&resp).unwrap_or_default(), light: Self::light_bg(&resp).unwrap_or_default(),
csi_16t, csi_16t,
force_16t: Self::force_16t(csi_16t), force_16t: Self::force_16t(csi_16t),
}) })
} }
pub const fn unknown() -> Self { pub fn adapters(&self) -> &'static [Adapter] {
Self {
kind: Either::Right(Unknown::default()),
light: false,
csi_16t: (0, 0),
force_16t: false,
}
}
pub fn adapters(self) -> &'static [Adapter] {
match self.kind { match self.kind {
Either::Left(brand) => brand.adapters(), Either::Left(brand) => brand.adapters(),
Either::Right(unknown) => unknown.adapters(), Either::Right(unknown) => unknown.adapters(),
@ -182,6 +183,11 @@ impl Emulator {
Some((w.parse().ok()?, h.parse().ok()?)) Some((w.parse().ok()?, h.parse().ok()?))
} }
fn csi_gt_q(resp: &str) -> Option<String> {
let (_, s) = resp.split_once("\x1bP>|")?;
Some(s[..s.find("\x1b\\")?].to_owned())
}
fn light_bg(resp: &str) -> Result<bool> { fn light_bg(resp: &str) -> Result<bool> {
match resp.split_once("]11;rgb:") { match resp.split_once("]11;rgb:") {
Some((_, s)) if s.len() >= 14 => { Some((_, s)) if s.len() >= 14 => {

69
yazi-adapter/src/icc.rs Normal file
View file

@ -0,0 +1,69 @@
use anyhow::Context;
use image::{ColorType, DynamicImage, GrayAlphaImage, GrayImage, ImageDecoder, RgbImage, RgbaImage, metadata::Cicp};
use moxcms::{CicpColorPrimaries, ColorProfile, DataColorSpace, Layout, TransferCharacteristics, TransformOptions};
pub(super) struct Icc;
impl Icc {
pub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result<DynamicImage> {
if let Some(layout) = Self::color_type_to_layout(decoder.color_type())
&& let Some(icc) = decoder.icc_profile().unwrap_or_default()
&& let Ok(profile) = ColorProfile::new_from_slice(&icc)
&& Self::requires_transform(&profile)
{
let mut buf = vec![0u8; decoder.total_bytes() as usize];
let (w, h) = decoder.dimensions();
decoder.read_image(&mut buf)?;
let transformer = profile
// TODO: Use `create_transform_in_place_nbit` in the next minor version of moxcms.
.create_transform_8bit(layout, &ColorProfile::new_srgb(), layout, TransformOptions::default())
.context("cannot make a profile transformer")?;
let mut converted = vec![0u8; buf.len()];
transformer.transform(&buf, &mut converted).context("cannot transform image")?;
let mut image: DynamicImage = match layout {
Layout::Gray => {
GrayImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
}
Layout::GrayAlpha => {
GrayAlphaImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
}
Layout::Rgb => {
RgbImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
}
Layout::Rgba => {
RgbaImage::from_raw(w, h, converted).context("cannot load transformed image")?.into()
}
_ => unreachable!(),
};
image.set_rgb_primaries(Cicp::SRGB.primaries);
image.set_transfer_function(Cicp::SRGB.transfer);
Ok(image)
} else {
Ok(DynamicImage::from_decoder(decoder)?)
}
}
fn color_type_to_layout(color_type: ColorType) -> Option<Layout> {
match color_type {
ColorType::L8 => Some(Layout::Gray),
ColorType::La8 => Some(Layout::GrayAlpha),
ColorType::Rgb8 => Some(Layout::Rgb),
ColorType::Rgba8 => Some(Layout::Rgba),
_ => None,
}
}
fn requires_transform(profile: &ColorProfile) -> bool {
if profile.color_space == DataColorSpace::Cmyk {
return false;
}
profile.cicp.is_none_or(|c| {
c.color_primaries != CicpColorPrimaries::Bt709
|| c.transfer_characteristics != TransferCharacteristics::Srgb
})
}
}

View file

@ -1,18 +1,18 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::Result;
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation}; use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_fs::provider::{Provider, local::Local}; use yazi_fs::provider::{Provider, local::Local};
use crate::Dimension; use crate::{Dimension, Icc};
pub struct Image; pub struct Image;
impl Image { impl Image {
pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> { pub async fn precache(src: PathBuf, cache: &Path) -> Result<()> {
let (mut img, orientation, icc) = Self::decode_from(src).await?; let (mut img, orientation) = Self::decode_from(src).await?;
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height)); let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
let buf = tokio::task::spawn_blocking(move || { let buf = tokio::task::spawn_blocking(move || {
@ -25,25 +25,22 @@ impl Image {
let mut buf = Vec::new(); let mut buf = Vec::new();
if img.color().has_alpha() { if img.color().has_alpha() {
let rgba = img.into_rgba8(); let encoder = PngEncoder::new(&mut buf);
let mut encoder = PngEncoder::new(&mut buf); img.write_with_encoder(encoder)?;
icc.map(|b| encoder.set_icc_profile(b));
encoder.write_image(&rgba, rgba.width(), rgba.height(), ExtendedColorType::Rgba8)?;
} else { } else {
let mut encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality); let encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
icc.map(|b| encoder.set_icc_profile(b)); img.write_with_encoder(encoder)?;
encoder.encode_image(&img.into_rgb8())?;
} }
Ok::<_, ImageError>(buf) Ok::<_, ImageError>(buf)
}) })
.await??; .await??;
Ok(Local.write(cache, buf).await?) Ok(Local::regular(&cache).write(buf).await?)
} }
pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage> { pub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage> {
let (mut img, orientation, _) = Self::decode_from(path).await?; let (mut img, orientation) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect)); let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
// Fast path. // Fast path.
@ -96,7 +93,7 @@ impl Image {
} }
} }
async fn decode_from(path: PathBuf) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> { async fn decode_from(path: PathBuf) -> Result<(DynamicImage, Orientation)> {
let mut limits = Limits::no_limits(); let mut limits = Limits::no_limits();
if YAZI.tasks.image_alloc > 0 { if YAZI.tasks.image_alloc > 0 {
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64); limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
@ -114,9 +111,7 @@ impl Image {
let mut decoder = reader.with_guessed_format()?.into_decoder()?; let mut decoder = reader.with_guessed_format()?.into_decoder()?;
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms); let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
let icc = decoder.icc_profile().unwrap_or_default(); Ok((Icc::transform(decoder)?, orientation))
Ok((DynamicImage::from_decoder(decoder)?, orientation, icc))
}) })
.await .await
.map_err(|e| ImageError::IoError(e.into()))? .map_err(|e| ImageError::IoError(e.into()))?

View file

@ -1,12 +1,10 @@
#![allow(clippy::option_map_unit_fn, clippy::unit_arg)]
yazi_macro::mod_pub!(drivers); yazi_macro::mod_pub!(drivers);
yazi_macro::mod_flat!(adapter brand dimension emulator image info mux unknown); yazi_macro::mod_flat!(adapter brand dimension emulator icc image info mux unknown);
use yazi_shared::{SyncCell, in_wsl}; use yazi_shared::{RoCell, SyncCell, in_wsl};
pub static EMULATOR: SyncCell<Emulator> = SyncCell::new(Emulator::unknown()); pub static EMULATOR: RoCell<Emulator> = RoCell::new();
pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa); pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa);
// Image state // Image state
@ -26,8 +24,8 @@ pub fn init() -> anyhow::Result<()> {
WSL.set(in_wsl()); WSL.set(in_wsl());
// Emulator detection // Emulator detection
EMULATOR.set(Emulator::detect().unwrap_or_default()); let mut emulator = Emulator::detect().unwrap_or_default();
TMUX.set(EMULATOR.get().kind.is_left_and(|&b| b == Brand::Tmux)); TMUX.set(emulator.kind.is_left_and(|&b| b == Brand::Tmux));
// Tmux support // Tmux support
if TMUX.get() { if TMUX.get() {
@ -35,12 +33,13 @@ pub fn init() -> anyhow::Result<()> {
START.set("\x1bPtmux;\x1b\x1b"); START.set("\x1bPtmux;\x1b\x1b");
CLOSE.set("\x1b\\"); CLOSE.set("\x1b\\");
Mux::tmux_passthrough(); Mux::tmux_passthrough();
EMULATOR.set(Emulator::detect().unwrap_or_default()); emulator = Emulator::detect().unwrap_or_default();
} }
yazi_config::init_flavor(EMULATOR.get().light)?; EMULATOR.init(emulator);
yazi_config::init_flavor(EMULATOR.light)?;
ADAPTOR.set(Adapter::matches(EMULATOR.get())); ADAPTOR.set(Adapter::matches(&EMULATOR));
ADAPTOR.get().start(); ADAPTOR.get().start();
Ok(()) Ok(())
} }

View file

@ -1,14 +1,12 @@
use crate::Adapter; use crate::Adapter;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Default)]
pub struct Unknown { pub struct Unknown {
pub kgp: bool, pub kgp: bool,
pub sixel: bool, pub sixel: bool,
} }
impl Unknown { impl Unknown {
pub(super) const fn default() -> Self { Self { kgp: false, sixel: false } }
pub(super) fn adapters(self) -> &'static [Adapter] { pub(super) fn adapters(self) -> &'static [Adapter] {
use Adapter as A; use Adapter as A;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "yazi-binding" name = "yazi-binding"
version = "25.9.15" version = "26.1.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ] authors = [ "sxyazi <sxyazi@gmail.com>" ]
@ -8,19 +8,23 @@ description = "Yazi Lua bindings"
homepage = "https://yazi-rs.github.io" homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[lints]
workspace = true
[features] [features]
default = [ "vendored-lua" ] default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ] vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-adapter = { path = "../yazi-adapter", version = "25.9.15" } yazi-adapter = { path = "../yazi-adapter", version = "26.1.4" }
yazi-config = { path = "../yazi-config", version = "25.9.15" } yazi-config = { path = "../yazi-config", version = "26.1.4" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
paste = { workspace = true } paste = { workspace = true }

5
yazi-binding/README.md Normal file
View file

@ -0,0 +1,5 @@
# yazi-binding
This crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.
[source]: https://github.com/sxyazi/yazi

View file

@ -20,7 +20,7 @@ pub struct Border {
pub r#type: ratatui::widgets::BorderType, pub r#type: ratatui::widgets::BorderType,
pub style: ratatui::style::Style, pub style: ratatui::style::Style,
pub titles: Vec<(ratatui::widgets::block::Position, ratatui::text::Line<'static>)>, pub titles: Vec<(ratatui::widgets::TitlePosition, ratatui::text::Line<'static>)>,
} }
impl Border { impl Border {
@ -51,8 +51,8 @@ impl Border {
for title in self.titles { for title in self.titles {
block = match title { block = match title {
(ratatui::widgets::block::Position::Top, line) => block.title(line), (ratatui::widgets::TitlePosition::Top, line) => block.title(line),
(ratatui::widgets::block::Position::Bottom, line) => block.title(line), (ratatui::widgets::TitlePosition::Bottom, line) => block.title(line),
}; };
} }
@ -80,9 +80,9 @@ impl UserData for Border {
"title", "title",
|_, (ud, line, position): (AnyUserData, Value, Option<u8>)| { |_, (ud, line, position): (AnyUserData, Value, Option<u8>)| {
let position = if position == Some(Borders::BOTTOM.bits()) { let position = if position == Some(Borders::BOTTOM.bits()) {
ratatui::widgets::block::Position::Bottom ratatui::widgets::TitlePosition::Bottom
} else { } else {
ratatui::widgets::block::Position::Top ratatui::widgets::TitlePosition::Top
}; };
ud.borrow_mut::<Self>()?.titles.push((position, Line::try_from(line)?.inner)); ud.borrow_mut::<Self>()?.titles.push((position, Line::try_from(line)?.inner));

View file

@ -227,7 +227,7 @@ mod tests {
.call(()) .call(())
.unwrap(); .unwrap();
line.spans.iter().map(|s| s.content.as_ref()).collect() line.spans.iter().map(|s| &*s.content).collect()
} }
#[test] #[test]

View file

@ -36,7 +36,7 @@ impl Rect {
})?; })?;
let index = lua.create_function(move |lua, (_, key): (Table, mlua::String)| { let index = lua.create_function(move |lua, (_, key): (Table, mlua::String)| {
Ok(match key.as_bytes().as_ref() { Ok(match &*key.as_bytes() {
b"default" => { b"default" => {
deprecate!(lua, "`ui.Rect.default` is deprecated, use `ui.Rect{{}}` instead, in your {}\nSee #2927 for more details: https://github.com/sxyazi/yazi/pull/2927"); deprecate!(lua, "`ui.Rect.default` is deprecated, use `ui.Rect{{}}` instead, in your {}\nSee #2927 for more details: https://github.com/sxyazi/yazi/pull/2927");
Some(Self(Default::default())) Some(Self(Default::default()))

View file

@ -14,9 +14,20 @@ pub enum Error {
impl Error { impl Error {
pub fn install(lua: &Lua) -> mlua::Result<()> { pub fn install(lua: &Lua) -> mlua::Result<()> {
let new = lua.create_function(|_, msg: String| Ok(Self::custom(msg)))?; let custom = lua.create_function(|_, msg: String| Ok(Self::custom(msg)))?;
lua.globals().raw_set("Error", lua.create_table_from([("custom", new)])?) let fs = lua.create_function(|_, value: Value| {
Ok(Self::Fs(match value {
Value::Table(t) => yazi_fs::error::Error::custom(
&t.raw_get::<mlua::String>("kind")?.to_str()?,
t.raw_get("code")?,
&t.raw_get::<mlua::String>("message")?.to_str()?,
)?,
_ => Err("expected a table".into_lua_err())?,
}))
})?;
lua.globals().raw_set("Error", lua.create_table_from([("custom", custom), ("fs", fs)])?)
} }
pub fn custom(msg: impl Into<SStr>) -> Self { Self::Custom(msg.into()) } pub fn custom(msg: impl Into<SStr>) -> Self { Self::Custom(msg.into()) }
@ -60,13 +71,20 @@ impl UserData for Error {
_ => None, _ => None,
}) })
}); });
fields.add_field_method_get("kind", |_, me| {
Ok(match me {
Self::Io(e) => Some(yazi_fs::error::Error::from(e.kind()).kind_str()),
Self::Fs(e) => Some(e.kind_str()),
_ => None,
})
});
} }
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| { methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {
Ok(match me { Ok(match me {
Self::Io(_) | Self::Fs(_) | Self::Serde(_) => lua.create_string(me.to_string()), Self::Io(_) | Self::Fs(_) | Self::Serde(_) => lua.create_string(me.to_string()),
Self::Custom(s) => lua.create_string(s.as_ref()), Self::Custom(s) => lua.create_string(&**s),
}) })
}); });
methods.add_meta_function(MetaMethod::Concat, |lua, (lhs, rhs): (Value, Value)| { methods.add_meta_function(MetaMethod::Concat, |lua, (lhs, rhs): (Value, Value)| {

View file

@ -1,11 +1,14 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{ExternalError, FromLua, Lua, Table, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use mlua::{AnyUserData, ExternalError, FromLua, Lua, ObjectLike, Table, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use crate::{Cha, Url, impl_file_fields, impl_file_methods}; use crate::{Cha, Url, impl_file_fields, impl_file_methods};
pub type FileRef = UserDataRef<File>; pub type FileRef = UserDataRef<File>;
const EXPECTED: &str = "expected a table, File, or fs::File";
#[derive(Clone)]
pub struct File { pub struct File {
inner: yazi_fs::File, inner: yazi_fs::File,
@ -14,6 +17,7 @@ pub struct File {
v_link_to: Option<Value>, v_link_to: Option<Value>,
v_name: Option<Value>, v_name: Option<Value>,
v_path: Option<Value>,
v_cache: Option<Value>, v_cache: Option<Value>,
} }
@ -28,23 +32,53 @@ impl From<File> for yazi_fs::File {
} }
impl File { impl File {
pub fn new(inner: yazi_fs::File) -> Self { pub fn new(inner: impl Into<yazi_fs::File>) -> Self {
Self { inner, v_cha: None, v_url: None, v_link_to: None, v_name: None, v_cache: None } Self {
inner: inner.into(),
v_cha: None,
v_url: None,
v_link_to: None,
v_name: None,
v_path: None,
v_cache: None,
}
}
pub fn install(lua: &Lua) -> mlua::Result<()> {
lua.globals().raw_set("File", lua.create_function(|_, value: Value| Self::try_from(value))?)
} }
} }
impl File { impl TryFrom<Value> for File {
pub fn install(lua: &Lua) -> mlua::Result<()> { type Error = mlua::Error;
lua.globals().raw_set(
"File", fn try_from(value: Value) -> Result<Self, Self::Error> {
lua.create_function(|_, t: Table| { match value {
Ok(Self::new(yazi_fs::File { Value::Table(tbl) => Self::try_from(tbl),
url: t.raw_get::<Url>("url")?.into(), Value::UserData(ud) => Self::try_from(ud),
cha: *t.raw_get::<Cha>("cha")?, _ => Err(EXPECTED.into_lua_err())?,
..Default::default() }
})) }
})?, }
)
impl TryFrom<Table> for File {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
Ok(Self::new(yazi_fs::File {
url: value.raw_get::<Url>("url")?.into(),
cha: *value.raw_get::<Cha>("cha")?,
..Default::default()
}))
}
}
impl TryFrom<AnyUserData> for File {
type Error = mlua::Error;
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> {
Ok(if let Ok(me) = value.borrow::<Self>() { me.clone() } else { value.get("bare")? })
} }
} }
@ -52,7 +86,7 @@ impl FromLua for File {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value { Ok(match value {
Value::UserData(ud) => Self::new(ud.take::<Self>()?.inner), Value::UserData(ud) => Self::new(ud.take::<Self>()?.inner),
_ => Err("Expected a File".into_lua_err())?, _ => Err("expected a File".into_lua_err())?,
}) })
} }
} }

View file

@ -0,0 +1,16 @@
use mlua::{MultiValue, UserData, UserDataMethods};
use tokio::task::JoinHandle;
pub enum Handle {
AsyncFn(JoinHandle<mlua::Result<MultiValue>>),
}
impl UserData for Handle {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("abort", |_, me, ()| {
Ok(match me {
Self::AsyncFn(h) => h.abort(),
})
});
}
}

View file

@ -1,7 +1,5 @@
#![allow(clippy::unit_arg)]
mod macros; mod macros;
yazi_macro::mod_pub!(elements); yazi_macro::mod_pub!(elements);
yazi_macro::mod_flat!(cha color composer error file icon id iter permit runtime scheme stage style url urn utils); yazi_macro::mod_flat!(cha color composer error file handle icon id iter path permit runtime scheme stage style url utils);

View file

@ -82,65 +82,122 @@ macro_rules! impl_style_method {
macro_rules! impl_style_shorthands { macro_rules! impl_style_shorthands {
($methods:ident, $($field:tt).+) => { ($methods:ident, $($field:tt).+) => {
$methods.add_function_mut("fg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| { $methods.add_function_mut("fg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {
use ratatui::style::Modifier;
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
match value { match value {
mlua::Value::Nil => { mlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {
ud.borrow::<Self>()?.$($field).+.fg.map($crate::Color).into_lua(lua) me.bg.map($crate::Color).into_lua(lua)
}, }
mlua::Value::Nil | mlua::Value::Boolean(_) => {
me.fg.map($crate::Color).into_lua(lua)
}
_ => { _ => {
ud.borrow_mut::<Self>()?.$($field).+.fg = Some($crate::Color::try_from(value)?.0); me.fg = Some($crate::Color::try_from(value)?.0);
ud.into_lua(lua) ud.into_lua(lua)
} }
} }
}); });
$methods.add_function_mut("bg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| { $methods.add_function_mut("bg", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {
use ratatui::style::Modifier;
let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
match value { match value {
mlua::Value::Nil => { mlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {
ud.borrow::<Self>()?.$($field).+.bg.map($crate::Color).into_lua(lua) me.fg.map($crate::Color).into_lua(lua)
}
mlua::Value::Nil | mlua::Value::Boolean(_) => {
me.bg.map($crate::Color).into_lua(lua)
} }
_ => { _ => {
ud.borrow_mut::<Self>()?.$($field).+.bg = Some($crate::Color::try_from(value)?.0); me.bg = Some($crate::Color::try_from(value)?.0);
ud.into_lua(lua) ud.into_lua(lua)
} }
} }
}); });
$methods.add_function_mut("bold", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("bold", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::BOLD; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::BOLD);
} else {
*me = me.add_modifier(ratatui::style::Modifier::BOLD);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("dim", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("dim", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::DIM; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::DIM);
} else {
*me = me.add_modifier(ratatui::style::Modifier::DIM);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("italic", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("italic", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::ITALIC; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::ITALIC);
} else {
*me = me.add_modifier(ratatui::style::Modifier::ITALIC);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("underline", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("underline", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::UNDERLINED; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::UNDERLINED);
} else {
*me = me.add_modifier(ratatui::style::Modifier::UNDERLINED);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("blink", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("blink", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::SLOW_BLINK; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::SLOW_BLINK);
} else {
*me = me.add_modifier(ratatui::style::Modifier::SLOW_BLINK);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("blink_rapid", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("blink_rapid", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::RAPID_BLINK; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::RAPID_BLINK);
} else {
*me = me.add_modifier(ratatui::style::Modifier::RAPID_BLINK);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("reverse", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("reverse", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::REVERSED; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::REVERSED);
} else {
*me = me.add_modifier(ratatui::style::Modifier::REVERSED);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("hidden", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("hidden", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::HIDDEN; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::HIDDEN);
} else {
*me = me.add_modifier(ratatui::style::Modifier::HIDDEN);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("crossed", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("crossed", |_, (ud, remove): (mlua::AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::CROSSED_OUT; let me = &mut ud.borrow_mut::<Self>()?.$($field).+;
if remove {
*me = me.remove_modifier(ratatui::style::Modifier::CROSSED_OUT);
} else {
*me = me.add_modifier(ratatui::style::Modifier::CROSSED_OUT);
}
Ok(ud) Ok(ud)
}); });
$methods.add_function_mut("reset", |_, ud: mlua::AnyUserData| { $methods.add_function_mut("reset", |_, ud: mlua::AnyUserData| {
ud.borrow_mut::<Self>()?.$($field).+.add_modifier = ratatui::style::Modifier::empty(); ud.borrow_mut::<Self>()?.$($field).+ = ratatui::style::Style::reset();
Ok(ud) Ok(ud)
}); });
}; };
@ -151,14 +208,19 @@ macro_rules! impl_file_fields {
($fields:ident) => { ($fields:ident) => {
$crate::cached_field!($fields, cha, |_, me| Ok($crate::Cha(me.cha))); $crate::cached_field!($fields, cha, |_, me| Ok($crate::Cha(me.cha)));
$crate::cached_field!($fields, url, |_, me| Ok($crate::Url::new(me.url_owned()))); $crate::cached_field!($fields, url, |_, me| Ok($crate::Url::new(me.url_owned())));
$crate::cached_field!($fields, link_to, |_, me| Ok(me.link_to.clone().map($crate::Url::new))); $crate::cached_field!($fields, link_to, |_, me| Ok(me.link_to.as_ref().map($crate::Path::new)));
$crate::cached_field!($fields, name, |lua, me| { $crate::cached_field!($fields, name, |lua, me| {
me.name().map(|s| lua.create_string(s.as_encoded_bytes())).transpose() me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
$crate::cached_field!($fields, path, |_, me| {
use yazi_fs::FsUrl;
use yazi_shared::url::AsUrl;
Ok($crate::Path::new(me.url.as_url().unified_path()))
}); });
$crate::cached_field!($fields, cache, |_, me| { $crate::cached_field!($fields, cache, |_, me| {
use yazi_fs::FsUrl; use yazi_fs::FsUrl;
Ok(me.url.cache().map($crate::Url::new)) Ok(me.url.cache().map($crate::Path::new))
}); });
}; };
} }

136
yazi-binding/src/path.rs Normal file
View file

@ -0,0 +1,136 @@
use std::ops::Deref;
use mlua::{ExternalError, ExternalResult, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_shared::{path::{PathBufDyn, PathLike, StripPrefixError}, strand::{AsStrand, Strand, StrandCow}};
use crate::cached_field;
pub type PathRef = UserDataRef<Path>;
pub struct Path {
inner: PathBufDyn,
v_ext: Option<Value>,
v_name: Option<Value>,
v_parent: Option<Value>,
v_stem: Option<Value>,
}
impl Deref for Path {
type Target = PathBufDyn;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<Path> for PathBufDyn {
fn from(value: Path) -> Self { value.inner }
}
impl AsStrand for Path {
fn as_strand(&self) -> Strand<'_> { self.inner.as_strand() }
}
impl AsStrand for &Path {
fn as_strand(&self) -> Strand<'_> { self.inner.as_strand() }
}
impl Path {
pub fn new(path: impl Into<PathBufDyn>) -> Self {
Self {
inner: path.into(),
v_ext: None,
v_name: None,
v_parent: None,
v_stem: None,
}
}
fn ends_with(&self, child: Value) -> mlua::Result<bool> {
match child {
Value::String(s) => {
self.try_ends_with(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()
}
Value::UserData(ud) => self.try_ends_with(&*ud.borrow::<Self>()?).into_lua_err(),
_ => Err("must be a string or Path".into_lua_err())?,
}
}
fn join(&self, other: Value) -> mlua::Result<Self> {
Ok(Self::new(match other {
Value::String(s) => {
self.try_join(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()?
}
Value::UserData(ref ud) => {
let path = ud.borrow::<Self>()?;
self.try_join(&*path).into_lua_err()?
}
_ => Err("must be a string or Path".into_lua_err())?,
}))
}
fn starts_with(&self, base: Value) -> mlua::Result<bool> {
match base {
Value::String(s) => {
self.try_starts_with(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()
}
Value::UserData(ud) => self.try_starts_with(&*ud.borrow::<Self>()?).into_lua_err(),
_ => Err("must be a string or Path".into_lua_err())?,
}
}
fn strip_prefix(&self, base: Value) -> mlua::Result<Option<Self>> {
let strip = match base {
Value::String(s) => self.try_strip_prefix(StrandCow::with(self.kind(), &*s.as_bytes())?),
Value::UserData(ud) => self.try_strip_prefix(&*ud.borrow::<Self>()?),
_ => Err("must be a string or Path".into_lua_err())?,
};
Ok(match strip {
Ok(p) => Some(Self::new(p)),
Err(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None,
Err(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?,
})
}
}
impl FromLua for Path {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value {
Value::UserData(ud) => ud.take()?,
_ => Err("Expected a Path".into_lua_err())?,
})
}
}
impl UserData for Path {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, ext, |lua, me| {
me.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, name, |lua, me| {
me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));
cached_field!(fields, stem, |lua, me| {
me.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
fields.add_field_method_get("is_absolute", |_, me| Ok(me.is_absolute()));
fields.add_field_method_get("has_root", |_, me| Ok(me.has_root()));
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("ends_with", |_, me, child: Value| me.ends_with(child));
methods.add_method("join", |_, me, other: Value| me.join(other));
methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base));
methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base));
methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| {
lua.create_string([lhs.encoded_bytes(), &rhs.as_bytes()].concat())
});
methods.add_meta_method(MetaMethod::Eq, |_, me, other: PathRef| Ok(me.inner == other.inner));
methods
.add_meta_method(MetaMethod::ToString, |lua, me, ()| lua.create_string(me.encoded_bytes()));
}
}

View file

@ -1,42 +1,51 @@
use std::{mem, ops::Deref}; use std::{mem, ops::Deref};
use futures::{FutureExt, future::BoxFuture};
use mlua::{UserData, prelude::LuaUserDataMethods}; use mlua::{UserData, prelude::LuaUserDataMethods};
use tokio::sync::SemaphorePermit; use tokio::sync::SemaphorePermit;
pub type PermitRef<F> = mlua::UserDataRef<Permit<F>>; pub type PermitRef = mlua::UserDataRef<Permit>;
pub struct Permit<F: FnOnce()> { pub struct Permit {
inner: Option<SemaphorePermit<'static>>, inner: Option<SemaphorePermit<'static>>,
destruct: Option<F>, destruct: Option<BoxFuture<'static, ()>>,
} }
impl<F: FnOnce()> Deref for Permit<F> { impl Deref for Permit {
type Target = Option<SemaphorePermit<'static>>; type Target = Option<SemaphorePermit<'static>>;
fn deref(&self) -> &Self::Target { &self.inner } fn deref(&self) -> &Self::Target { &self.inner }
} }
impl<F: FnOnce()> Permit<F> { impl Permit {
pub fn new(inner: SemaphorePermit<'static>, f: F) -> Self { pub fn new<F>(inner: SemaphorePermit<'static>, f: F) -> Self
Self { inner: Some(inner), destruct: Some(f) } where
F: Future<Output = ()> + 'static + Send,
{
Self { inner: Some(inner), destruct: Some(f.boxed()) }
} }
fn dropping(&mut self) { fn dropping(&mut self) -> impl Future<Output = ()> + 'static {
if let Some(f) = self.destruct.take() { let inner = self.inner.take();
f(); let destruct = self.destruct.take();
}
if let Some(p) = self.inner.take() { async move {
mem::drop(p); if let Some(f) = destruct {
f.await;
}
if let Some(p) = inner {
mem::drop(p);
}
} }
} }
} }
impl<F: FnOnce()> Drop for Permit<F> { impl Drop for Permit {
fn drop(&mut self) { self.dropping(); } fn drop(&mut self) { tokio::spawn(self.dropping()); }
} }
impl<F: FnOnce()> UserData for Permit<F> { impl UserData for Permit {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("drop", |_, me, ()| Ok(me.dropping())); methods.add_async_method_mut("drop", |_, mut me, ()| async move { Ok(me.dropping().await) });
} }
} }

View file

@ -3,10 +3,11 @@ use std::collections::VecDeque;
use hashbrown::HashMap; use hashbrown::HashMap;
use mlua::Function; use mlua::Function;
#[derive(Debug, Default)] #[derive(Debug)]
pub struct Runtime { pub struct Runtime {
frames: VecDeque<RuntimeFrame>, frames: VecDeque<RuntimeFrame>,
blocks: HashMap<String, Vec<Function>>, blocks: HashMap<String, Vec<Function>>,
pub initing: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -16,10 +17,13 @@ struct RuntimeFrame {
} }
impl Runtime { impl Runtime {
pub fn new(id: &str) -> Self { pub fn new() -> Self { Self { frames: <_>::default(), blocks: <_>::default(), initing: true } }
pub fn new_isolate(id: &str) -> Self {
Self { Self {
frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]), frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]),
..Default::default() blocks: <_>::default(),
initing: false,
} }
} }
@ -49,7 +53,7 @@ impl Runtime {
if let Some(v) = self.blocks.get_mut(&cur.id) { if let Some(v) = self.blocks.get_mut(&cur.id) {
v.push(f); v.push(f);
} else { } else {
self.blocks.insert(cur.id.to_owned(), vec![f]); self.blocks.insert(cur.id.clone(), vec![f]);
} }
true true
} }

View file

@ -4,7 +4,7 @@ use mlua::{UserData, UserDataFields, Value};
use yazi_fs::FsScheme; use yazi_fs::FsScheme;
use yazi_shared::scheme::SchemeLike; use yazi_shared::scheme::SchemeLike;
use crate::{Url, cached_field}; use crate::{Path, cached_field};
pub struct Scheme { pub struct Scheme {
inner: yazi_shared::scheme::Scheme, inner: yazi_shared::scheme::Scheme,
@ -20,15 +20,15 @@ impl Deref for Scheme {
} }
impl Scheme { impl Scheme {
pub fn new(scheme: &yazi_shared::scheme::Scheme) -> Self { pub fn new(scheme: impl Into<yazi_shared::scheme::Scheme>) -> Self {
Self { inner: scheme.clone(), v_kind: None, v_cache: None } Self { inner: scheme.into(), v_kind: None, v_cache: None }
} }
} }
impl UserData for Scheme { impl UserData for Scheme {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, kind, |_, me| Ok(me.kind())); cached_field!(fields, kind, |_, me| Ok(me.kind().as_str()));
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Url::new))); cached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));
fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual())); fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual()));
} }

View file

@ -1,4 +1,6 @@
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use mlua::{AnyUserData, ExternalError, IntoLua, Lua, LuaSerdeExt, MetaMethod, Table, UserData, UserDataMethods, Value};
use crate::SER_OPT;
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub struct Style(pub ratatui::style::Style); pub struct Style(pub ratatui::style::Style);
@ -34,6 +36,9 @@ impl UserData for Style {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_style_shorthands!(methods, 0); crate::impl_style_shorthands!(methods, 0);
methods
.add_method("raw", |lua, me, ()| lua.to_value_with(&yazi_config::Style::from(me.0), SER_OPT));
methods.add_function_mut("patch", |_, (ud, value): (AnyUserData, Value)| { methods.add_function_mut("patch", |_, (ud, value): (AnyUserData, Value)| {
{ {
let mut me = ud.borrow_mut::<Self>()?; let mut me = ud.borrow_mut::<Self>()?;

View file

@ -1,16 +1,19 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_fs::{FsHash64, FsHash128}; use yazi_fs::{FsHash64, FsHash128, FsUrl};
use yazi_shared::{IntoOsStr, scheme::SchemeLike, url::{AsUrl, UrlCow, UrlLike}}; use yazi_shared::{path::{PathLike, StripPrefixError}, scheme::SchemeCow, strand::{StrandLike, ToStrand}, url::{AsUrl, UrlCow, UrlLike}};
use crate::{Scheme, cached_field, deprecate}; use crate::{Path, Scheme, cached_field, deprecate};
pub type UrlRef = UserDataRef<Url>; pub type UrlRef = UserDataRef<Url>;
const EXPECTED: &str = "expected a string, Url, or Path";
pub struct Url { pub struct Url {
inner: yazi_shared::url::UrlBuf, inner: yazi_shared::url::UrlBuf,
v_path: Option<Value>,
v_name: Option<Value>, v_name: Option<Value>,
v_stem: Option<Value>, v_stem: Option<Value>,
v_ext: Option<Value>, v_ext: Option<Value>,
@ -20,6 +23,8 @@ pub struct Url {
v_scheme: Option<Value>, v_scheme: Option<Value>,
v_domain: Option<Value>, v_domain: Option<Value>,
v_cache: Option<Value>,
} }
impl Deref for Url { impl Deref for Url {
@ -28,10 +33,6 @@ impl Deref for Url {
fn deref(&self) -> &Self::Target { &self.inner } fn deref(&self) -> &Self::Target { &self.inner }
} }
impl AsRef<yazi_shared::url::UrlBuf> for Url {
fn as_ref(&self) -> &yazi_shared::url::UrlBuf { &self.inner }
}
impl AsUrl for Url { impl AsUrl for Url {
fn as_url(&self) -> yazi_shared::url::Url<'_> { self.inner.as_url() } fn as_url(&self) -> yazi_shared::url::Url<'_> { self.inner.as_url() }
} }
@ -48,12 +49,14 @@ impl From<Url> for yazi_shared::url::UrlBufCov {
fn from(value: Url) -> Self { Self(value.inner) } fn from(value: Url) -> Self { Self(value.inner) }
} }
impl From<Url> for yazi_shared::url::UrlCow<'_> {
fn from(value: Url) -> Self { value.inner.into() }
}
impl TryFrom<&[u8]> for Url { impl TryFrom<&[u8]> for Url {
type Error = mlua::Error; type Error = mlua::Error;
fn try_from(value: &[u8]) -> mlua::Result<Self> { fn try_from(value: &[u8]) -> mlua::Result<Self> { Ok(Self::new(UrlCow::try_from(value)?)) }
Ok(Self::new(UrlCow::try_from(value)?.into_owned()))
}
} }
impl Url { impl Url {
@ -61,6 +64,7 @@ impl Url {
Self { Self {
inner: url.into(), inner: url.into(),
v_path: None,
v_name: None, v_name: None,
v_stem: None, v_stem: None,
v_ext: None, v_ext: None,
@ -70,6 +74,8 @@ impl Url {
v_scheme: None, v_scheme: None,
v_domain: None, v_domain: None,
v_cache: None,
} }
} }
@ -78,13 +84,82 @@ impl Url {
"Url", "Url",
lua.create_function(|_, value: Value| { lua.create_function(|_, value: Value| {
Ok(match value { Ok(match value {
Value::String(s) => Self::try_from(s.as_bytes().as_ref())?, Value::String(s) => Self::try_from(&*s.as_bytes())?,
Value::UserData(ud) => Self::new(ud.borrow::<Self>()?.inner.clone()), Value::UserData(ud) => {
_ => Err("Expected a string or a Url".into_lua_err())?, if let Ok(url) = ud.borrow::<Self>() {
Self::new(&url.inner)
} else if let Ok(path) = ud.borrow::<Path>() {
Self::new(path.as_os().into_lua_err()?)
} else {
Err(EXPECTED.into_lua_err())?
}
}
_ => Err(EXPECTED.into_lua_err())?,
}) })
})?, })?,
) )
} }
fn ends_with(&self, child: Value) -> mlua::Result<bool> {
match child {
Value::String(s) => self.try_ends_with(UrlCow::try_from(&*s.as_bytes())?).into_lua_err(),
Value::UserData(ud) => self.try_ends_with(&*ud.borrow::<Self>()?).into_lua_err(),
_ => Err("must be a string or Url".into_lua_err())?,
}
}
fn hash(&self, long: Option<bool>) -> mlua::Result<String> {
Ok(if long.unwrap_or(false) {
format!("{:x}", self.hash_u128())
} else {
format!("{:x}", self.hash_u64())
})
}
fn join(&self, lua: &Lua, other: Value) -> mlua::Result<Value> {
match other {
Value::String(s) => {
let b = s.as_bytes();
let (scheme, path) = SchemeCow::parse(&b)?;
if scheme == self.scheme() {
Self::new(self.try_join(path).into_lua_err()?).into_lua(lua)
} else {
Self::new(UrlCow::try_from((scheme, path))?).into_lua(lua)
}
}
Value::UserData(ref ud) => {
let url = ud.borrow::<Self>()?;
if url.scheme() == self.scheme() {
Self::new(self.try_join(url.loc()).into_lua_err()?).into_lua(lua)
} else {
Ok(other)
}
}
_ => Err("must be a string or Url".into_lua_err())?,
}
}
fn starts_with(&self, base: Value) -> mlua::Result<bool> {
match base {
Value::String(s) => self.try_starts_with(UrlCow::try_from(&*s.as_bytes())?).into_lua_err(),
Value::UserData(ud) => self.try_starts_with(&*ud.borrow::<Self>()?).into_lua_err(),
_ => Err("must be a string or Url".into_lua_err())?,
}
}
fn strip_prefix(&self, base: Value) -> mlua::Result<Option<Path>> {
let strip = match base {
Value::String(s) => self.try_strip_prefix(UrlCow::try_from(&*s.as_bytes())?),
Value::UserData(ud) => self.try_strip_prefix(&*ud.borrow::<Self>()?),
_ => Err("must be a string or Url".into_lua_err())?,
};
Ok(match strip {
Ok(p) => Some(Path::new(p)),
Err(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None,
Err(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?,
})
}
} }
impl FromLua for Url { impl FromLua for Url {
@ -98,27 +173,32 @@ impl FromLua for Url {
impl UserData for Url { impl UserData for Url {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, path, |_, me| Ok(Path::new(me.loc())));
cached_field!(fields, name, |lua, me| { cached_field!(fields, name, |lua, me| {
me.name().map(|s| lua.create_string(s.as_encoded_bytes())).transpose() me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()
}); });
cached_field!(fields, stem, |lua, me| { cached_field!(fields, stem, |lua, me| {
me.stem().map(|s| lua.create_string(s.as_encoded_bytes())).transpose() me.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose()
}); });
cached_field!(fields, ext, |lua, me| { cached_field!(fields, ext, |lua, me| {
me.ext().map(|s| lua.create_string(s.as_encoded_bytes())).transpose() me.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, urn, |_, me| Ok(Path::new(me.urn())));
cached_field!(fields, base, |_, me| {
Ok(Some(me.base()).filter(|u| !u.loc().is_empty()).map(Self::new))
}); });
cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new))); cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));
cached_field!(fields, urn, |_, me| Ok(super::Path::new(me.urn())));
cached_field!(fields, base, |_, me| Ok(me.base().map(Self::new)));
cached_field!(fields, scheme, |_, me| Ok(Scheme::new(&me.scheme))); cached_field!(fields, scheme, |_, me| Ok(Scheme::new(me.scheme())));
cached_field!(fields, domain, |lua, me| { cached_field!(fields, domain, |lua, me| {
me.scheme.domain().map(|s| lua.create_string(s)).transpose() me.scheme().domain().map(|s| lua.create_string(s)).transpose()
}); });
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));
fields.add_field_method_get("frag", |lua, me| { fields.add_field_method_get("frag", |lua, me| {
deprecate!(lua, "`frag` property of Url is deprecated and renamed to `domain`, please use the new name instead, in your {}"); deprecate!(lua, "`frag` property of Url is deprecated and renamed to `domain`, please use the new name instead, in your {}");
me.scheme.domain().map(|s| lua.create_string(s)).transpose() me.scheme().domain().map(|s| lua.create_string(s)).transpose()
}); });
fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular())); fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular()));
@ -129,59 +209,23 @@ impl UserData for Url {
} }
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("hash", |_, me, long: Option<bool>| { methods.add_method("ends_with", |_, me, child: Value| me.ends_with(child));
Ok(if long.unwrap_or(false) { methods.add_method("hash", |_, me, long: Option<bool>| me.hash(long));
format!("{:x}", me.hash_u128()) methods.add_method("join", |lua, me, other: Value| me.join(lua, other));
} else { methods.add_method("starts_with", |_, me, base: Value| me.starts_with(base));
format!("{:x}", me.hash_u64()) methods.add_method("strip_prefix", |_, me, base: Value| me.strip_prefix(base));
})
});
methods.add_method("join", |_, me, other: Value| {
Ok(Self::new(match other {
Value::String(s) => me.join(s.as_bytes().into_os_str()?),
Value::UserData(ud) => {
let url = ud.borrow::<Self>()?;
if !me.scheme.covariant(&url.scheme) {
return Err("cannot join Urls with different schemes".into_lua_err());
}
me.join(&url.loc)
}
_ => Err("must be a string or Url".into_lua_err())?,
}))
});
methods.add_method("starts_with", |_, me, base: Value| {
Ok(match base {
Value::String(s) => me.loc.starts_with(s.as_bytes().into_os_str()?),
Value::UserData(ud) => me.starts_with(&*ud.borrow::<Self>()?),
_ => Err("must be a string or Url".into_lua_err())?,
})
});
methods.add_method("ends_with", |_, me, child: Value| {
Ok(match child {
Value::String(s) => me.loc.ends_with(s.as_bytes().into_os_str()?),
Value::UserData(ud) => me.ends_with(&*ud.borrow::<Self>()?),
_ => Err("must be a string or Url".into_lua_err())?,
})
});
methods.add_method("strip_prefix", |_, me, base: Value| {
let path = match base {
Value::String(s) => me.loc().strip_prefix(&s.as_bytes().into_os_str()?).map(Into::into),
Value::UserData(ud) => me.strip_prefix(&*ud.borrow::<Self>()?),
_ => Err("must be a string or Url".into_lua_err())?,
};
Ok(path.map(Self::new)) // TODO: return `Path` instead of `Url`
});
methods.add_function_mut("into_search", |_, (ud, domain): (AnyUserData, mlua::String)| { methods.add_function_mut("into_search", |_, (ud, domain): (AnyUserData, mlua::String)| {
Ok(Self::new(ud.take::<Self>()?.inner.into_search(domain.to_str()?))) let url = ud.take::<Self>()?.inner.into_search(domain.to_str()?).into_lua_err()?;
Ok(Self::new(url))
}); });
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner)); methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner));
methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| { methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {
lua.create_string(me.os_str().as_encoded_bytes()) lua.create_string(me.to_strand().encoded_bytes())
}); });
methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| { methods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| {
lua.create_string([lhs.os_str().as_encoded_bytes(), &rhs.as_bytes()].concat()) lua.create_string([lhs.to_strand().encoded_bytes(), &rhs.as_bytes()].concat())
}); });
} }
} }

View file

@ -1,27 +0,0 @@
use std::ops::Deref;
use mlua::{ExternalError, FromLua, Lua, UserData, Value};
use yazi_shared::path::PathBufDyn;
pub struct Path(pub PathBufDyn);
impl Deref for Path {
type Target = PathBufDyn;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl Path {
pub fn new(urn: impl Into<PathBufDyn>) -> Self { Self(urn.into()) }
}
impl FromLua for Path {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value {
Value::UserData(ud) => ud.take()?,
_ => Err("Expected a Path".into_lua_err())?,
})
}
}
impl UserData for Path {}

View file

@ -1,4 +1,7 @@
use mlua::{IntoLua, Lua, Table, Value}; use mlua::{IntoLua, Lua, SerializeOptions, Table, Value};
pub const SER_OPT: SerializeOptions =
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
pub fn get_metatable(lua: &Lua, value: impl IntoLua) -> mlua::Result<Table> { pub fn get_metatable(lua: &Lua, value: impl IntoLua) -> mlua::Result<Table> {
let (_, mt): (Value, Table) = unsafe { let (_, mt): (Value, Table) = unsafe {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "yazi-boot" name = "yazi-boot"
version = "25.9.15" version = "26.1.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ] authors = [ "sxyazi <sxyazi@gmail.com>" ]
@ -8,27 +8,29 @@ description = "Yazi bootstrapper"
homepage = "https://yazi-rs.github.io" homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
[lints]
workspace = true
[dependencies] [dependencies]
yazi-adapter = { path = "../yazi-adapter", version = "25.9.15" } yazi-adapter = { path = "../yazi-adapter", version = "26.1.4" }
yazi-config = { path = "../yazi-config", version = "25.9.15" } yazi-config = { path = "../yazi-config", version = "26.1.4" }
yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } yazi-vfs = { path = "../yazi-vfs", version = "26.1.4" }
# External dependencies # External dependencies
clap = { workspace = true } clap = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
serde = { workspace = true }
[build-dependencies] [build-dependencies]
yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
# External dependencies # External dependencies
clap = { workspace = true } clap = { workspace = true }
clap_complete = "4.5.60" clap_complete = "4.5.64"
clap_complete_fig = "4.5.2" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.10" clap_complete_nushell = "4.5.10"
vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] } vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] }

5
yazi-boot/README.md Normal file
View file

@ -0,0 +1,5 @@
# yazi-boot
This crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.
[source]: https://github.com/sxyazi/yazi

View file

@ -2,7 +2,8 @@ use std::{env, ffi::OsStr, fmt::Write, path::Path};
use regex::Regex; use regex::Regex;
use yazi_adapter::Mux; use yazi_adapter::Mux;
use yazi_config::YAZI; use yazi_config::{THEME, YAZI};
use yazi_fs::Xdg;
use yazi_shared::timestamp_us; use yazi_shared::timestamp_us;
use super::Actions; use super::Actions;
@ -19,12 +20,20 @@ impl Actions {
writeln!(s, "\nYa")?; writeln!(s, "\nYa")?;
writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?; writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?;
writeln!(s, "\nConfig")?;
writeln!(s, " Yazi : {}", Self::config_state("yazi"))?;
writeln!(s, " Keymap : {}", Self::config_state("keymap"))?;
writeln!(s, " Theme : {}", Self::config_state("theme"))?;
writeln!(s, " VFS : {}", Self::config_state("vfs"))?;
writeln!(s, " Package : {}", Self::config_state("package"))?;
writeln!(s, " Dark/light flavor: {:?} / {:?}", THEME.flavor.dark, THEME.flavor.light)?;
writeln!(s, "\nEmulator")?; writeln!(s, "\nEmulator")?;
writeln!(s, " TERM : {:?}", env::var_os("TERM"))?; writeln!(s, " TERM : {:?}", env::var_os("TERM"))?;
writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?; writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?;
writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?; writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?;
writeln!(s, " Brand.from_env : {:?}", yazi_adapter::Brand::from_env())?; writeln!(s, " Brand.from_env : {:?}", yazi_adapter::Brand::from_env())?;
writeln!(s, " Emulator.detect : {:?}", yazi_adapter::EMULATOR)?; writeln!(s, " Emulator.detect : {:?}", &*yazi_adapter::EMULATOR)?;
writeln!(s, "\nAdapter")?; writeln!(s, "\nAdapter")?;
writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?; writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?;
@ -115,6 +124,16 @@ impl Actions {
Ok(s) Ok(s)
} }
fn config_state(name: &str) -> String {
let p = Xdg::config_dir().join(format!("{name}.toml"));
match std::fs::read_to_string(&p) {
Ok(s) if s.is_empty() => format!("{} (empty)", p.display()),
Ok(s) if s.trim().is_empty() => format!("{} (whitespaces)", p.display()),
Ok(s) => format!("{} ({} chars)", p.display(), s.chars().count()),
Err(e) => format!("{} ({e})", p.display()),
}
}
fn process_output(name: impl AsRef<OsStr>, arg: impl AsRef<OsStr>) -> String { fn process_output(name: impl AsRef<OsStr>, arg: impl AsRef<OsStr>) -> String {
match std::process::Command::new(&name).arg(arg).output() { match std::process::Command::new(&name).arg(arg).output() {
Ok(out) if out.status.success() => { Ok(out) if out.status.success() => {

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