Compare commits

..

No commits in common. "main" and "v25.12.29" have entirely different histories.

1276 changed files with 20927 additions and 38138 deletions

View file

@ -22,10 +22,10 @@ body:
validations:
required: true
- type: textarea
id: env
id: debug
attributes:
label: "`ya env` output"
description: Run `ya env` and paste the environment information here.
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
render: Shell
validations:
required: true

View file

@ -26,10 +26,10 @@ body:
validations:
required: true
- type: textarea
id: env
id: debug
attributes:
label: "`ya env` output"
description: Run `ya env` and paste the environment information here.
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
render: Shell
validations:
required: true
@ -65,6 +65,6 @@ body:
options:
- label: I tried the [latest nightly build](https://yazi-rs.github.io/docs/installation#binaries), and the issue is still reproducible
required: true
- label: I updated the environment information (`ya env`) field to the nightly that I tried
- label: I updated the debug information (`yazi --debug`) input box to the nightly that I tried
required: true
- label: I can reproduce it after disabling all custom configs/plugins (`mv ~/.config/yazi ~/.config/yazi-backup`)

View file

@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 🚧 Build Issues
url: https://github.com/sxyazi/yazi/discussions/new?category=3-build-issues
about: If you have issues building Yazi from source code
- name: 📝 Documentation Improvement
url: https://github.com/yazi-rs/yazi-rs.github.io
about: If you'd like to help improve the documentation

View file

@ -4,10 +4,10 @@ labels: [feature]
assignees: []
body:
- type: textarea
id: env
id: debug
attributes:
label: "`ya env` output"
description: Run `ya env` and paste the environment information here.
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
render: Shell
validations:
required: true

View file

@ -15,10 +15,3 @@ A clear and concise description of the rationale of the changes, to help our rev
If it has already been detailed in the associated issue, please skip this section.
-->
## Checklist
- [ ] I have read [CONTRIBUTING.md](https://github.com/sxyazi/yazi/blob/main/CONTRIBUTING.md)
- [ ] I confirm this PR follows the [AI Policy](https://github.com/sxyazi/yazi/blob/main/CONTRIBUTING.md#ai-policy)
<!-- AI bots are not allowed to open PRs in this repository. All PRs must be made by humans and comply with the AI policy. -->

View file

@ -15,13 +15,13 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Nix
uses: cachix/install-nix-action@v31
- name: Authenticate with Cachix
uses: cachix/cachix-action@v17
uses: cachix/cachix-action@v16
with:
name: yazi
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"

View file

@ -17,7 +17,7 @@ jobs:
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Setup Rust toolchain
run: |
@ -25,7 +25,7 @@ jobs:
rustup component add clippy
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Clippy
run: cargo clippy --all
@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Setup Rust toolchain
run: |
@ -42,16 +42,16 @@ jobs:
rustup component add rustfmt --toolchain nightly
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Rustfmt
run: find . -name '*.rs' -not -path './target/*' -exec rustfmt +nightly --check {} +
run: rustfmt +nightly --check **/*.rs
stylua:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- uses: JohnnyMorganz/stylua-action@v5
- uses: actions/checkout@v6
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest

View file

@ -42,7 +42,7 @@ jobs:
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install gcc
if: matrix.gcc != ''
@ -53,13 +53,13 @@ jobs:
targets: ${{ matrix.target }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Build
run: ./scripts/build.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: |
@ -81,14 +81,14 @@ jobs:
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Build
run: cargo build --profile release-windows --locked --target ${{ matrix.target }}
@ -97,16 +97,15 @@ jobs:
env:
TARGET_NAME: yazi-${{ matrix.target }}
run: |
New-Item -ItemType Directory -Path "${env:TARGET_NAME}\completions"
New-Item -ItemType Directory -Path ${env:TARGET_NAME}
Copy-Item -Path "target\${{ matrix.target }}\release-windows\ya.exe" -Destination ${env:TARGET_NAME}
Copy-Item -Path "target\${{ matrix.target }}\release-windows\yazi.exe" -Destination ${env:TARGET_NAME}
Copy-Item -Path "yazi-cli\completions\*" -Destination "${env:TARGET_NAME}\completions"
Copy-Item -Path "yazi-boot\completions\*" -Destination "${env:TARGET_NAME}\completions"
Copy-Item -Path "yazi-boot\completions" -Destination ${env:TARGET_NAME} -Recurse
Copy-Item -Path "README.md", "LICENSE" -Destination ${env:TARGET_NAME}
Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath "${env:TARGET_NAME}.zip"
- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: yazi-${{ matrix.target }}.zip
@ -121,20 +120,20 @@ jobs:
container:
image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Build
run: ./scripts/build.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: |
@ -151,7 +150,7 @@ jobs:
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
fetch-depth: 0
@ -168,7 +167,7 @@ jobs:
run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap
- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: snap-${{ matrix.arch }}
path: yazi-${{ matrix.arch }}.snap
@ -177,7 +176,7 @@ jobs:
runs-on: ubuntu-latest
needs: [build-snap]
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: snap-*
merge-multiple: true
@ -206,12 +205,12 @@ jobs:
runs-on: ubuntu-latest
needs: [build-unix, build-windows, build-musl, build-snap]
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
merge-multiple: true
- name: Draft
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
@ -231,12 +230,11 @@ 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@v7
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
merge-multiple: true
@ -247,7 +245,7 @@ jobs:
git tag --force nightly && git push --force origin tag nightly
- name: Nightly
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@v2
with:
tag_name: nightly
prerelease: true

View file

@ -11,7 +11,7 @@ jobs:
winget:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
merge-multiple: true
@ -22,13 +22,6 @@ 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@v8
with:
merge-multiple: true
- name: Promote snap to stable
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}

View file

@ -21,13 +21,13 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Setup Rust toolchain
run: rustup toolchain install stable --profile minimal
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.9
- name: Build
run: cargo build --verbose

View file

@ -1,4 +1,4 @@
name: Validate Issue
name: Validate Form
on:
issues:
@ -12,10 +12,10 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@v6
with:
node-version: 20
@ -24,8 +24,8 @@ jobs:
cd scripts/validate-form
npm ci
- name: Validate Issue
uses: actions/github-script@v9
- name: Validate Form
uses: actions/github-script@v8
with:
script: |
const script = require('./scripts/validate-form/main.js')

View file

@ -1,36 +0,0 @@
name: Validate PR
on:
pull_request:
types: [opened, edited, reopened, synchronize]
permissions:
contents: read
pull-requests: read
jobs:
check-list:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 20
- name: Install Dependencies
run: |
cd scripts/validate-form
npm ci
- name: Validate PR
uses: actions/github-script@v9
with:
script: |
const script = require('./scripts/validate-form/main.js')
await script({github, context, core})
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,9 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "Lua 5.5",
"runtime.special": {
"fail": "error"
},
"workspace.library": ["~/.config/yazi/plugins/types.yazi/"],
"diagnostics.disable": ["redefined-local"]
"runtime.version": "Lua 5.4",
"workspace.library": ["~/.config/yazi/plugins/types.yazi/"]
}

View file

@ -12,142 +12,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
## [Unreleased]
### Added
- Drag and drop ([#4005])
- Trash bin ([#4144])
- Bulk create ([#3793])
- Make help menu a command palette ([#4074])
- Input history ([#4104])
- Experimental `%y`, `%Y`, `%t`, `%T`, `%yN`, `%YN`, `%tN`, `%TN` shell formatting parameters ([#4108])
- Custom VFS provider ([#4118])
- Make visual mode support wraparound scrolling ([#4101])
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
- Context-aware icons for inputs ([#4080])
- Show file icons in trash/delete/overwrite confirmations ([#4096])
- Dynamic keymap Lua API ([#4031])
- New `ui.Input` element ([#4040])
- Image preview with Überzug++ on Niri ([#3990])
- New gait for input `backward` and `forward` actions ([#4012])
### Changed
- Rename SFTP sections in `vfs.toml` from `[services.domain]` to `[sftp.domain]` ([#4120]).
- Rename `<BackTab>` to `<S-Tab>` ([#3989])
- Remove `Url.is_archive` - `archive://` is no longer built in and can now be registered by plugins ([#4118])
- Make `mgr::Yanked`, `tab::Selected`, and the `@yank` DDS event return `File` instead of `Url` from `__pairs()` ([#4096])
- Remove `help:filter` action since the filter input is now always available ([#4074])
- `[help]` of `theme.toml`: supersede `on` with `chord`, supersede `run` and `desc` with `action`, remove `footer` ([#4074])
- Remove Legacy Console Mode on Windows ([#3989])
### Deprecated
- Deprecate `backward --far` and `forward --far` in favor of `backward wide` and `forward wide`, respectively ([#4012])
- Deprecate `tab::Mode.is_visual` in favor of the new `tab::Mode.is_normal` ([#4101])
- Deprecate `Url.is_regular`, `Url.is_search`, and `Url.domain` in favor of `Url.spec.is_regular`, `Url.spec.is_search`, and `Url.spec.domain`, respectively ([#4118])
### Fixed
- Normalize `\\?\`-prefixed Verbatim paths when creating relative symlinks on Windows ([#4067])
- Keep package hashes indifferent to line endings when `ya pkg` pulls packages ([#4064])
- Use WebP as `magick` preset preloader cache format to keep image transparency ([#4065])
- Use cleaned and normalized `$PWD` as startup CWD to avoid MSYS2/git-bash path quirks ([#4068])
### Improved
- Eliminate hacks in image and UI conflict resolution ([#4022])
## [v26.5.6]
### Added
- Custom tab name ([#3666])
- New `--in` for `search` action to set search directory ([#3696])
- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854])
- Drag-resize panes with mouse ([#3890])
- Multi-file spotter ([#3733])
- New `app:theme` action that hot-reload user themes/flavors ([#3906])
- Dynamic open/opener Lua API ([#3901])
- Dynamic previewer Lua API ([#3891])
- Custom styles for plugins ([#3934])
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
- Certificate authentication for SFTP VFS provider ([#3716])
- New `hovered` condition specifying different icons for hovered files ([#3728])
- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638])
- New `ya.exec()` API and `ya exec` subcommand to execute an action and await its result ([#3780])
- New `sort_fallback` option to control fallback sorting behavior ([#3077])
- New `fs.access()` API to access the filesystem ([#3668])
- New `relay-notify-push` DDS event to customize the notification handler ([#3642])
- New `ind-app-title` DDS event to customize the app title ([#3684])
- New `ind-hidden` and `key-hidden` DDS events to change hidden status in Lua ([#3748])
- New `marker_symbol` option to specify the symbol used for marking files ([#3689])
- New `--discard` for `ya pkg` that discard local changes made to packages ([#3781])
- New `bulk_exit` action that customizes the prompt for bulk operations ([#3792])
- New `fs.unique()` creates a unique file or directory ([#3677])
- New `download` DDS event fires when remote files are downloaded ([#3687])
- New `ind-which-activate` DDS event to change the which component behavior ([#3608])
- New `hey` DDS event fires when static messages are restored from persistence ([#3725])
- New `cx.which` API to access the which component state ([#3617])
- New experimental `ya.co()` API that creates a coroutine ([#3757])
### Changed
- Upgrade Lua to 5.5 ([#3633])
- Change preset <kbd>t</kbd> for creating tabs to <kbd>t</kbd><kbd>t</kbd> to avoid conflict with new <kbd>t</kbd><kbd>r</kbd> for renaming tabs ([#3666])
- Make `ui.Style` immutable ([#3862])
- Remove `title_format` in favor of new `ind-app-title` DDS event for flexible title customization ([#3684])
- Remove `micro_workers` and `macro_workers` in favor of finer control over concurrent workers ([#3661])
- Rename `id` of the fetcher rule to `group` ([#3943])
### Deprecated
- Deprecate `fs.unique_name()` in favor of `fs.unique()` to fix a TOCTOU race condition ([#3677])
### Fixed
- Chafa v1.18.1 causes random ghost keypresses when previewing images ([#3678])
- `paste --force` doesn't work on existing read-only files with the same name ([#3894])
- Be a little defensive while parsing the output of `7zz -ba` ([#3744])
- Make `ya pkg` ignore default remote name in user Git config ([#3648])
- Archive extraction fails for target paths with non-ASCII characters on Windows ([#3607])
- Escape backslashes in ImageMagick font path parameter ([#3708])
### Improved
- Reduce memory allocations by using Lua 5.5 external strings ([#3634])
- Reuse previewed and spotted widgets when possible ([#3765])
- Avoid unnecessary allocations in code highlighting ([#3804])
## [v26.1.22]
### Added
- Tree view for the preset archive previewer ([#3525])
- Support compressed tarballs (`.tar.gz`, `.tar.bz2`, etc.) in the preset archive previewer ([#3518])
- Check and refresh the file list when the terminal gains focus ([#3561])
- Experimental module-level async support ([#3594])
- Disable ANSI escape sequences in `ya pkg` when stdout is not a TTY ([#3566])
- New `Path.os()` API creates an OS-native `Path` ([#3541])
### Fixed
- Smart-case in interactive `cd` broken due to a typo ([#3540])
- Fix shell formatting for non-spread opener rules ([#3532])
- `sort extension` excludes directories since only files have extensions ([#3582])
- Account for URL covariance in `Url:join()` ([#3514])
## [v26.1.4]
### Added
- 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
@ -163,11 +27,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New experimental `ya.async()` API ([#3422])
- New `overall` option to set the overall background color ([#3317])
- Rounded corners for indicator bar ([#3419])
- New `bulk_rename` action always renames files with the editor ([#2984])
- New `bulk_rename` command always renames files with the editor ([#2984])
- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037])
- New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])
- `filter` by full path (prefix + filename) in search view instead of just filename ([#2915])
- New `casefy` action for case conversion of the input content ([#3235])
- New `casefy` command for case conversion of the input content ([#3235])
- Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964])
- Support `.deb` packages ([#2807], [#3128], [#3209])
- Port several widespread GUI keys to the input component ([#2849])
@ -188,7 +52,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Rename `name` to `url` for open, fetchers, spotters, preloaders, previewers, filetype, and `globs` icon rules to support virtual file system ([#3034])
- Rename `mime` fetcher to `mime.local`, and introduce `mime.dir` fetcher to support folder MIME types ([#3222])
- Reclassify `hovered` and `preview_hovered` under `[mgr]` of `theme.toml` into `[indicator]` as `current` and `preview`, respectively ([#3419])
- Remove `$0` parameter in opener rules to make the `open` action work under empty directories ([#3226])
- Remove `$0` parameter in opener rules to make the `open` command work under empty directories ([#3226])
- Return `Path` instead of `Url` from `Url:strip_prefix()` and `File.link_to` to enforce type safety ([#3361], [#3385])
- Use `body` instead of the term `content` in confirmations ([#2921])
- Use `u16` instead of `u32` as the type of `max_width` and `max_height` options to avoid memory exhaustion ([#3313])
@ -196,7 +60,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Deprecated
- Deprecate `$n`, `$@` (\*nix) and `%n`, `%*` (Windows) in `shell` action and opener rules in favor of new shell formatting ([#3232])
- Deprecate `$n`, `$@` (\*nix) and `%n`, `%*` (Windows) in `shell` command and opener rules in favor of new shell formatting ([#3232])
- Deprecate `ya.hide`, `ya.render`, and `ya.truncate` in favor of `ui.hide`, `ui.render`, and `ui.truncate` ([#2939])
- Deprecate `position` property of `ya.input()` in favor of `pos` to align with `ya.confirm()` and its type `ui.Pos` ([#2921])
- Deprecate `cx.tasks.progress` in favor of `cx.tasks.summary` ([#3131])
@ -207,7 +71,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- User-prepended open rules do not override presets ([#3360])
- Respect user's system media opener instead of hardcoding `mpv` ([#2959])
- Incorrect `$0` and `$@` parameters in `shell` action under empty directories ([#3225])
- Incorrect `$0` and `$@` parameters in `shell` command under empty directories ([#3225])
- Avoid appending a newline when reading clipboard contents ([#3059])
- Renew package `rev` only when it's empty ([#3200])
- Suspend only when there is a parent process ([#3008])
@ -261,15 +125,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New `ya.emit()` API ([#2653])
- New `fs.calc_size()` API ([#2691])
- Allow custom exit code with `quit --code` ([#2609])
- New `--hovered` for the `copy` action ([#2709])
- New `--hovered` for the `copy` command ([#2709])
- `s` and `S` keybinds in the input component ([#2678])
- Limit memory usage for previewing large images ([#2602])
- Show error when image preview fails ([#2706])
- New `ui.Align`, `ui.Wrap`, and `ui.Edge` ([#2802])
- Make `ui.Line` renderable ([#2743])
- Checks in `ya pub` and `ya emit` subcommands to verify receiver exists and has necessary abilities ([#2696])
- Make the hover state for `reveal`, `sort`, and `hidden` actions stable ([#2657])
- New `--no-dummy` option for `reveal` action ([#2664])
- Make the hover state for `reveal`, `sort`, and `hidden` commands stable ([#2657])
- New `--no-dummy` option for `reveal` command ([#2664])
- Fall back to `CSI 16 t` when PowerShell OpenSSH returns a fake terminal size ([#2636])
### Changed
@ -313,7 +177,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Enhance `fzf` integration ([#2553])
- Platform-specific key binding ([#2526])
- Custom search engine Lua API ([#2452])
- New `follow` action to follow files pointed to by symlinks ([#2543])
- New `follow` command to follow files pointed to by symlinks ([#2543])
- Allow `tab_swap` to cycle tabs ([#2456])
- Show error message when directory fails to load ([#2527])
- New `symlink_target` to style the target of symbolic links ([#2522])
@ -321,7 +185,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New `<C-A>` and `<C-E>` keybindings to select entire line for the input component ([#2439])
- New `fs.expand_url()` API ([#2476])
- New `ui.Text:scroll()` API for setting text to scroll horizontally or vertically ([#2589])
- Allow initializing input when opening it with actions like `rename`, `create`, `find`, `filter`, etc. ([#2578])
- Allow initializing input when opening it with commands like `rename`, `create`, `find`, `filter`, etc. ([#2578])
- New `@sync peek` annotation for sync previewers ([#2487])
- New `ya.id("app")` to get `YAZI_ID` in plugins ([#2503])
- New `base` field for the `Url` ([#2492])
@ -331,7 +195,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Changed
- Navigation wraparound with new `arrow prev` and `arrow next` actions ([#2485], [#2540])
- Navigation wraparound with new `arrow prev` and `arrow next` commands ([#2485], [#2540])
- Swap default key bindings for fzf and zoxide ([#2546])
- Switch to `resvg` as the SVG renderer ([#2581])
- Make `frag`, `name`, `stem`, `ext`, and `parent` on `Url`, `name` on `tab::Tab`, and `is_hovered` on `fs::File` properties ([#2572])
@ -373,11 +237,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added
- Allow to specify layer for keymap actions ([#2399])
- Allow to specify layer for keymap commands ([#2399])
- New `rt` and `th` allow to access user configuration and theme scheme in sync/async plugins consistently ([#2389], [#2392], [#2393], [#2397])
- New `tbl_col` and `tbl_cell` in theme system for spotter table styling ([#2391])
- Allow different separators to be applied individually to the left and right sides of the status bar ([#2313])
- `ripgrep-all` support for the `search` action ([#2383])
- `ripgrep-all` support for the `search` command ([#2383])
- Respect the user's `max_width` setting in the preset PDF preloader ([#2331])
- Respect the user's `wrap` setting in the preset JSON previewer ([#2337])
- Respect the user's `image_alloc` setting in the preset ImageMagick previewer ([#2403])
@ -418,15 +282,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Mount manager ([#2199])
- New `ya.confirm()` API ([#2095])
- New `arrow top` and `arrow bot` actions to jump to the top and bottom ([#2294])
- Support end of options (`--`) marker for all actions ([#2298])
- New `arrow top` and `arrow bot` commands to jump to the top and bottom ([#2294])
- Support end of options (`--`) marker for all commands ([#2298])
- Replace mode and Vim motions (`W`, `E`, `B`, `^`, `_`) for inputs ([#2143])
- New `ya pack -d` subcommand to delete packages ([#2181])
- `ya pack` supports adding and deleting multiple packages at once ([#2257])
- Theme support for the spotter border and title ([#2002])
- Use positional argument instead of `--args` for the `plugin` action ([#2299])
- Use positional argument instead of `--args` for the `plugin` command ([#2299])
- Support and hide Windows system files by default ([#2149])
- New `--no-cwd-file` option for the `close` action ([#2185])
- New `--no-cwd-file` option for the `close` command ([#2185])
- Prompt users missing fzf in the zoxide plugin ([#2122])
- More decent package locking mechanism ([#2168])
- Custom modal component API ([#2205])
@ -442,7 +306,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Deprecated
- Deprecate `--args` in the `plugin` action in favor of a 2nd positional parameter ([#2299])
- Deprecate `--args` in the `plugin` command in favor of a 2nd positional parameter ([#2299])
- Deprecate plugin entry file `init.lua` in favor of `main.lua` ([#2168])
- Deprecate `arrow -99999999` and `arrow 99999999` in favor of `arrow top` and `arrow bot` ([#2294])
- Deprecate the numeric return value of preloaders in favor of a boolean return value ([#2253])
@ -450,7 +314,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Fixed
- `before_ext` excludes directories since only files have extensions ([#2132])
- `before_ext` should exclude directories since only files have extensions ([#2132])
- Element style of `ui.Text` was not applied to the entire area ([#2093])
- Incorrect monorepo sub-plugin path resolution ([#2186])
- Use `u32` for parsing Linux partition blocks ([#2234])
@ -469,7 +333,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- More supported archive formats to the preset config ([#1926])
- New `fs.create()` Lua API ([#2068])
- New `--cwd` parameter for the `shell` action and `fs.cwd()` API ([#2060])
- New `--cwd` parameter for the `shell` command and `fs.cwd()` API ([#2060])
- Allow `noop` for single-key chords by removing the mixing length limit ([#2064])
- Support for Android platform in the `for` qualifier of opener ([#2041])
@ -487,7 +351,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Fixed
- Correctly handle CRLF on Windows in preset archive and JSON plugins ([#2017])
- Correctly handle CRLF on Windows in preset `archive` and `json` plugins ([#2017])
- Failed to parse certain image dimensions for Überzug++ backend ([#2020])
- Disable passthrough when the user launches Yazi in Neovim inside tmux ([#2014])
@ -498,8 +362,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Spotter ([#1802])
- Support transparent image preview ([#1556])
- Auto switch between dark and light icons/flavors based on terminal backgrounds ([#1946])
- Allow disabling certain preset keybinds with the new `noop` virtual action ([#1882])
- New `ya emit` and `ya emit-to` subcommands to emit actions to a specified instance for execution ([#1979])
- Allow disabling certain preset keybinds with the new `noop` virtual command ([#1882])
- New `ya emit` and `ya emit-to` subcommands to emit commands to a specified instance for execution ([#1979])
- Custom styles for the `confirm` component ([#1789])
- Make the builtin `extract` plugin support compressed tarballs (`*.tar.gz`, `*.tar.bz2`, etc.) ([#1583])
- Launch from preset settings if the user's config cannot be parsed ([#1832])
@ -533,15 +397,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Use an `Error` userdata instead of a plain error code for I/O errors ([#1939])
- Remove `ui.ListItem` since it's no longer necessary ([#1772])
- Decouple coordinates from `ui.List`, `ui.Bar`, `ui.Border`, and `ui.Gauge` ([#1782])
- Make `backspace` action not close the input even when value is empty ([#1680])
- Remove the meaningless `--confirm` option to simplify the `shell` action ([#1982])
- Make `backspace` command not close the input even when value is empty ([#1680])
- Remove the meaningless `--confirm` option to simplify the `shell` command ([#1982])
- Use `dark` and `light` instead of `use` under `[flavor]` to support auto-switching between light and dark modes ([#1946])
- Unify the `fg_dark` and `fg_light` into one `fg` since `fg_light` is redundant and never used ([#1946])
- Extend the available styles for `mode` by separating `mode` from the `separator` styles ([#1953])
### Deprecated
- Deprecate `--sync` option for the `plugin` action ([#1891])
- Deprecate `--sync` option for the `plugin` command ([#1891])
- Deprecate `ui.Paragraph` in favor of `ui.Text` ([#1776])
- Deprecate the task info of `peek()`, `seek()`, and `preload()` from `self` in favor of a `job` parameter ([#1966])
- Deprecate parameter list of `entry()` from its first argument in favor of a `job` parameter ([#1966])
@ -598,7 +462,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New confirm component ([#1167])
- Word wrapping in `code` previewer ([#1159])
- New `--dir` option for `create` action ([#1505])
- New `--dir` option for `create` command ([#1505])
- New `ext()` method for `Url` ([#1528])
- Make the builtin `code` previewer handle invalid carriage return chars and binary streams better ([#1550])
@ -621,10 +485,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Start with multiple tabs with different paths ([#1443])
- Key notion shorthands such as `<C-S-x>` as `<C-X>` ([#1448])
- Support `F13` - `F19` keys ([#1446])
- New `--cursor` for the `shell` action ([#1422])
- New `search_do` action to make it easier to achieve a flat view ([#1431])
- New `--cursor` for the `shell` command ([#1422])
- New `search_do` command to make it easier to achieve a flat view ([#1431])
- Portrait orientation preview for EXIF image ([#1412])
- Keybinding for the `hardlink` action ([#1461])
- Keybinding for the `hardlink` command ([#1461])
- New `empty` previewer for empty and `/proc/*` files ([#1482])
- Note about filtering in the help menu ([#1361])
- New `tab` DDS event on tab switch ([#1474])
@ -652,12 +516,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Font preview ([#1048])
- SVG, HEIC, AVIF, and JPEG XL preview support ([#1050], [#1249])
- Simplify keybindings ([#1241])
- New `hardlink` action to create hard links ([#1268])
- New command `hardlink` to create hard links ([#1268])
- Keep file creation time on macOS and Windows ([#1169])
- Sort randomly ([#1291])
- New linemode to show file ownership ([#1238])
- New linemode to show file ctime ([#1295])
- New `--hovered` option for the `rename` and `remove` actions ([#1227])
- New `--hovered` option for the `rename` and `remove` commands ([#1227])
- Support Super/Command/Windows key with `D-` notation ([#1069])
- Interactive `cd` path auto-completion supports `~` expansion ([#1081])
- Preview files containing non-UTF-8 characters ([#958])
@ -682,7 +546,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Use Ctrl+c instead of Ctrl+q as the universal close key to follow the conventions
- Replace Alt+k/Alt+j with K/J as the `seek` keybindings to avoid Alt key not working in certain terminals
- Replace Ctrl+Enter with Shift+Enter as the alternative key for Shift+o so that it corresponds to Enter being `o` (without Shift)
- keep original state of `sort` action in favor of specifying `yes` or `no` to explicitly apply a new state to its `--reverse`, `--dir-first`, and `--translit`
- keep original state of `sort` command in favor of specifying `yes` or `no` to explicitly apply a new state to its `--reverse`, `--dir-first`, and `--translit`
- Move `mime` plugin from `[plugin.preloaders]` to `[plugin.fetchers]` of yazi.toml
- Turn `success` and `code` into properties of `Status` instead of methods
- Remove `fs.cha_follow(url)` in favor of `fs.cha(url, true)`
@ -709,7 +573,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Data distribution service ([#826], [#855], [#861], [#867], [#868], [#871], [#880], [#895], [#913], [#928], [#933], [#940])
- Re-implement fzf and zoxide as built-in plugins ([#884], [#881])
- Preserve files' modified at timestamp while copying ([#926])
- New `--orphan` option for `shell` action ([#887])
- New `--orphan` option for `shell` command ([#887])
- Smart-case for completion of interactive `cd` paths ([#910])
- Allow creating a tab with the startup directory through `tab_create` without specifying a path ([#917])
- Bunch of new debugging information to `yazi --debug` ([#824])
@ -722,7 +586,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Deprecated
- Deprecate the `jump` action in favor of `plugin fzf` and `plugin zoxide` ([#884], [#881])
- Deprecate the `jump` command in favor of `plugin fzf` and `plugin zoxide` ([#884], [#881])
### Fixed
@ -754,7 +618,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New `<Home>`, `<End>`, and `<Delete>` keybindings for inputs ([#665])
- New `<C-p>` and `<C-n>` for the select component to move the cursor up/down ([#779])
- New `Ctrl+[` as an alternative to the escape key ([#763])
- New option `--hovered` for the `open` action allows only to open the currently hovered file ([#687])
- New option `--hovered` for the `open` command allows only to open the currently hovered file ([#687])
- Support musl builds for Linux ([#759])
- New `--debug` flag to print debug information ([#794])
- Send a foreground notification to the user when the process fails to run ([#775])
@ -765,7 +629,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Ability to sort candidates in the which component ([#662])
- Expose selected/yanked files as Lua API ([#674])
- New `cx.yanked` API to access yanked files ([#788])
- New `$0` (Unix) / `%0` (Windows) to access the hovered file in `shell` action ([#738])
- New `$0` (Unix) / `%0` (Windows) to access the hovered file in `shell` command ([#738])
- New `ya.hide()` API to hide the UI temporarily ([#792])
- Allow both `/` and `\` for folder creation on Windows ([#751])
- New `parse()` method for the line elements to parse ANSI sequences
@ -792,13 +656,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added
- Preview image over SSH ([#585])
- New `unyank` action ([#313])
- New `unyank` command ([#313])
- Customize number of columns of the which component ([#571])
- Support passing arguments to plugin ([#587])
- New `image_quality` and `sixel_fraction` options to configure the image preview quality ([#576])
- New `ya.which()` API for custom key events ([#617])
- New `ya.quote()` API to quote strings safely
- `plugin` action for each layer
- `plugin` command for each layer
- Plugin-specific state persistence ([#590])
- Allow to configure image filter ([#586])
- Shorten unit names and add more units to `ya.readable_size()`
@ -836,7 +700,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Improved
- New event system allows multiple actions to reuse a single render ([#561])
- New event system allows multiple commands to reuse a single render ([#561])
## [v0.2.1]
@ -848,17 +712,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added
- New `filter` action to filter files on the fly ([#454])
- New `filter` command to filter files on the fly ([#454])
- Sort by file extension ([#405])
- Custom preloader and previewer ([#401])
- New `plugin` action to run Lua plugins
- New `plugin` command to run Lua plugins
- Auto-completion for input component ([#324], [#353], [#352])
- Start with the specified file hovers over ([#358])
- Emacs readline keybindings for inputs ([#345], [#382])
- New `--empty` and `--cursor` options for the `rename` action ([#513])
- New `--follow` option for `paste` action ([#436])
- Make `copy` action work over SSH with OSC 52 ([#447])
- New `reveal` action ([#341])
- New `--empty` and `--cursor` options for the `rename` command ([#513])
- New `--follow` option for `paste` command ([#436])
- Make `copy` command work over SSH with OSC 52 ([#447])
- New `reveal` command ([#341])
- Support colored icons ([#503])
- Support highlighting specific file types ([#510])
- Make the position of input and select components customizable ([#361])
@ -880,8 +744,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Changed
- Rename the option `layout` to `ratio` to make it more self-explanatory
- Rename the `peek` action to `seek` to better convey the action of "seeking for" content to preview
- Rename the `--dir_first` option of `sort` action to `--dir-first` to make it consistent with the style of other actions
- Rename the `peek` command to `seek` to better convey the action of "seeking for" content to preview
- Rename the `--dir_first` option of `sort` command to `--dir-first` to make it consistent with the style of other commands
- Replace `[plugins.preload]` with the `init.lua` entry file
### Fixed
@ -906,22 +770,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added
- New `find` action to find files ([#104])
- New `find` command to find files ([#104])
- Linemode to show extra file info ([#291])
- New `sort_sensitive` option ([#155])
- Cross-platform opener rules ([#289])
- Multiple openers for a single open rule ([#154])
- Vim-like `gg`, `G` in the preset key mappings for boundary jumps
- Theme system ([#161])
- New `--force` option for `remove`, `create`, `rename` actions ([#179], [#208])
- New `--force` option for `remove`, `create`, `rename` commands ([#179], [#208])
- Image preview within tmux ([#147])
- New `link` action creates symlinks to the yanked files ([#167])
- New `link` command that creates symlinks to the yanked files ([#167])
- New `orphan` option for opener rules to detach processes from the task scheduler ([#216])
- New `backward` and `forward` actions
- New `--smart` option for the `find` action to support smart case ([#240])
- New `backward` and `forward` commands
- New `--smart` option for the `find` command to support smart case ([#240])
- Sorting for each tab individually ([#131])
- Suspend process with `Ctrl+z` ([#120])
- Percentage values for the `arrow` action to scroll half/full page (with newly added Vi-like `<C-u>`, `<C-d>`, `<C-b>`, and `<C-f>` keybindings) ([#213])
- Percentage values for the `arrow` command to scroll half/full page (with newly added Vi-like `<C-u>`, `<C-d>`, `<C-b>`, and `<C-f>` keybindings) ([#213])
- Show keywords when in search mode ([#152])
- Tab switch wraparound ([#160])
- Highlight matched keywords in find mode ([#211])
@ -930,7 +794,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Use of environment variables in `cd` paths ([#241])
- Nix Flakes package ([#205])
- New `V`, `D`, `C` Vim-like keybindings for the input component
- New `--no-cwd-file` option for the `quit` action to exit without writing the CWD file ([#245])
- New `--no-cwd-file` option for the `quit` command to exit without writing the CWD file ([#245])
- Fallback to built-in code highlighting if `jq` is not installed ([#151])
- New `realtime` option for the input to support real-time input feedback ([#127])
- RGBA-16 image preview ([#250])
@ -967,7 +831,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Scrollable preview ([#86])
- Natural sorting ([#82])
- Windows support
- New `copy` action to copy file paths to clipboard ([#72])
- New `copy` command to copy file paths to clipboard ([#72])
- File chooser mode ([#69])
- Show symlink path ([#67])
- Respect `$EDITOR` environment variable when opening text files ([#91])
@ -1000,8 +864,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Code highlighting supports more languages ([#22])
- Change the shell CWD on exit with the shell wrapper ([#40])
- Allow customizing the display name of openers ([#31])
- New `shell` action ([#24])
- Command template support for the `shell` action ([#48])
- New `shell` command ([#24])
- Command template support for the `shell` command ([#48])
- Interactive `cd` ([#43])
- Show the output of running tasks in real time ([#17])
- Allow using the current directory name as tab name ([#41])
@ -1016,7 +880,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- PDF cache cannot be generated with a large `max_width` value ([#28])
- `show_hidden` option not working ([#47])
- Wrong task name when `shell` action has no arguments
- Wrong task name when `shell` command has no arguments
### Improved
@ -1029,7 +893,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added
- New `sort` action to change sorting method on the fly ([#7])
- New `sort` command to change sorting method on the fly ([#7])
- Which-key component to support multi-key chords ([#4])
- Hover the cursor over newly created files automatically ([#10])
- Make folders openable ([#9])
@ -1050,8 +914,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Arrow keys are now bound for navigation by default (along with existing Vim-style bindings)
- Horizontal scrolling support for the `input` component
- Visual mode for the input component
- New `yank` and `paste` actions for the input component
- New `undo` and `redo` actions for the `input` component
- New `yank` and `paste` commands for the input component
- New `undo` and `redo` commands for the `input` component
### Fixed
@ -1066,7 +930,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added
- Preset configurations
- New `open` action
- New `open` command
- Select component for interactive `open`
- Plain text and archive preview
- Search files with `fd` and `rg`
@ -1112,9 +976,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[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
[v26.1.22]: https://github.com/sxyazi/yazi/compare/v26.1.4...v26.1.22
[v26.5.6]: https://github.com/sxyazi/yazi/compare/v26.1.22...v26.5.6
[#4]: https://github.com/sxyazi/yazi/pull/4
[#5]: https://github.com/sxyazi/yazi/pull/5
[#6]: https://github.com/sxyazi/yazi/pull/6
@ -1652,7 +1513,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3038]: https://github.com/sxyazi/yazi/pull/3038
[#3059]: https://github.com/sxyazi/yazi/pull/3059
[#3067]: https://github.com/sxyazi/yazi/pull/3067
[#3077]: https://github.com/sxyazi/yazi/pull/3077
[#3083]: https://github.com/sxyazi/yazi/pull/3083
[#3084]: https://github.com/sxyazi/yazi/pull/3084
[#3091]: https://github.com/sxyazi/yazi/pull/3091
@ -1691,9 +1551,7 @@ 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
@ -1707,79 +1565,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#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
[#3514]: https://github.com/sxyazi/yazi/pull/3514
[#3518]: https://github.com/sxyazi/yazi/pull/3518
[#3525]: https://github.com/sxyazi/yazi/pull/3525
[#3532]: https://github.com/sxyazi/yazi/pull/3532
[#3540]: https://github.com/sxyazi/yazi/pull/3540
[#3541]: https://github.com/sxyazi/yazi/pull/3541
[#3561]: https://github.com/sxyazi/yazi/pull/3561
[#3566]: https://github.com/sxyazi/yazi/pull/3566
[#3582]: https://github.com/sxyazi/yazi/pull/3582
[#3594]: https://github.com/sxyazi/yazi/pull/3594
[#3607]: https://github.com/sxyazi/yazi/pull/3607
[#3608]: https://github.com/sxyazi/yazi/pull/3608
[#3617]: https://github.com/sxyazi/yazi/pull/3617
[#3633]: https://github.com/sxyazi/yazi/pull/3633
[#3634]: https://github.com/sxyazi/yazi/pull/3634
[#3638]: https://github.com/sxyazi/yazi/pull/3638
[#3642]: https://github.com/sxyazi/yazi/pull/3642
[#3648]: https://github.com/sxyazi/yazi/pull/3648
[#3661]: https://github.com/sxyazi/yazi/pull/3661
[#3666]: https://github.com/sxyazi/yazi/pull/3666
[#3668]: https://github.com/sxyazi/yazi/pull/3668
[#3677]: https://github.com/sxyazi/yazi/pull/3677
[#3678]: https://github.com/sxyazi/yazi/pull/3678
[#3684]: https://github.com/sxyazi/yazi/pull/3684
[#3687]: https://github.com/sxyazi/yazi/pull/3687
[#3689]: https://github.com/sxyazi/yazi/pull/3689
[#3696]: https://github.com/sxyazi/yazi/pull/3696
[#3708]: https://github.com/sxyazi/yazi/pull/3708
[#3716]: https://github.com/sxyazi/yazi/pull/3716
[#3725]: https://github.com/sxyazi/yazi/pull/3725
[#3728]: https://github.com/sxyazi/yazi/pull/3728
[#3733]: https://github.com/sxyazi/yazi/pull/3733
[#3744]: https://github.com/sxyazi/yazi/pull/3744
[#3748]: https://github.com/sxyazi/yazi/pull/3748
[#3757]: https://github.com/sxyazi/yazi/pull/3757
[#3765]: https://github.com/sxyazi/yazi/pull/3765
[#3780]: https://github.com/sxyazi/yazi/pull/3780
[#3781]: https://github.com/sxyazi/yazi/pull/3781
[#3792]: https://github.com/sxyazi/yazi/pull/3792
[#3793]: https://github.com/sxyazi/yazi/pull/3793
[#3804]: https://github.com/sxyazi/yazi/pull/3804
[#3813]: https://github.com/sxyazi/yazi/pull/3813
[#3846]: https://github.com/sxyazi/yazi/pull/3846
[#3854]: https://github.com/sxyazi/yazi/pull/3854
[#3862]: https://github.com/sxyazi/yazi/pull/3862
[#3890]: https://github.com/sxyazi/yazi/pull/3890
[#3891]: https://github.com/sxyazi/yazi/pull/3891
[#3894]: https://github.com/sxyazi/yazi/pull/3894
[#3901]: https://github.com/sxyazi/yazi/pull/3901
[#3906]: https://github.com/sxyazi/yazi/pull/3906
[#3934]: https://github.com/sxyazi/yazi/pull/3934
[#3943]: https://github.com/sxyazi/yazi/pull/3943
[#3970]: https://github.com/sxyazi/yazi/pull/3970
[#3989]: https://github.com/sxyazi/yazi/pull/3989
[#3990]: https://github.com/sxyazi/yazi/pull/3990
[#4005]: https://github.com/sxyazi/yazi/pull/4005
[#4012]: https://github.com/sxyazi/yazi/pull/4012
[#4022]: https://github.com/sxyazi/yazi/pull/4022
[#4031]: https://github.com/sxyazi/yazi/pull/4031
[#4040]: https://github.com/sxyazi/yazi/pull/4040
[#4064]: https://github.com/sxyazi/yazi/pull/4064
[#4065]: https://github.com/sxyazi/yazi/pull/4065
[#4067]: https://github.com/sxyazi/yazi/pull/4067
[#4068]: https://github.com/sxyazi/yazi/pull/4068
[#4074]: https://github.com/sxyazi/yazi/pull/4074
[#4080]: https://github.com/sxyazi/yazi/pull/4080
[#4096]: https://github.com/sxyazi/yazi/pull/4096
[#4101]: https://github.com/sxyazi/yazi/pull/4101
[#4104]: https://github.com/sxyazi/yazi/pull/4104
[#4108]: https://github.com/sxyazi/yazi/pull/4108
[#4118]: https://github.com/sxyazi/yazi/pull/4118
[#4120]: https://github.com/sxyazi/yazi/pull/4120
[#4144]: https://github.com/sxyazi/yazi/pull/4144

View file

@ -11,7 +11,6 @@ This guide will help you understand how to contribute to the project.
3. [Development Setup](#development-setup)
4. [How to Contribute](#how-to-contribute)
5. [Pull Requests](#pull-requests)
6. [AI Policy](#ai-policy)
## Getting Started
@ -165,10 +164,3 @@ We want you to succeed, and it can be discouraging to find that a lot of re-work
- Your code passes all tests and lints.
- Your pull request description clearly explains the changes and why they are needed.
4. Address any review comments. Make sure to push updates to the same branch on your fork.
## AI Policy
1. All issue, PR, and discussion descriptions must be written by humans, not AI.
2. Any use of AI must be disclosed. You must declare which model you used and the extent of AI assistance.
3. Any AI-generated code must be reviewed, tested, and simplified by a human before publishing. This requires you to fully understand how it interacts with the greater system without AI assistance.
4. Any AI tools used must explicitly state they do not assert copyright over the work.

3402
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,8 @@
[workspace]
resolver = "3"
resolver = "2"
members = [ "yazi-*" ]
default-members = [ "yazi-fm", "yazi-cli" ]
[workspace.package]
edition = "2024"
version = "26.5.6"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
rust-version = "1.95.0"
[profile.dev]
debug = "line-tables-only"
@ -35,53 +26,43 @@ lto = false
debug = false
[workspace.dependencies]
ansi-to-tui = "8.0.1"
anyhow = "1.0.104"
arc-swap = { version = "1.9.2", features = [ "serde" ] }
ansi-to-tui = "8.0.0"
anyhow = "1.0.100"
base64 = "0.22.1"
bitflags = { version = "2.13.1", features = [ "serde" ] }
chrono = "0.4.45"
clap = { version = "4.6.2", features = [ "derive" ] }
compact_str = { version = "0.10.0", features = [ "serde" ] }
bitflags = { version = "2.10.0", features = [ "serde" ] }
clap = { version = "4.5.53", features = [ "derive" ] }
core-foundation-sys = "0.8.7"
data-encoding = "2.11.0"
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0"
dyn-clone = "1.0.20"
either = { version = "1.16.0" }
foldhash = "0.2.0"
futures = "0.3.33"
globset = "0.4.19"
hashbrown = { version = "0.17.1", features = [ "serde" ] }
image = { version = "0.25.10", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp" ] }
indexmap = { version = "2.14.0", features = [ "serde" ] }
inventory = "0.3.24"
libc = "0.2.186"
lru = "0.18.1"
mlua = { version = "0.12.0", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] }
objc2 = "0.6.4"
ordered-float = { version = "5.3.0", features = [ "serde" ] }
futures = "0.3.31"
globset = "0.4.18"
hashbrown = { version = "0.16.1", features = [ "serde" ] }
indexmap = { version = "2.12.1", features = [ "serde" ] }
libc = "0.2.178"
lru = "0.16.2"
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"
rand = { version = "0.10.2", default-features = false, features = [ "std", "sys_rng" ] }
ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] }
ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] }
regex = "1.13.1"
russh = { version = "0.62.2", default-features = false, features = [ "ring", "rsa" ] }
rand = { version = "0.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] }
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.2"
russh = { version = "0.56.0", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"
serde = { version = "1.0.229", features = [ "derive" ] }
serde_json = "1.0.151"
serde_with = "3.21.0"
strum = { version = "0.28.0", features = [ "derive" ] }
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.148"
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.19"
tokio = { version = "1.53.0", features = [ "full" ] }
tokio-stream = "0.1.18"
tokio-util = "0.7.18"
toml = { version = "1.1.3" }
thiserror = "2.0.17"
tokio = { version = "1.48.0", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.17"
toml = { version = "0.9.10" }
tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] }
twox-hash = { version = "2.1.3", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
typed-path = "0.12.3"
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
typed-path = "0.12.0"
unicode-width = { version = "0.2.2", default-features = false }
uzers = "0.12.2"

View file

@ -1,11 +1,17 @@
<div align="center">
<sup>Special thanks to:</sup><br>
| <a href="https://go.warp.dev/yazi" target="_blank"><img alt="Warp sponsorship" width=350 src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png"><br><b>Warp, built for coding with multiple AI agents</b><br><sup>Available for macOS, Linux and Windows</sup></a> |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
<a href="https://go.warp.dev/yazi" target="_blank">
<sup>Special thanks to:</sup>
<br>
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png">
<br>
<h>Warp, built for coding with multiple AI agents</b>
<br>
<sup>Available for macOS, Linux and Windows</sup>
</a>
</div>
<br>
## Yazi - ⚡️ Blazing Fast Terminal File Manager
Yazi (means "duck") is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and customizable file management experience.
@ -23,8 +29,8 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
- 🧰 Integration with ripgrep, fd, fzf, zoxide
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
- 🔄 Bulk Rename/Create, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
- 🎨 Theme System, Mouse Support, Drag and Drop, Trash Bin, Custom Layouts, CSI u, OSC 52
- 🔄 Bulk Renaming, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
- ... and more!
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7
@ -47,24 +53,24 @@ Yazi is currently in heavy development, expect breaking changes.
## Image Preview
| Platform | Protocol | Support |
| ---------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) (>= 0.3.9) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required (>= 1.16.0) |
| Platform | Protocol | Support |
| ---------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |
| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio renders images at incorrect sizes |
| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required |
See https://yazi-rs.github.io/docs/image-preview for details.

View file

@ -1,11 +1,11 @@
[Desktop Entry]
Name=Yazi File Manager
Name=Yazi
Icon=yazi
Comment=Blazing fast terminal file manager written in Rust, based on async I/O
Terminal=true
TryExec=yazi
Exec=yazi %f
Exec=yazi %u
Type=Application
MimeType=inode/directory
Categories=System;FileManager;FileTools;ConsoleOnly
Categories=Utility;Core;System;FileTools;FileManager;ConsoleOnly
Keywords=File;Manager;Explorer;Browser;Launcher

View file

@ -1 +1 @@
{"flagWords":[],"version":"0.2","language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU","fellback","watchee","Textlike","sstr","pointee","writef","wakeup","nonblocking","sigwinch","timespec","termios","tcgetattr","tcsetattr","tcgetwinsize","rustix","codepoint","codepoints","Raterm","mimetypes","Mimelist","renderables","redrawer","Padable"]}
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing"],"version":"0.2","language":"en"}

12
flake.lock generated
View file

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1783279667,
"narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=",
"lastModified": 1763806073,
"narHash": "sha256-FHsEKDvfWpzdADWj99z7vBk4D716Ujdyveo5+A048aI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b",
"rev": "878e468e02bfabeda08c79250f7ad583037f2227",
"type": "github"
},
"original": {
@ -48,11 +48,11 @@
]
},
"locked": {
"lastModified": 1783320166,
"narHash": "sha256-l7C/OsjcnWDOk2K3ssj+SBduwL67LashjBqis9+t468=",
"lastModified": 1763952169,
"narHash": "sha256-+PeDBD8P+NKauH+w7eO/QWCIp8Cx4mCfWnh9sJmy9CM=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "20ee15370c9256669d66968b89ee20a4b0a4e673",
"rev": "ab726555a9a72e6dc80649809147823a813fa95b",
"type": "github"
},
"original": {

View file

@ -32,7 +32,7 @@
rev = self.shortRev or self.dirtyShortRev or "dirty";
date = self.lastModifiedDate or self.lastModified or "19700101";
version =
(builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version
(builtins.fromTOML (builtins.readFile ./yazi-fm/Cargo.toml)).package.version
+ "pre${builtins.substring 0 8 date}_${rev}";
in
{

View file

@ -2,7 +2,7 @@
mkShell,
yazi,
toolchain,
cspell,
nodePackages,
yazi-unwrapped,
}:
@ -16,7 +16,7 @@ mkShell {
"clippy"
];
})
cspell
nodePackages.cspell
];
inputsFrom = [ yazi-unwrapped ];

View file

@ -28,6 +28,9 @@ rustPlatform.buildRustPackage (finalAttrs: {
cargoLock = {
lockFile = "${src}/Cargo.lock";
#outputHashes = {
# "mlua-0.10.0" = "sha256-Xg6/jc+UP8tbJJ6x1sbAgt8ZHt051xEBBcjmikQqYlw=";
#};
};
env = {
@ -51,11 +54,6 @@ rustPlatform.buildRustPackage (finalAttrs: {
--fish ./yazi-boot/completions/yazi.fish \
--zsh ./yazi-boot/completions/_yazi
installShellCompletion --cmd ya \
--bash ./yazi-cli/completions/ya.bash \
--fish ./yazi-cli/completions/ya.fish \
--zsh ./yazi-cli/completions/_ya
# Resize logo
for RES in 16 24 32 48 64 128 256; do
mkdir -p $out/share/icons/hicolor/"$RES"x"$RES"/apps
@ -77,7 +75,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
hash = "sha256-kEVXejDg4ChFoMNBvKlwdFEyUuTcY2VuK9j0PdafKus=";
};
};
meta = {
description = "Blazing fast terminal file manager written in Rust, based on async I/O";

View file

@ -8,11 +8,6 @@ export YAZI_GEN_COMPLETIONS=1
git config --global --add safe.directory "*"
cargo build --release --locked --target "$1"
# Copy the binaries to a known location
mkdir -p "target/release"
cp "target/$1/release/ya" "target/release/ya"
cp "target/$1/release/yazi" "target/release/yazi"
# Package deb
if [[ "$ARTIFACT_NAME" == *-linux-* ]] && { [[ "$ARTIFACT_NAME" == *-aarch64-* ]] || [[ "$ARTIFACT_NAME" == *-x86_64-* ]]; }; then
cargo install cargo-deb
@ -21,8 +16,8 @@ fi
# Create the artifact
mkdir -p "$ARTIFACT_NAME/completions"
cp "target/release/ya" "$ARTIFACT_NAME"
cp "target/release/yazi" "$ARTIFACT_NAME"
cp "target/$1/release/ya" "$ARTIFACT_NAME"
cp "target/$1/release/yazi" "$ARTIFACT_NAME"
cp yazi-cli/completions/* "$ARTIFACT_NAME/completions"
cp yazi-boot/completions/* "$ARTIFACT_NAME/completions"
cp README.md LICENSE "$ARTIFACT_NAME"

View file

@ -7,14 +7,7 @@ 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 -c ~/.config/rules/eslint/eslint.config.js --fix -- $TOML_FILES
ESLINT_USE_FLAT_CONFIG=true eslint -c ~/.config/rules/eslint/eslint.config.cjs --fix -- $TOML_FILES

20
scripts/publish.sh Executable file
View file

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

View file

@ -2,32 +2,19 @@ const LABEL_NAME = "needs info"
const RE_VERSION = /Yazi\s+Version\s*:\s\d+\.\d+\.\d+\s\(/gm
const RE_DEPENDENCIES = /Dependencies\s+[/a-z]+\s*:\s/gm
const RE_CHECKLIST = /#{3}\s+Checklist\s+(?:^-\s+\[x]\s+.+?(?:\n|\r\n|$)){2}/gm
const RE_PR_CHECKLIST = /#{2}\s+Checklist\s+(?:^-\s+\[x]\s+.+?(?:\n|\r\n|$)){2}/gm
function pullRequestBody(content) {
if (RE_PR_CHECKLIST.test(content)) {
return null
}
return "All required checklist items must be checked in the PR description."
}
function bugReportBody(creator, content, hash) {
if (RE_DEPENDENCIES.test(content) && RE_CHECKLIST.test(content) && new RegExp(` \\(${hash}[a-f0-9]? `).test(content)) {
return null
}
return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!
I noticed that you did not correctly follow the issue template. Please ensure that:
return `Hey @${creator}, I noticed that you did not correctly follow the issue template. Please ensure that:
- The bug can still be reproduced on the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).
- The environment info (\`ya env\`) is updated for the newest nightly.
- The debug information (\`yazi --debug\`) is updated for the newest nightly.
- The non-optional items in the checklist are checked.
Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏
`
}
@ -36,26 +23,16 @@ function featureRequestBody(creator, content) {
return null
}
return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!
I noticed that you did not correctly follow the issue template. Please ensure that:
return `Hey @${creator}, I noticed that you did not correctly follow the issue template. Please ensure that:
- The requested feature does not exist in the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).
- The environment info (\`ya env\`) is updated for the newest nightly.
- The debug information (\`yazi --debug\`) is updated for the newest nightly.
- The non-optional items in the checklist are checked.
Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏
`
}
function skipValidation(context) {
const login = context.payload.issue?.user?.login || context.payload.pull_request?.user?.login
const owner = context.payload.repository?.owner?.login || context.repo.owner
return !!login && login === owner
}
module.exports = async ({ github, context, core }) => {
async function nightlyHash() {
try {
@ -89,7 +66,7 @@ module.exports = async ({ github, context, core }) => {
const { data: events } = await github.rest.issues.listEvents({
...context.repo,
issue_number: id,
per_page: 100,
per_page : 100,
})
const all = events.filter(v => v.event === "labeled" && v.label?.name === LABEL_NAME)
@ -105,7 +82,7 @@ module.exports = async ({ github, context, core }) => {
const { data: events } = await github.rest.issues.listEvents({
...context.repo,
issue_number: id,
per_page: 100,
per_page : 100,
})
const all = events.filter(v => v.event === "unlabeled" && v.label?.name === LABEL_NAME)
@ -124,14 +101,14 @@ module.exports = async ({ github, context, core }) => {
await github.rest.issues.removeLabel({
...context.repo,
issue_number: id,
name: LABEL_NAME,
name : LABEL_NAME,
})
await hideOldComments(id)
} else if (mark && !marked && !await removedLabelManually(id)) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: id,
labels: [LABEL_NAME],
labels : [LABEL_NAME],
})
await hideOldComments(id)
await github.rest.issues.createComment({
@ -150,7 +127,7 @@ module.exports = async ({ github, context, core }) => {
const comments = await github.paginate(github.rest.issues.listComments, {
...context.repo,
issue_number: id,
per_page: 100,
per_page : 100,
})
for (const c of comments) {
@ -180,7 +157,7 @@ module.exports = async ({ github, context, core }) => {
try {
const { data: issues } = await github.rest.issues.listForRepo({
...context.repo,
state: "open",
state : "open",
labels: LABEL_NAME,
})
@ -193,13 +170,13 @@ module.exports = async ({ github, context, core }) => {
await github.rest.issues.update({
...context.repo,
issue_number: issue.number,
state: "closed",
state : "closed",
state_reason: "not_planned",
})
await github.rest.issues.createComment({
...context.repo,
issue_number: issue.number,
body: `This issue has been automatically closed because it was marked as \`${LABEL_NAME}\` for more than 2 days without updates.
body : `This issue has been automatically closed because it was marked as \`${LABEL_NAME}\` for more than 2 days without updates.
If the problem persists, please file a new issue and complete the issue template so we can capture all the details necessary to investigate further.`,
})
}
@ -214,13 +191,13 @@ If the problem persists, please file a new issue and complete the issue template
await github.rest.issues.update({
...context.repo,
issue_number: id,
state: "closed",
state : "closed",
state_reason: "not_planned",
})
await github.rest.issues.createComment({
...context.repo,
issue_number: id,
body: `Unsupported issue template.
body : `Unsupported issue template.
Either the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.yml) or [Feature Request](https://github.com/sxyazi/yazi/issues/new?template=feature.yml) template should be used.`,
})
} catch (e) {
@ -229,23 +206,19 @@ Either the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.y
}
async function main() {
const hash = await nightlyHash()
if (!hash) return
if (context.eventName === "schedule") {
await closeOldIssues()
return
}
if (skipValidation(context)) {
return
}
if (context.eventName === "issues") {
const id = context.payload.issue.number
const content = context.payload.issue.body || ""
const creator = context.payload.issue.user.login
const hash = await nightlyHash()
if (!hash) return
if (await hasLabel(id, "bug")) {
const body = bugReportBody(creator, content, hash)
await updateLabels(id, !!body, body)
@ -255,13 +228,6 @@ Either the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.y
} else if (context.payload.action === "opened") {
await closeUnsupportedIssue(id)
}
} else if (context.eventName === "pull_request" || context.eventName === "pull_request_target") {
const content = context.payload.pull_request.body || ""
const body = pullRequestBody(content)
if (body) {
core.setFailed(body)
}
}
}

View file

@ -8,225 +8,258 @@
"name": "validate-form",
"version": "1.0.0",
"dependencies": {
"@actions/core": "^3.0.1",
"@actions/github": "^9.1.1"
"@actions/core": "^2.0.1",
"@actions/github": "^6.0.0"
}
},
"node_modules/@actions/core": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz",
"integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.1.tgz",
"integrity": "sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^3.0.0",
"@actions/http-client": "^4.0.0"
"@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": "3.0.0",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
"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": "^3.0.2"
"@actions/io": "^2.0.0"
}
},
"node_modules/@actions/github": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-9.1.1.tgz",
"integrity": "sha512-tL5JbYOBZHc0ngEnCsaDcryUizIUIlQyIMwy1Wkx93H5HzbBJ7TbiPx2PnFjBwZW0Vh05JmfFZhecE6gglYegA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.1.tgz",
"integrity": "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^3.0.2",
"@octokit/core": "^7.0.6",
"@octokit/plugin-paginate-rest": "^14.0.0",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
"@octokit/request": "^10.0.7",
"@octokit/request-error": "^7.1.0",
"undici": "^6.23.0"
}
},
"node_modules/@actions/github/node_modules/@actions/http-client": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz",
"integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^6.23.0"
"@actions/http-client": "^2.2.0",
"@octokit/core": "^5.0.1",
"@octokit/plugin-paginate-rest": "^9.2.2",
"@octokit/plugin-rest-endpoint-methods": "^10.4.0",
"@octokit/request": "^8.4.1",
"@octokit/request-error": "^5.1.1",
"undici": "^5.28.5"
}
},
"node_modules/@actions/http-client": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz",
"integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^6.23.0"
"undici": "^5.25.4"
}
},
"node_modules/@actions/io": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
"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/@octokit/auth-token": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz",
"integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==",
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">= 20"
"node": ">=14"
}
},
"node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/core": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz",
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
"version": "5.2.1",
"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": "^6.0.0",
"@octokit/graphql": "^9.0.3",
"@octokit/request": "^10.0.6",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"before-after-hook": "^4.0.0",
"universal-user-agent": "^7.0.0"
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
"@octokit/request": "^8.4.1",
"@octokit/request-error": "^5.1.1",
"@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz",
"integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==",
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.2"
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/graphql": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz",
"integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
"integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^10.0.6",
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.0"
"@octokit/request": "^8.4.1",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/openapi-types": {
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz",
"integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==",
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz",
"integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==",
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz",
"integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==",
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/request": {
"version": "10.0.8",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz",
"integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==",
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.3",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"fast-content-type-parse": "^3.0.0",
"json-with-bigint": "^3.5.3",
"universal-user-agent": "^7.0.2"
"@octokit/endpoint": "^9.0.6",
"@octokit/request-error": "^5.1.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/request-error": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz",
"integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
"@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/types": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz",
"integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==",
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^27.0.0"
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/before-after-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz",
"integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
"license": "Apache-2.0"
},
"node_modules/fast-content-type-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz",
"integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
"license": "ISC"
},
"node_modules/json-with-bigint": {
"version": "3.5.8",
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz",
"integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==",
"license": "MIT"
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tunnel": {
"version": "0.0.6",
@ -238,18 +271,27 @@
}
},
"node_modules/undici": {
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=18.17"
"node": ">=14.0"
}
},
"node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"license": "ISC"
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}

View file

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

View file

@ -133,16 +133,9 @@ parts:
- --prefix=/usr
build-attributes:
- enable-patchelf
organize:
# `libgomp1` installs libgomp into the multiarch directory, but the
# `magick` binary's RPATH only searches `$ORIGIN/../lib` (i.e. usr/lib).
# Move it alongside the Magick libraries so the bundled binary can load
# it at runtime; otherwise magick fails with:
# "libgomp.so.1: cannot open shared object file: No such file or directory"
usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so*: usr/lib/
prime:
- usr/bin/magick
- usr/lib/libgomp.so*
- usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so.1
- usr/lib/libMagickCore-7.Q16HDRI.so*
- usr/lib/libMagickWand-7.Q16HDRI.so*

View file

@ -1,13 +1,12 @@
[package]
name = "yazi-actor"
description = "Yazi actor model"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
name = "yazi-actor"
version = "25.12.29"
edition = "2024"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi actor model"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[lints]
workspace = true
@ -17,42 +16,37 @@ default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies]
yazi-binding = { path = "../yazi-binding", version = "26.5.6" }
yazi-boot = { path = "../yazi-boot", version = "26.5.6" }
yazi-config = { path = "../yazi-config", version = "26.5.6" }
yazi-core = { path = "../yazi-core", version = "26.5.6" }
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
yazi-emulator = { path = "../yazi-emulator", version = "26.5.6" }
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-parser = { path = "../yazi-parser", version = "26.5.6" }
yazi-plugin = { path = "../yazi-plugin", version = "26.5.6" }
yazi-proxy = { path = "../yazi-proxy", version = "26.5.6" }
yazi-runner = { path = "../yazi-runner", version = "26.5.6" }
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
yazi-term = { path = "../yazi-term", version = "26.5.6" }
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
yazi-tui = { path = "../yazi-tui", version = "26.5.6" }
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
yazi-binding = { path = "../yazi-binding", version = "25.12.29" }
yazi-boot = { path = "../yazi-boot", version = "25.12.29" }
yazi-config = { path = "../yazi-config", version = "25.12.29" }
yazi-core = { path = "../yazi-core", version = "25.12.29" }
yazi-dds = { path = "../yazi-dds", version = "25.12.29" }
yazi-fs = { path = "../yazi-fs", version = "25.12.29" }
yazi-macro = { path = "../yazi-macro", version = "25.12.29" }
yazi-parser = { path = "../yazi-parser", version = "25.12.29" }
yazi-plugin = { path = "../yazi-plugin", version = "25.12.29" }
yazi-proxy = { path = "../yazi-proxy", version = "25.12.29" }
yazi-scheduler = { path = "../yazi-scheduler", version = "25.12.29" }
yazi-shared = { path = "../yazi-shared", version = "25.12.29" }
yazi-term = { path = "../yazi-term", version = "25.12.29" }
yazi-vfs = { path = "../yazi-vfs", version = "25.12.29" }
yazi-watcher = { path = "../yazi-watcher", version = "25.12.29" }
yazi-widgets = { path = "../yazi-widgets", version = "25.12.29" }
# External dependencies
anyhow = { workspace = true }
either = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
indexmap = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true }
paste = { workspace = true }
ratatui-core = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }
crossterm = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
mlua = { workspace = true }
paste = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
[target."cfg(unix)".dependencies]
libc = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -1,15 +1,15 @@
use anyhow::Result;
use yazi_parser::spark::SparkKind;
use yazi_dds::spark::SparkKind;
use yazi_shared::data::Data;
use crate::Ctx;
pub trait Actor {
type Form;
type Options;
const NAME: &str;
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data>;
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data>;
fn hook(_cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> { None }
fn hook(_cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> { None }
}

View file

@ -1,42 +0,0 @@
use anyhow::Result;
use mlua::IntoLua;
use tracing::error;
use yazi_actor::lives::Lives;
use yazi_binding::runtime_scope;
use yazi_dds::{LOCAL, Payload, REMOTE};
use yazi_macro::succ;
use yazi_plugin::LUA;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct AcceptPayload;
impl Actor for AcceptPayload {
type Form = Payload<'static>;
const NAME: &str = "accept_payload";
fn act(cx: &mut Ctx, payload: Payload) -> Result<Data> {
let kind = payload.body.kind();
let lock = if payload.receiver == 0 || payload.receiver != payload.sender {
REMOTE.read()
} else {
LOCAL.read()
};
let Some(handlers) = lock.get(kind).filter(|&m| !m.is_empty()).cloned() else { succ!() };
drop(lock);
let kind = kind.to_owned();
succ!(Lives::scope(cx.core, |_| {
let body = payload.body.into_lua(&LUA)?;
for (id, cb) in handlers {
if let Err(e) = runtime_scope!(LUA, &id, cb.call::<()>(body.clone())) {
error!("Failed to run `{kind}` event handler in your `{id}` plugin: {e}");
}
}
Ok(())
})?);
}
}

View file

@ -1,32 +0,0 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_boot::BOOT;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ};
use yazi_parser::VoidForm;
use yazi_shared::{data::Data, strand::StrandLike, url::UrlLike};
use crate::Actor;
pub struct Bootstrap;
impl Actor for Bootstrap {
type Form = VoidForm;
const NAME: &str = "bootstrap";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
cx.mgr.tabs.resize_with(BOOT.files.len(), Default::default);
for (i, file) in BOOT.files.iter().enumerate().rev() {
cx.tab = i;
if file.is_empty() {
act!(mgr:cd, cx, (BOOT.cwds[i].clone(), CdSource::Tab))?;
} else if let Ok(u) = BOOT.cwds[i].try_join(file) {
act!(mgr:reveal, cx, (u, CdSource::Tab))?;
}
}
succ!();
}
}

View file

@ -1,24 +0,0 @@
use anyhow::Result;
use yazi_core::notify::{MessageLevel, MessageOpt};
use yazi_macro::act;
use yazi_parser::app::DeprecateForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Deprecate;
impl Actor for Deprecate {
type Form = DeprecateForm;
const NAME: &str = "deprecate";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(notify:push, cx, MessageOpt {
title: "Deprecated API".to_owned(),
content: form.content.into_owned(),
level: MessageLevel::Warn,
timeout: std::time::Duration::from_secs(20),
})
}
}

View file

@ -1,43 +0,0 @@
use anyhow::Result;
use mlua::{ObjectLike, Table};
use tracing::error;
use yazi_actor::lives::Lives;
use yazi_binding::runtime_scope;
use yazi_macro::succ;
use yazi_parser::app::DndForm;
use yazi_plugin::LUA;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Dnd;
impl Actor for Dnd {
type Form = DndForm;
const NAME: &str = "dnd";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
let area = yazi_binding::elements::Rect::from(size);
let result = Lives::scope(cx.core, move |_| {
runtime_scope!(LUA, "root", {
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
if form.event.is_drag() {
root.call_method::<()>("drag", form.event)?;
} else {
root.call_method::<()>("drop", form.event)?;
}
Ok(())
})
});
if let Err(ref e) = result {
error!("{e}");
}
succ!(result?);
}
}

View file

@ -1,17 +0,0 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_macro::act;
use yazi_parser::VoidForm;
use yazi_shared::data::Data;
use crate::Actor;
pub struct Focus;
impl Actor for Focus {
type Form = VoidForm;
const NAME: &str = "focus";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> { act!(mgr:refresh, cx) }
}

View file

@ -1,24 +0,0 @@
use anyhow::Result;
use yazi_binding::runtime_scope;
use yazi_macro::succ;
use yazi_parser::app::LuaForm;
use yazi_plugin::LUA;
use yazi_shared::data::{Data, Sendable};
use crate::{Actor, Ctx, lives::Lives};
pub struct Lua;
impl Actor for Lua {
type Form = LuaForm;
const NAME: &str = "lua";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let chunk = LUA.load(&*form.code).set_name("anonymous");
let result = Lives::scope(cx.core, |_| {
runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
});
succ!(result?);
}
}

View file

@ -1,52 +0,0 @@
use anyhow::Result;
use mlua::{ObjectLike, Table};
use tracing::error;
use yazi_actor::lives::Lives;
use yazi_binding::runtime_scope;
use yazi_macro::succ;
use yazi_parser::app::MouseForm;
use yazi_plugin::LUA;
use yazi_shared::data::Data;
use yazi_term::event::MouseEventKind;
use crate::{Actor, Ctx};
pub struct Mouse;
impl Actor for Mouse {
type Form = MouseForm;
const NAME: &str = "mouse";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
let area = yazi_binding::elements::Rect::from(size);
let result = Lives::scope(cx.core, move |_| {
runtime_scope!(LUA, "root", {
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
match form.event.kind {
MouseEventKind::Down(_) => root.call_method("click", (form.event, false))?,
MouseEventKind::Up(_) => root.call_method("click", (form.event, true))?,
MouseEventKind::ScrollDown => root.call_method("scroll", (form.event, 1))?,
MouseEventKind::ScrollUp => root.call_method("scroll", (form.event, -1))?,
MouseEventKind::ScrollRight => root.call_method("touch", (form.event, 1))?,
MouseEventKind::ScrollLeft => root.call_method("touch", (form.event, -1))?,
MouseEventKind::Moved => root.call_method("move", form.event)?,
MouseEventKind::Drag(_) => root.call_method("drag", form.event)?,
}
Ok(())
})
});
if let Err(ref e) = result {
error!("{e}");
}
succ!(result?);
}
}

View file

@ -1,40 +0,0 @@
use anyhow::Result;
use yazi_core::app::PluginMode;
use yazi_macro::{act, succ};
use yazi_parser::app::PluginForm;
use yazi_proxy::AppProxy;
use yazi_runner::loader::LOADER;
use yazi_scheduler::NotifyProxy;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Plugin;
impl Actor for Plugin {
type Form = PluginForm;
const NAME: &str = "plugin";
fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
let mut hits = false;
if let Some(chunk) = LOADER.read().get(&*opt.id) {
hits = true;
opt.mode = opt.mode.auto_then(chunk.sync_entry);
}
if opt.mode == PluginMode::Async {
succ!(cx.core.tasks.scheduler.plugin_entry(opt.into()));
} else if opt.mode == PluginMode::Sync && hits {
return act!(app:plugin_do, cx, opt);
}
tokio::spawn(async move {
match LOADER.ensure(&opt.id, |_| ()).await {
Ok(()) => AppProxy::plugin_do(opt),
Err(e) => NotifyProxy::push_error("Plugin load failed", e.to_string()),
}
});
succ!();
}
}

View file

@ -1,58 +0,0 @@
use anyhow::Result;
use mlua::ObjectLike;
use scopeguard::defer;
use tracing::{error, warn};
use yazi_binding::runtime_mut;
use yazi_core::app::PluginMode;
use yazi_macro::succ;
use yazi_parser::app::PluginForm;
use yazi_plugin::LUA;
use yazi_runner::{entry::EntryJob, loader::{LOADER, Loader}};
use yazi_scheduler::NotifyProxy;
use yazi_shared::data::Data;
use crate::{Actor, Ctx, lives::Lives};
pub struct PluginDo;
impl Actor for PluginDo {
type Form = PluginForm;
const NAME: &str = "plugin_do";
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
let loader = LOADER.read();
let Some(chunk) = loader.get(&*opt.id) else {
succ!(warn!("plugin `{}` not found", opt.id));
};
if let Err(e) = Loader::compatible_or_error(&opt.id, chunk) {
succ!(NotifyProxy::push_error("Incompatible plugin", e.to_string()));
}
if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync {
succ!(cx.core.tasks.scheduler.plugin_entry(opt.into()));
}
let blocking = runtime_mut!(LUA)?.critical_push(&opt.id, true);
defer! { _ = runtime_mut!(LUA).map(|mut r| r.critical_pop(blocking)) }
let plugin = match LOADER.load_chunk(&LUA, &opt.id, chunk) {
Ok(t) => t,
Err(e) => succ!(warn!("{e}")),
};
drop(loader);
let result = Lives::scope(cx.core, |_| {
if let Some(cb) = opt.callback {
cb(&LUA, plugin)
} else {
plugin.call_method("entry", EntryJob { args: opt.args, ..Default::default() })
}
});
if let Err(ref e) = result {
error!("Sync plugin `{}` failed: {e}", opt.id);
}
succ!(result?);
}
}

View file

@ -1,50 +0,0 @@
use std::process;
use anyhow::Result;
use yazi_boot::ARGS;
use yazi_fs::engine::{Engine, local::Local};
use yazi_parser::app::QuitForm;
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
use yazi_tui::Raterm;
use crate::{Actor, Ctx};
pub struct Quit;
impl Actor for Quit {
type Form = QuitForm;
const NAME: &str = "quit";
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
cx.tasks.shutdown();
cx.mgr.shutdown();
futures::executor::block_on(async {
_ = futures::join!(
yazi_dds::shutdown(),
yazi_dds::STATE.drain(),
Self::cwd_to_file(cx, opt.no_cwd_file),
Self::selected_to_file(opt.selected)
);
});
Raterm::stop();
process::exit(opt.code);
}
}
impl Quit {
async fn cwd_to_file(cx: &Ctx<'_>, no: bool) {
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) {
let cwd = cx.mgr.cwd().to_strand();
Local::regular(p).write(cwd.encoded_bytes()).await.ok();
}
}
async fn selected_to_file(selected: Option<StrandBuf>) {
if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) {
Local::regular(p).write(s.encoded_bytes()).await.ok();
}
}
}

View file

@ -1,25 +0,0 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_macro::act;
use yazi_parser::app::ReflowForm;
use yazi_shared::data::Data;
use crate::Actor;
pub struct Resize;
impl Actor for Resize {
type Form = ReflowForm;
const NAME: &str = "resize";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(app:reflow, cx, form)?;
cx.current_mut().arrow(0);
cx.parent_mut().map(|f| f.arrow(0));
cx.current_mut().sync_page(true);
act!(mgr:peek, cx)
}
}

View file

@ -1,29 +0,0 @@
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::app::ReflowForm;
use yazi_shared::data::Data;
use yazi_tui::Raterm;
use crate::{Actor, Ctx};
pub struct Resume;
impl Actor for Resume {
type Form = ReflowForm;
const NAME: &str = "resume";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.active_mut().preview.reset();
drop(cx.term.take());
*cx.term = Some(Raterm::start()?);
// While the app resumes, it's possible that the terminal size has changed.
// We need to trigger a resize, and render the UI based on the resized area.
act!(app:resize, cx, form.reflow)?;
act!(app:title, cx).ok();
succ!(render!());
}
}

View file

@ -1,22 +0,0 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::VoidForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Stop;
impl Actor for Stop {
type Form = VoidForm;
const NAME: &str = "stop";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
cx.active_mut().preview.reset_image();
*cx.term = None;
succ!();
}
}

View file

@ -1,25 +0,0 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_config::{THEME, build_flavor};
use yazi_emulator::EMULATOR;
use yazi_macro::{render, succ};
use yazi_parser::VoidForm;
use yazi_shared::data::Data;
use yazi_shim::serde::Overlay;
use crate::Actor;
pub struct Theme;
impl Actor for Theme {
type Form = VoidForm;
const NAME: &str = "theme";
fn act(_cx: &mut Ctx, _: Self::Form) -> Result<Data> {
THEME.overlay(build_flavor(EMULATOR.light, true)?);
yazi_plugin::theme::reset()?;
succ!(render!());
}
}

View file

@ -1,32 +0,0 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_macro::{succ, writef};
use yazi_parser::{app::TitleForm, spark::SparkKind};
use yazi_shared::{Source, data::Data};
use yazi_tty::{TTY, sequence::SetTitle};
use yazi_tui::RatermState;
use crate::Actor;
pub struct Title;
impl Actor for Title {
type Form = TitleForm;
const NAME: &str = "title";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let s = form.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
writef!(TTY.writer(), "{}", SetTitle(&s))?;
yazi_tui::STATE.set(RatermState { title: !s.is_empty(), ..yazi_tui::STATE.get() });
succ!()
}
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Ind => Some(SparkKind::IndAppTitle),
_ => None,
}
}
}

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{render, succ};
use yazi_parser::ArrowForm;
use yazi_parser::ArrowOpt;
use yazi_shared::data::Data;
use yazi_widgets::Scrollable;
@ -9,11 +9,11 @@ use crate::{Actor, Ctx};
pub struct Arrow;
impl Actor for Arrow {
type Form = ArrowForm;
type Options = ArrowOpt;
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
succ!(render!(cx.cmp.scroll(form.step)));
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
succ!(render!(cx.cmp.scroll(opt.step)));
}
}

View file

@ -2,46 +2,26 @@ use std::mem;
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::cmp::CloseForm;
use yazi_parser::{cmp::CloseOpt, input::CompleteOpt};
use yazi_shared::data::Data;
use yazi_widgets::input::parser::CompleteOpt;
use crate::{Actor, Ctx};
pub struct Close;
impl Actor for Close {
type Form = CloseForm;
type Options = CloseOpt;
const NAME: &str = "close";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.submit && cx.cmp.visible {
Self::flush_last_input(cx)?;
}
let cmp = &mut cx.cmp;
if let Some(item) = cmp.selected().filter(|_| form.submit).cloned() {
return act!(input:complete, cx, CompleteOpt { name: item.name, is_dir: item.is_dir, ticket: cmp.ticket });
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let cmp = &mut cx.core.cmp;
if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {
return act!(input:complete, cx, CompleteOpt { item, ticket: cmp.ticket });
}
cmp.caches.clear();
cmp.ticket = Default::default();
cmp.handle.take().map(|h| h.abort());
succ!(render!(mem::replace(&mut cmp.visible, false)));
}
}
impl Close {
fn flush_last_input(cx: &mut Ctx) -> Result<Data> {
let Some(guard) = cx.input.lock() else { succ!() };
if cx.cmp.ticket == guard.ticket.current() {
succ!();
}
let before = guard.partition().0.to_owned();
drop(guard);
act!(cmp:trigger, cx, before)
}
}

View file

@ -1,10 +1,9 @@
use std::{mem, ops::ControlFlow};
use anyhow::Result;
use yazi_core::cmp::CmpItem;
use yazi_macro::{render, succ};
use yazi_parser::cmp::ShowForm;
use yazi_shared::{data::Data, path::{DynPath, PathDyn}, strand::StrandLike};
use yazi_parser::cmp::{CmpItem, ShowOpt};
use yazi_shared::{data::Data, path::{AsPath, PathDyn}, strand::StrandLike};
use crate::{Actor, Ctx};
@ -13,11 +12,11 @@ const LIMIT: usize = 30;
pub struct Show;
impl Actor for Show {
type Form = ShowForm;
type Options = ShowOpt;
const NAME: &str = "show";
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let cmp = &mut cx.cmp;
if cmp.ticket != opt.ticket {
succ!();
@ -30,8 +29,8 @@ impl Actor for Show {
succ!();
};
cmp.matches = Self::match_candidates(opt.word.dyn_path(), cache);
if cmp.matches.is_empty() {
cmp.cands = Self::match_candidates(opt.word.as_path(), cache);
if cmp.cands.is_empty() {
succ!(render!(mem::replace(&mut cmp.visible, false)));
}
@ -47,11 +46,8 @@ impl Show {
let smart = !word.encoded_bytes().iter().any(|&b| b.is_ascii_uppercase());
let flow = cache.iter().try_fold((Vec::new(), Vec::new()), |(mut exact, mut fuzzy), item| {
let starts_with = if smart {
item.name.starts_with_ignore_ascii_case(word)
} else {
item.name.starts_with(word)
};
let starts_with =
if smart { item.name.eq_ignore_ascii_case(word) } else { item.name.starts_with(word) };
if starts_with {
exact.push(item);

View file

@ -1,42 +1,43 @@
use std::{io, mem};
use std::mem;
use anyhow::Result;
use yazi_core::cmp::{CmpItem, CmpOpt};
use yazi_fs::{engine::{DirReader, FileHolder}, path::clean_url};
use yazi_fs::{path::clean_url, provider::{DirReader, FileHolder}};
use yazi_macro::{act, render, succ};
use yazi_parser::cmp::TriggerForm;
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
use yazi_proxy::CmpProxy;
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{DynPath, PathBufDyn, PathLike}, spec::Spec, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::engine;
use 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};
pub struct Trigger;
impl Actor for Trigger {
type Form = TriggerForm;
type Options = TriggerOpt;
const NAME: &str = "trigger";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
succ!();
} else if form.ticket.is_none() {
cx.cmp.ticket = cx.input.lock().map(|g| g.ticket.current()).unwrap_or_default();
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let cmp = &mut cx.cmp;
if let Some(t) = opt.ticket {
if t < cmp.ticket {
succ!();
}
cmp.ticket = t;
}
cx.cmp.handle.take().map(|h| h.abort());
let Some((parent, word)) = Self::split_url(&form.word) else {
let Some((parent, word)) = Self::split_url(&opt.word) else {
return act!(cmp:close, cx, false);
};
let ticket = cx.cmp.ticket;
if cx.cmp.caches.contains_key(&parent) {
return act!(cmp:show, cx, CmpOpt { cache: vec![], cache_name: parent, word, ticket });
if cmp.caches.contains_key(&parent) {
let ticket = cmp.ticket;
return act!(cmp:show, cx, ShowOpt { cache: vec![], cache_name: parent, word, ticket });
}
cx.cmp.handle = Some(tokio::spawn(async move {
let mut dir = engine::read_dir(&parent).await?;
let ticket = cmp.ticket;
tokio::spawn(async move {
let mut dir = provider::read_dir(&parent).await?;
let mut cache = vec![];
// "/" is both a directory separator and the root directory per se
@ -53,39 +54,45 @@ impl Actor for Trigger {
if !cache.is_empty() {
cache
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), true));
CmpProxy::show(CmpOpt { cache, cache_name: parent, word, ticket });
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false));
CmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket });
}
Ok::<_, io::Error>(())
}));
Ok::<_, anyhow::Error>(())
});
succ!(render!(mem::replace(&mut cx.cmp.visible, false)));
succ!(render!(mem::replace(&mut cmp.visible, false)));
}
}
impl Trigger {
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
let (spec, path) = Spec::parse(s.as_bytes()).ok()?;
if path.is_empty() && !AnyAsciiChar::SEP.predicate(s.bytes().last()?) {
let sep = if cfg!(windows) {
AnyAsciiChar::new(b"/\\").unwrap()
} else {
AnyAsciiChar::new(b"/").unwrap()
};
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
if path.is_empty() && !sep.predicate(s.bytes().last()?) {
return None; // We don't complete a `sftp://test`, but `sftp://test/`
}
// Spec
let spec = spec.zeroed();
if spec.kind.is_local() && path.as_strand() == "~" {
// Scheme
let scheme = scheme.zeroed();
if scheme.is_local() && path.as_strand() == "~" {
return None; // We don't complete a `~`, but `~/`
}
// Child
let child = path.rsplit_pred(AnyAsciiChar::SEP).map_or(path.dyn_path(), |(_, c)| c).to_owned();
let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned();
// Parent
let url = UrlCow::try_from((spec.clone().zeroed(), path)).ok()?;
let abs = if let Some(u) = engine::try_absolute(&url) { u } else { url };
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((spec, parent)).ok()?), child))
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child))
}
}
@ -108,11 +115,10 @@ mod tests {
#[test]
fn test_split() {
yazi_shared::init_tests();
yazi_config::init_tests();
yazi_fs::init();
assert_eq!(Trigger::split_url(""), None);
assert_eq!(Trigger::split_url("sftp://vps"), None);
assert_eq!(Trigger::split_url("sftp://test"), None);
compare(" ", "", " ");
compare("/", "/", "");
@ -128,18 +134,16 @@ mod tests {
compare("/foo/bar", "/foo/", "bar");
compare("///foo/bar", "/foo/", "bar");
CWD.set(&"sftp://vps".parse::<UrlBuf>().unwrap(), || {});
compare("sftp://vps/a", "sftp://vps/.", "a");
compare("sftp://vps//a", "sftp://vps//", "a");
compare("test-scope://aws/a", "test-scope://aws/.", "a");
compare("test-scope://aws//a", "test-scope://aws//", "a");
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)]
#[test]
fn test_split() {
yazi_shared::init_tests();
yazi_config::init_tests();
yazi_fs::init();
compare("foo", "", "foo");

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{render, succ};
use yazi_parser::ArrowForm;
use yazi_parser::ArrowOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
@ -8,18 +8,18 @@ use crate::{Actor, Ctx};
pub struct Arrow;
impl Actor for Arrow {
type Form = ArrowForm;
type Options = ArrowOpt;
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let confirm = &mut cx.core.confirm;
let area = cx.core.mgr.area(confirm.position);
let len = confirm.list.line_count(area.width);
let old = confirm.offset;
confirm.offset = form.step.add(confirm.offset, len, area.height as _, 0, 0);
confirm.offset = opt.step.add(confirm.offset, len, area.height as _);
succ!(render!(old != confirm.offset));
}

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{render, succ};
use yazi_parser::confirm::CloseForm;
use yazi_parser::confirm::CloseOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
@ -8,12 +8,15 @@ use crate::{Actor, Ctx};
pub struct Close;
impl Actor for Close {
type Form = CloseForm;
type Options = CloseOpt;
const NAME: &str = "close";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.confirm.token.complete(form.submit);
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
if let Some(cb) = cx.confirm.callback.take() {
_ = cb.send(opt.submit);
}
cx.confirm.visible = false;
succ!(render!());
}

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::confirm::ShowForm;
use yazi_parser::confirm::ShowOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
@ -8,22 +8,22 @@ use crate::{Actor, Ctx};
pub struct Show;
impl Actor for Show {
type Form = ShowForm;
type Options = ShowOpt;
const NAME: &str = "show";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(confirm:close, cx)?;
let confirm = &mut cx.confirm;
confirm.title = form.cfg.title;
confirm.body = form.cfg.body;
confirm.list = form.cfg.list;
confirm.title = opt.cfg.title;
confirm.body = opt.cfg.body;
confirm.list = opt.cfg.list;
confirm.position = form.cfg.position;
confirm.position = opt.cfg.position;
confirm.offset = 0;
confirm.token = form.token;
confirm.callback = Some(opt.tx);
confirm.visible = true;
succ!(render!());

View file

@ -1,14 +1,12 @@
use std::ops::{Deref, DerefMut};
use anyhow::{Result, anyhow};
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}};
use yazi_fs::file::File;
use yazi_shared::{Source, event::Action, id::Id, url::UrlBuf};
use yazi_tui::Raterm;
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks};
use yazi_fs::File;
use yazi_shared::{Id, Source, event::Cmd, url::UrlBuf};
pub struct Ctx<'a> {
pub core: &'a mut Core,
pub term: &'a mut Option<Raterm>,
pub tab: usize,
pub level: usize,
pub source: Source,
@ -27,8 +25,9 @@ impl DerefMut for Ctx<'_> {
}
impl<'a> Ctx<'a> {
pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Raterm>) -> Result<Self> {
let tab = if let Ok(id) = action.get::<Id>("tab") {
#[inline]
pub fn new(core: &'a mut Core, cmd: &Cmd) -> Result<Self> {
let tab = if let Ok(id) = cmd.get::<Id>("tab") {
core
.mgr
.tabs
@ -41,20 +40,19 @@ impl<'a> Ctx<'a> {
Ok(Self {
core,
term,
tab,
level: 0,
source: action.source,
source: cmd.source,
#[cfg(debug_assertions)]
backtrace: vec![],
})
}
#[inline]
pub fn renew<'b>(cx: &'a mut Ctx<'b>) -> Self {
let tab = cx.core.mgr.tabs.cursor;
Self {
core: cx.core,
term: cx.term,
tab,
level: cx.level,
source: cx.source,
@ -63,11 +61,11 @@ impl<'a> Ctx<'a> {
}
}
pub fn active(core: &'a mut Core, term: &'a mut Option<Raterm>) -> Self {
#[inline]
pub fn active(core: &'a mut Core) -> Self {
let tab = core.mgr.tabs.cursor;
Self {
core,
term,
tab,
level: 0,
source: Source::Unknown,
@ -108,9 +106,6 @@ impl<'a> Ctx<'a> {
#[inline]
pub fn hovered(&self) -> Option<&File> { self.tab().hovered() }
#[inline]
pub fn hovered_url(&self) -> Option<&UrlBuf> { self.tab().hovered_url() }
#[inline]
pub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() }
@ -119,5 +114,9 @@ impl<'a> Ctx<'a> {
self.tab_mut().hovered_folder_mut()
}
#[inline]
pub fn tasks(&self) -> &Tasks { &self.tasks }
#[inline]
pub fn source(&self) -> Source { if self.level != 1 { Source::Ind } else { self.source } }
}

View file

@ -1,8 +1,7 @@
use anyhow::Result;
use mlua::{ErrorContext, ExternalError, IntoLua, Value};
use yazi_binding::runtime_scope;
use yazi_dds::LOCAL;
use yazi_parser::spark::{Spark, SparkKind};
use yazi_binding::runtime_mut;
use yazi_dds::{LOCAL, spark::{Spark, SparkKind}};
use yazi_plugin::LUA;
use crate::{Ctx, lives::Lives};
@ -12,14 +11,18 @@ pub struct Preflight;
impl Preflight {
pub fn act<'a>(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> {
let kind = opt.0;
let Some(handlers) = LOCAL.read().get(kind.into()).filter(|&m| !m.is_empty()).cloned() else {
let Some(handlers) = LOCAL.read().get(kind.as_ref()).filter(|&m| !m.is_empty()).cloned() else {
return Ok(opt.1);
};
Ok(Lives::scope(cx.core, |_| {
Ok(Lives::scope(cx.core, || {
let mut body = opt.1.into_lua(&LUA)?;
for (id, cb) in handlers {
match runtime_scope!(LUA, &id, cb.call::<Value>(&body)) {
runtime_mut!(LUA)?.push(&id);
let result = cb.call::<Value>(&body);
runtime_mut!(LUA)?.pop();
match result {
Ok(Value::Nil) => {
Err(format!("`{kind}` event cancelled by `{id}` plugin on preflight").into_lua_err())?
}

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{render, succ};
use yazi_parser::ArrowForm;
use yazi_parser::ArrowOpt;
use yazi_shared::data::Data;
use yazi_widgets::Scrollable;
@ -9,11 +9,11 @@ use crate::{Actor, Ctx};
pub struct Arrow;
impl Actor for Arrow {
type Form = ArrowForm;
type Options = ArrowOpt;
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
succ!(render!(cx.help.scroll(form.step)));
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
succ!(render!(cx.help.scroll(opt.step)));
}
}

View file

@ -1,25 +0,0 @@
use anyhow::Result;
use yazi_macro::{emit, render, succ};
use yazi_parser::help::CloseForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Close;
impl Actor for Close {
type Form = CloseForm;
const NAME: &str = "close";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
let help = &mut cx.help;
if let Some(chord) = help.bindings.get(help.cursor).filter(|_| opt.submit) {
emit!(Seq(chord.to_seq(help.layer)));
}
help.visible = false;
succ!(render!());
}
}

View file

@ -1,24 +1,27 @@
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::VoidForm;
use yazi_parser::VoidOpt;
use yazi_shared::data::Data;
use yazi_widgets::input::InputMode;
use crate::{Actor, Ctx};
pub struct Escape;
impl Actor for Escape {
type Form = VoidForm;
type Options = VoidOpt;
const NAME: &str = "escape";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
if cx.help.input.mode() == InputMode::Normal {
return act!(help:close, cx);
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
if cx.help.keyword().is_none() {
return act!(help:toggle, cx, cx.help.layer);
}
act!(escape, cx.help.input)?;
let help = &mut cx.help;
help.keyword = String::new();
help.in_filter = None;
help.filter_apply();
succ!(render!());
}
}

View file

@ -0,0 +1,22 @@
use anyhow::Result;
use yazi_macro::{render, succ};
use yazi_parser::VoidOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Filter;
impl Actor for Filter {
type Options = VoidOpt;
const NAME: &str = "filter";
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
let help = &mut cx.help;
help.in_filter = Some(Default::default());
help.filter_apply();
succ!(render!());
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(arrow close escape toggle);
yazi_macro::mod_flat!(arrow escape filter toggle);

View file

@ -1,39 +1,29 @@
use anyhow::Result;
use ratatui_core::layout::Margin;
use yazi_config::popup::Help;
use yazi_macro::{render, succ};
use yazi_parser::help::ToggleForm;
use yazi_parser::help::ToggleOpt;
use yazi_shared::data::Data;
use yazi_widgets::input::Input;
use crate::{Actor, Ctx};
pub struct Toggle;
impl Actor for Toggle {
type Form = ToggleForm;
type Options = ToggleOpt;
const NAME: &str = "toggle";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let position = Help::position();
let area = cx.mgr.area(position);
let input_area = area.inner(Margin::new(1, 1));
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let help = &mut cx.help;
help.visible = true;
help.layer = form.layer;
help.position = position;
help.height = area.height;
help.input = Input::default();
help.input.repos(input_area);
help.visible = !help.visible;
help.layer = opt.layer;
help.keyword.clear();
help.offset = 0;
help.cursor = 0;
help.keyword = String::new();
help.in_filter = None;
help.filter_apply();
help.offset = 0;
help.cursor = 0;
succ!(render!());
}
}

View file

@ -1,47 +1,28 @@
use anyhow::Result;
use yazi_core::input::InputMutGuard;
use yazi_macro::{act, render, succ};
use yazi_parser::{input::CloseForm, spark::SparkKind};
use yazi_shared::{Source, data::Data};
use yazi_widgets::input::InputEvent;
use yazi_parser::input::CloseOpt;
use yazi_shared::{data::Data, errors::InputError};
use crate::{Actor, Ctx};
pub struct Close;
impl Actor for Close {
type Form = CloseForm;
type Options = CloseOpt;
const NAME: &str = "close";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(mut guard) = cx.input.lock_mut() else {
succ!();
};
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let input = &mut cx.input;
input.visible = false;
input.ticket.next();
guard.ticket.next();
if let Some(cb) = guard.cb.take() {
let value = guard.value().to_owned();
cb(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
if let Some(tx) = input.tx.take() {
let value = input.snap().value.clone();
_ = tx.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
}
if form.submit
&& let InputMutGuard::Main(input) = guard
{
input.histories.remember(&input.main.history.name, input.main.value());
}
cx.input.main.visible = false;
act!(cmp:close, cx)?;
succ!(render!());
}
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Key => Some(SparkKind::KeyInputClose),
Source::Ind => Some(SparkKind::IndInputClose),
_ => None,
}
}
}

View file

@ -1,26 +1,23 @@
use anyhow::Result;
use yazi_macro::{act, succ};
use yazi_parser::input::CompleteOpt;
use yazi_shared::data::Data;
use yazi_widgets::input::parser::CompleteOpt;
use crate::{Actor, Ctx};
pub struct Complete;
impl Actor for Complete {
type Form = CompleteOpt;
type Options = CompleteOpt;
const NAME: &str = "complete";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(mut guard) = cx.input.lock_mut() else {
succ!();
};
if guard.ticket.current() != form.ticket {
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, guard, form)
act!(complete, input, opt)
}
}

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::VoidForm;
use yazi_parser::VoidOpt;
use yazi_shared::data::Data;
use yazi_widgets::input::InputOp;
@ -9,26 +9,22 @@ use crate::{Actor, Ctx};
pub struct Escape;
impl Actor for Escape {
type Form = VoidForm;
type Options = VoidOpt;
const NAME: &str = "escape";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
use yazi_widgets::input::InputMode as M;
let Some(mut guard) = cx.input.lock_mut() else {
succ!();
};
let input = &mut cx.input;
let (mode, op) = (guard.snap().mode, guard.snap().op);
act!(escape, guard)?;
drop(guard);
let mode = input.snap().mode;
match mode {
M::Normal if op == InputOp::None => act!(input:close, cx),
M::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx),
M::Insert => act!(cmp:close, cx),
M::Normal | M::Replace => Ok(().into()),
}?;
act!(escape, cx.input)?;
succ!(render!());
}
}

View file

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

View file

@ -1,32 +0,0 @@
use anyhow::Result;
use yazi_core::input::InputMutGuard;
use yazi_macro::succ;
use yazi_parser::input::RecallForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Recall;
impl Actor for Recall {
type Form = RecallForm;
const NAME: &str = "recall";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(input) = cx.input.lock_mut() else {
succ!();
};
match input {
InputMutGuard::Main(input) => {
let entries = input.histories.get(&input.main.history.name);
input.main.recall(entries, form.step)
}
InputMutGuard::Alt(input, mut guard) => {
let entries = input.histories.get(&guard.history.name);
guard.recall(entries, form.step)
}
}
}
}

View file

@ -1,33 +0,0 @@
use anyhow::Result;
use yazi_core::input::InputMutGuard;
use yazi_macro::succ;
use yazi_parser::VoidForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct Remember;
impl Actor for Remember {
type Form = VoidForm;
const NAME: &str = "remember";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
let Some(mut input) = cx.input.lock_mut() else {
succ!();
};
match &mut input {
InputMutGuard::Main(input) => {
input.histories.remember(&input.main.history.name, input.main.value());
}
InputMutGuard::Alt(input, guard) => {
input.histories.remember(&guard.history.name, guard.value());
}
}
input.history.take();
succ!();
}
}

View file

@ -1,35 +1,54 @@
use std::ops::DerefMut;
use anyhow::Result;
use yazi_config::{THEME, YAZI};
use yazi_config::YAZI;
use yazi_macro::{act, render, succ};
use yazi_parser::input::ShowForm;
use yazi_shared::data::Data;
use yazi_shim::ratatui::Padable;
use yazi_parser::input::ShowOpt;
use yazi_shared::{data::Data, errors::InputError};
use yazi_widgets::input::InputCallback;
use crate::{Actor, Ctx};
pub struct Show;
impl Actor for Show {
type Form = ShowForm;
type Options = ShowOpt;
const NAME: &str = "show";
fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(input:close, cx)?;
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
let input = &mut cx.input;
input.main.name = opt.name.clone();
input.main.title = opt.title.clone();
input.main.position = opt.position;
input.main.visible = true;
input.visible = true;
input.title = opt.cfg.title;
input.position = opt.cfg.position;
opt.styles = (&THEME.input).into();
opt.blinking = YAZI.input.cursor_blink;
*input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?;
input.main.repos(area);
// Typing
input.tx = Some(opt.tx.clone());
let ticket = input.ticket.clone();
// Reset input
let cb: InputCallback = Box::new(move |before, after| {
if opt.cfg.realtime {
opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok();
} else if opt.cfg.completion {
opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok();
}
});
*input.deref_mut() = yazi_widgets::input::Input::new(
opt.cfg.value,
opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize,
opt.cfg.obscure,
cb,
);
// Set cursor after reset
// TODO: remove this
if let Some(cursor) = opt.cfg.cursor {
input.snap_mut().cursor = cursor;
act!(r#move, input)?;
}
succ!(render!());
}

View file

@ -1,7 +1,5 @@
extern crate self as yazi_actor;
yazi_macro::mod_pub!(app cmp confirm core help input lives mgr notify pick spot tasks which);
yazi_macro::mod_pub!(cmp confirm core help input lives mgr pick spot tasks which);
yazi_macro::mod_flat!(actor context);
pub fn init() { lives::init(); }

View file

@ -1,29 +0,0 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataMethods};
use super::{Lives, PtrCell};
pub(super) struct Behavior {
inner: PtrCell<yazi_scheduler::Behavior>,
}
impl Deref for Behavior {
type Target = yazi_scheduler::Behavior;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Behavior {
pub(super) fn make(inner: &yazi_scheduler::Behavior) -> mlua::Result<AnyUserData> {
let inner = PtrCell::from(inner);
Lives::scoped_userdata(Self { inner })
}
}
impl UserData for Behavior {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("reset", |_, me, ()| Ok(me.reset()));
}
}

View file

@ -1,12 +1,10 @@
use std::ops::Deref;
use mlua::{AnyUserData, IntoLua, LuaString, MetaMethod, UserData, UserDataMethods, UserDataRef, Value};
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, Value};
use paste::paste;
use super::{Lives, PtrCell};
pub(super) type CoreRef = UserDataRef<Core>;
pub(super) struct Core {
inner: PtrCell<yazi_core::Core>,
@ -14,8 +12,6 @@ pub(super) struct Core {
c_tabs: Option<Value>,
c_tasks: Option<Value>,
c_yanked: Option<Value>,
c_input: Option<Value>,
c_which: Option<Value>,
c_layer: Option<Value>,
}
@ -34,16 +30,14 @@ impl Core {
c_tabs: None,
c_tasks: None,
c_yanked: None,
c_input: None,
c_which: None,
c_layer: None,
})
}
}
impl UserData for Core {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: LuaString| {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: mlua::String| {
macro_rules! reuse {
($key:ident, $value:expr) => {
match paste! { &me.[<c_ $key>] } {
@ -61,9 +55,9 @@ impl UserData for Core {
b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),
b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)),
b"input" => reuse!(input, super::Input::make(&me.input)),
b"which" => reuse!(which, super::Which::make(&me.which)),
b"layer" => reuse!(layer, Ok::<_, mlua::Error>(me.layer())),
b"layer" => {
reuse!(layer, Ok::<_, mlua::Error>(yazi_plugin::bindings::Layer::from(me.layer())))
}
_ => Value::Nil,
})
});

View file

@ -1,28 +1,37 @@
use std::{ops::Deref, ptr};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods};
use yazi_binding::{Range, style::Style};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Style, cached_field};
use yazi_config::THEME;
use yazi_fs::file::FileInventory;
use yazi_shared::{path::DynPath, url::UrlLike};
use yazi_plugin::bindings::Range;
use yazi_shared::{path::AsPath, url::UrlLike};
use super::{FILE_CACHE, Lives};
use crate::lives::{CoreRef, PtrCell};
use super::Lives;
use crate::lives::PtrCell;
pub(super) struct File {
idx: usize,
folder: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_cha: Option<Value>,
v_url: Option<Value>,
v_link_to: Option<Value>,
v_name: Option<Value>,
v_cache: Option<Value>,
v_bare: Option<Value>,
}
impl Deref for File {
type Target = yazi_fs::file::File;
type Target = yazi_fs::File;
fn deref(&self) -> &Self::Target { &self.folder.entries[self.idx] }
fn deref(&self) -> &Self::Target { &self.folder.files[self.idx] }
}
impl AsRef<yazi_fs::file::File> for File {
fn as_ref(&self) -> &yazi_fs::file::File { self }
impl AsRef<yazi_fs::File> for File {
fn as_ref(&self) -> &yazi_fs::File { self }
}
impl File {
@ -33,28 +42,37 @@ impl File {
) -> mlua::Result<AnyUserData> {
use hashbrown::hash_map::Entry;
Ok(
match unsafe { (*FILE_CACHE.get()).assume_init_mut() }.entry(PtrCell(&folder.entries[idx])) {
Entry::Occupied(oe) => oe.into_mut().clone(),
Entry::Vacant(ve) => {
let ud = Lives::scoped_userdata(Self { idx, folder: folder.into(), tab: tab.into() })?;
ve.insert(ud.clone());
ud
}
},
)
}
Ok(match super::FILE_CACHE.borrow_mut().entry(PtrCell(&folder.files[idx])) {
Entry::Occupied(oe) => oe.into_mut().clone(),
Entry::Vacant(ve) => {
let ud = Lives::scoped_userdata(Self {
idx,
folder: folder.into(),
tab: tab.into(),
#[inline]
fn is_hovered(&self) -> bool { self.idx == self.folder.cursor }
v_cha: None,
v_url: None,
v_link_to: None,
v_name: None,
v_cache: None,
v_bare: None,
})?;
ve.insert(ud.clone());
ud
}
})
}
}
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.is_hovered()));
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.idx == me.folder.cursor && me.tab.hovered().is_some_and(|f| f.url == me.folder.url))
@ -64,24 +82,13 @@ impl UserData for File {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
yazi_binding::impl_file_methods!(methods);
methods.add_method("icon", |lua, me, ()| {
yazi_binding::deprecate!(
lua,
"{}: `File:icon()` is deprecated, use `th.icon:match(file)` instead"
);
// TODO: use a cache
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
});
methods.add_method("size", |_, me, ()| {
Ok(if me.is_dir() {
me.folder.entries.sizes.get(&me.entry_key()).copied()
} else {
Some(me.len)
})
Ok(if me.is_dir() { me.folder.files.sizes.get(&me.urn()).copied() } else { Some(me.len) })
});
methods.add_method("mime", |lua, me, ()| {
let core: CoreRef = lua.named_registry_value("cx")?;
core.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose()
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
core.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose()
})?
});
methods.add_method("prefix", |lua, me, ()| {
if !me.url.has_trail() {
@ -90,68 +97,64 @@ impl UserData for File {
let mut comp = me.url.try_strip_prefix(me.url.trail()).unwrap_or(me.url.loc()).components();
comp.next_back();
Some(lua.create_string(comp.dyn_path().encoded_bytes())).transpose()
Some(lua.create_string(comp.as_path().encoded_bytes())).transpose()
});
methods.add_method("style", |lua, me, ()| {
let core: CoreRef = lua.named_registry_value("cx")?;
let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();
Ok(THEME.filetype.match_style(me, mime).map(Style::from))
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();
THEME.filetype.iter().find(|&x| x.matches(me, mime)).map(|x| Style::from(x.style))
})
});
methods.add_method("is_yanked", |lua, me, ()| {
let core: CoreRef = lua.named_registry_value("cx")?;
Ok(if !core.mgr.yanked.contains(&me.url) {
0u8
} else if core.mgr.yanked.cut {
2u8
} else {
1u8
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
if !core.mgr.yanked.contains(&me.url) {
0u8
} else if core.mgr.yanked.cut {
2u8
} else {
1u8
}
})
});
methods.add_method("is_marked", |_, me, ()| {
let Some(visual) = me.tab.mode.visual() else {
return Ok(0u8);
};
if !visual.contains(me.idx, me.tab.current.cursor, me.folder.entries.len()) {
use yazi_core::tab::Mode::*;
if !me.tab.mode.is_visual() || me.folder.url != me.tab.current.url {
return Ok(0u8);
}
if me.folder.url != me.tab.current.url {
return Ok(0u8);
}
Ok(if me.tab.mode.is_select() { 1u8 } else { 2u8 })
Ok(match &me.tab.mode {
Select(_, indices) if indices.contains(&me.idx) => 1u8,
Unset(_, indices) if indices.contains(&me.idx) => 2u8,
_ => 0u8,
})
});
methods.add_method("is_selected", |_, me, ()| Ok(me.tab.selected.contains(&me.url)));
methods.add_method("found", |lua, me, ()| {
let core: CoreRef = lua.named_registry_value("cx")?;
let Some(finder) = &core.active().finder else {
return Ok(None);
};
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
let Some(finder) = &core.active().finder else {
return Ok(None);
};
let Some(idx) = finder.matched_idx(&me.folder, me.entry_key()) else {
return Ok(None);
};
let Some(idx) = finder.matched_idx(&me.folder, me.urn()) else {
return Ok(None);
};
lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?]).map(Some)
Some(lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?]))
.transpose()
})
});
methods.add_method("highlights", |lua, me, ()| {
let core: CoreRef = lua.named_registry_value("cx")?;
let Some(finder) = &core.active().finder else {
return Ok(None);
};
if me.folder.url != me.tab.current.url {
return Ok(None);
}
let Some(Some(h)) = me.url.name().map(|s| finder.filter.highlighted(s)) else {
return Ok(None);
};
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
let Some(finder) = &core.active().finder else {
return None;
};
if me.folder.url != me.tab.current.url {
return None;
}
lua.create_sequence_from(h.into_iter().map(Range::from)).map(Some)
let h = finder.filter.highlighted(me.url.name()?)?;
Some(h.into_iter().map(Range::from).collect::<Vec<_>>())
})
});
}
}
inventory::submit! {
FileInventory {
register: |_| {},
borrow: |ud, f| f(&*ud.borrow::<File>()?),
}
}

View file

@ -1,35 +1,37 @@
use std::ops::{Deref, Range};
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
use yazi_shim::mlua::UserDataFieldsExt;
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::cached_field;
use super::{File, Filter, Lives, PtrCell};
pub(super) struct Entries {
pub(super) struct Files {
window: Range<usize>,
folder: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_filter: Option<Value>,
}
impl Deref for Entries {
type Target = yazi_fs::Entries;
impl Deref for Files {
type Target = yazi_fs::Files;
fn deref(&self) -> &Self::Target { &self.folder.entries }
fn deref(&self) -> &Self::Target { &self.folder.files }
}
impl Entries {
impl Files {
pub(super) fn make(
window: Range<usize>,
folder: &yazi_core::tab::Folder,
tab: &yazi_core::tab::Tab,
) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into() })
Lives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into(), v_filter: None })
}
}
impl UserData for Entries {
impl UserData for Files {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_static_field("filter", |_, me| me.filter().map(Filter::make).transpose());
cached_field!(fields, filter, |_, me| me.filter().map(Filter::make).transpose());
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -1,15 +1,21 @@
use std::ops::{Deref, Range};
use mlua::{AnyUserData, UserData, UserDataFields};
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::{FolderStage, Url, cached_field};
use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Entries, File, Lives, PtrCell};
use super::{File, Files, Lives, PtrCell};
pub(super) struct Folder {
window: Range<usize>,
inner: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_cwd: Option<Value>,
v_files: Option<Value>,
v_stage: Option<Value>,
v_window: Option<Value>,
v_hovered: Option<Value>,
}
impl Deref for Folder {
@ -28,24 +34,34 @@ impl Folder {
Some(w) => w,
None => {
let limit = LAYOUT.get().preview.height as usize;
inner.offset..inner.entries.len().min(inner.offset + limit)
inner.offset..inner.files.len().min(inner.offset + limit)
}
};
Lives::scoped_userdata(Self { window, inner: inner.into(), tab: tab.into() })
Lives::scoped_userdata(Self {
window,
inner: inner.into(),
tab: tab.into(),
v_cwd: None,
v_files: None,
v_stage: None,
v_window: None,
v_hovered: None,
})
}
}
impl UserData for Folder {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("cwd", |_, me| Ok(me.url.clone()));
fields.add_static_field("files", |_, me| Entries::make(0..me.entries.len(), me, &me.tab));
fields.add_cached_field("stage", |_, me| Ok(me.stage.clone()));
fields.add_static_field("window", |_, me| Entries::make(me.window.clone(), me, &me.tab));
cached_field!(fields, cwd, |_, me| Ok(Url::new(&me.url)));
cached_field!(fields, files, |_, me| Files::make(0..me.files.len(), me, &me.tab));
cached_field!(fields, stage, |_, me| Ok(FolderStage::new(me.stage.clone())));
cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab));
fields.add_field_method_get("offset", |_, me| Ok(me.offset));
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
fields.add_static_field("hovered", |_, me| {
cached_field!(fields, hovered, |_, me| {
me.hovered().map(|_| File::make(me.cursor, me, &me.tab)).transpose()
});
}

View file

@ -1,29 +0,0 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
use crate::lives::InputAlt;
pub(super) struct Input {
inner: PtrCell<yazi_core::input::Input>,
}
impl Deref for Input {
type Target = yazi_core::input::Input;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Input {
pub(super) fn make(inner: &yazi_core::input::Input) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Input {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("alt", |_, me| me.alt.as_ref().map(InputAlt::make).transpose());
}
}

View file

@ -1,26 +0,0 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
pub(super) struct InputAlt(PtrCell<yazi_core::input::InputAlt>);
impl Deref for InputAlt {
type Target = yazi_core::input::InputAlt;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl InputAlt {
pub(super) fn make(inner: &yazi_core::input::InputAlt) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self(inner.into()))
}
}
impl UserData for InputAlt {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("value", |lua, me| lua.create_string(me.lock().value()));
}
}

View file

@ -1,38 +1,36 @@
use std::mem::MaybeUninit;
use std::cell::RefCell;
use hashbrown::HashMap;
use mlua::{AnyUserData, UserData};
use scopeguard::defer;
use tracing::error;
use yazi_plugin::LUA;
use yazi_shared::RoCell;
use super::{Core, PtrCell};
use crate::lives::MutCell;
pub(super) static TO_DESTROY: MutCell<Vec<AnyUserData>> = MutCell::new(Vec::new());
pub(super) static FILE_CACHE: MutCell<
MaybeUninit<HashMap<PtrCell<yazi_fs::file::File>, AnyUserData>>,
> = MutCell::new(MaybeUninit::uninit());
static TO_DESTROY: RoCell<RefCell<Vec<AnyUserData>>> = RoCell::new_const(RefCell::new(Vec::new()));
pub(super) static FILE_CACHE: RoCell<RefCell<HashMap<PtrCell<yazi_fs::File>, AnyUserData>>> =
RoCell::new();
pub struct Lives;
impl Lives {
pub fn scope<T, F>(core: &mut yazi_core::Core, f: F) -> mlua::Result<T>
where
F: FnOnce(&mut yazi_core::Core) -> mlua::Result<T>,
{
defer! {
unsafe {
(*FILE_CACHE.get()).assume_init_mut().clear();
for ud in (*TO_DESTROY.get()).drain(..) {
pub fn scope<T>(core: &yazi_core::Core, f: impl FnOnce() -> mlua::Result<T>) -> mlua::Result<T> {
FILE_CACHE.init(Default::default());
defer! { FILE_CACHE.drop(); }
let result = LUA.scope(|scope| {
scope.add_destructor(|| {
for ud in TO_DESTROY.borrow_mut().drain(..) {
ud.destroy().expect("failed to destruct scoped userdata");
}
}
}
});
LUA.set_named_registry_value("cx", Core::make(core)?)?;
LUA.globals().raw_set("cx", Core::make(core)?)?;
let result = f(core);
LUA.set_named_registry_value("cx", scope.create_any_userdata_ref(core)?)?;
LUA.globals().raw_set("cx", Core::make(core)?)?;
f()
});
if let Err(ref e) = result {
error!("{e}");
@ -45,7 +43,7 @@ impl Lives {
T: UserData + 'static,
{
let ud = LUA.create_userdata(data)?;
unsafe { &mut *TO_DESTROY.get() }.push(ud.clone());
TO_DESTROY.borrow_mut().push(ud.clone());
Ok(ud)
}
}

View file

@ -1,5 +1 @@
yazi_macro::mod_flat!(behavior core entries file filter finder folder input input_alt lives mode mut_cell preference preview ptr selected tab tabs task tasks which yanked);
pub(super) fn init() {
unsafe { FILE_CACHE.get().write(std::mem::MaybeUninit::new(<_>::default())) };
}
yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks yanked);

View file

@ -1,7 +1,6 @@
use std::ops::Deref;
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
use yazi_binding::deprecate;
use super::{Lives, PtrCell};
@ -23,13 +22,9 @@ impl Mode {
impl UserData for Mode {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("is_normal", |_, me| Ok(me.is_normal()));
fields.add_field_method_get("is_select", |_, me| Ok(me.is_select()));
fields.add_field_method_get("is_unset", |_, me| Ok(me.is_unset()));
fields.add_field_method_get("is_visual", |lua, me| {
deprecate!(lua, "{}: `mode.is_visual` is deprecated, use `not mode.is_normal` instead. \nSee #4101 for more details: https://github.com/sxyazi/yazi/pull/4101.");
Ok(!me.is_normal())
});
fields.add_field_method_get("is_visual", |_, me| Ok(me.is_visual()));
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -1,11 +0,0 @@
use std::cell::UnsafeCell;
pub(super) struct MutCell<T>(UnsafeCell<T>);
unsafe impl<T> Sync for MutCell<T> {}
impl<T> MutCell<T> {
pub(super) const fn new(value: T) -> Self { Self(UnsafeCell::new(value)) }
pub(super) fn get(&self) -> *mut T { self.0.get() }
}

View file

@ -1,12 +1,15 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use super::{Lives, PtrCell};
pub(super) struct Preference {
inner: PtrCell<yazi_core::tab::Preference>,
v_sort_by: Option<Value>,
v_linemode: Option<Value>,
}
impl Deref for Preference {
@ -17,23 +20,19 @@ impl Deref for Preference {
impl Preference {
pub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
Lives::scoped_userdata(Self { inner: inner.into(), v_sort_by: None, v_linemode: None })
}
}
impl UserData for Preference {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// Display
fields.add_cached_field("name", |lua, me| lua.create_string(&me.name));
fields.add_cached_field("linemode", |lua, me| lua.create_string(&*me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
// Sorting
fields.add_cached_field("sort_by", |_, me| Ok(me.sort_by.into_str()));
cached_field!(fields, sort_by, |_, me| Ok(me.sort_by.to_string()));
fields.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
fields.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
fields.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit));
fields.add_field_method_get("sort_fallback", |_, me| Ok(me.sort_fallback.into_str()));
cached_field!(fields, linemode, |lua, me| lua.create_string(&me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
}
}

View file

@ -1,13 +1,15 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Folder, Lives, PtrCell};
pub(super) struct Preview {
tab: PtrCell<yazi_core::tab::Tab>,
v_folder: Option<Value>,
}
impl Deref for Preview {
@ -18,19 +20,19 @@ impl Deref for Preview {
impl Preview {
pub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { tab: tab.into() })
Lives::scoped_userdata(Self { tab: tab.into(), v_folder: None })
}
}
impl UserData for Preview {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("skip", |_, me| Ok(me.skip));
fields.add_static_field("folder", |_, me| {
cached_field!(fields, folder, |_, me| {
me.tab
.hovered_folder()
.map(|f| {
let limit = LAYOUT.get().preview.height as usize;
Folder::make(Some(me.skip..f.entries.len().min(me.skip + limit)), f, &me.tab)
Folder::make(Some(me.skip..f.files.len().min(me.skip + limit)), f, &me.tab)
})
.transpose()
});

View file

@ -1,6 +1,7 @@
use mlua::AnyUserData;
use super::{Lives, PtrCell};
use super::Lives;
use crate::lives::PtrCell;
#[derive(Clone, Copy)]
pub(super) struct Selected;
@ -10,7 +11,7 @@ impl Selected {
let inner = PtrCell::from(inner);
Lives::scoped_userdata(yazi_binding::Iter::new(
inner.as_static().files().cloned(),
inner.as_static().values().map(yazi_binding::Url::new),
Some(inner.len()),
))
}

View file

@ -1,13 +1,22 @@
use std::{borrow::Cow, ops::Deref};
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods};
use yazi_shared::url::UrlRef;
use yazi_shim::mlua::UserDataFieldsExt;
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Id, UrlRef, cached_field};
use yazi_shared::url::UrlLike;
use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};
pub(super) struct Tab {
inner: PtrCell<yazi_core::tab::Tab>,
v_name: Option<Value>,
v_mode: Option<Value>,
v_pref: Option<Value>,
v_current: Option<Value>,
v_parent: Option<Value>,
v_selected: Option<Value>,
v_preview: Option<Value>,
v_finder: Option<Value>,
}
impl Deref for Tab {
@ -18,29 +27,40 @@ impl Deref for Tab {
impl Tab {
pub(super) fn make(inner: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
Lives::scoped_userdata(Self {
inner: inner.into(),
v_name: None,
v_mode: None,
v_pref: None,
v_current: None,
v_parent: None,
v_selected: None,
v_preview: None,
v_finder: None,
})
}
}
impl UserData for Tab {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(me.id));
fields.add_cached_field("name", |lua, me| match me.name() {
Cow::Borrowed(s) => lua.create_string(s),
Cow::Owned(s) => lua.create_external_string(s),
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| {
let url = &me.current.url;
lua.create_string(url.name().map_or_else(|| url.loc().encoded_bytes(), |n| n.encoded_bytes()))
});
fields.add_static_field("mode", |_, me| Mode::make(&me.mode));
fields.add_static_field("pref", |_, me| Preference::make(&me.pref));
fields.add_static_field("current", |_, me| Folder::make(None, &me.current, me));
fields.add_static_field("parent", |_, me| {
cached_field!(fields, mode, |_, me| Mode::make(&me.mode));
cached_field!(fields, pref, |_, me| Preference::make(&me.pref));
cached_field!(fields, current, |_, me| Folder::make(None, &me.current, me));
cached_field!(fields, parent, |_, me| {
me.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose()
});
fields.add_static_field("selected", |_, me| Selected::make(&me.selected));
cached_field!(fields, selected, |_, me| Selected::make(&me.selected));
fields.add_static_field("preview", |_, me| Preview::make(me));
fields.add_static_field("finder", |_, me| me.finder.as_ref().map(Finder::make).transpose());
cached_field!(fields, preview, |_, me| Preview::make(me));
cached_field!(fields, finder, |_, me| me.finder.as_ref().map(Finder::make).transpose());
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -1,13 +1,15 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
use yazi_scheduler::Progress;
use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{SER_OPT, cached_field};
use super::{Lives, PtrCell};
pub(super) struct TaskSnap {
inner: PtrCell<yazi_scheduler::TaskSnap>,
v_name: Option<Value>,
v_prog: Option<Value>,
}
impl Deref for TaskSnap {
@ -18,17 +20,17 @@ impl Deref for TaskSnap {
impl TaskSnap {
pub(super) fn make(inner: &yazi_scheduler::TaskSnap) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
Lives::scoped_userdata(Self { inner: inner.into(), v_name: None, v_prog: None })
}
}
impl UserData for TaskSnap {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("title", |lua, me| lua.create_string(&me.title));
fields.add_cached_field("prog", |lua, me| lua.to_value_with(&me.prog, SER_OPT));
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("running", |_, me| Ok(me.prog.running()));
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()));
fields.add_field_method_get("percent", |_, me| Ok(me.prog.percent()));

View file

@ -1,13 +1,17 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{SER_OPT, cached_field, deprecate};
use super::{Lives, PtrCell};
use crate::lives::{Behavior, TaskSnap};
use crate::lives::TaskSnap;
pub(super) struct Tasks {
inner: PtrCell<yazi_core::tasks::Tasks>,
v_snaps: Option<Value>,
v_summary: Option<Value>,
v_progress: Option<Value>,
}
impl Deref for Tasks {
@ -18,7 +22,13 @@ impl Deref for Tasks {
impl Tasks {
pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
Lives::scoped_userdata(Self {
inner: inner.into(),
v_snaps: None,
v_summary: None,
v_progress: None,
})
}
}
@ -26,9 +36,7 @@ impl UserData for Tasks {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
fields.add_static_field("behavior", |_, me| Behavior::make(&me.scheduler.behavior));
fields.add_static_field("snaps", |lua, me| {
cached_field!(fields, snaps, |lua, me| {
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
for snap in &me.snaps {
tbl.raw_push(TaskSnap::make(snap)?)?;
@ -36,6 +44,20 @@ impl UserData for Tasks {
Ok(tbl)
});
fields.add_cached_field("summary", |lua, me| lua.to_value_with(&me.summary, SER_OPT));
cached_field!(fields, summary, |lua, me| lua.to_value_with(&me.summary, SER_OPT));
cached_field!(fields, progress, |lua, me| {
deprecate!(
lua,
"`cx.tasks.progress` is deprecated, use `cx.tasks.summary` instead, in your {}"
);
lua.create_table_from([
("total", me.summary.total),
("succ", me.summary.success),
("fail", me.summary.failed),
("found", 0),
("processed", 0),
])
});
}
}

View file

@ -1,35 +0,0 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
pub(super) struct Which {
inner: PtrCell<yazi_core::which::Which>,
}
impl Deref for Which {
type Target = yazi_core::which::Which;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Which {
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Which {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("tx", |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
fields.add_cached_field("cands", |lua, me| {
lua.create_sequence_from(me.inner.cands.iter().cloned())
});
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
fields.add_field_method_get("active", |_, me| Ok(me.inner.active));
fields.add_field_method_get("silent", |_, me| Ok(me.inner.silent));
}
}

View file

@ -1,9 +1,7 @@
use std::ops::Deref;
use mlua::{AnyUserData, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods};
use yazi_binding::Iter;
use yazi_fs::file::File;
use yazi_shim::mlua::get_metatable;
use yazi_binding::{Iter, get_metatable};
use super::{Lives, PtrCell};
@ -25,7 +23,7 @@ impl Yanked {
Lives::scoped_userdata(Self {
inner,
iter: Lives::scoped_userdata(Iter::new(
inner.as_static().iter().map(File::from),
inner.as_static().iter().map(yazi_binding::Url::new),
Some(inner.len()),
))?,
})

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::ArrowForm;
use yazi_parser::ArrowOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
@ -8,30 +8,26 @@ use crate::{Actor, Ctx};
pub struct Arrow;
impl Actor for Arrow {
type Form = ArrowForm;
type Options = ArrowOpt;
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let tab = cx.tab_mut();
let old = tab.current.cursor;
if !tab.current.arrow(form.step) {
if !tab.current.arrow(opt.step) {
succ!();
}
// Retrace
tab.current.retrace();
// Visual selection
if let Some(visual) = tab.mode.visual_mut() {
visual.arrow(form.step, old, tab.current.cursor);
if let Some((start, items)) = tab.mode.visual_mut() {
let end = tab.current.cursor;
*items = (start.min(end)..=end.max(start)).collect();
}
act!(mgr:hover, cx)?;
act!(mgr:peek, cx)?;
act!(mgr:watch, cx)?;
cx.tasks.scheduler.behavior.reset();
succ!(render!());
}
}

View file

@ -1,7 +1,6 @@
use anyhow::Result;
use yazi_core::mgr::CdSource;
use yazi_macro::{act, succ};
use yazi_parser::VoidForm;
use yazi_parser::{VoidOpt, mgr::CdSource};
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
@ -9,11 +8,11 @@ use crate::{Actor, Ctx};
pub struct Back;
impl Actor for Back {
type Form = VoidForm;
type Options = VoidOpt;
const NAME: &str = "back";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
if let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() {
act!(mgr:cd, cx, (u, CdSource::Back))?;
}

View file

@ -1,175 +0,0 @@
use std::{fmt::{self, Display}, io::{self, Read, Write}, path::{MAIN_SEPARATOR, Path}};
use anyhow::{Result, anyhow};
use scopeguard::defer;
use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRuleArc};
use yazi_fs::{FilesOp, Splatter, engine::{Engine, local::Local}};
use yazi_macro::{succ, writef};
use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
use yazi_shared::{data::Data, strand::Strand, url::{UrlBuf, UrlLike}};
use yazi_shim::path::CROSS_SEPARATOR;
use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::engine;
use yazi_watcher::WATCHER;
use crate::{Actor, Ctx};
pub struct BulkCreate;
impl Actor for BulkCreate {
type Form = VoidForm;
const NAME: &str = "bulk_create";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
let Some(opener) = Self::opener() else {
succ!(NotifyProxy::push_warn("Bulk create", "No text opener found"));
};
let cwd = cx.cwd().clone();
tokio::spawn(async move {
let tmp = YAZI.preview.tmpfile("bulk-create");
let file = engine::create_new(&tmp).await?.file().await?;
defer! {
let tmp = tmp.clone();
tokio::spawn(async move {
Local::regular(&tmp).remove_file().await
});
}
TasksProxy::process_exec(ShellOpt {
cwd: cwd.clone(),
cmd: Splatter::new(&[file]).splat(&opener.run),
block: opener.block,
orphan: opener.orphan,
})
.await;
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());
AppProxy::stop().await;
let content = Local::regular(&tmp).read_to_string().await?;
Self::r#do(cwd, content.lines().filter_map(Entry::parse).collect()).await
});
succ!()
}
}
impl BulkCreate {
async fn r#do(cwd: UrlBuf, todo: Vec<Entry<'_>>) -> Result<()> {
writef!(TTY.writer(), "{EraseScreen}\n")?;
if todo.is_empty() {
return Ok(());
} else if !Self::ask_continue(&todo, None)? {
return Ok(()); // TODO: support `bulk_exit`?
}
let _permit = WATCHER.acquire().await.unwrap();
let (mut failed, mut succeeded) = (vec![], Vec::with_capacity(todo.len()));
for entry in todo {
let Ok(dist) = cwd.try_join(entry.path) else {
failed.push((entry, anyhow!("Invalid path")));
continue;
};
let result: io::Result<()> = if entry.is_dir {
engine::create_dir_all(&dist).await
} else if let Some(parent) = dist.parent() {
engine::create_dir_all(parent).await.ok();
engine::create_new(&dist).await.map(|_| ())
} else {
Err(io::Error::other("No parent directory"))
};
if let Err(e) = result {
failed.push((entry, e.into()));
} else if let Ok(f) = engine::file(dist).await {
succeeded.push(f);
} else {
failed.push((entry, anyhow!("Failed to retrieve file info")));
}
}
if !succeeded.is_empty() {
// err!(Pubsub::pub_after_bulk_create(it)); // FIXME
FilesOp::create(succeeded);
}
drop(_permit);
if !failed.is_empty() {
Self::output_failed(failed).await?;
}
Ok(())
}
fn opener() -> Option<OpenerRuleArc> {
YAZI
.open
.match_dummy(Path::new("bulk-create.txt"), "text/plain")
.and_then(|r| YAZI.opener.block(&r))
}
fn ask_continue(todo: &[Entry], decision: Option<bool>) -> Result<bool> {
if let Some(decision) = decision {
return Ok(decision);
}
{
let mut w = TTY.lockout();
for entry in todo {
writeln!(w, "{entry}")?;
}
write!(w, "Continue to create? (y/N): ")?;
w.flush()?;
}
let mut buf = [0; 10];
_ = TTY.reader().read(&mut buf)?;
Ok(buf[0] == b'y' || buf[0] == b'Y')
}
async fn output_failed(failed: Vec<(Entry<'_>, anyhow::Error)>) -> Result<()> {
let mut stdout = TTY.lockout();
writeln!(stdout, "{EraseScreen}")?;
writeln!(stdout, "Failed to create:")?;
for (entry, err) in failed {
writeln!(stdout, "{entry}: {err}")?;
}
writeln!(stdout, "\nPress ENTER to exit")?;
stdout.flush()?;
TTY.reader().read_exact(&mut [0])?;
Ok(())
}
}
// --- Entry
struct Entry<'a> {
path: Strand<'a>,
is_dir: bool,
}
impl<'a> Entry<'a> {
fn parse(s: &'a str) -> Option<Self> {
let (path, is_dir) = match s.strip_suffix(CROSS_SEPARATOR) {
Some(p) => (p, true),
None => (s, false),
};
(!path.is_empty()).then_some(Self { path: path.into(), is_dir })
}
}
impl Display for Entry<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_dir {
write!(f, "{}{MAIN_SEPARATOR}", self.path.display())
} else {
self.path.display().fmt(f)
}
}
}

View file

@ -1,19 +0,0 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_parser::mgr::BulkExitForm;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct BulkExit;
impl Actor for BulkExit {
type Form = BulkExitForm;
const NAME: &str = "bulk_exit";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.mgr.batcher.decide(form.target, form.accept);
succ!();
}
}

View file

@ -1,21 +1,20 @@
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::OpenerRuleArc};
use yazi_config::{YAZI, opener::OpenerRule};
use yazi_dds::Pubsub;
use yazi_fs::{FilesOp, Splatter, engine::{Engine, FileBuilder, local::Local}, max_common_root, path::skip_url};
use yazi_macro::{err, succ, writef};
use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::{engine::{self, Demand}, maybe_exists};
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
use yazi_macro::{err, succ};
use yazi_parser::VoidOpt;
use yazi_proxy::{AppProxy, HIDER, TasksProxy};
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_term::tty::TTY;
use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER;
use crate::{Actor, Ctx};
@ -23,18 +22,18 @@ use crate::{Actor, Ctx};
pub struct BulkRename;
impl Actor for BulkRename {
type Form = VoidForm;
type Options = VoidOpt;
const NAME: &str = "bulk_rename";
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
let Some(opener) = Self::opener() else {
succ!(NotifyProxy::push_warn("Bulk rename", "No text opener found"));
succ!(AppProxy::notify_warn("Bulk rename", "No text opener found"));
};
let selected: Vec<_> = cx.tab().selected_or_hovered_urls().cloned().collect();
let selected: Vec<_> = cx.tab().selected_or_hovered().cloned().collect();
if selected.is_empty() {
succ!(NotifyProxy::push_warn("Bulk rename", "No files selected"));
succ!(AppProxy::notify_warn("Bulk rename", "No files selected"));
}
let root = max_common_root(&selected);
@ -42,31 +41,32 @@ impl Actor for BulkRename {
selected.iter().enumerate().map(|(i, u)| Tuple::new(i, skip_url(u, root))).collect();
let cwd = cx.cwd().clone();
let batcher = cx.core.mgr.batcher.clone();
tokio::spawn(async move {
let tmp = YAZI.preview.tmpfile("bulk-rename");
let mut rw = Demand::default().write(true).create_new(true).open(&tmp).await?;
rw.write_all(old.join(Strand::Utf8("\n")).encoded_bytes()).await?;
let tmp = YAZI.preview.tmpfile("bulk");
Gate::default()
.write(true)
.create_new(true)
.open(&tmp)
.await?
.write_all(old.join(Strand::Utf8("\n")).encoded_bytes())
.await?;
defer! {
let tmp = tmp.clone();
batcher.drain(&tmp);
tokio::spawn(async move {
Local::regular(&tmp).remove_file().await
});
}
batcher.prime(&tmp);
TasksProxy::process_exec(ShellOpt {
cwd,
cmd: Splatter::new(&[rw.into_file().await?]).splat(&opener.run),
block: opener.block,
orphan: opener.orphan,
})
TasksProxy::process_exec(
cwd.into(),
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
opener.block,
opener.orphan,
)
.await;
let _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());
let _permit = Permit::new(HIDER.acquire().await.unwrap(), AppProxy::resume());
AppProxy::stop().await;
let new: Vec<_> = Local::regular(&tmp)
@ -78,8 +78,7 @@ impl Actor for BulkRename {
.map(|(i, s)| Tuple::new(i, s))
.collect();
let decision = batcher.drain(&tmp);
Self::r#do(root, old, new, selected, decision).await
Self::r#do(root, old, new, selected).await
});
succ!();
}
@ -91,12 +90,12 @@ impl BulkRename {
old: Vec<Tuple>,
new: Vec<Tuple>,
selected: Vec<UrlBuf>,
decision: Option<bool>,
) -> Result<()> {
writef!(TTY.writer(), "{EraseScreen}\n")?;
terminal_clear(TTY.writer())?;
if old.len() != new.len() {
#[rustfmt::skip]
writef!(TTY.writer(), "Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len())?;
let s = format!("Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len());
execute!(TTY.writer(), Print(s))?;
TTY.reader().read_exact(&mut [0])?;
return Ok(());
@ -108,7 +107,18 @@ impl BulkRename {
return Ok(());
}
if !Self::ask_continue(&todo, decision)? {
{
let mut w = TTY.lockout();
for (old, new) in &todo {
writeln!(w, "{} -> {}", old.display(), new.display())?;
}
write!(w, "Continue to rename? (y/N): ")?;
w.flush()?;
}
let mut buf = [0; 10];
_ = TTY.reader().read(&mut buf)?;
if buf[0] != b'y' && buf[0] != b'Y' {
return Ok(());
}
@ -122,11 +132,11 @@ impl BulkRename {
continue;
};
if maybe_exists(&new).await && !engine::must_identical(&old, &new).await {
if maybe_exists(&new).await && !provider::must_identical(&old, &new).await {
failed.push((o, n, anyhow!("Destination already exists")));
} else if let Err(e) = engine::rename(&old, &new).await {
} else if let Err(e) = provider::rename(&old, &new).await {
failed.push((o, n, e.into()));
} else if let Ok(f) = engine::file(new).await {
} else if let Ok(f) = File::new(new).await {
succeeded.insert(old, f);
} else {
failed.push((o, n, anyhow!("Failed to retrieve file info")));
@ -135,7 +145,7 @@ impl BulkRename {
if !succeeded.is_empty() {
let it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));
err!(Pubsub::pub_after_bulk_rename(it));
err!(Pubsub::pub_after_bulk(it));
FilesOp::rename(succeeded);
}
drop(permit);
@ -146,39 +156,17 @@ impl BulkRename {
Ok(())
}
fn opener() -> Option<OpenerRuleArc> {
YAZI
.open
.match_dummy(Path::new("bulk-rename.txt"), "text/plain")
.and_then(|r| YAZI.opener.block(&r))
fn opener() -> Option<&'static OpenerRule> {
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())
}
fn ask_continue(todo: &[(Tuple, Tuple)], decision: Option<bool>) -> Result<bool> {
if let Some(decision) = decision {
return Ok(decision);
}
{
let mut w = TTY.lockout();
for (old, new) in todo {
writeln!(w, "{} -> {}", old.display(), new.display())?;
}
write!(w, "Continue to rename? (y/N): ")?;
w.flush()?;
}
let mut buf = [0; 10];
_ = TTY.reader().read(&mut buf)?;
Ok(buf[0] == b'y' || buf[0] == b'Y')
}
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {
let mut stdout = TTY.lockout();
writeln!(stdout, "{EraseScreen}")?;
terminal_clear(&mut *stdout)?;
writeln!(stdout, "Failed to rename:")?;
for (old, new, err) in failed {

View file

@ -3,67 +3,64 @@ use std::{mem, time::Duration};
use anyhow::Result;
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::YAZI;
use yazi_core::mgr::CdSource;
use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub;
use yazi_fs::{FilesOp, path::{clean_url, expand_url}};
use yazi_macro::{act, err, input, render, succ};
use yazi_parser::mgr::CdForm;
use yazi_proxy::{CmpProxy, MgrProxy};
use yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_vfs::engine;
use yazi_widgets::input::InputEvent;
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
use yazi_macro::{act, err, render, succ};
use yazi_parser::mgr::CdOpt;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
use yazi_shared::{Debounce, data::Data, errors::InputError, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, provider};
use crate::{Actor, Ctx};
pub struct Cd;
impl Actor for Cd {
type Form = CdForm;
type Options = CdOpt;
const NAME: &str = "cd";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
if form.interactive {
if opt.interactive {
return Self::cd_interactive(cx);
} else if form.target == *cx.cwd() {
}
let tab = cx.tab_mut();
if opt.target == *tab.cwd() {
succ!();
}
// Stash first so it's possible to access the original cwd in hooks
act!(mgr:stash, cx, &form).ok();
// Take parent to history
let tab = cx.tab_mut();
if let Some(t) = tab.parent.take() {
tab.history.insert(t.url.clone(), t);
}
// Current
let rep = tab.history.remove_or(&form.target);
let rep = tab.history.remove_or(&opt.target);
let rep = mem::replace(&mut tab.current, rep);
tab.history.insert(rep.url.clone(), rep);
// Parent
if let Some(parent) = form.target.parent() {
if let Some(parent) = opt.target.parent() {
tab.parent = Some(tab.history.remove_or(parent));
}
err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
act!(mgr:displace, cx)?;
act!(mgr:hidden, cx).ok();
act!(mgr:hidden, cx)?;
act!(mgr:sort, cx).ok();
act!(mgr:hover, cx)?;
act!(mgr:refresh, cx)?;
act!(app:title, cx).ok();
act!(mgr:stash, cx, opt).ok();
succ!(render!());
}
}
impl Cd {
fn cd_interactive(cx: &mut Ctx) -> Result<Data> {
let input = input!(cx, YAZI.input.cd(cx.cwd().as_url()))?;
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));
@ -71,22 +68,22 @@ impl Cd {
while let Some(result) = rx.next().await {
match result {
InputEvent::Submit(s) => {
Ok(s) => {
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
let Ok(url) = engine::absolute(&url).await else { return };
let Ok(url) = provider::absolute(&url).await else { return };
let url = clean_url(url);
let Ok(file) = engine::file(&url).await else { return };
let Ok(file) = File::new(&url).await else { return };
if file.is_dir() {
return MgrProxy::cd(&url, CdSource::Cd);
return MgrProxy::cd(&url);
}
if let Some((p, k)) = url.pair2() {
FilesOp::Upserting(p.into(), [(k.into(), file)].into()).emit();
if let Some(p) = url.parent() {
FilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();
}
MgrProxy::reveal(url);
}
InputEvent::Trigger(before, ticket) => {
Err(InputError::Completed(before, ticket)) => {
CmpProxy::trigger(before, ticket);
}
_ => break,

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use yazi_macro::act;
use yazi_parser::{mgr::CloseForm, spark::SparkKind};
use yazi_parser::mgr::CloseOpt;
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
@ -8,19 +8,15 @@ use crate::{Actor, Ctx};
pub struct Close;
impl Actor for Close {
type Form = CloseForm;
type Options = CloseOpt;
const NAME: &str = "close";
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
if cx.tabs().len() > 1 {
act!(mgr:tab_close, cx, cx.tabs().cursor)
} else {
act!(mgr:quit, cx, opt)
act!(mgr:quit, cx, opt.0)
}
}
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
cx.source().is_key().then_some(SparkKind::KeyClose)
}
}

View file

@ -1,6 +1,6 @@
use anyhow::{Result, bail};
use yazi_macro::{act, succ};
use yazi_parser::mgr::CopyForm;
use yazi_parser::mgr::CopyOpt;
use yazi_shared::{data::Data, strand::ToStrand, url::UrlLike};
use yazi_widgets::CLIPBOARD;
@ -9,39 +9,39 @@ use crate::{Actor, Ctx};
pub struct Copy;
impl Actor for Copy {
type Form = CopyForm;
type Options = CopyOpt;
const NAME: &str = "copy";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let mut s = Vec::<u8>::new();
let mut it = if form.hovered {
Box::new(cx.hovered_url().into_iter())
let mut it = if opt.hovered {
Box::new(cx.hovered().map(|h| &h.url).into_iter())
} else {
cx.tab().selected_or_hovered_urls()
cx.tab().selected_or_hovered()
}
.peekable();
while let Some(u) = it.next() {
match form.r#type.as_ref() {
match opt.r#type.as_ref() {
// TODO: rename to "url"
"path" => {
s.extend_from_slice(&form.separator.transform(&u.to_strand()));
s.extend_from_slice(&opt.separator.transform(&u.to_strand()));
}
"dirname" => {
if let Some(p) = u.parent() {
s.extend_from_slice(&form.separator.transform(&p.to_strand()));
s.extend_from_slice(&opt.separator.transform(&p.to_strand()));
}
}
"filename" => {
s.extend_from_slice(&form.separator.transform(&u.name().unwrap_or_default()));
s.extend_from_slice(&opt.separator.transform(&u.name().unwrap_or_default()));
}
"name_without_ext" => {
s.extend_from_slice(&form.separator.transform(&u.stem().unwrap_or_default()));
s.extend_from_slice(&opt.separator.transform(&u.stem().unwrap_or_default()));
}
_ => bail!("Unknown copy type: {}", form.r#type),
_ => bail!("Unknown copy type: {}", opt.r#type),
};
if it.peek().is_some() {
s.push(b'\n');
@ -49,8 +49,8 @@ impl Actor for Copy {
}
// Copy the CWD path regardless even if the directory is empty
if s.is_empty() && form.r#type == "dirname" {
s.extend_from_slice(&form.separator.transform(&cx.cwd().to_strand()));
if s.is_empty() && opt.r#type == "dirname" {
s.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand()));
}
futures::executor::block_on(CLIPBOARD.set(s));

View file

@ -1,15 +1,11 @@
use std::pin::Pin;
use anyhow::{Result, bail};
use futures::{Stream, StreamExt};
use tokio_stream::wrappers::UnboundedReceiverStream;
use yazi_config::{YAZI, popup::ConfirmCfg};
use yazi_fs::{FilesOp, file::File};
use yazi_macro::{input, ok_or_not_found, succ};
use yazi_parser::mgr::CreateForm;
use yazi_proxy::{ConfirmProxy, MgrProxy};
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, strand::{StrandBuf, StrandLike}, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, engine};
use yazi_config::popup::{ConfirmCfg, InputCfg};
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, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER;
use crate::{Actor, Ctx};
@ -17,41 +13,32 @@ use crate::{Actor, Ctx};
pub struct Create;
impl Actor for Create {
type Form = CreateForm;
type Options = CreateOpt;
const NAME: &str = "create";
fn act(cx: &mut Ctx, CreateForm { target, dir, force }: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let cwd = cx.cwd().to_owned();
let mut target: Pin<Box<dyn Stream<Item = StrandBuf> + Send>> = if target.is_empty() {
let input = input!(cx, YAZI.input.create(dir))?;
Box::pin(
UnboundedReceiverStream::new(input).filter_map(|event| async { event.map(Into::into) }),
)
} else {
Box::pin(tokio_stream::iter(vec![target]))
};
let mut input = InputProxy::show(InputCfg::create(opt.dir));
tokio::spawn(async move {
let Some(name) = target.next().await else { return Ok(()) };
let Some(Ok(name)) = input.recv().await else { return };
if name.is_empty() {
return Ok(());
return;
}
let Ok(new) = cwd.try_join(&name) else {
bail!("Failed to join new name with CWD");
return;
};
if !force
&& let Some(file) = File::maybe_new(&new).await?
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&file)).await
if !opt.force
&& maybe_exists(&new).await
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
{
return Ok(());
return;
}
let end_sep = AnyAsciiChar::SEP.predicate(*name.encoded_bytes().last().unwrap());
Self::r#do(new, dir || end_sep).await
_ = Self::r#do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await;
});
succ!();
}
@ -62,26 +49,26 @@ impl Create {
let _permit = WATCHER.acquire().await.unwrap();
if dir {
engine::create_dir_all(&new).await?;
} else if let Ok(real) = engine::casefold(&new).await
&& let Some((parent, key)) = real.pair2()
provider::create_dir_all(&new).await?;
} else if let Ok(real) = provider::casefold(&new).await
&& let Some((parent, urn)) = real.pair()
{
ok_or_not_found!(engine::remove_file(&new).await);
FilesOp::Deleting(parent.into(), [key.into()].into()).emit();
engine::create(&new).await?;
ok_or_not_found!(provider::remove_file(&new).await);
FilesOp::Deleting(parent.into(), [urn.into()].into()).emit();
provider::create(&new).await?;
} else if let Some(parent) = new.parent() {
engine::create_dir_all(parent).await.ok();
ok_or_not_found!(engine::remove_file(&new).await);
engine::create(&new).await?;
provider::create_dir_all(parent).await.ok();
ok_or_not_found!(provider::remove_file(&new).await);
provider::create(&new).await?;
} else {
bail!("Cannot create file at root");
}
if let Ok(real) = engine::casefold(&new).await
&& let Some((parent, key)) = real.pair2()
if let Ok(real) = provider::casefold(&new).await
&& let Some((parent, urn)) = real.pair()
{
let file = engine::file(&real).await?;
FilesOp::Upserting(parent.into(), [(key.into(), file)].into()).emit();
let file = File::new(&real).await?;
FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()).emit();
MgrProxy::reveal(&real);
}

View file

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

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