mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Merge branch 'main' into ignores
This commit is contained in:
commit
712823cf5b
470 changed files with 13228 additions and 7108 deletions
5
.github/workflows/cachix.yml
vendored
5
.github/workflows/cachix.yml
vendored
|
|
@ -4,6 +4,9 @@ on:
|
|||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Flake
|
||||
|
|
@ -12,7 +15,7 @@ jobs:
|
|||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
|
|
|||
9
.github/workflows/check.yml
vendored
9
.github/workflows/check.yml
vendored
|
|
@ -10,11 +10,14 @@ env:
|
|||
SCCACHE_GHA_ENABLED: true
|
||||
RUSTC_WRAPPER: sccache
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -31,7 +34,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -47,7 +50,7 @@ jobs:
|
|||
stylua:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: JohnnyMorganz/stylua-action@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
30
.github/workflows/draft.yml
vendored
30
.github/workflows/draft.yml
vendored
|
|
@ -9,6 +9,9 @@ on:
|
|||
env:
|
||||
SCCACHE_GHA_ENABLED: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-unix:
|
||||
strategy:
|
||||
|
|
@ -39,7 +42,7 @@ jobs:
|
|||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
|
||||
CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install gcc
|
||||
if: matrix.gcc != ''
|
||||
|
|
@ -56,7 +59,7 @@ jobs:
|
|||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
|
@ -78,7 +81,7 @@ jobs:
|
|||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -102,7 +105,7 @@ jobs:
|
|||
Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath "${env:TARGET_NAME}.zip"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: yazi-${{ matrix.target }}.zip
|
||||
|
|
@ -117,7 +120,7 @@ jobs:
|
|||
container:
|
||||
image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -130,7 +133,7 @@ jobs:
|
|||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
|
@ -147,12 +150,12 @@ jobs:
|
|||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup LXD
|
||||
uses: canonical/setup-lxd@v0.1.3
|
||||
uses: canonical/setup-lxd@v1
|
||||
|
||||
- name: Setup snapcraft
|
||||
run: sudo snap install --classic snapcraft
|
||||
|
|
@ -164,7 +167,7 @@ jobs:
|
|||
run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: snap-${{ matrix.arch }}
|
||||
path: yazi-${{ matrix.arch }}.snap
|
||||
|
|
@ -173,7 +176,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [build-snap]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: snap-*
|
||||
merge-multiple: true
|
||||
|
|
@ -202,7 +205,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [build-unix, build-windows, build-musl, build-snap]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
@ -227,11 +230,12 @@ jobs:
|
|||
echo 'NIGHTLY_BODY<<EOF' >> $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 "Nightly changelog: https://github.com/sxyazi/yazi/blob/main/CHANGELOG.md#unreleased" >> $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:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
|
|||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
pull-requests: write
|
||||
discussions: write
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
- uses: dessant/lock-threads@v6
|
||||
with:
|
||||
issue-inactive-days: "30"
|
||||
issue-comment: >
|
||||
|
|
|
|||
12
.github/workflows/publish.yml
vendored
12
.github/workflows/publish.yml
vendored
|
|
@ -4,11 +4,14 @@ on:
|
|||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
winget:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
|
|
@ -19,6 +22,13 @@ jobs:
|
|||
installers-regex: 'yazi-(x86_64|aarch64)-pc-windows-msvc\.zip$'
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
snapcraft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Promote snap to stable
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||
|
|
|
|||
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
|
|
@ -11,6 +11,9 @@ env:
|
|||
RUSTC_WRAPPER: sccache
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
|
|
@ -18,7 +21,7 @@ jobs:
|
|||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup toolchain install stable --profile minimal
|
||||
|
|
|
|||
2
.github/workflows/validate-form.yml
vendored
2
.github/workflows/validate-form.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
|
|
|||
54
CHANGELOG.md
54
CHANGELOG.md
|
|
@ -12,16 +12,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [v26.1.4]
|
||||
|
||||
### 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])
|
||||
- Shell formatting ([#3232])
|
||||
- Multi-entry support for plugin system ([#3154])
|
||||
- Zoom in or out of the preview image ([#2864])
|
||||
- Improve the UX of the pick and input components ([#2906], [#2935])
|
||||
- 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])
|
||||
- Rounded corners for indicator bar ([#3419])
|
||||
- New `bulk_rename` command always renames files with the editor ([#2984])
|
||||
- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037])
|
||||
- New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])
|
||||
|
|
@ -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])
|
||||
- Support `.deb` packages ([#2807], [#3128], [#3209])
|
||||
- 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])
|
||||
- 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])
|
||||
- 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])
|
||||
- Offload mimetype fetching on opening files to the task scheduler ([#3141])
|
||||
- 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 `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])
|
||||
- Return `Path` instead of `Url` from `Url:strip_prefix()` and `File.link_to` to enforce type safety ([#3361], [#3385])
|
||||
- Use `body` instead of the term `content` in confirmations ([#2921])
|
||||
- Use `u16` instead of `u32` as the type of `max_width` and `max_height` options to avoid memory exhaustion ([#3313])
|
||||
- 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
|
||||
|
||||
- User-prepended open rules do not override presets ([#3360])
|
||||
- Respect user's system media opener instead of hardcoding `mpv` ([#2959])
|
||||
- Incorrect `$0` and `$@` parameters in `shell` command under empty directories ([#3225])
|
||||
- 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])
|
||||
- Check compatibility when reusing previewer bytecode cache ([#3190])
|
||||
- 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])
|
||||
- Fallback to `PollWatcher` for file changes watching on NetBSD ([#2941])
|
||||
- Generate unique image IDs for Kgp to tolerate tmux ([#3038])
|
||||
|
||||
### Improved
|
||||
|
||||
- Make copy, cut, delete, link, hardlink, download, and upload tasks immediately cancellable ([#3429])
|
||||
- Make preload tasks discardable ([#2875])
|
||||
- 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])
|
||||
- Switch to a higher-performance hash algorithm ([#3083])
|
||||
- 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])
|
||||
- Tab switch wraparound ([#160])
|
||||
- Highlight matched keywords in find mode ([#211])
|
||||
- Customable main UI border styles ([#278])
|
||||
- Customizable main UI border styles ([#278])
|
||||
- `<BackTab>` key notion ([#209])
|
||||
- Use of environment variables in `cd` paths ([#241])
|
||||
- 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.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.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
|
||||
[#5]: https://github.com/sxyazi/yazi/pull/5
|
||||
[#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
|
||||
[#3313]: https://github.com/sxyazi/yazi/pull/3313
|
||||
[#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
1953
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
50
Cargo.toml
50
Cargo.toml
|
|
@ -3,6 +3,9 @@ resolver = "2"
|
|||
members = [ "yazi-*" ]
|
||||
default-members = [ "yazi-fm", "yazi-cli" ]
|
||||
|
||||
[profile.dev]
|
||||
debug = "line-tables-only"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
|
@ -19,46 +22,57 @@ codegen-units = 256
|
|||
incremental = true
|
||||
lto = false
|
||||
|
||||
[profile.dev.package."*"]
|
||||
debug = false
|
||||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "7.0.0"
|
||||
ansi-to-tui = "8.0.0"
|
||||
anyhow = "1.0.100"
|
||||
base64 = "0.22.1"
|
||||
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"
|
||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
||||
dirs = "6.0.0"
|
||||
foldhash = "0.2.0"
|
||||
futures = "0.3.31"
|
||||
globset = "0.4.18"
|
||||
hashbrown = { version = "0.16.0", features = [ "serde" ] }
|
||||
indexmap = { version = "2.12.0", features = [ "serde" ] }
|
||||
libc = "0.2.177"
|
||||
hashbrown = { version = "0.16.1", features = [ "serde" ] }
|
||||
indexmap = { version = "2.12.1", features = [ "serde" ] }
|
||||
libc = "0.2.179"
|
||||
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"
|
||||
ordered-float = { version = "5.1.0", features = [ "serde" ] }
|
||||
parking_lot = "0.12.5"
|
||||
paste = "1.0.15"
|
||||
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"
|
||||
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"
|
||||
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" ] }
|
||||
tokio = { version = "1.48.0", features = [ "full" ] }
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.49.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.17"
|
||||
toml = { version = "0.9.8" }
|
||||
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
toml = { version = "0.9.10" }
|
||||
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" ] }
|
||||
unicode-width = { version = "0.2.0", default-features = false }
|
||||
uzers = "0.12.1"
|
||||
typed-path = "0.12.0"
|
||||
unicode-width = { version = "0.2.2", default-features = false }
|
||||
uzers = "0.12.2"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
format_push_string = "warn"
|
||||
implicit_clone = "warn"
|
||||
module_inception = "allow"
|
||||
use_self = "warn"
|
||||
format_push_string = "warn"
|
||||
if_same_then_else = "allow"
|
||||
implicit_clone = "warn"
|
||||
len_without_is_empty = "allow"
|
||||
missing_safety_doc = "allow"
|
||||
module_inception = "allow"
|
||||
option_map_unit_fn = "allow"
|
||||
unit_arg = "allow"
|
||||
use_self = "warn"
|
||||
|
|
|
|||
|
|
@ -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 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.
|
||||
- ☁️ **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.
|
||||
- 📦 **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
|
||||
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
|
||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
|
||||
- 🔄 Bulk Renaming, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
|
||||
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, Virtual Filesystem, CSI u, OSC 52
|
||||
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
|
||||
- ... and more!
|
||||
|
||||
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7
|
||||
|
|
|
|||
|
|
@ -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
12
flake.lock
generated
|
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1762286042,
|
||||
"narHash": "sha256-OD5HsZ+sN7VvNucbrjiCz7CHF5zf9gP51YVJvPwYIH8=",
|
||||
"lastModified": 1763806073,
|
||||
"narHash": "sha256-FHsEKDvfWpzdADWj99z7vBk4D716Ujdyveo5+A048aI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "12c1f0253aa9a54fdf8ec8aecaafada64a111e24",
|
||||
"rev": "878e468e02bfabeda08c79250f7ad583037f2227",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -48,11 +48,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762396738,
|
||||
"narHash": "sha256-BarSecuxtzp1boERdABLkkoxQTi6s/V33lJwUbWLrLY=",
|
||||
"lastModified": 1763952169,
|
||||
"narHash": "sha256-+PeDBD8P+NKauH+w7eO/QWCIp8Cx4mCfWnh9sJmy9CM=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "c63598992afd54d215d54f2b764adc0484c2b159",
|
||||
"rev": "ab726555a9a72e6dc80649809147823a813fa95b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -52,10 +52,13 @@
|
|||
};
|
||||
|
||||
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;
|
||||
}
|
||||
)
|
||||
// {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
callPackage,
|
||||
rust-bin,
|
||||
mkShell,
|
||||
yazi,
|
||||
toolchain,
|
||||
nodePackages,
|
||||
yazi-unwrapped,
|
||||
}:
|
||||
let
|
||||
mainPkg = callPackage ./yazi.nix { };
|
||||
in
|
||||
mainPkg.overrideAttrs (oa: {
|
||||
nativeBuildInputs = [
|
||||
(rust-bin.nightly.latest.default.override {
|
||||
|
||||
mkShell {
|
||||
packages = yazi.passthru.runtimePaths ++ [
|
||||
(toolchain.override {
|
||||
extensions = [
|
||||
"rust-src"
|
||||
"rustfmt"
|
||||
|
|
@ -16,10 +16,10 @@ mainPkg.overrideAttrs (oa: {
|
|||
"clippy"
|
||||
];
|
||||
})
|
||||
|
||||
nodePackages.cspell
|
||||
]
|
||||
++ (oa.nativeBuildInputs or [ ]);
|
||||
];
|
||||
|
||||
inputsFrom = [ yazi-unwrapped ];
|
||||
|
||||
env.RUST_BACKTRACE = "1";
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
lib,
|
||||
|
||||
installShellFiles,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
rust-jemalloc-sys,
|
||||
|
||||
imagemagick,
|
||||
|
|
@ -22,7 +22,7 @@ let
|
|||
];
|
||||
};
|
||||
in
|
||||
rustPlatform.buildRustPackage rec {
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "yazi";
|
||||
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
|
||||
done
|
||||
|
||||
installManPage ${finalAttrs.passthru.srcs.man_src}/yazi{.1,-config.5}
|
||||
|
||||
mkdir -p $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 = {
|
||||
description = "Blazing fast terminal file manager written in Rust, based on async I/O";
|
||||
homepage = "https://github.com/sxyazi/yazi";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "yazi";
|
||||
};
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ runCommand yazi-unwrapped.name
|
|||
inherit (yazi-unwrapped) pname version meta;
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
|
||||
passthru.runtimePaths = runtimePaths;
|
||||
}
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
|
|
|
|||
|
|
@ -7,7 +7,14 @@ cd "$SCRIPT_DIR/.."
|
|||
echo "Bumping version: $1"
|
||||
|
||||
TOML_FILES="$(git ls-files '*Cargo.toml')"
|
||||
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/^version .*= .*$/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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
37
scripts/validate-form/package-lock.json
generated
37
scripts/validate-form/package-lock.json
generated
|
|
@ -8,27 +8,37 @@
|
|||
"name": "validate-form",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.1.tgz",
|
||||
"integrity": "sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/http-client": "^2.0.1"
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz",
|
||||
"integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
"@actions/io": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
|
|
@ -57,9 +67,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz",
|
||||
"integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
|
|
@ -85,6 +95,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz",
|
||||
"integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.1.0",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-actor"
|
||||
version = "25.9.15"
|
||||
version = "26.1.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -8,27 +8,30 @@ description = "Yazi actor model"
|
|||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = [ "vendored-lua" ]
|
||||
vendored-lua = [ "mlua/vendored" ]
|
||||
|
||||
[dependencies]
|
||||
yazi-binding = { path = "../yazi-binding", version = "25.9.15" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "25.9.15" }
|
||||
yazi-config = { path = "../yazi-config", version = "25.9.15" }
|
||||
yazi-core = { path = "../yazi-core", version = "25.9.15" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "25.9.15" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "25.9.15" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "25.9.15" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
yazi-term = { path = "../yazi-term", version = "25.9.15" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" }
|
||||
yazi-binding = { path = "../yazi-binding", version = "26.1.4" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "26.1.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.1.4" }
|
||||
yazi-core = { path = "../yazi-core", version = "26.1.4" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "26.1.4" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "26.1.4" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "26.1.4" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "26.1.4" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.1.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.1.4" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.1.4" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.1.4" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.1.4" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
|||
5
yazi-actor/README.md
Normal file
5
yazi-actor/README.md
Normal 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
|
||||
|
|
@ -17,10 +17,11 @@ impl Actor for Close {
|
|||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cmp = &mut cx.core.cmp;
|
||||
if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {
|
||||
return act!(complete, cx.core.input, CompleteOpt { item, _ticket: cmp.ticket });
|
||||
return act!(input:complete, cx, CompleteOpt { item, ticket: cmp.ticket });
|
||||
}
|
||||
|
||||
cmp.caches.clear();
|
||||
cmp.ticket = Default::default();
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::{ffi::OsStr, mem, ops::ControlFlow};
|
||||
use std::{mem, ops::ControlFlow};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
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};
|
||||
|
||||
|
|
@ -29,8 +29,7 @@ impl Actor for Show {
|
|||
succ!();
|
||||
};
|
||||
|
||||
cmp.ticket = opt.ticket;
|
||||
cmp.cands = Self::match_candidates(opt.word.as_os_str(), cache);
|
||||
cmp.cands = Self::match_candidates(opt.word.as_path(), cache);
|
||||
if cmp.cands.is_empty() {
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
|
|
@ -43,16 +42,19 @@ impl Actor for Show {
|
|||
}
|
||||
|
||||
impl Show {
|
||||
fn match_candidates(word: &OsStr, cache: &[CmpItem]) -> Vec<CmpItem> {
|
||||
let smart = !word.as_encoded_bytes().iter().any(|c| c.is_ascii_uppercase());
|
||||
fn match_candidates(word: PathDyn, cache: &[CmpItem]) -> Vec<CmpItem> {
|
||||
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| {
|
||||
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);
|
||||
if exact.len() >= LIMIT {
|
||||
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.
|
||||
fuzzy.push(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, PathBuf}};
|
||||
use std::mem;
|
||||
|
||||
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_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
|
||||
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 crate::{Actor, Ctx};
|
||||
|
|
@ -32,7 +32,7 @@ impl Actor for Trigger {
|
|||
|
||||
if cmp.caches.contains_key(&parent) {
|
||||
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;
|
||||
|
|
@ -42,8 +42,8 @@ impl Actor for Trigger {
|
|||
|
||||
// "/" 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
|
||||
if parent.loc.as_os_str() == "/" {
|
||||
cache.push(CmpItem { name: OsString::new(), is_dir: true });
|
||||
if parent.loc() == "/" {
|
||||
cache.push(CmpItem { name: Default::default(), is_dir: true });
|
||||
}
|
||||
|
||||
while let Ok(Some(ent)) = dir.next().await {
|
||||
|
|
@ -53,9 +53,8 @@ impl Actor for Trigger {
|
|||
}
|
||||
|
||||
if !cache.is_empty() {
|
||||
cache.sort_unstable_by(|a, b| {
|
||||
natsort(a.name.as_encoded_bytes(), b.name.as_encoded_bytes(), false)
|
||||
});
|
||||
cache
|
||||
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false));
|
||||
CmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket });
|
||||
}
|
||||
|
||||
|
|
@ -67,38 +66,47 @@ impl Actor for Trigger {
|
|||
}
|
||||
|
||||
impl Trigger {
|
||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBuf)> {
|
||||
let (scheme, path, ..) = UrlCow::parse(s.as_bytes()).ok()?;
|
||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
||||
let sep = if cfg!(windows) {
|
||||
AnyAsciiChar::new(b"/\\").unwrap()
|
||||
} else {
|
||||
AnyAsciiChar::new(b"/").unwrap()
|
||||
};
|
||||
|
||||
if scheme.is_local() && path.as_os_str() == "~" {
|
||||
return None; // We don't autocomplete a `~`, but `~/`
|
||||
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
||||
if path.is_empty() && !sep.predicate(s.bytes().last()?) {
|
||||
return None; // We don't complete a `sftp://test`, but `sftp://test/`
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const SEP: &[char] = &['/', '\\'];
|
||||
#[cfg(not(windows))]
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
// Scheme
|
||||
let scheme = scheme.zeroed();
|
||||
if scheme.is_local() && path.as_strand() == "~" {
|
||||
return None; // We don't complete a `~`, but `~/`
|
||||
}
|
||||
|
||||
Some(match path.as_os_str().rsplit_once(SEP) {
|
||||
Some((p, c)) if p.is_empty() => {
|
||||
(UrlBuf { loc: MAIN_SEPARATOR_STR.into(), scheme: scheme.into() }, c.into())
|
||||
}
|
||||
Some((p, c)) => (expand_url(UrlBuf { loc: p.into(), scheme: scheme.into() }), c.into()),
|
||||
None => (CWD.load().as_ref().clone(), path.into()),
|
||||
})
|
||||
// Child
|
||||
let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned();
|
||||
|
||||
// Parent
|
||||
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
||||
let abs = if let Some(u) = provider::try_absolute(&url) { u } else { url };
|
||||
let parent = abs.loc().try_strip_suffix(&child).ok()?;
|
||||
|
||||
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yazi_fs::CWD;
|
||||
use yazi_shared::url::UrlLike;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn compare(s: &str, parent: &str, child: &str) {
|
||||
let (mut p, c) = Trigger::split_url(s).unwrap();
|
||||
if let Some(u) = p.strip_prefix(yazi_fs::CWD.load().as_ref()) {
|
||||
p = UrlBuf::from(u);
|
||||
if let Ok(u) = p.try_strip_prefix(yazi_fs::CWD.load().as_ref()) {
|
||||
p = UrlBuf::Regular(u.as_os().unwrap().into());
|
||||
}
|
||||
assert_eq!((p, c.to_str().unwrap()), (parent.parse().unwrap(), child));
|
||||
}
|
||||
|
|
@ -106,14 +114,31 @@ mod tests {
|
|||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_split() {
|
||||
yazi_shared::init_tests();
|
||||
yazi_fs::init();
|
||||
compare("", "", "");
|
||||
|
||||
assert_eq!(Trigger::split_url(""), None);
|
||||
assert_eq!(Trigger::split_url("sftp://test"), None);
|
||||
compare(" ", "", " ");
|
||||
|
||||
compare("/", "/", "");
|
||||
compare("//", "//", "");
|
||||
compare("//", "/", "");
|
||||
compare("///", "/", "");
|
||||
|
||||
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");
|
||||
|
||||
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)]
|
||||
|
|
@ -121,9 +146,11 @@ mod tests {
|
|||
fn test_split() {
|
||||
yazi_fs::init();
|
||||
compare("foo", "", "foo");
|
||||
|
||||
compare(r"foo\", r"foo\", "");
|
||||
compare(r"foo\bar", r"foo\", "bar");
|
||||
compare(r"foo\bar\", r"foo\bar\", "");
|
||||
|
||||
compare(r"C:\", r"C:\", "");
|
||||
compare(r"C:\foo", r"C:\", "foo");
|
||||
compare(r"C:\foo\", r"C:\foo\", "");
|
||||
|
|
|
|||
|
|
@ -118,13 +118,5 @@ impl<'a> Ctx<'a> {
|
|||
pub fn tasks(&self) -> &Tasks { &self.tasks }
|
||||
|
||||
#[inline]
|
||||
pub fn source(&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,
|
||||
}
|
||||
}
|
||||
pub fn source(&self) -> Source { if self.level != 1 { Source::Ind } else { self.source } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use anyhow::Result;
|
||||
use mlua::{ErrorContext, ExternalError, IntoLua, Value};
|
||||
use yazi_binding::runtime_mut;
|
||||
|
|
@ -8,12 +6,10 @@ use yazi_plugin::LUA;
|
|||
|
||||
use crate::{Ctx, lives::Lives};
|
||||
|
||||
pub struct Preflight<'a> {
|
||||
_lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
pub struct Preflight;
|
||||
|
||||
impl<'a> Preflight<'a> {
|
||||
pub fn act(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> {
|
||||
impl Preflight {
|
||||
pub fn act<'a>(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> {
|
||||
let kind = opt.0;
|
||||
let Some(handlers) = LOCAL.read().get(kind.as_ref()).filter(|&m| !m.is_empty()).cloned() else {
|
||||
return Ok(opt.1);
|
||||
|
|
@ -27,10 +23,9 @@ impl<'a> Preflight<'a> {
|
|||
runtime_mut!(LUA)?.pop();
|
||||
|
||||
match result {
|
||||
Ok(Value::Nil) => Err(
|
||||
format!("Cancelled by `{kind}` event handler in `{id}` plugin on preflight")
|
||||
.into_lua_err(),
|
||||
)?,
|
||||
Ok(Value::Nil) => {
|
||||
Err(format!("`{kind}` event cancelled by `{id}` plugin on preflight").into_lua_err())?
|
||||
}
|
||||
Ok(v) => body = v,
|
||||
Err(e) => Err(
|
||||
format!("Failed to run `{kind}` event handler in `{id}` plugin: {e}").into_lua_err(),
|
||||
|
|
|
|||
23
yazi-actor/src/input/complete.rs
Normal file
23
yazi-actor/src/input/complete.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(close escape show);
|
||||
yazi_macro::mod_flat!(close complete escape show);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
#![allow(clippy::if_same_then_else, clippy::option_map_unit_fn)]
|
||||
|
||||
extern crate self as yazi_actor;
|
||||
|
||||
yazi_macro::mod_pub!(cmp confirm core help input lives mgr pick spot tasks which);
|
||||
|
|
|
|||
|
|
@ -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"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
|
||||
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::ops::Deref;
|
||||
use std::{ops::Deref, ptr};
|
||||
|
||||
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_binding::Style;
|
||||
use yazi_binding::{Style, cached_field};
|
||||
use yazi_config::THEME;
|
||||
use yazi_plugin::bindings::Range;
|
||||
use yazi_shared::url::UrlLike;
|
||||
use yazi_shared::{path::AsPath, url::UrlLike};
|
||||
|
||||
use super::Lives;
|
||||
use crate::lives::PtrCell;
|
||||
|
|
@ -19,7 +19,10 @@ pub(super) struct File {
|
|||
v_link_to: Option<Value>,
|
||||
|
||||
v_name: Option<Value>,
|
||||
v_path: Option<Value>,
|
||||
v_cache: Option<Value>,
|
||||
|
||||
v_bare: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for File {
|
||||
|
|
@ -53,7 +56,10 @@ impl File {
|
|||
v_link_to: None,
|
||||
|
||||
v_name: None,
|
||||
v_path: None,
|
||||
v_cache: None,
|
||||
|
||||
v_bare: None,
|
||||
})?;
|
||||
ve.insert(ud.clone());
|
||||
ud
|
||||
|
|
@ -65,11 +71,13 @@ impl File {
|
|||
impl UserData for File {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
yazi_binding::impl_file_fields!(fields);
|
||||
cached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me)));
|
||||
|
||||
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
||||
fields.add_field_method_get("is_hovered", |_, me| Ok(me.idx == me.folder.cursor));
|
||||
fields.add_field_method_get("in_current", |_, me| Ok(ptr::eq(&*me.folder, &me.tab.current)));
|
||||
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() {
|
||||
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();
|
||||
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, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ impl Folder {
|
|||
|
||||
impl UserData for Folder {
|
||||
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, stage, |_, me| Ok(FolderStage::new(me.stage.clone())));
|
||||
cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab));
|
||||
|
|
|
|||
|
|
@ -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_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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ impl Selected {
|
|||
let inner = PtrCell::from(inner);
|
||||
|
||||
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()),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ impl UserData for Tab {
|
|||
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
|
||||
cached_field!(fields, name, |lua, me| {
|
||||
let url = &me.current.url;
|
||||
lua.create_string(url.name().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));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
|
||||
use yazi_binding::cached_field;
|
||||
use yazi_plugin::runtime::SER_OPT;
|
||||
use yazi_binding::{SER_OPT, cached_field};
|
||||
|
||||
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, 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("success", |_, me| Ok(me.prog.success()));
|
||||
fields.add_field_method_get("failed", |_, me| Ok(me.prog.failed()));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
|
||||
use yazi_binding::{cached_field, deprecate};
|
||||
use yazi_plugin::runtime::SER_OPT;
|
||||
use yazi_binding::{SER_OPT, cached_field, deprecate};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
use crate::lives::TaskSnap;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Yanked {
|
|||
Lives::scoped_userdata(Self {
|
||||
inner,
|
||||
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()),
|
||||
))?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ impl Actor for Back {
|
|||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
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!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 crossterm::{execute, style::Print};
|
||||
use hashbrown::HashMap;
|
||||
use scopeguard::defer;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use yazi_binding::Permit;
|
||||
use yazi_config::{YAZI, opener::OpenerRule};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_macro::{err, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER, TasksProxy};
|
||||
use yazi_shared::{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_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
|
@ -47,10 +48,15 @@ impl Actor for BulkRename {
|
|||
.create_new(true)
|
||||
.open(&tmp)
|
||||
.await?
|
||||
.write_all(old.join(OsStr::new("\n")).as_encoded_bytes())
|
||||
.write_all(old.join(Strand::Utf8("\n")).encoded_bytes())
|
||||
.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(
|
||||
cwd.into(),
|
||||
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
|
||||
|
|
@ -60,12 +66,11 @@ impl Actor for BulkRename {
|
|||
)
|
||||
.await;
|
||||
|
||||
let _permit = HIDER.acquire().await.unwrap();
|
||||
defer!(AppProxy::resume());
|
||||
let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
|
||||
AppProxy::stop().await;
|
||||
|
||||
let new: Vec<_> = Local
|
||||
.read_to_string(&tmp)
|
||||
let new: Vec<_> = Local::regular(&tmp)
|
||||
.read_to_string()
|
||||
.await?
|
||||
.lines()
|
||||
.take(old.len())
|
||||
|
|
@ -120,10 +125,12 @@ impl BulkRename {
|
|||
let permit = WATCHER.acquire().await.unwrap();
|
||||
let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));
|
||||
for (o, n) in todo {
|
||||
let (old, new): (UrlBuf, UrlBuf) = (
|
||||
selected[o.0].components().take(root).chain([Component::Normal(&o)]).collect(),
|
||||
selected[n.0].components().take(root).chain([Component::Normal(&n)]).collect(),
|
||||
);
|
||||
let (Ok(old), Ok(new)) =
|
||||
(Self::replace_url(&selected[o.0], root, &o), Self::replace_url(&selected[n.0], root, &n))
|
||||
else {
|
||||
failed.push((o, n, anyhow!("Invalid new or old file name")));
|
||||
continue;
|
||||
};
|
||||
|
||||
if maybe_exists(&new).await && !provider::must_identical(&old, &new).await {
|
||||
failed.push((o, n, anyhow!("Destination already exists")));
|
||||
|
|
@ -137,7 +144,7 @@ impl BulkRename {
|
|||
}
|
||||
|
||||
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));
|
||||
FilesOp::rename(succeeded);
|
||||
}
|
||||
|
|
@ -153,6 +160,10 @@ impl BulkRename {
|
|||
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<()> {
|
||||
let mut stdout = TTY.lockout();
|
||||
terminal_clear(&mut *stdout)?;
|
||||
|
|
@ -209,10 +220,10 @@ impl BulkRename {
|
|||
|
||||
// --- Tuple
|
||||
#[derive(Clone, Debug)]
|
||||
struct Tuple(usize, OsString);
|
||||
struct Tuple(usize, StrandBuf);
|
||||
|
||||
impl Deref for Tuple {
|
||||
type Target = OsStr;
|
||||
type Target = StrandBuf;
|
||||
|
||||
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); }
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for Tuple {
|
||||
fn as_ref(&self) -> &OsStr { &self.1 }
|
||||
impl AsStrand for &Tuple {
|
||||
fn as_strand(&self) -> Strand<'_> { self.1.as_strand() }
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ use tokio::pin;
|
|||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::popup::InputCfg;
|
||||
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_parser::mgr::CdOpt;
|
||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data, errors::InputError, path::PathLike, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::VfsFile;
|
||||
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ impl Actor for Cd {
|
|||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
if opt.interactive {
|
||||
return Self::cd_interactive();
|
||||
return Self::cd_interactive(cx);
|
||||
}
|
||||
|
||||
let tab = cx.tab_mut();
|
||||
|
|
@ -33,18 +33,8 @@ impl Actor for Cd {
|
|||
}
|
||||
|
||||
// Take parent to history
|
||||
if let Some(rep) = tab.parent.take() {
|
||||
tab.history.insert(rep.url.to_owned(), rep);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
if let Some(t) = tab.parent.take() {
|
||||
tab.history.insert(t.url.clone(), t);
|
||||
}
|
||||
|
||||
// Current
|
||||
|
|
@ -60,7 +50,7 @@ impl Actor for Cd {
|
|||
rep.stage = Default::default();
|
||||
}
|
||||
let rep = mem::replace(&mut tab.current, rep);
|
||||
tab.history.insert(rep.url.to_owned(), rep);
|
||||
tab.history.insert(rep.url.clone(), rep);
|
||||
|
||||
// Parent
|
||||
if let Some(parent) = opt.target.parent() {
|
||||
|
|
@ -74,8 +64,9 @@ impl Actor for Cd {
|
|||
tab.parent = Some(parent_folder);
|
||||
}
|
||||
err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
|
||||
act!(mgr:displace, cx)?;
|
||||
act!(mgr:hidden, cx)?;
|
||||
act!(mgr:sort, cx)?;
|
||||
act!(mgr:sort, cx).ok();
|
||||
|
||||
// Apply config excludes if no plugin patterns are set
|
||||
// This ensures config patterns work when gitignore plugin is disabled
|
||||
|
|
@ -86,13 +77,14 @@ impl Actor for Cd {
|
|||
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:refresh, cx)?;
|
||||
act!(mgr:stash, cx, opt).ok();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
||||
impl Cd {
|
||||
fn cd_interactive() -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::cd());
|
||||
fn cd_interactive(cx: &Ctx) -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::cd(cx.cwd().as_url()));
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
|
||||
|
|
@ -102,6 +94,8 @@ impl Cd {
|
|||
match result {
|
||||
Ok(s) => {
|
||||
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 };
|
||||
if file.is_dir() {
|
||||
|
|
@ -109,12 +103,12 @@ impl Cd {
|
|||
}
|
||||
|
||||
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)) => {
|
||||
CmpProxy::trigger(&before, ticket);
|
||||
CmpProxy::trigger(before, ticket);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use std::ffi::OsString;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use yazi_macro::{act, succ};
|
||||
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 crate::{Actor, Ctx};
|
||||
|
|
@ -18,7 +16,7 @@ impl Actor for Copy {
|
|||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
let mut s = OsString::new();
|
||||
let mut s = Vec::<u8>::new();
|
||||
let mut it = if opt.hovered {
|
||||
Box::new(cx.hovered().map(|h| &h.url).into_iter())
|
||||
} else {
|
||||
|
|
@ -30,29 +28,29 @@ impl Actor for Copy {
|
|||
match opt.r#type.as_ref() {
|
||||
// TODO: rename to "url"
|
||||
"path" => {
|
||||
s.push(opt.separator.transform(&u.os_str()));
|
||||
s.extend_from_slice(&opt.separator.transform(&u.to_strand()));
|
||||
}
|
||||
"dirname" => {
|
||||
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" => {
|
||||
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" => {
|
||||
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),
|
||||
};
|
||||
if it.peek().is_some() {
|
||||
s.push("\n");
|
||||
s.push(b'\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the CWD path regardless even if the directory is empty
|
||||
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));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use yazi_fs::{File, FilesOp};
|
|||
use yazi_macro::{ok_or_not_found, succ};
|
||||
use yazi_parser::mgr::CreateOpt;
|
||||
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_watcher::WATCHER;
|
||||
|
||||
|
|
@ -27,7 +27,10 @@ impl Actor for Create {
|
|||
return;
|
||||
}
|
||||
|
||||
let new = cwd.join(&name);
|
||||
let Ok(new) = cwd.try_join(&name) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !opt.force
|
||||
&& maybe_exists(&new).await
|
||||
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
|
||||
|
|
@ -51,7 +54,7 @@ impl Create {
|
|||
&& let Some((parent, urn)) = real.pair()
|
||||
{
|
||||
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?;
|
||||
} else if let Some(parent) = new.parent() {
|
||||
provider::create_dir_all(parent).await.ok();
|
||||
|
|
@ -65,7 +68,7 @@ impl Create {
|
|||
&& let Some((parent, urn)) = real.pair()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
30
yazi-actor/src/mgr/displace.rs
Normal file
30
yazi-actor/src/mgr/displace.rs
Normal 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!();
|
||||
}
|
||||
}
|
||||
36
yazi-actor/src/mgr/displace_do.rs
Normal file
36
yazi-actor/src/mgr/displace_do.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,11 @@ use std::{mem, time::{Duration, Instant}};
|
|||
use anyhow::Result;
|
||||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use hashbrown::HashSet;
|
||||
use tokio::sync::oneshot;
|
||||
use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::{DownloadOpt, OpenOpt};
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_shared::{data::Data, url::UrlCow};
|
||||
use yazi_shared::{data::Data, url::{UrlCow, UrlLike}};
|
||||
use yazi_vfs::VfsFile;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -29,9 +28,8 @@ impl Actor for Download {
|
|||
|
||||
let mut wg1 = FuturesUnordered::new();
|
||||
for url in opt.urls {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
scheduler.file_download(url.to_owned(), Some(tx));
|
||||
wg1.push(async move { (rx.await == Ok(false), url) });
|
||||
let done = scheduler.file_download(url.to_owned());
|
||||
wg1.push(async move { (done.future().await, url) });
|
||||
}
|
||||
|
||||
let mut wg2 = vec![];
|
||||
|
|
@ -78,7 +76,7 @@ impl Download {
|
|||
let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect();
|
||||
for mut root in roots {
|
||||
root.push("%lock");
|
||||
Local.create_dir_all(root).await.ok();
|
||||
Local::regular(&root).create_dir_all().await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, mgr::CdSource};
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ impl Actor for Enter {
|
|||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::{Result, bail};
|
|||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::{VoidOpt, mgr::EscapeOpt};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use yazi_fs::Filter;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::FilterOpt;
|
||||
use yazi_shared::{data::Data, path::PathLike};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ impl Actor for FilterDo {
|
|||
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 hovered = cx.hovered().map(|f| f.urn().owned());
|
||||
let hovered = cx.hovered().map(|f| f.urn().into());
|
||||
cx.current_mut().files.set_filter(filter);
|
||||
|
||||
if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use yazi_fs::path::clean_url;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_parser::{VoidOpt, mgr::CdSource};
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -16,11 +16,8 @@ impl Actor for Follow {
|
|||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let Some(file) = cx.hovered() else { succ!() };
|
||||
let Some(link_to) = &file.link_to else { succ!() };
|
||||
|
||||
if let Some(p) = file.url.parent() {
|
||||
act!(mgr:reveal, cx, clean_url(p.join(link_to)))
|
||||
} else {
|
||||
succ!()
|
||||
}
|
||||
let Some(parent) = file.url.parent() else { succ!() };
|
||||
let Ok(joined) = parent.try_join(link_to) else { succ!() };
|
||||
act!(mgr:reveal, cx, (clean_url(joined), CdSource::Follow))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ impl Actor for Forward {
|
|||
const NAME: &str = "forward";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
match cx.tab_mut().backstack.shift_forward().cloned() {
|
||||
Some(u) => act!(mgr:cd, cx, (u, CdSource::Forward)),
|
||||
None => succ!(),
|
||||
if let Some(u) = cx.tab_mut().backstack.shift_forward().cloned() {
|
||||
act!(mgr:cd, cx, (u, CdSource::Forward))?;
|
||||
}
|
||||
succ!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use yazi_core::tab::Folder;
|
|||
use yazi_fs::FolderStage;
|
||||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::mgr::HiddenOpt;
|
||||
use yazi_shared::{data::Data, path::PathLike};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ impl Actor for Hidden {
|
|||
let state = opt.state.bool(cx.tab().pref.show_hidden);
|
||||
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| {
|
||||
if f.stage == FolderStage::Loading {
|
||||
render!();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ impl Actor for Hover {
|
|||
|
||||
// Parent should always track CWD
|
||||
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
|
||||
|
|
@ -30,7 +30,7 @@ impl Actor for Hover {
|
|||
{
|
||||
// `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.
|
||||
tab.current.trace = Some(u.to_owned());
|
||||
tab.current.trace = Some(u.clone());
|
||||
}
|
||||
|
||||
// Publish through DDS
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl Actor for Leave {
|
|||
|
||||
let Some(mut url) = url else { succ!() };
|
||||
if url.is_search() {
|
||||
url = url.into_regular();
|
||||
url = url.as_regular()?;
|
||||
}
|
||||
|
||||
act!(mgr:cd, cx, (url, CdSource::Leave))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ yazi_macro::mod_flat!(
|
|||
close
|
||||
copy
|
||||
create
|
||||
displace
|
||||
displace_do
|
||||
download
|
||||
enter
|
||||
escape
|
||||
|
|
@ -39,6 +41,7 @@ yazi_macro::mod_flat!(
|
|||
shell
|
||||
sort
|
||||
spot
|
||||
stash
|
||||
suspend
|
||||
tab_close
|
||||
tab_create
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ffi::OsString, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{select, time};
|
||||
|
|
@ -7,7 +7,7 @@ use yazi_dds::spark::SparkKind;
|
|||
use yazi_macro::{emit, succ};
|
||||
use yazi_parser::mgr::QuitOpt;
|
||||
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};
|
||||
|
||||
|
|
@ -66,17 +66,12 @@ impl Actor for Quit {
|
|||
}
|
||||
|
||||
impl Quit {
|
||||
pub(super) fn with_selected<'a, I, T>(selected: I)
|
||||
pub(super) fn with_selected<I>(selected: I)
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<UrlCow<'a>>,
|
||||
I: IntoIterator,
|
||||
I::Item: AsUrl,
|
||||
{
|
||||
let paths = selected.into_iter().fold(OsString::new(), |mut s, u| {
|
||||
s.push(u.into().as_url().os_str());
|
||||
s.push("\n");
|
||||
s
|
||||
});
|
||||
|
||||
let paths = selected.into_iter().join(Strand::Utf8("\n"));
|
||||
if !paths.is_empty() {
|
||||
emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() }));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use yazi_fs::{CWD, Files, FilesOp, cha::Cha};
|
|||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
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_vfs::{VfsFiles, VfsFilesOp};
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ impl Actor for Refresh {
|
|||
|
||||
impl Refresh {
|
||||
fn cwd_changed() {
|
||||
if CWD.load().scheme.is_virtual() {
|
||||
if CWD.load().kind().is_virtual() {
|
||||
MgrProxy::watch();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,8 +58,8 @@ impl Refresh {
|
|||
|
||||
let futs: Vec<_> = folders
|
||||
.iter()
|
||||
.filter(|&f| f.url.is_internal())
|
||||
.map(|&f| go(f.url.to_owned(), f.cha))
|
||||
.filter(|&f| f.url.is_absolute() && f.url.is_internal())
|
||||
.map(|&f| go(f.url.clone(), f.cha))
|
||||
.collect();
|
||||
if !futs.is_empty() {
|
||||
tokio::spawn(futures::future::join_all(futs));
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use yazi_fs::{File, FilesOp};
|
|||
use yazi_macro::{act, err, ok_or_not_found, succ};
|
||||
use yazi_parser::mgr::RenameOpt;
|
||||
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_watcher::WATCHER;
|
||||
|
||||
|
|
@ -49,7 +49,10 @@ impl Actor for Rename {
|
|||
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 {
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
|
||||
|
|
@ -70,10 +73,11 @@ impl Rename {
|
|||
provider::rename(&old, &new).await?;
|
||||
|
||||
if let Ok(u) = overwritten
|
||||
&& u != new
|
||||
&& let Some((parent, urn)) = u.pair()
|
||||
{
|
||||
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?;
|
||||
|
|
@ -81,10 +85,10 @@ impl Rename {
|
|||
|
||||
let file = File::new(&new).await?;
|
||||
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 {
|
||||
FilesOp::Deleting(old_p.into(), [old_n.owned()].into()).emit();
|
||||
FilesOp::Upserting(new_p.into(), [(new_n.owned(), file)].into()).emit();
|
||||
FilesOp::Deleting(old_p.into(), [old_n.into()].into()).emit();
|
||||
FilesOp::Upserting(new_p.into(), [(new_n.into(), file)].into()).emit();
|
||||
}
|
||||
|
||||
MgrProxy::reveal(&new);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use yazi_fs::{File, FilesOp};
|
||||
use yazi_macro::{act, render, succ};
|
||||
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};
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ impl Actor for Reveal {
|
|||
// Cd to the parent directory
|
||||
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();
|
||||
render!(tab.current.hover(child));
|
||||
|
||||
|
|
@ -30,8 +30,8 @@ impl Actor for Reveal {
|
|||
tab.current.update_pub(tab.id, op);
|
||||
}
|
||||
|
||||
// Now, we can safely hover on the target
|
||||
act!(mgr:hover, cx, Some(child.owned()))?;
|
||||
// Now, we can safely hover over the target
|
||||
act!(mgr:hover, cx, Some(child.into()))?;
|
||||
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, mem, time::Duration};
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
|
|
@ -6,10 +6,10 @@ use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
|||
use yazi_config::popup::InputCfg;
|
||||
use yazi_fs::{FilesOp, cha::Cha};
|
||||
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_proxy::{InputProxy, MgrProxy};
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_proxy::{AppProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -52,8 +52,10 @@ impl Actor for SearchDo {
|
|||
handle.abort();
|
||||
}
|
||||
|
||||
let cwd = tab.cwd().to_search(opt.subject.as_ref());
|
||||
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 {
|
||||
let rx = match opt.via {
|
||||
|
|
@ -107,15 +109,10 @@ impl Actor for SearchStop {
|
|||
handle.abort();
|
||||
}
|
||||
|
||||
if !tab.cwd().is_search() {
|
||||
succ!();
|
||||
if tab.cwd().is_search() {
|
||||
act!(mgr:cd, cx, (tab.cwd().to_regular()?, CdSource::Escape))?;
|
||||
}
|
||||
|
||||
let rep = tab.history.remove_or(tab.cwd().to_regular());
|
||||
drop(mem::replace(&mut tab.current, rep));
|
||||
|
||||
act!(mgr:hidden, cx)?;
|
||||
act!(mgr:sort, cx)?;
|
||||
act!(mgr:refresh, cx)
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl Actor for Shell {
|
|||
|
||||
TasksProxy::open_shell_compat(ProcessOpenOpt {
|
||||
cwd: cwd.into(),
|
||||
cmd: Splatter::new(&selected).splat(opt.run.as_ref()),
|
||||
cmd: Splatter::new(&selected).splat(&*opt.run),
|
||||
args: selected,
|
||||
block: opt.block,
|
||||
orphan: opt.orphan,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::tab::Folder;
|
||||
use yazi_dds::spark::SparkKind;
|
||||
use yazi_fs::{FilesSorter, FolderStage};
|
||||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::mgr::SortOpt;
|
||||
use yazi_shared::{data::Data, path::PathLike};
|
||||
use yazi_shared::{Source, data::Data};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ impl Actor for Sort {
|
|||
pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit);
|
||||
|
||||
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| {
|
||||
if f.stage == FolderStage::Loading {
|
||||
render!();
|
||||
|
|
@ -56,4 +57,12 @@ impl Actor for Sort {
|
|||
|
||||
succ!();
|
||||
}
|
||||
|
||||
fn hook(cx: &Ctx, _: &Self::Options) -> Option<SparkKind> {
|
||||
match cx.source() {
|
||||
Source::Ind => Some(SparkKind::IndSort),
|
||||
Source::Key => Some(SparkKind::KeySort),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
yazi-actor/src/mgr/stash.rs
Normal file
31
yazi-actor/src/mgr/stash.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ use yazi_core::tab::Tab;
|
|||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::{CdSource, TabCreateOpt};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -25,14 +25,17 @@ impl Actor for TabCreate {
|
|||
}
|
||||
|
||||
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())
|
||||
} else if let Some(h) = cx.hovered() {
|
||||
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 {
|
||||
tab.pref = cx.tab().pref.clone();
|
||||
(true, cx.cwd().to_regular())
|
||||
(true, cx.cwd().clone())
|
||||
};
|
||||
|
||||
let tabs = &mut cx.mgr.tabs;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ impl Actor for UpdateFiles {
|
|||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let revision = cx.current().files.revision;
|
||||
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) {
|
||||
cx.mgr.yanked.apply_op(&op);
|
||||
Self::update_tab(cx, op).ok();
|
||||
|
|
@ -25,7 +26,7 @@ impl Actor for UpdateFiles {
|
|||
|
||||
render!(cx.mgr.yanked.catchup_revision(false));
|
||||
act!(mgr:hidden, cx)?;
|
||||
act!(mgr:sort, cx)?;
|
||||
act!(mgr:sort, cx).ok();
|
||||
|
||||
if revision != cx.current().files.revision {
|
||||
act!(mgr:hover, cx)?;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use anyhow::Result;
|
|||
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use scopeguard::defer;
|
||||
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
||||
use yazi_binding::Permit;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
|
|
@ -26,7 +27,7 @@ impl Actor for Inspect {
|
|||
};
|
||||
|
||||
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 buffered = {
|
||||
|
|
@ -37,9 +38,7 @@ impl Actor for Inspect {
|
|||
task.logs.clone()
|
||||
};
|
||||
|
||||
defer!(AppProxy::resume());
|
||||
AppProxy::stop().await;
|
||||
|
||||
terminal_clear(TTY.writer()).ok();
|
||||
TTY.writer().write_all(buffered.as_bytes()).ok();
|
||||
TTY.writer().flush().ok();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-adapter"
|
||||
version = "25.9.15"
|
||||
version = "26.1.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -8,21 +8,25 @@ description = "Yazi image adapter"
|
|||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
yazi-config = { path = "../yazi-config", version = "25.9.15" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
yazi-term = { path = "../yazi-term", version = "25.9.15" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.1.4" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.1.4" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
base64 = { 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 }
|
||||
quantette = { version = "0.3.0", default-features = false }
|
||||
quantette = { version = "0.5.1", default-features = false }
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
5
yazi-adapter/README.md
Normal file
5
yazi-adapter/README.md
Normal 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
|
||||
|
|
@ -84,7 +84,7 @@ impl Adapter {
|
|||
}
|
||||
|
||||
impl Adapter {
|
||||
pub fn matches(emulator: Emulator) -> Self {
|
||||
pub fn matches(emulator: &Emulator) -> Self {
|
||||
let mut protocols = emulator.adapters().to_owned();
|
||||
if env_exists("ZELLIJ_SESSION_NAME") {
|
||||
protocols.retain(|p| *p == Self::Sixel);
|
||||
|
|
|
|||
|
|
@ -44,42 +44,40 @@ impl Brand {
|
|||
}
|
||||
|
||||
pub fn from_env() -> Option<Self> {
|
||||
use Brand as B;
|
||||
|
||||
let (term, program) = B::env();
|
||||
let (term, program) = Self::env();
|
||||
let vars = [
|
||||
("KITTY_WINDOW_ID", B::Kitty),
|
||||
("KONSOLE_VERSION", B::Konsole),
|
||||
("ITERM_SESSION_ID", B::Iterm2),
|
||||
("WEZTERM_EXECUTABLE", B::WezTerm),
|
||||
("GHOSTTY_RESOURCES_DIR", B::Ghostty),
|
||||
("WT_Session", B::Microsoft),
|
||||
("WARP_HONOR_PS1", B::Warp),
|
||||
("VSCODE_INJECTION", B::VSCode),
|
||||
("TABBY_CONFIG_DIRECTORY", B::Tabby),
|
||||
("KITTY_WINDOW_ID", Self::Kitty),
|
||||
("KONSOLE_VERSION", Self::Konsole),
|
||||
("ITERM_SESSION_ID", Self::Iterm2),
|
||||
("WEZTERM_EXECUTABLE", Self::WezTerm),
|
||||
("GHOSTTY_RESOURCES_DIR", Self::Ghostty),
|
||||
("WT_Session", Self::Microsoft),
|
||||
("WARP_HONOR_PS1", Self::Warp),
|
||||
("VSCODE_INJECTION", Self::VSCode),
|
||||
("TABBY_CONFIG_DIRECTORY", Self::Tabby),
|
||||
];
|
||||
|
||||
match term.as_str() {
|
||||
"xterm-kitty" => return Some(B::Kitty),
|
||||
"foot" => return Some(B::Foot),
|
||||
"foot-extra" => return Some(B::Foot),
|
||||
"xterm-ghostty" => return Some(B::Ghostty),
|
||||
"rio" => return Some(B::Rio),
|
||||
"rxvt-unicode-256color" => return Some(B::Urxvt),
|
||||
"xterm-kitty" => return Some(Self::Kitty),
|
||||
"foot" => return Some(Self::Foot),
|
||||
"foot-extra" => return Some(Self::Foot),
|
||||
"xterm-ghostty" => return Some(Self::Ghostty),
|
||||
"rio" => return Some(Self::Rio),
|
||||
"rxvt-unicode-256color" => return Some(Self::Urxvt),
|
||||
_ => {}
|
||||
}
|
||||
match program.as_str() {
|
||||
"iTerm.app" => return Some(B::Iterm2),
|
||||
"WezTerm" => return Some(B::WezTerm),
|
||||
"ghostty" => return Some(B::Ghostty),
|
||||
"WarpTerminal" => return Some(B::Warp),
|
||||
"rio" => return Some(B::Rio),
|
||||
"BlackBox" => return Some(B::BlackBox),
|
||||
"vscode" => return Some(B::VSCode),
|
||||
"Tabby" => return Some(B::Tabby),
|
||||
"Hyper" => return Some(B::Hyper),
|
||||
"mintty" => return Some(B::Mintty),
|
||||
"Apple_Terminal" => return Some(B::Apple),
|
||||
"iTerm.app" => return Some(Self::Iterm2),
|
||||
"WezTerm" => return Some(Self::WezTerm),
|
||||
"ghostty" => return Some(Self::Ghostty),
|
||||
"WarpTerminal" => return Some(Self::Warp),
|
||||
"rio" => return Some(Self::Rio),
|
||||
"BlackBox" => return Some(Self::BlackBox),
|
||||
"vscode" => return Some(Self::VSCode),
|
||||
"Tabby" => return Some(Self::Tabby),
|
||||
"Hyper" => return Some(Self::Hyper),
|
||||
"mintty" => return Some(Self::Mintty),
|
||||
"Apple_Terminal" => return Some(Self::Apple),
|
||||
_ => {}
|
||||
}
|
||||
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] {
|
||||
use Brand as B;
|
||||
|
||||
use crate::Adapter as A;
|
||||
|
||||
match self {
|
||||
B::Kitty => &[A::Kgp],
|
||||
B::Konsole => &[A::KgpOld],
|
||||
B::Iterm2 => &[A::Iip, A::Sixel],
|
||||
B::WezTerm => &[A::Iip, A::Sixel],
|
||||
B::Foot => &[A::Sixel],
|
||||
B::Ghostty => &[A::Kgp],
|
||||
B::Microsoft => &[A::Sixel],
|
||||
B::Warp => &[A::Iip, A::KgpOld],
|
||||
B::Rio => &[A::Iip, A::Sixel],
|
||||
B::BlackBox => &[A::Sixel],
|
||||
B::VSCode => &[A::Iip, A::Sixel],
|
||||
B::Tabby => &[A::Iip, A::Sixel],
|
||||
B::Hyper => &[A::Iip, A::Sixel],
|
||||
B::Mintty => &[A::Iip],
|
||||
B::Tmux => &[],
|
||||
B::VTerm => &[],
|
||||
B::Apple => &[],
|
||||
B::Urxvt => &[],
|
||||
B::Bobcat => &[A::Iip, A::Sixel],
|
||||
Self::Kitty => &[A::Kgp],
|
||||
Self::Konsole => &[A::KgpOld],
|
||||
Self::Iterm2 => &[A::Iip, A::Sixel],
|
||||
Self::WezTerm => &[A::Iip, A::Sixel],
|
||||
Self::Foot => &[A::Sixel],
|
||||
Self::Ghostty => &[A::Kgp],
|
||||
Self::Microsoft => &[A::Sixel],
|
||||
Self::Warp => &[A::Iip, A::KgpOld],
|
||||
Self::Rio => &[A::Iip, A::Sixel],
|
||||
Self::BlackBox => &[A::Sixel],
|
||||
Self::VSCode => &[A::Iip, A::Sixel],
|
||||
Self::Tabby => &[A::Iip, A::Sixel],
|
||||
Self::Hyper => &[A::Iip, A::Sixel],
|
||||
Self::Mintty => &[A::Iip],
|
||||
Self::Tmux => &[],
|
||||
Self::VTerm => &[],
|
||||
Self::Apple => &[],
|
||||
Self::Urxvt => &[],
|
||||
Self::Bobcat => &[A::Iip, A::Sixel],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ impl Dimension {
|
|||
}
|
||||
|
||||
pub fn cell_size() -> Option<(f64, f64)> {
|
||||
let emu = EMULATOR.get();
|
||||
let emu = &*EMULATOR;
|
||||
Some(if emu.force_16t {
|
||||
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
|
||||
} else if let Some(r) = Self::available().ratio() {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@ use anyhow::{Result, bail};
|
|||
use crossterm::{cursor::MoveTo, queue};
|
||||
use image::{DynamicImage, GenericImageView, RgbImage};
|
||||
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 crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
|
||||
|
||||
pub(crate) struct Sixel;
|
||||
|
||||
struct QuantizeOutput<T> {
|
||||
indices: Vec<u8>,
|
||||
palette: Vec<T>,
|
||||
}
|
||||
|
||||
impl Sixel {
|
||||
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
|
||||
let img = Image::downscale(path, max).await?;
|
||||
|
|
@ -50,7 +55,7 @@ impl Sixel {
|
|||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
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
|
||||
for (i, c) in qo.palette.iter().enumerate() {
|
||||
|
|
@ -110,12 +115,14 @@ impl Sixel {
|
|||
|
||||
fn quantify(rgb: &RgbImage, alpha: bool) -> Result<QuantizeOutput<Srgb<u8>>> {
|
||||
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(
|
||||
&slice,
|
||||
PaletteSize::try_from(256u16 - alpha as u16)?,
|
||||
&UIntBinner::<32>,
|
||||
))
|
||||
let wu = WuU8x3::run_slice(colors, BinnerU8x3::rgb())?;
|
||||
let color_map = wu.color_map(PaletteSize::try_from(256u16 - alpha as u16)?);
|
||||
|
||||
Ok(QuantizeOutput {
|
||||
indices: color_map.map_to_indices(colors),
|
||||
palette: color_map.into_palette().into_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,16 +10,25 @@ use yazi_term::tty::{Handle, TTY};
|
|||
|
||||
use crate::{Adapter, Brand, Dimension, Mux, TMUX, Unknown};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Emulator {
|
||||
pub kind: Either<Brand, Unknown>,
|
||||
pub version: String,
|
||||
pub light: bool,
|
||||
pub csi_16t: (u16, u16),
|
||||
pub force_16t: bool,
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -60,22 +69,14 @@ impl Emulator {
|
|||
let csi_16t = Self::csi_16t(&resp).unwrap_or_default();
|
||||
Ok(Self {
|
||||
kind,
|
||||
version: Self::csi_gt_q(&resp).unwrap_or_default(),
|
||||
light: Self::light_bg(&resp).unwrap_or_default(),
|
||||
csi_16t,
|
||||
force_16t: Self::force_16t(csi_16t),
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn unknown() -> Self {
|
||||
Self {
|
||||
kind: Either::Right(Unknown::default()),
|
||||
light: false,
|
||||
csi_16t: (0, 0),
|
||||
force_16t: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adapters(self) -> &'static [Adapter] {
|
||||
pub fn adapters(&self) -> &'static [Adapter] {
|
||||
match self.kind {
|
||||
Either::Left(brand) => brand.adapters(),
|
||||
Either::Right(unknown) => unknown.adapters(),
|
||||
|
|
@ -182,6 +183,11 @@ impl Emulator {
|
|||
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> {
|
||||
match resp.split_once("]11;rgb:") {
|
||||
Some((_, s)) if s.len() >= 14 => {
|
||||
|
|
|
|||
69
yazi-adapter/src/icc.rs
Normal file
69
yazi-adapter/src/icc.rs
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
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 yazi_config::YAZI;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
|
||||
use crate::Dimension;
|
||||
use crate::{Dimension, Icc};
|
||||
|
||||
pub struct Image;
|
||||
|
||||
impl Image {
|
||||
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 buf = tokio::task::spawn_blocking(move || {
|
||||
|
|
@ -25,25 +25,22 @@ impl Image {
|
|||
|
||||
let mut buf = Vec::new();
|
||||
if img.color().has_alpha() {
|
||||
let rgba = img.into_rgba8();
|
||||
let mut encoder = PngEncoder::new(&mut buf);
|
||||
icc.map(|b| encoder.set_icc_profile(b));
|
||||
encoder.write_image(&rgba, rgba.width(), rgba.height(), ExtendedColorType::Rgba8)?;
|
||||
let encoder = PngEncoder::new(&mut buf);
|
||||
img.write_with_encoder(encoder)?;
|
||||
} else {
|
||||
let mut encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
|
||||
icc.map(|b| encoder.set_icc_profile(b));
|
||||
encoder.encode_image(&img.into_rgb8())?;
|
||||
let encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
|
||||
img.write_with_encoder(encoder)?;
|
||||
}
|
||||
|
||||
Ok::<_, ImageError>(buf)
|
||||
})
|
||||
.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> {
|
||||
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));
|
||||
|
||||
// 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();
|
||||
if YAZI.tasks.image_alloc > 0 {
|
||||
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 orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
let icc = decoder.icc_profile().unwrap_or_default();
|
||||
|
||||
Ok((DynamicImage::from_decoder(decoder)?, orientation, icc))
|
||||
Ok((Icc::transform(decoder)?, orientation))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
#![allow(clippy::option_map_unit_fn, clippy::unit_arg)]
|
||||
|
||||
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);
|
||||
|
||||
// Image state
|
||||
|
|
@ -26,8 +24,8 @@ pub fn init() -> anyhow::Result<()> {
|
|||
WSL.set(in_wsl());
|
||||
|
||||
// Emulator detection
|
||||
EMULATOR.set(Emulator::detect().unwrap_or_default());
|
||||
TMUX.set(EMULATOR.get().kind.is_left_and(|&b| b == Brand::Tmux));
|
||||
let mut emulator = Emulator::detect().unwrap_or_default();
|
||||
TMUX.set(emulator.kind.is_left_and(|&b| b == Brand::Tmux));
|
||||
|
||||
// Tmux support
|
||||
if TMUX.get() {
|
||||
|
|
@ -35,12 +33,13 @@ pub fn init() -> anyhow::Result<()> {
|
|||
START.set("\x1bPtmux;\x1b\x1b");
|
||||
CLOSE.set("\x1b\\");
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
use crate::Adapter;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Unknown {
|
||||
pub kgp: bool,
|
||||
pub sixel: bool,
|
||||
}
|
||||
|
||||
impl Unknown {
|
||||
pub(super) const fn default() -> Self { Self { kgp: false, sixel: false } }
|
||||
|
||||
pub(super) fn adapters(self) -> &'static [Adapter] {
|
||||
use Adapter as A;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-binding"
|
||||
version = "25.9.15"
|
||||
version = "26.1.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -8,19 +8,23 @@ description = "Yazi Lua bindings"
|
|||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = [ "vendored-lua" ]
|
||||
vendored-lua = [ "mlua/vendored" ]
|
||||
|
||||
[dependencies]
|
||||
yazi-adapter = { path = "../yazi-adapter", version = "25.9.15" }
|
||||
yazi-config = { path = "../yazi-config", version = "25.9.15" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
yazi-adapter = { path = "../yazi-adapter", version = "26.1.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.1.4" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
|
|
|||
5
yazi-binding/README.md
Normal file
5
yazi-binding/README.md
Normal 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
|
||||
|
|
@ -20,7 +20,7 @@ pub struct Border {
|
|||
pub r#type: ratatui::widgets::BorderType,
|
||||
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 {
|
||||
|
|
@ -51,8 +51,8 @@ impl Border {
|
|||
|
||||
for title in self.titles {
|
||||
block = match title {
|
||||
(ratatui::widgets::block::Position::Top, line) => block.title(line),
|
||||
(ratatui::widgets::block::Position::Bottom, line) => block.title(line),
|
||||
(ratatui::widgets::TitlePosition::Top, line) => block.title(line),
|
||||
(ratatui::widgets::TitlePosition::Bottom, line) => block.title(line),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +80,9 @@ impl UserData for Border {
|
|||
"title",
|
||||
|_, (ud, line, position): (AnyUserData, Value, Option<u8>)| {
|
||||
let position = if position == Some(Borders::BOTTOM.bits()) {
|
||||
ratatui::widgets::block::Position::Bottom
|
||||
ratatui::widgets::TitlePosition::Bottom
|
||||
} else {
|
||||
ratatui::widgets::block::Position::Top
|
||||
ratatui::widgets::TitlePosition::Top
|
||||
};
|
||||
|
||||
ud.borrow_mut::<Self>()?.titles.push((position, Line::try_from(line)?.inner));
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ mod tests {
|
|||
.call(())
|
||||
.unwrap();
|
||||
|
||||
line.spans.iter().map(|s| s.content.as_ref()).collect()
|
||||
line.spans.iter().map(|s| &*s.content).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl Rect {
|
|||
})?;
|
||||
|
||||
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" => {
|
||||
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()))
|
||||
|
|
|
|||
|
|
@ -14,9 +14,20 @@ pub enum Error {
|
|||
|
||||
impl Error {
|
||||
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()) }
|
||||
|
|
@ -60,13 +71,20 @@ impl UserData for Error {
|
|||
_ => 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) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {
|
||||
Ok(match me {
|
||||
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)| {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
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};
|
||||
|
||||
pub type FileRef = UserDataRef<File>;
|
||||
|
||||
const EXPECTED: &str = "expected a table, File, or fs::File";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct File {
|
||||
inner: yazi_fs::File,
|
||||
|
||||
|
|
@ -14,6 +17,7 @@ pub struct File {
|
|||
v_link_to: Option<Value>,
|
||||
|
||||
v_name: Option<Value>,
|
||||
v_path: Option<Value>,
|
||||
v_cache: Option<Value>,
|
||||
}
|
||||
|
||||
|
|
@ -28,23 +32,53 @@ impl From<File> for yazi_fs::File {
|
|||
}
|
||||
|
||||
impl File {
|
||||
pub fn new(inner: yazi_fs::File) -> Self {
|
||||
Self { inner, v_cha: None, v_url: None, v_link_to: None, v_name: None, v_cache: None }
|
||||
pub fn new(inner: impl Into<yazi_fs::File>) -> Self {
|
||||
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 {
|
||||
pub fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.globals().raw_set(
|
||||
"File",
|
||||
lua.create_function(|_, t: Table| {
|
||||
Ok(Self::new(yazi_fs::File {
|
||||
url: t.raw_get::<Url>("url")?.into(),
|
||||
cha: *t.raw_get::<Cha>("cha")?,
|
||||
..Default::default()
|
||||
}))
|
||||
})?,
|
||||
)
|
||||
impl TryFrom<Value> for File {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Value) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Value::Table(tbl) => Self::try_from(tbl),
|
||||
Value::UserData(ud) => Self::try_from(ud),
|
||||
_ => Err(EXPECTED.into_lua_err())?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
Ok(match value {
|
||||
Value::UserData(ud) => Self::new(ud.take::<Self>()?.inner),
|
||||
_ => Err("Expected a File".into_lua_err())?,
|
||||
_ => Err("expected a File".into_lua_err())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
yazi-binding/src/handle.rs
Normal file
16
yazi-binding/src/handle.rs
Normal 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(),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
#![allow(clippy::unit_arg)]
|
||||
|
||||
mod macros;
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -82,65 +82,122 @@ macro_rules! impl_style_method {
|
|||
macro_rules! impl_style_shorthands {
|
||||
($methods:ident, $($field:tt).+) => {
|
||||
$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 {
|
||||
mlua::Value::Nil => {
|
||||
ud.borrow::<Self>()?.$($field).+.fg.map($crate::Color).into_lua(lua)
|
||||
},
|
||||
mlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
});
|
||||
$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 {
|
||||
mlua::Value::Nil => {
|
||||
ud.borrow::<Self>()?.$($field).+.bg.map($crate::Color).into_lua(lua)
|
||||
mlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
});
|
||||
$methods.add_function_mut("bold", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::BOLD;
|
||||
$methods.add_function_mut("bold", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("dim", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::DIM;
|
||||
$methods.add_function_mut("dim", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("italic", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::ITALIC;
|
||||
$methods.add_function_mut("italic", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("underline", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::UNDERLINED;
|
||||
$methods.add_function_mut("underline", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("blink", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::SLOW_BLINK;
|
||||
$methods.add_function_mut("blink", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("blink_rapid", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::RAPID_BLINK;
|
||||
$methods.add_function_mut("blink_rapid", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("reverse", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::REVERSED;
|
||||
$methods.add_function_mut("reverse", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("hidden", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::HIDDEN;
|
||||
$methods.add_function_mut("hidden", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$methods.add_function_mut("crossed", |_, ud: mlua::AnyUserData| {
|
||||
ud.borrow_mut::<Self>()?.$($field).+.add_modifier |= ratatui::style::Modifier::CROSSED_OUT;
|
||||
$methods.add_function_mut("crossed", |_, (ud, remove): (mlua::AnyUserData, bool)| {
|
||||
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)
|
||||
});
|
||||
$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)
|
||||
});
|
||||
};
|
||||
|
|
@ -151,14 +208,19 @@ macro_rules! impl_file_fields {
|
|||
($fields:ident) => {
|
||||
$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, 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| {
|
||||
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| {
|
||||
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
136
yazi-binding/src/path.rs
Normal 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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +1,51 @@
|
|||
use std::{mem, ops::Deref};
|
||||
|
||||
use futures::{FutureExt, future::BoxFuture};
|
||||
use mlua::{UserData, prelude::LuaUserDataMethods};
|
||||
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>>,
|
||||
destruct: Option<F>,
|
||||
destruct: Option<BoxFuture<'static, ()>>,
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Deref for Permit<F> {
|
||||
impl Deref for Permit {
|
||||
type Target = Option<SemaphorePermit<'static>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Permit<F> {
|
||||
pub fn new(inner: SemaphorePermit<'static>, f: F) -> Self {
|
||||
Self { inner: Some(inner), destruct: Some(f) }
|
||||
impl Permit {
|
||||
pub fn new<F>(inner: SemaphorePermit<'static>, f: F) -> Self
|
||||
where
|
||||
F: Future<Output = ()> + 'static + Send,
|
||||
{
|
||||
Self { inner: Some(inner), destruct: Some(f.boxed()) }
|
||||
}
|
||||
|
||||
fn dropping(&mut self) {
|
||||
if let Some(f) = self.destruct.take() {
|
||||
f();
|
||||
}
|
||||
if let Some(p) = self.inner.take() {
|
||||
mem::drop(p);
|
||||
fn dropping(&mut self) -> impl Future<Output = ()> + 'static {
|
||||
let inner = self.inner.take();
|
||||
let destruct = self.destruct.take();
|
||||
|
||||
async move {
|
||||
if let Some(f) = destruct {
|
||||
f.await;
|
||||
}
|
||||
if let Some(p) = inner {
|
||||
mem::drop(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Drop for Permit<F> {
|
||||
fn drop(&mut self) { self.dropping(); }
|
||||
impl Drop for Permit {
|
||||
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) {
|
||||
methods.add_method_mut("drop", |_, me, ()| Ok(me.dropping()));
|
||||
methods.add_async_method_mut("drop", |_, mut me, ()| async move { Ok(me.dropping().await) });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ use std::collections::VecDeque;
|
|||
use hashbrown::HashMap;
|
||||
use mlua::Function;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct Runtime {
|
||||
frames: VecDeque<RuntimeFrame>,
|
||||
blocks: HashMap<String, Vec<Function>>,
|
||||
frames: VecDeque<RuntimeFrame>,
|
||||
blocks: HashMap<String, Vec<Function>>,
|
||||
pub initing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -16,10 +17,13 @@ struct RuntimeFrame {
|
|||
}
|
||||
|
||||
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 {
|
||||
frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]),
|
||||
..Default::default()
|
||||
frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]),
|
||||
blocks: <_>::default(),
|
||||
initing: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +53,7 @@ impl Runtime {
|
|||
if let Some(v) = self.blocks.get_mut(&cur.id) {
|
||||
v.push(f);
|
||||
} else {
|
||||
self.blocks.insert(cur.id.to_owned(), vec![f]);
|
||||
self.blocks.insert(cur.id.clone(), vec![f]);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use mlua::{UserData, UserDataFields, Value};
|
|||
use yazi_fs::FsScheme;
|
||||
use yazi_shared::scheme::SchemeLike;
|
||||
|
||||
use crate::{Url, cached_field};
|
||||
use crate::{Path, cached_field};
|
||||
|
||||
pub struct Scheme {
|
||||
inner: yazi_shared::scheme::Scheme,
|
||||
|
|
@ -20,15 +20,15 @@ impl Deref for Scheme {
|
|||
}
|
||||
|
||||
impl Scheme {
|
||||
pub fn new(scheme: &yazi_shared::scheme::Scheme) -> Self {
|
||||
Self { inner: scheme.clone(), v_kind: None, v_cache: None }
|
||||
pub fn new(scheme: impl Into<yazi_shared::scheme::Scheme>) -> Self {
|
||||
Self { inner: scheme.into(), v_kind: None, v_cache: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Scheme {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
cached_field!(fields, kind, |_, me| Ok(me.kind()));
|
||||
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Url::new)));
|
||||
cached_field!(fields, kind, |_, me| Ok(me.kind().as_str()));
|
||||
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));
|
||||
|
||||
fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
pub struct Style(pub ratatui::style::Style);
|
||||
|
|
@ -34,6 +36,9 @@ impl UserData for Style {
|
|||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
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)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
|
||||
use yazi_fs::{FsHash64, FsHash128};
|
||||
use yazi_shared::{IntoOsStr, scheme::SchemeLike, url::{AsUrl, UrlCow, UrlLike}};
|
||||
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
|
||||
use yazi_fs::{FsHash64, FsHash128, FsUrl};
|
||||
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>;
|
||||
|
||||
const EXPECTED: &str = "expected a string, Url, or Path";
|
||||
|
||||
pub struct Url {
|
||||
inner: yazi_shared::url::UrlBuf,
|
||||
|
||||
v_path: Option<Value>,
|
||||
v_name: Option<Value>,
|
||||
v_stem: Option<Value>,
|
||||
v_ext: Option<Value>,
|
||||
|
|
@ -20,6 +23,8 @@ pub struct Url {
|
|||
|
||||
v_scheme: Option<Value>,
|
||||
v_domain: Option<Value>,
|
||||
|
||||
v_cache: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Url {
|
||||
|
|
@ -28,10 +33,6 @@ impl Deref for Url {
|
|||
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 {
|
||||
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) }
|
||||
}
|
||||
|
||||
impl From<Url> for yazi_shared::url::UrlCow<'_> {
|
||||
fn from(value: Url) -> Self { value.inner.into() }
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Url {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: &[u8]) -> mlua::Result<Self> {
|
||||
Ok(Self::new(UrlCow::try_from(value)?.into_owned()))
|
||||
}
|
||||
fn try_from(value: &[u8]) -> mlua::Result<Self> { Ok(Self::new(UrlCow::try_from(value)?)) }
|
||||
}
|
||||
|
||||
impl Url {
|
||||
|
|
@ -61,6 +64,7 @@ impl Url {
|
|||
Self {
|
||||
inner: url.into(),
|
||||
|
||||
v_path: None,
|
||||
v_name: None,
|
||||
v_stem: None,
|
||||
v_ext: None,
|
||||
|
|
@ -70,6 +74,8 @@ impl Url {
|
|||
|
||||
v_scheme: None,
|
||||
v_domain: None,
|
||||
|
||||
v_cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,13 +84,82 @@ impl Url {
|
|||
"Url",
|
||||
lua.create_function(|_, value: Value| {
|
||||
Ok(match value {
|
||||
Value::String(s) => Self::try_from(s.as_bytes().as_ref())?,
|
||||
Value::UserData(ud) => Self::new(ud.borrow::<Self>()?.inner.clone()),
|
||||
_ => Err("Expected a string or a Url".into_lua_err())?,
|
||||
Value::String(s) => Self::try_from(&*s.as_bytes())?,
|
||||
Value::UserData(ud) => {
|
||||
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 {
|
||||
|
|
@ -98,27 +173,32 @@ impl FromLua for Url {
|
|||
|
||||
impl UserData for Url {
|
||||
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| {
|
||||
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| {
|
||||
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| {
|
||||
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, 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| {
|
||||
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| {
|
||||
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()));
|
||||
|
|
@ -129,59 +209,23 @@ impl UserData for Url {
|
|||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("hash", |_, me, long: Option<bool>| {
|
||||
Ok(if long.unwrap_or(false) {
|
||||
format!("{:x}", me.hash_u128())
|
||||
} else {
|
||||
format!("{:x}", me.hash_u64())
|
||||
})
|
||||
});
|
||||
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_method("ends_with", |_, me, child: Value| me.ends_with(child));
|
||||
methods.add_method("hash", |_, me, long: Option<bool>| me.hash(long));
|
||||
methods.add_method("join", |lua, me, other: Value| me.join(lua, 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_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::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| {
|
||||
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())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
@ -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> {
|
||||
let (_, mt): (Value, Table) = unsafe {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-boot"
|
||||
version = "25.9.15"
|
||||
version = "26.1.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -8,27 +8,29 @@ description = "Yazi bootstrapper"
|
|||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
yazi-adapter = { path = "../yazi-adapter", version = "25.9.15" }
|
||||
yazi-config = { path = "../yazi-config", version = "25.9.15" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "25.9.15" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" }
|
||||
yazi-adapter = { path = "../yazi-adapter", version = "26.1.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.1.4" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.1.4" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.1.4" }
|
||||
|
||||
# External dependencies
|
||||
clap = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.4" }
|
||||
|
||||
# External dependencies
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.5.60"
|
||||
clap_complete = "4.5.64"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.5.10"
|
||||
vergen-gitcl = { version = "1.0.8", features = [ "build", "rustc" ] }
|
||||
|
|
|
|||
5
yazi-boot/README.md
Normal file
5
yazi-boot/README.md
Normal 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
|
||||
|
|
@ -2,7 +2,8 @@ use std::{env, ffi::OsStr, fmt::Write, path::Path};
|
|||
|
||||
use regex::Regex;
|
||||
use yazi_adapter::Mux;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_config::{THEME, YAZI};
|
||||
use yazi_fs::Xdg;
|
||||
use yazi_shared::timestamp_us;
|
||||
|
||||
use super::Actions;
|
||||
|
|
@ -19,12 +20,20 @@ impl Actions {
|
|||
writeln!(s, "\nYa")?;
|
||||
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, " TERM : {:?}", env::var_os("TERM"))?;
|
||||
writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?;
|
||||
writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?;
|
||||
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, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?;
|
||||
|
|
@ -115,6 +124,16 @@ impl Actions {
|
|||
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 {
|
||||
match std::process::Command::new(&name).arg(arg).output() {
|
||||
Ok(out) if out.status.success() => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue