mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-22 07:11:03 +00:00
Compare commits
No commits in common. "main" and "v26.5.6" have entirely different histories.
961 changed files with 12410 additions and 20527 deletions
6
.github/DISCUSSION_TEMPLATE/1-q-a.yml
vendored
6
.github/DISCUSSION_TEMPLATE/1-q-a.yml
vendored
|
|
@ -22,10 +22,10 @@ body:
|
|||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: 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
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/bug.yml
vendored
8
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -26,10 +26,10 @@ body:
|
|||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: 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`)
|
||||
|
|
|
|||
6
.github/ISSUE_TEMPLATE/feature.yml
vendored
6
.github/ISSUE_TEMPLATE/feature.yml
vendored
|
|
@ -4,10 +4,10 @@ labels: [feature]
|
|||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: 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
|
||||
|
|
|
|||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
|
|
@ -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. -->
|
||||
|
|
|
|||
2
.github/workflows/cachix.yml
vendored
2
.github/workflows/cachix.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
|
|
|||
8
.github/workflows/check.yml
vendored
8
.github/workflows/check.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -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: |
|
||||
|
|
@ -45,12 +45,12 @@ jobs:
|
|||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- 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: actions/checkout@v6
|
||||
- uses: JohnnyMorganz/stylua-action@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
15
.github/workflows/draft.yml
vendored
15
.github/workflows/draft.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
|
||||
CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install gcc
|
||||
if: matrix.gcc != ''
|
||||
|
|
@ -81,7 +81,7 @@ jobs:
|
|||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -97,11 +97,10 @@ 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"
|
||||
|
||||
|
|
@ -121,7 +120,7 @@ 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:
|
||||
|
|
@ -151,7 +150,7 @@ jobs:
|
|||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
@ -234,7 +233,7 @@ jobs:
|
|||
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
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup toolchain install stable --profile minimal
|
||||
|
|
|
|||
|
|
@ -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,7 +24,7 @@ jobs:
|
|||
cd scripts/validate-form
|
||||
npm ci
|
||||
|
||||
- name: Validate Issue
|
||||
- name: Validate Form
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
36
.github/workflows/validate-pr.yml
vendored
36
.github/workflows/validate-pr.yml
vendored
|
|
@ -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 }}
|
||||
71
CHANGELOG.md
71
CHANGELOG.md
|
|
@ -12,51 +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
|
||||
|
|
@ -97,7 +52,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- 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
|
||||
|
||||
|
|
@ -1114,7 +1068,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[v25.12.29]: https://github.com/sxyazi/yazi/compare/v25.5.31...v25.12.29
|
||||
[v26.1.4]: https://github.com/sxyazi/yazi/compare/v25.12.29...v26.1.4
|
||||
[v26.1.22]: https://github.com/sxyazi/yazi/compare/v26.1.4...v26.1.22
|
||||
[v26.5.6]: https://github.com/sxyazi/yazi/compare/v26.1.22...v26.5.6
|
||||
[v26.5.6]: https://github.com/sxyazi/yazi/compare/v26.5.6...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
|
||||
|
|
@ -1749,7 +1703,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#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
|
||||
|
|
@ -1761,25 +1714,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3901]: https://github.com/sxyazi/yazi/pull/3901
|
||||
[#3906]: https://github.com/sxyazi/yazi/pull/3906
|
||||
[#3934]: https://github.com/sxyazi/yazi/pull/3934
|
||||
[#3943]: https://github.com/sxyazi/yazi/pull/3943
|
||||
[#3970]: https://github.com/sxyazi/yazi/pull/3970
|
||||
[#3989]: https://github.com/sxyazi/yazi/pull/3989
|
||||
[#3990]: https://github.com/sxyazi/yazi/pull/3990
|
||||
[#4005]: https://github.com/sxyazi/yazi/pull/4005
|
||||
[#4012]: https://github.com/sxyazi/yazi/pull/4012
|
||||
[#4022]: https://github.com/sxyazi/yazi/pull/4022
|
||||
[#4031]: https://github.com/sxyazi/yazi/pull/4031
|
||||
[#4040]: https://github.com/sxyazi/yazi/pull/4040
|
||||
[#4064]: https://github.com/sxyazi/yazi/pull/4064
|
||||
[#4065]: https://github.com/sxyazi/yazi/pull/4065
|
||||
[#4067]: https://github.com/sxyazi/yazi/pull/4067
|
||||
[#4068]: https://github.com/sxyazi/yazi/pull/4068
|
||||
[#4074]: https://github.com/sxyazi/yazi/pull/4074
|
||||
[#4080]: https://github.com/sxyazi/yazi/pull/4080
|
||||
[#4096]: https://github.com/sxyazi/yazi/pull/4096
|
||||
[#4101]: https://github.com/sxyazi/yazi/pull/4101
|
||||
[#4104]: https://github.com/sxyazi/yazi/pull/4104
|
||||
[#4108]: https://github.com/sxyazi/yazi/pull/4108
|
||||
[#4118]: https://github.com/sxyazi/yazi/pull/4118
|
||||
[#4120]: https://github.com/sxyazi/yazi/pull/4120
|
||||
[#4144]: https://github.com/sxyazi/yazi/pull/4144
|
||||
|
|
|
|||
|
|
@ -11,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.
|
||||
|
|
|
|||
2870
Cargo.lock
generated
2870
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
50
Cargo.toml
50
Cargo.toml
|
|
@ -36,51 +36,47 @@ debug = false
|
|||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "8.0.1"
|
||||
anyhow = "1.0.104"
|
||||
arc-swap = { version = "1.9.2", features = [ "serde" ] }
|
||||
anyhow = "1.0.102"
|
||||
arc-swap = { version = "1.9.1", features = [ "serde" ] }
|
||||
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.11.1", features = [ "serde" ] }
|
||||
chrono = "0.4.44"
|
||||
clap = { version = "4.6.1", 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" }
|
||||
either = { version = "1.15.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" ] }
|
||||
futures = "0.3.32"
|
||||
globset = "0.4.18"
|
||||
hashbrown = { version = "0.17.0", features = [ "serde" ] }
|
||||
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" ] }
|
||||
lru = "0.18.0"
|
||||
mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] }
|
||||
objc2 = "0.6.4"
|
||||
ordered-float = { version = "5.3.0", features = [ "serde" ] }
|
||||
parking_lot = "0.12.5"
|
||||
paste = "1.0.15"
|
||||
percent-encoding = "2.3.2"
|
||||
rand = { version = "0.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.10.1", default-features = false, features = [ "std", "sys_rng" ] }
|
||||
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
regex = "1.12.3"
|
||||
russh = { version = "0.60.2", 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"
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.149"
|
||||
serde_with = "3.19.0"
|
||||
strum = { version = "0.28.0", features = [ "derive" ] }
|
||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
thiserror = "2.0.19"
|
||||
tokio = { version = "1.53.0", features = [ "full" ] }
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.52.2", features = [ "full" ] }
|
||||
tokio-stream = "0.1.18"
|
||||
tokio-util = "0.7.18"
|
||||
toml = { version = "1.1.3" }
|
||||
toml = { version = "1.1.2" }
|
||||
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" ] }
|
||||
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
typed-path = "0.12.3"
|
||||
unicode-width = { version = "0.2.2", default-features = false }
|
||||
uzers = "0.12.2"
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
|
|||
- 🧰 Integration with ripgrep, fd, fzf, zoxide
|
||||
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
|
||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
|
||||
- 🔄 Bulk 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
|
||||
|
|
|
|||
|
|
@ -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"]}
|
||||
{"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"],"version":"0.2","language":"en","flagWords":[]}
|
||||
12
flake.lock
generated
12
flake.lock
generated
|
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1783279667,
|
||||
"narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=",
|
||||
"lastModified": 1776329215,
|
||||
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b",
|
||||
"rev": "b86751bc4085f48661017fa226dee99fab6c651b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -48,11 +48,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1783320166,
|
||||
"narHash": "sha256-l7C/OsjcnWDOk2K3ssj+SBduwL67LashjBqis9+t468=",
|
||||
"lastModified": 1776741231,
|
||||
"narHash": "sha256-k9G98qzn+7npROUaks8VqCFm7cFtEG8ulQLBBo5lItg=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "20ee15370c9256669d66968b89ee20a4b0a4e673",
|
||||
"rev": "02061303f7c4c964f7b4584dabd9e985b4cd442b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -2,15 +2,6 @@ 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)) {
|
||||
|
|
@ -22,7 +13,7 @@ function bugReportBody(creator, content, hash) {
|
|||
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.
|
||||
|
|
@ -41,7 +32,7 @@ function featureRequestBody(creator, content) {
|
|||
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.
|
||||
|
|
@ -50,12 +41,6 @@ Our maintainers work on Yazi in their free time, this helps them work efficientl
|
|||
`
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -229,23 +214,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 +236,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
6
scripts/validate-form/package-lock.json
generated
6
scripts/validate-form/package-lock.json
generated
|
|
@ -238,9 +238,9 @@
|
|||
}
|
||||
},
|
||||
"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": "6.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
|
||||
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
||||
|
|
|
|||
|
|
@ -33,26 +33,28 @@ 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-tty = { path = "../yazi-tty", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
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 }
|
||||
either = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { 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" ] }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ impl Actor for AcceptPayload {
|
|||
drop(lock);
|
||||
|
||||
let kind = kind.to_owned();
|
||||
succ!(Lives::scope(cx.core, |_| {
|
||||
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())) {
|
||||
|
|
|
|||
|
|
@ -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?);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_dds::Sendable;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::LuaForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::{Data, Sendable};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx, lives::Lives};
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ impl Actor for 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, |_| {
|
||||
let result = Lives::scope(cx.core, || {
|
||||
runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
|
||||
});
|
||||
succ!(result?);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ yazi_macro::mod_flat!(
|
|||
accept_payload
|
||||
bootstrap
|
||||
deprecate
|
||||
dnd
|
||||
focus
|
||||
lua
|
||||
mouse
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::event::MouseEventKind;
|
||||
use mlua::{ObjectLike, Table};
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
|
|
@ -7,7 +8,6 @@ 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};
|
||||
|
||||
|
|
@ -19,29 +19,31 @@ impl Actor for Mouse {
|
|||
const NAME: &str = "mouse";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let event = yazi_binding::MouseEvent::from(form.event);
|
||||
|
||||
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)?;
|
||||
let result = Lives::scope(cx.core, move || {
|
||||
let root = runtime_scope!(LUA, "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))?,
|
||||
match event.kind {
|
||||
MouseEventKind::Down(_) => root.call_method("click", (event, false))?,
|
||||
MouseEventKind::Up(_) => root.call_method("click", (event, true))?,
|
||||
|
||||
MouseEventKind::ScrollDown => root.call_method("scroll", (form.event, 1))?,
|
||||
MouseEventKind::ScrollUp => root.call_method("scroll", (form.event, -1))?,
|
||||
MouseEventKind::ScrollDown => root.call_method("scroll", (event, 1))?,
|
||||
MouseEventKind::ScrollUp => root.call_method("scroll", (event, -1))?,
|
||||
|
||||
MouseEventKind::ScrollRight => root.call_method("touch", (form.event, 1))?,
|
||||
MouseEventKind::ScrollLeft => root.call_method("touch", (form.event, -1))?,
|
||||
MouseEventKind::ScrollRight => root.call_method("touch", (event, 1))?,
|
||||
MouseEventKind::ScrollLeft => root.call_method("touch", (event, -1))?,
|
||||
|
||||
MouseEventKind::Moved => root.call_method("move", form.event)?,
|
||||
MouseEventKind::Drag(_) => root.call_method("drag", form.event)?,
|
||||
}
|
||||
MouseEventKind::Moved => root.call_method("move", event)?,
|
||||
MouseEventKind::Drag(_) => root.call_method("drag", event)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(ref e) = result {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ impl Actor for PluginDo {
|
|||
};
|
||||
drop(loader);
|
||||
|
||||
let result = Lives::scope(cx.core, |_| {
|
||||
let result = Lives::scope(cx.core, || {
|
||||
if let Some(cb) = opt.callback {
|
||||
cb(&LUA, plugin)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use std::process;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_fs::engine::{Engine, local::Local};
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_parser::app::QuitForm;
|
||||
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||
use yazi_tui::Raterm;
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -29,8 +27,7 @@ impl Actor for Quit {
|
|||
);
|
||||
});
|
||||
|
||||
Raterm::stop();
|
||||
process::exit(opt.code);
|
||||
Term::goodbye(|| opt.code);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use mlua::{LuaString, Value};
|
||||
use ratatui_core::layout::Position;
|
||||
use mlua::Value;
|
||||
use ratatui::layout::Position;
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_config::LAYOUT;
|
||||
|
|
@ -21,7 +21,7 @@ impl Actor for Reflow {
|
|||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
let mut layout = LAYOUT.get();
|
||||
|
||||
let result = Lives::scope(cx.core, |_| {
|
||||
let result = Lives::scope(cx.core, || {
|
||||
let comps = (form.reflow)((Position::ORIGIN, size).into())?;
|
||||
|
||||
for v in comps.sequence_values::<Value>() {
|
||||
|
|
@ -30,7 +30,7 @@ impl Actor for Reflow {
|
|||
continue;
|
||||
};
|
||||
|
||||
let id: LuaString = t.get("_id")?;
|
||||
let id: mlua::String = t.get("_id")?;
|
||||
match &*id.as_bytes() {
|
||||
b"current" => layout.current = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
|
||||
b"preview" => layout.preview = *t.raw_get::<yazi_binding::elements::Rect>("_area")?,
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::app::ReflowForm;
|
||||
use yazi_parser::app::ResumeForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_tui::Raterm;
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Resume;
|
||||
|
||||
impl Actor for Resume {
|
||||
type Form = ReflowForm;
|
||||
type Form = ResumeForm;
|
||||
|
||||
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()?);
|
||||
*cx.term = Some(Term::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)?;
|
||||
|
||||
form.tx.send((true, form.replier))?;
|
||||
|
||||
act!(app:title, cx).ok();
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_parser::app::StopForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,15 +8,20 @@ use crate::{Actor, Ctx};
|
|||
pub struct Stop;
|
||||
|
||||
impl Actor for Stop {
|
||||
type Form = VoidForm;
|
||||
type Form = StopForm;
|
||||
|
||||
const NAME: &str = "stop";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
cx.active_mut().preview.reset_image();
|
||||
|
||||
// We need to destroy the `term` first before stopping the `signals`
|
||||
// to prevent any signal from triggering the term to render again
|
||||
// while the app is being suspended.
|
||||
*cx.term = None;
|
||||
|
||||
form.tx.send((false, form.replier))?;
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::{execute, terminal::SetTitle};
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::{app::TitleForm, spark::SparkKind};
|
||||
use yazi_shared::{Source, data::Data};
|
||||
use yazi_tty::{TTY, sequence::SetTitle};
|
||||
use yazi_tui::RatermState;
|
||||
use yazi_term::TermState;
|
||||
use yazi_tty::TTY;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
|
|
@ -17,9 +18,9 @@ impl Actor for 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))?;
|
||||
execute!(TTY.writer(), SetTitle(&s))?;
|
||||
|
||||
yazi_tui::STATE.set(RatermState { title: !s.is_empty(), ..yazi_tui::STATE.get() });
|
||||
yazi_term::STATE.set(TermState { title: !s.is_empty(), ..yazi_term::STATE.get() });
|
||||
succ!()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ impl Actor for Close {
|
|||
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 });
|
||||
|
|
@ -31,17 +27,3 @@ impl Actor for Close {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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_shared::{data::Data, path::{AsPath, PathDyn}, strand::StrandLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ impl Actor for Show {
|
|||
succ!();
|
||||
};
|
||||
|
||||
cmp.matches = Self::match_candidates(opt.word.dyn_path(), cache);
|
||||
cmp.matches = Self::match_candidates(opt.word.as_path(), cache);
|
||||
if cmp.matches.is_empty() {
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ use std::{io, 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_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};
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ impl Actor for Trigger {
|
|||
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();
|
||||
cx.cmp.ticket = cx.input.ticket.current();
|
||||
}
|
||||
|
||||
cx.cmp.handle.take().map(|h| h.abort());
|
||||
|
|
@ -36,7 +36,7 @@ impl Actor for Trigger {
|
|||
}
|
||||
|
||||
cx.cmp.handle = Some(tokio::spawn(async move {
|
||||
let mut dir = engine::read_dir(&parent).await?;
|
||||
let mut dir = provider::read_dir(&parent).await?;
|
||||
let mut cache = vec![];
|
||||
|
||||
// "/" is both a directory separator and the root directory per se
|
||||
|
|
@ -53,7 +53,7 @@ impl Actor for Trigger {
|
|||
|
||||
if !cache.is_empty() {
|
||||
cache
|
||||
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), true));
|
||||
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false));
|
||||
CmpProxy::show(CmpOpt { cache, cache_name: parent, word, ticket });
|
||||
}
|
||||
|
||||
|
|
@ -66,26 +66,32 @@ impl Actor for Trigger {
|
|||
|
||||
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 +114,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 +133,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");
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Actor for Arrow {
|
|||
let len = confirm.list.line_count(area.width);
|
||||
|
||||
let old = confirm.offset;
|
||||
confirm.offset = form.step.add(confirm.offset, len, area.height as _, 0, 0);
|
||||
confirm.offset = form.step.add(confirm.offset, len, area.height as _);
|
||||
|
||||
succ!(render!(old != confirm.offset));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ 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_fs::File;
|
||||
use yazi_shared::{Id, Source, event::Action, url::UrlBuf};
|
||||
use yazi_term::Term;
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
pub core: &'a mut Core,
|
||||
pub term: &'a mut Option<Raterm>,
|
||||
pub term: &'a mut Option<Term>,
|
||||
pub tab: usize,
|
||||
pub level: usize,
|
||||
pub source: Source,
|
||||
|
|
@ -27,7 +27,7 @@ impl DerefMut for Ctx<'_> {
|
|||
}
|
||||
|
||||
impl<'a> Ctx<'a> {
|
||||
pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Raterm>) -> Result<Self> {
|
||||
pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Term>) -> Result<Self> {
|
||||
let tab = if let Ok(id) = action.get::<Id>("tab") {
|
||||
core
|
||||
.mgr
|
||||
|
|
@ -63,7 +63,7 @@ impl<'a> Ctx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn active(core: &'a mut Core, term: &'a mut Option<Raterm>) -> Self {
|
||||
pub fn active(core: &'a mut Core, term: &'a mut Option<Term>) -> Self {
|
||||
let tab = core.mgr.tabs.cursor;
|
||||
Self {
|
||||
core,
|
||||
|
|
@ -108,9 +108,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() }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Preflight {
|
|||
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)) {
|
||||
|
|
|
|||
|
|
@ -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!());
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ use anyhow::Result;
|
|||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::InputMode;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -14,11 +13,15 @@ impl Actor for Escape {
|
|||
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);
|
||||
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!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
yazi-actor/src/help/filter.rs
Normal file
22
yazi-actor/src/help/filter.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Filter;
|
||||
|
||||
impl Actor for Filter {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.in_filter = Some(Default::default());
|
||||
help.filter_apply();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(arrow close escape toggle);
|
||||
yazi_macro::mod_flat!(arrow escape filter toggle);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use ratatui_core::layout::Margin;
|
||||
use yazi_config::popup::Help;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::help::ToggleForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::Input;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -16,24 +13,17 @@ impl Actor for Toggle {
|
|||
const NAME: &str = "toggle";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let position = Help::position();
|
||||
let area = cx.mgr.area(position);
|
||||
let input_area = area.inner(Margin::new(1, 1));
|
||||
|
||||
let help = &mut cx.help;
|
||||
help.visible = true;
|
||||
|
||||
help.visible = !help.visible;
|
||||
help.layer = form.layer;
|
||||
help.position = position;
|
||||
help.height = area.height;
|
||||
|
||||
help.input = Input::default();
|
||||
help.input.repos(input_area);
|
||||
|
||||
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!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
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_parser::input::CloseForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -15,33 +14,16 @@ impl Actor for Close {
|
|||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some(mut guard) = cx.input.lock_mut() else {
|
||||
succ!();
|
||||
};
|
||||
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 form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
|
||||
}
|
||||
|
||||
if form.submit
|
||||
&& let InputMutGuard::Main(input) = guard
|
||||
{
|
||||
input.histories.remember(&input.main.history.name, input.main.value());
|
||||
}
|
||||
|
||||
cx.input.main.visible = false;
|
||||
|
||||
act!(cmp:close, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
match cx.source() {
|
||||
Source::Key => Some(SparkKind::KeyInputClose),
|
||||
Source::Ind => Some(SparkKind::IndInputClose),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,11 @@ impl Actor for Complete {
|
|||
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 {
|
||||
let input = &mut cx.input;
|
||||
if !input.visible || input.ticket.current() != form.ticket {
|
||||
succ!();
|
||||
}
|
||||
|
||||
act!(complete, guard, form)
|
||||
act!(complete, input, form)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,20 +15,16 @@ impl Actor for Escape {
|
|||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> 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!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(close complete escape recall remember show);
|
||||
yazi_macro::mod_flat!(close complete escape show);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,27 @@
|
|||
use std::ops::DerefMut;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_config::{THEME, YAZI};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::input::ShowForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_shim::ratatui::Padable;
|
||||
use yazi_widgets::input::InputOpt;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Form = ShowForm;
|
||||
type Form = InputOpt;
|
||||
|
||||
const NAME: &str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
act!(input:close, cx)?;
|
||||
|
||||
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
||||
let input = &mut cx.input;
|
||||
input.main.name = opt.name.clone();
|
||||
input.main.title = opt.title.clone();
|
||||
input.main.position = opt.position;
|
||||
input.main.visible = true;
|
||||
|
||||
opt.styles = (&THEME.input).into();
|
||||
opt.blinking = YAZI.input.cursor_blink;
|
||||
*input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?;
|
||||
input.main.repos(area);
|
||||
input.visible = true;
|
||||
input.title = form.cfg.title.clone();
|
||||
input.position = form.cfg.position;
|
||||
*input.deref_mut() = yazi_widgets::input::Input::new(form)?;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,3 @@ 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_flat!(actor context);
|
||||
|
||||
pub fn init() { lives::init(); }
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, IntoLua, LuaString, MetaMethod, UserData, UserDataMethods, UserDataRef, Value};
|
||||
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataMethods, Value};
|
||||
use paste::paste;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) type CoreRef = UserDataRef<Core>;
|
||||
|
||||
pub(super) struct Core {
|
||||
inner: PtrCell<yazi_core::Core>,
|
||||
|
||||
|
|
@ -14,9 +12,8 @@ pub(super) struct Core {
|
|||
c_tabs: Option<Value>,
|
||||
c_tasks: Option<Value>,
|
||||
c_yanked: Option<Value>,
|
||||
c_input: Option<Value>,
|
||||
c_which: Option<Value>,
|
||||
c_layer: Option<Value>,
|
||||
c_which: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Core {
|
||||
|
|
@ -34,16 +31,15 @@ impl Core {
|
|||
c_tabs: None,
|
||||
c_tasks: None,
|
||||
c_yanked: None,
|
||||
c_input: None,
|
||||
c_which: None,
|
||||
c_layer: None,
|
||||
c_which: 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| {
|
||||
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 +57,10 @@ 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"layer" => {
|
||||
reuse!(layer, Ok::<_, mlua::Error>(yazi_binding::Layer::from(me.layer())))
|
||||
}
|
||||
b"which" => reuse!(which, super::Which::make(&me.which)),
|
||||
b"layer" => reuse!(layer, Ok::<_, mlua::Error>(me.layer())),
|
||||
_ => Value::Nil,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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::{Range, Style, cached_field};
|
||||
use yazi_config::THEME;
|
||||
use yazi_fs::file::FileInventory;
|
||||
use yazi_shared::{path::DynPath, url::UrlLike};
|
||||
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_path: 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,16 +42,28 @@ 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(),
|
||||
|
||||
v_cha: None,
|
||||
v_url: None,
|
||||
v_link_to: None,
|
||||
|
||||
v_name: None,
|
||||
v_path: None,
|
||||
v_cache: None,
|
||||
|
||||
v_bare: None,
|
||||
})?;
|
||||
ve.insert(ud.clone());
|
||||
ud
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -52,6 +73,7 @@ impl File {
|
|||
impl UserData for File {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
yazi_binding::impl_file_fields!(fields);
|
||||
cached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me)));
|
||||
|
||||
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
||||
fields.add_field_method_get("is_hovered", |_, me| Ok(me.is_hovered()));
|
||||
|
|
@ -64,24 +86,18 @@ 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"
|
||||
);
|
||||
methods.add_method("icon", |_, me, ()| {
|
||||
use yazi_binding::Icon;
|
||||
// TODO: use a cache
|
||||
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
|
||||
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()).map(Icon::from))
|
||||
});
|
||||
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 +106,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.match_style(me, mime).map(Style::from)
|
||||
})
|
||||
});
|
||||
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>()?),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,39 @@
|
|||
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_shim::cell::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>
|
||||
pub fn scope<T, F>(core: &yazi_core::Core, f: F) -> mlua::Result<T>
|
||||
where
|
||||
F: FnOnce(&mut yazi_core::Core) -> mlua::Result<T>,
|
||||
F: FnOnce() -> mlua::Result<T>,
|
||||
{
|
||||
defer! {
|
||||
unsafe {
|
||||
(*FILE_CACHE.get()).assume_init_mut().clear();
|
||||
for ud in (*TO_DESTROY.get()).drain(..) {
|
||||
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 +46,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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 which yanked);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
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 yazi_shim::strum::IntoStr;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct Preference {
|
||||
inner: PtrCell<yazi_core::tab::Preference>,
|
||||
|
||||
v_name: Option<Value>,
|
||||
v_linemode: Option<Value>,
|
||||
|
||||
v_sort_by: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Preference {
|
||||
|
|
@ -17,19 +23,26 @@ 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_name: None,
|
||||
v_linemode: None,
|
||||
|
||||
v_sort_by: 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));
|
||||
cached_field!(fields, name, |lua, me| lua.create_string(&me.name));
|
||||
cached_field!(fields, 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.into_str()));
|
||||
fields.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
|
||||
fields.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
|
||||
fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
use std::{borrow::Cow, 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 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 +26,42 @@ 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| {
|
||||
match me.name() {
|
||||
Cow::Borrowed(s) => lua.create_string(s),
|
||||
Cow::Owned(s) => lua.create_external_string(s),
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
|
||||
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
|
||||
use yazi_binding::{SER_OPT, cached_field};
|
||||
use yazi_scheduler::Progress;
|
||||
use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct TaskSnap {
|
||||
inner: PtrCell<yazi_scheduler::TaskSnap>,
|
||||
|
||||
v_title: Option<Value>,
|
||||
v_prog: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for TaskSnap {
|
||||
|
|
@ -18,14 +21,14 @@ 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_title: 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, title, |lua, me| lua.create_string(&me.title));
|
||||
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()));
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
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};
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
impl Deref for Tasks {
|
||||
|
|
@ -18,7 +21,12 @@ 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -26,9 +34,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 +42,6 @@ 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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataFields};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
use mlua::{AnyUserData, UserData, UserDataFields, Value};
|
||||
use yazi_binding::cached_field;
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct Which {
|
||||
inner: PtrCell<yazi_core::which::Which>,
|
||||
|
||||
v_tx: Option<Value>,
|
||||
v_cands: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Which {
|
||||
|
|
@ -17,17 +20,17 @@ impl Deref for Which {
|
|||
|
||||
impl Which {
|
||||
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { inner: inner.into() })
|
||||
Lives::scoped_userdata(Self { inner: inner.into(), v_tx: None, v_cands: None })
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
});
|
||||
cached_field!(fields, tx, |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
|
||||
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
|
||||
cached_field!(fields, cands, |lua, me| {
|
||||
lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::ChordCow))
|
||||
});
|
||||
|
||||
fields.add_field_method_get("active", |_, me| Ok(me.inner.active));
|
||||
fields.add_field_method_get("silent", |_, me| Ok(me.inner.silent));
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
))?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ impl Actor for Arrow {
|
|||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
let old = tab.current.cursor;
|
||||
if !tab.current.arrow(form.step) {
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -23,8 +22,9 @@ impl Actor for Arrow {
|
|||
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)?;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path};
|
||||
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path, sync::Arc};
|
||||
|
||||
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_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_macro::{err, succ};
|
||||
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_scheduler::{AppProxy, NotifyProxy};
|
||||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_tty::{TTY, sequence::EraseScreen};
|
||||
use yazi_vfs::{engine::{self, Demand}, maybe_exists};
|
||||
use yazi_tty::TTY;
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -32,7 +33,7 @@ impl Actor for BulkRename {
|
|||
succ!(NotifyProxy::push_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"));
|
||||
}
|
||||
|
|
@ -44,10 +45,15 @@ impl Actor for BulkRename {
|
|||
let cwd = cx.cwd().clone();
|
||||
let batcher = cx.core.mgr.batcher.clone();
|
||||
tokio::spawn(async move {
|
||||
let tmp = YAZI.preview.tmpfile("bulk-rename");
|
||||
let tmp = YAZI.preview.tmpfile("bulk");
|
||||
|
||||
let mut rw = Demand::default().write(true).create_new(true).open(&tmp).await?;
|
||||
rw.write_all(old.join(Strand::Utf8("\n")).encoded_bytes()).await?;
|
||||
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();
|
||||
|
|
@ -58,12 +64,13 @@ impl Actor for BulkRename {
|
|||
}
|
||||
|
||||
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());
|
||||
|
|
@ -93,10 +100,11 @@ impl BulkRename {
|
|||
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(());
|
||||
|
|
@ -122,11 +130,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 +143,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,7 +154,7 @@ impl BulkRename {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn opener() -> Option<OpenerRuleArc> {
|
||||
fn opener() -> Option<Arc<OpenerRule>> {
|
||||
YAZI
|
||||
.open
|
||||
.match_dummy(Path::new("bulk-rename.txt"), "text/plain")
|
||||
|
|
@ -178,7 +186,7 @@ impl BulkRename {
|
|||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ use std::{mem, time::Duration};
|
|||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_core::mgr::CdSource;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{FilesOp, path::{clean_url, expand_url}};
|
||||
use yazi_macro::{act, err, input, render, succ};
|
||||
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
|
||||
use yazi_macro::{act, err, render, succ};
|
||||
use yazi_parser::mgr::CdForm;
|
||||
use yazi_proxy::{CmpProxy, MgrProxy};
|
||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::engine;
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -62,8 +62,8 @@ impl Actor for Cd {
|
|||
}
|
||||
|
||||
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));
|
||||
|
|
@ -73,16 +73,16 @@ impl Cd {
|
|||
match result {
|
||||
InputEvent::Submit(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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@ impl Actor for Close {
|
|||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
cx.source().is_key().then_some(SparkKind::KeyClose)
|
||||
Some(SparkKind::KeyClose).filter(|_| cx.source().is_key())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ impl Actor for Copy {
|
|||
|
||||
let mut s = Vec::<u8>::new();
|
||||
let mut it = if form.hovered {
|
||||
Box::new(cx.hovered_url().into_iter())
|
||||
Box::new(cx.hovered().map(|h| &h.url).into_iter())
|
||||
} else {
|
||||
cx.tab().selected_or_hovered_urls()
|
||||
cx.tab().selected_or_hovered()
|
||||
}
|
||||
.peekable();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
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_config::popup::{ConfirmCfg, InputCfg};
|
||||
use yazi_fs::{File, FilesOp};
|
||||
use yazi_macro::{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_proxy::{ConfirmProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -21,37 +18,28 @@ impl Actor for Create {
|
|||
|
||||
const NAME: &str = "create";
|
||||
|
||||
fn act(cx: &mut Ctx, CreateForm { target, dir, force }: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> 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(form.dir));
|
||||
|
||||
tokio::spawn(async move {
|
||||
let Some(name) = target.next().await else { return Ok(()) };
|
||||
let Some(InputEvent::Submit(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 !form.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, form.dir || name.ends_with('/') || name.ends_with('\\')).await;
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -62,26 +50,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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use yazi_macro::succ;
|
|||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
use yazi_vfs::engine;
|
||||
use yazi_vfs::provider;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ impl Actor for Displace {
|
|||
let from = cx.cwd().to_owned();
|
||||
tokio::spawn(async move {
|
||||
MgrProxy::displace_do(tab, DisplaceOpt {
|
||||
to: engine::canonicalize(&from).await.map_err(Into::into),
|
||||
to: provider::canonicalize(&from).await.map_err(Into::into),
|
||||
from,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ use anyhow::Result;
|
|||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use hashbrown::HashSet;
|
||||
use yazi_core::mgr::OpenOpt;
|
||||
use yazi_fs::{FsSpec, engine::{Engine, local::Local}};
|
||||
use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::DownloadForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::engine;
|
||||
use yazi_vfs::VfsFile;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ impl Actor for Download {
|
|||
continue;
|
||||
}
|
||||
|
||||
let Ok(f) = engine::file(&url).await else { continue };
|
||||
let Ok(f) = File::new(&url).await else { continue };
|
||||
urls.push(url);
|
||||
files.push(f);
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ impl Actor for Download {
|
|||
|
||||
impl Download {
|
||||
async fn prepare(urls: &[UrlBuf]) {
|
||||
let roots: HashSet<_> = urls.iter().filter_map(|u| u.auth().cache()).collect();
|
||||
let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect();
|
||||
for mut root in roots {
|
||||
root.push("%lock");
|
||||
Local::regular(&root).create_dir_all().await.ok();
|
||||
|
|
|
|||
|
|
@ -67,16 +67,15 @@ impl Actor for EscapeVisual {
|
|||
let tab = cx.tab_mut();
|
||||
|
||||
let select = tab.mode.is_select();
|
||||
let Some(indices) = tab.mode.take_visual(tab.current.cursor, tab.current.entries.len()) else {
|
||||
succ!(false)
|
||||
};
|
||||
let Some((_, indices)) = tab.mode.take_visual() else { succ!(false) };
|
||||
|
||||
render!();
|
||||
let files: Vec<_> = indices.into_iter().filter_map(|i| tab.current.entries.get(i)).collect();
|
||||
let urls: Vec<_> =
|
||||
indices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect();
|
||||
|
||||
if !select {
|
||||
tab.selected.remove_many(files);
|
||||
} else if files.len() != tab.selected.add_many(files) {
|
||||
tab.selected.remove_many(urls);
|
||||
} else if urls.len() != tab.selected.add_many(urls) {
|
||||
NotifyProxy::push_warn(
|
||||
"Escape visual mode",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
|
|
@ -97,7 +96,7 @@ impl Actor for EscapeFilter {
|
|||
const NAME: &str = "escape_filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
if cx.current_mut().entries.filter().is_none() {
|
||||
if cx.current_mut().files.filter().is_none() {
|
||||
succ!(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use std::time::Duration;
|
|||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_core::mgr::FilterOpt;
|
||||
use yazi_macro::{input, succ};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::FilterForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_proxy::{InputProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data};
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
|
|
@ -20,8 +20,8 @@ impl Actor for Filter {
|
|||
|
||||
const NAME: &str = "filter";
|
||||
|
||||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
let input = input!(cx, YAZI.input.filter())?;
|
||||
fn act(_: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::filter());
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ impl Actor for FilterDo {
|
|||
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
|
||||
let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) };
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.entry_key().into());
|
||||
cx.current_mut().entries.set_filter(filter);
|
||||
let hovered = cx.hovered().map(|f| f.urn().into());
|
||||
cx.current_mut().files.set_filter(filter);
|
||||
|
||||
if cx.hovered().map(|f| f.entry_key()) != hovered.as_ref().map(Into::into) {
|
||||
if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
act!(mgr:hover, cx, hovered)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use std::time::Duration;
|
|||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_core::mgr::FindDoOpt;
|
||||
use yazi_macro::{input, succ};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::FindForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_proxy::{InputProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data};
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
|
|
@ -20,8 +20,8 @@ impl Actor for Find {
|
|||
|
||||
const NAME: &str = "find";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let input = input!(cx, YAZI.input.find(form.prev))?;
|
||||
fn act(_: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::find(form.prev));
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ impl Actor for FindArrow {
|
|||
|
||||
render!(finder.catchup(&tab.current));
|
||||
let offset = if form.prev {
|
||||
finder.prev(&tab.current.entries, tab.current.cursor, false)
|
||||
finder.prev(&tab.current.files, tab.current.cursor, false)
|
||||
} else {
|
||||
finder.next(&tab.current.entries, tab.current.cursor, false)
|
||||
finder.next(&tab.current.files, tab.current.cursor, false)
|
||||
};
|
||||
|
||||
if let Some(offset) = offset {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ impl Actor for FindDo {
|
|||
}
|
||||
|
||||
let step = if opt.prev {
|
||||
finder.prev(&cx.current().entries, cx.current().cursor, true)
|
||||
finder.prev(&cx.current().files, cx.current().cursor, true)
|
||||
} else {
|
||||
finder.next(&cx.current().entries, cx.current().cursor, true)
|
||||
finder.next(&cx.current().files, cx.current().cursor, true)
|
||||
};
|
||||
|
||||
if let Some(step) = step {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Actor for Follow {
|
|||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let Some(file) = cx.hovered() else { succ!() };
|
||||
let Some(link_to) = file.extra.link_to() else { succ!() };
|
||||
let Some(link_to) = &file.link_to else { succ!() };
|
||||
let Some(parent) = file.url.parent() else { succ!() };
|
||||
let Ok(joined) = parent.try_join(link_to) else { succ!() };
|
||||
act!(mgr:reveal, cx, (clean_url(joined), CdSource::Follow))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use yazi_fs::FolderStage;
|
|||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::{mgr::HiddenForm, spark::SparkKind};
|
||||
use yazi_shared::{Source, data::Data};
|
||||
use yazi_shim::OptionExt;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -19,19 +18,21 @@ impl Actor for Hidden {
|
|||
let state = form.state.bool(cx.tab().pref.show_hidden);
|
||||
cx.tab_mut().pref.show_hidden = state;
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.entry_key()).owned();
|
||||
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
||||
let apply = |f: &mut Folder| {
|
||||
if f.stage == FolderStage::Loading {
|
||||
render!();
|
||||
false
|
||||
} else {
|
||||
f.entries.set_show_hidden(state);
|
||||
render_and!(f.entries.catchup_revision())
|
||||
f.files.set_show_hidden(state);
|
||||
render_and!(f.files.catchup_revision())
|
||||
}
|
||||
};
|
||||
|
||||
// Apply to CWD and parent
|
||||
if apply(cx.current_mut()) | cx.parent_mut().is_some_and(apply) {
|
||||
if let (a, Some(b)) = (apply(cx.current_mut()), cx.parent_mut().map(apply))
|
||||
&& (a | b)
|
||||
{
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
}
|
||||
|
|
@ -42,7 +43,7 @@ impl Actor for Hidden {
|
|||
{
|
||||
render!(h.repos(None));
|
||||
act!(mgr:peek, cx, true)?;
|
||||
} else if cx.hovered().map(|f| f.entry_key()) != hovered.as_ref().map(Into::into) {
|
||||
} else if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,25 +18,25 @@ impl Actor for Hover {
|
|||
|
||||
// Parent should always track CWD
|
||||
if let Some(p) = &mut tab.parent {
|
||||
render!(p.repos(Some(tab.current.url.entry_key())));
|
||||
render!(p.repos(tab.current.url.try_strip_prefix(&p.url).ok()));
|
||||
}
|
||||
|
||||
// Repos CWD
|
||||
render!(tab.current.repos(form.key.as_ref().map(Into::into)));
|
||||
render!(tab.current.repos(form.urn.as_ref().map(Into::into)));
|
||||
|
||||
// Turn on tracing
|
||||
if let (Some(h), Some(key)) = (tab.hovered(), form.key)
|
||||
&& h.entry_key() == key
|
||||
if let (Some(h), Some(u)) = (tab.hovered(), form.urn)
|
||||
&& h.urn() == u
|
||||
{
|
||||
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
|
||||
// At this point, it's intuitive to track the entry regardless.
|
||||
tab.current.trace = Some(key.clone());
|
||||
// At this point, it's intuitive to track the file location regardless.
|
||||
tab.current.trace = Some(u.clone());
|
||||
cx.tasks.scheduler.behavior.reset();
|
||||
}
|
||||
|
||||
// Publish through DDS
|
||||
let tab = tab!(cx);
|
||||
err!(Pubsub::pub_after_hover(tab.id, tab.hovered_url()));
|
||||
err!(Pubsub::pub_after_hover(tab.id, tab.hovered().map(|h| &h.url)));
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
yazi_macro::mod_flat!(
|
||||
arrow
|
||||
back
|
||||
bulk_create
|
||||
bulk_exit
|
||||
bulk_rename
|
||||
cd
|
||||
|
|
@ -33,7 +32,6 @@ yazi_macro::mod_flat!(
|
|||
quit
|
||||
refresh
|
||||
remove
|
||||
remove_do
|
||||
rename
|
||||
reveal
|
||||
search
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ use futures::StreamExt;
|
|||
use hashbrown::HashSet;
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_core::mgr::OpenDoOpt;
|
||||
use yazi_fs::File;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::OpenForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_vfs::engine;
|
||||
use yazi_vfs::VfsFile;
|
||||
|
||||
use crate::{Actor, Ctx, mgr::Quit};
|
||||
|
||||
|
|
@ -23,10 +24,10 @@ impl Actor for Open {
|
|||
succ!(if !opt.targets.is_empty() {
|
||||
Quit::with_selected(opt.targets)
|
||||
} else if opt.hovered {
|
||||
Quit::with_selected(cx.hovered_url())
|
||||
Quit::with_selected(cx.hovered().map(|h| &h.url))
|
||||
} else {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
Quit::with_selected(cx.tab().selected_or_hovered_urls())
|
||||
Quit::with_selected(cx.tab().selected_or_hovered())
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ impl Actor for Open {
|
|||
cx.hovered().map(|h| vec![h.url.clone()]).unwrap_or_default()
|
||||
} else {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
cx.tab().selected_or_hovered_urls().cloned().collect()
|
||||
cx.tab().selected_or_hovered().cloned().collect()
|
||||
};
|
||||
}
|
||||
if opt.targets.is_empty() {
|
||||
|
|
@ -58,7 +59,7 @@ impl Actor for Open {
|
|||
|
||||
let it = futures::stream::iter(opt.targets)
|
||||
.enumerate()
|
||||
.map(|(i, url)| async move { engine::file(url).await.ok().map(|file| (i, file)) })
|
||||
.map(|(i, url)| async move { File::new(url).await.ok().map(|file| (i, file)) })
|
||||
.buffered(3)
|
||||
.filter_map(|item| async move { item });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use anyhow::Result;
|
||||
use hashbrown::HashMap;
|
||||
use indexmap::IndexSet;
|
||||
use yazi_config::{YAZI, opener::OpenerRule};
|
||||
use yazi_fs::{Splatter, file::File};
|
||||
use yazi_config::{YAZI, popup::PickCfg};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::OpenDoForm;
|
||||
use yazi_proxy::{PickProxy, TasksProxy};
|
||||
use yazi_scheduler::process::ShellOpt;
|
||||
use yazi_shared::{data::Data, url::UrlBuf};
|
||||
use yazi_scheduler::process::ProcessOpt;
|
||||
use yazi_shared::{data::Data, url::{UrlBuf, UrlCow}};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ impl Actor for OpenDo {
|
|||
if targets.is_empty() {
|
||||
succ!();
|
||||
} else if !opt.interactive {
|
||||
succ!(Self::match_and_open(opt.cwd, targets));
|
||||
succ!(Self::match_and_open(cx, opt.cwd, targets));
|
||||
}
|
||||
|
||||
let openers: IndexSet<_> =
|
||||
|
|
@ -41,11 +40,21 @@ impl Actor for OpenDo {
|
|||
succ!();
|
||||
}
|
||||
|
||||
let pick = PickProxy::show(YAZI.pick.open(openers.iter().map(|o| o.desc()).collect()));
|
||||
let files: Vec<_> = targets.into_iter().map(|(file, _)| file).collect();
|
||||
let pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect()));
|
||||
let urls: Vec<_> = [UrlCow::default()]
|
||||
.into_iter()
|
||||
.chain(targets.into_iter().map(|(file, _)| file.url.into()))
|
||||
.collect();
|
||||
tokio::spawn(async move {
|
||||
if let Some(choice) = pick.await {
|
||||
Self::open_with(&openers[choice], &opt.cwd, &files);
|
||||
TasksProxy::open_shell_compat(ProcessOpt {
|
||||
cwd: opt.cwd,
|
||||
cmd: openers[choice].run.clone().into(),
|
||||
args: urls,
|
||||
block: openers[choice].block,
|
||||
orphan: openers[choice].orphan,
|
||||
spread: openers[choice].spread,
|
||||
});
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
|
|
@ -53,28 +62,24 @@ impl Actor for OpenDo {
|
|||
}
|
||||
|
||||
impl OpenDo {
|
||||
fn match_and_open(cwd: UrlBuf, targets: Vec<(File, &str)>) {
|
||||
let mut openers: HashMap<_, Vec<_>> = Default::default();
|
||||
// TODO: remove
|
||||
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(yazi_fs::File, &str)>) {
|
||||
let mut openers = HashMap::new();
|
||||
for (file, mime) in targets {
|
||||
if let Some(open) = YAZI.open.matches(&file, mime)
|
||||
&& let Some(opener) = YAZI.opener.first(&open)
|
||||
{
|
||||
openers.entry(opener).or_default().push(file);
|
||||
openers.entry(opener).or_insert_with(|| vec![UrlCow::default()]).push(file.url.into());
|
||||
}
|
||||
}
|
||||
for (opener, files) in openers {
|
||||
Self::open_with(&opener, &cwd, &files);
|
||||
}
|
||||
}
|
||||
|
||||
fn open_with(opener: &OpenerRule, cwd: &UrlBuf, files: &[File]) {
|
||||
let size = if opener.spread { files.len().max(1) } else { 1 };
|
||||
for files in files.chunks(size) {
|
||||
TasksProxy::process_open(ShellOpt {
|
||||
cwd: cwd.clone(),
|
||||
cmd: Splatter::new(files).splat(&opener.run),
|
||||
block: opener.block,
|
||||
for (opener, args) in openers {
|
||||
cx.tasks.open_shell_compat(ProcessOpt {
|
||||
cwd: cwd.clone(),
|
||||
cmd: opener.run.clone().into(),
|
||||
args,
|
||||
block: opener.block,
|
||||
orphan: opener.orphan,
|
||||
spread: opener.spread,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl Actor for Paste {
|
|||
if mgr.yanked.cut {
|
||||
cx.core.tasks.file_cut(&mgr.yanked, dest, form.force);
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(mgr.yanked.urls()));
|
||||
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&*mgr.yanked));
|
||||
act!(mgr:unyank, cx)
|
||||
} else {
|
||||
succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, form.force, form.follow));
|
||||
|
|
|
|||
|
|
@ -21,14 +21,18 @@ impl Actor for Peek {
|
|||
}
|
||||
|
||||
let mime = cx.mgr.mimetype.owned(&hovered.url).unwrap_or_default();
|
||||
let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.file.clone()));
|
||||
let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));
|
||||
|
||||
if !cx.tab().preview.same_url(&hovered.url) {
|
||||
cx.tab_mut().preview.skip = folder.as_ref().map(|f| f.0).unwrap_or_default();
|
||||
cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();
|
||||
}
|
||||
if !cx.tab().preview.same_file(&hovered, &mime) {
|
||||
cx.tab_mut().preview.reset();
|
||||
}
|
||||
if !cx.tab().preview.same_folder(&hovered.url) {
|
||||
cx.tab_mut().preview.folder_lock = None;
|
||||
}
|
||||
|
||||
if matches!(form.only_if, Some(u) if u != hovered.url) {
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -42,13 +46,11 @@ impl Actor for Peek {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some((_, file)) = folder {
|
||||
cx.core.mgr.watcher.refresher.refresh([file]);
|
||||
} else if hovered.is_dir() {
|
||||
cx.core.mgr.watcher.refresher.load(&hovered);
|
||||
if hovered.is_dir() {
|
||||
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), mime, form.force);
|
||||
} else {
|
||||
cx.tab_mut().preview.go(hovered, mime, form.force);
|
||||
}
|
||||
|
||||
cx.tab_mut().preview.go(hovered, mime, form.force);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ impl Actor for Quit {
|
|||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
cx.source().is_key().then_some(SparkKind::KeyQuit)
|
||||
Some(SparkKind::KeyQuit).filter(|_| cx.source().is_key())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use yazi_fs::CWD;
|
||||
use yazi_core::tab::Folder;
|
||||
use yazi_fs::{CWD, Files, FilesOp, cha::Cha};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFiles, VfsFilesOp};
|
||||
use yazi_watcher::MgrProxy;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -17,19 +19,17 @@ impl Actor for Refresh {
|
|||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
CWD.set(cx.cwd(), Self::cwd_changed);
|
||||
|
||||
cx.core.mgr.watcher.refresher.refresh(
|
||||
[Some(cx.current()), cx.parent()]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|f| f.url.is_absolute() && !f.url.is_search())
|
||||
.map(|f| &f.file),
|
||||
);
|
||||
if let Some(p) = cx.parent() {
|
||||
Self::trigger_dirs(&[cx.current(), p]);
|
||||
} else {
|
||||
Self::trigger_dirs(&[cx.current()]);
|
||||
}
|
||||
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
|
||||
cx.tasks.prework_sorted(&cx.current().entries);
|
||||
cx.tasks.prework_sorted(&cx.current().files);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -40,4 +40,26 @@ impl Refresh {
|
|||
MgrProxy::watch();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: performance improvement
|
||||
fn trigger_dirs(folders: &[&Folder]) {
|
||||
async fn go(dir: UrlBuf, cha: Cha) {
|
||||
let Some(cha) = Files::assert_stale(&dir, cha).await else { return };
|
||||
|
||||
match Files::from_dir_bulk(&dir).await {
|
||||
Ok(files) => FilesOp::Full(dir, files, cha).emit(),
|
||||
Err(e) => FilesOp::issue_error(&dir, e).await,
|
||||
}
|
||||
}
|
||||
|
||||
let futs: Vec<_> = folders
|
||||
.iter()
|
||||
.filter(|&f| f.url.is_absolute() && f.url.is_internal())
|
||||
.map(|&f| go(f.url.clone(), f.cha))
|
||||
.collect();
|
||||
|
||||
if !futs.is_empty() {
|
||||
tokio::spawn(futures::future::join_all(futs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use yazi_config::popup::ConfirmCfg;
|
||||
use yazi_fs::file::Files;
|
||||
use yazi_macro::{act, confirm, succ};
|
||||
use yazi_parser::mgr::{RemoveDoForm, RemoveForm};
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::RemoveForm;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -15,31 +14,55 @@ impl Actor for Remove {
|
|||
|
||||
const NAME: &str = "remove";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
let targets = Files(if form.hovered {
|
||||
cx.hovered().map_or(vec![], |h| vec![h.clone()])
|
||||
form.targets = if form.hovered {
|
||||
cx.hovered().map_or(vec![], |h| vec![h.url.clone()])
|
||||
} else {
|
||||
cx.tab().selected_or_hovered_files().cloned().collect()
|
||||
});
|
||||
cx.tab().selected_or_hovered().cloned().collect()
|
||||
};
|
||||
|
||||
if targets.is_empty() {
|
||||
if form.targets.is_empty() {
|
||||
succ!();
|
||||
} else if form.force {
|
||||
return act!(mgr:remove_do, cx, RemoveDoForm { permanently: form.permanently, targets: targets.into() });
|
||||
return act!(mgr:remove_do, cx, form);
|
||||
}
|
||||
|
||||
let confirm = confirm!(
|
||||
cx,
|
||||
if form.permanently { ConfirmCfg::delete(&targets) } else { ConfirmCfg::trash(&targets) }
|
||||
)?;
|
||||
let confirm = ConfirmProxy::show(if form.permanently {
|
||||
ConfirmCfg::delete(&form.targets)
|
||||
} else {
|
||||
ConfirmCfg::trash(&form.targets)
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
if confirm.future().await {
|
||||
MgrProxy::remove_do(form.permanently, targets);
|
||||
if confirm.await {
|
||||
MgrProxy::remove_do(form.targets, form.permanently);
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Do
|
||||
pub struct RemoveDo;
|
||||
|
||||
impl Actor for RemoveDo {
|
||||
type Form = RemoveForm;
|
||||
|
||||
const NAME: &str = "remove_do";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let mgr = &mut cx.mgr;
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&form.targets);
|
||||
});
|
||||
|
||||
mgr.yanked.remove_many(&form.targets);
|
||||
mgr.yanked.catchup_revision(false);
|
||||
|
||||
cx.tasks.file_remove(form.targets, form.permanently);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::RemoveDoForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct RemoveDo;
|
||||
|
||||
impl Actor for RemoveDo {
|
||||
type Form = RemoveDoForm;
|
||||
|
||||
const NAME: &str = "remove_do";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let mgr = &mut cx.mgr;
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&form.targets);
|
||||
});
|
||||
|
||||
mgr.yanked.remove_many(&form.targets);
|
||||
mgr.yanked.catchup_revision(false);
|
||||
|
||||
cx.tasks.file_remove(form.targets, form.permanently);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
use anyhow::{Result, bail};
|
||||
use yazi_config::{YAZI, popup::ConfirmCfg};
|
||||
use anyhow::Result;
|
||||
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{FilesOp, file::File};
|
||||
use yazi_macro::{act, err, input, ok_or_not_found, succ};
|
||||
use yazi_fs::{File, FilesOp};
|
||||
use yazi_macro::{act, err, ok_or_not_found, succ};
|
||||
use yazi_parser::mgr::RenameForm;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::{data::Data, id::Id, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, engine};
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
|
|
@ -42,23 +42,23 @@ impl Actor for Rename {
|
|||
};
|
||||
|
||||
let (tab, old) = (cx.tab().id, hovered.url_owned());
|
||||
let mut input =
|
||||
input!(cx, YAZI.input.rename(hovered.is_dir()).with_value(name).with_cursor(cursor))?;
|
||||
let mut input = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor));
|
||||
|
||||
tokio::spawn(async move {
|
||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return Ok(()) };
|
||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
||||
if name.is_empty() {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(Ok(new)) = old.parent().map(|u| u.try_join(name)) else {
|
||||
bail!("Failed to join new name with parent directory");
|
||||
return;
|
||||
};
|
||||
|
||||
if form.force || Self::try_ask(&old, &new).await? {
|
||||
Self::r#do(tab, old, new).await?;
|
||||
if form.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await {
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
}
|
||||
Ok::<(), anyhow::Error>(())
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -66,30 +66,30 @@ impl Actor for Rename {
|
|||
|
||||
impl Rename {
|
||||
async fn r#do(tab: Id, old: UrlBuf, new: UrlBuf) -> Result<()> {
|
||||
let Some((old_p, old_k)) = old.pair2() else { return Ok(()) };
|
||||
let Some(_) = new.pair2() else { return Ok(()) };
|
||||
let Some((old_p, old_n)) = old.pair() else { return Ok(()) };
|
||||
let Some(_) = new.pair() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
let overwritten = engine::casefold(&new).await;
|
||||
engine::rename(&old, &new).await?;
|
||||
let overwritten = provider::casefold(&new).await;
|
||||
provider::rename(&old, &new).await?;
|
||||
|
||||
if let Ok(u) = overwritten
|
||||
&& u != new
|
||||
&& let Some((parent, key)) = u.pair2()
|
||||
&& let Some((parent, urn)) = u.pair()
|
||||
{
|
||||
ok_or_not_found!(engine::rename(&u, &new).await);
|
||||
FilesOp::Deleting(parent.to_owned(), [key.into()].into()).emit();
|
||||
ok_or_not_found!(provider::rename(&u, &new).await);
|
||||
FilesOp::Deleting(parent.to_owned(), [urn.into()].into()).emit();
|
||||
}
|
||||
|
||||
let new = engine::casefold(&new).await?;
|
||||
let Some((new_p, new_k)) = new.pair2() else { return Ok(()) };
|
||||
let new = provider::casefold(&new).await?;
|
||||
let Some((new_p, new_n)) = new.pair() else { return Ok(()) };
|
||||
|
||||
let file = engine::file(&new).await?;
|
||||
let file = File::new(&new).await?;
|
||||
if new_p == old_p {
|
||||
FilesOp::Upserting(old_p.into(), [(old_k.into(), file)].into()).emit();
|
||||
FilesOp::Upserting(old_p.into(), [(old_n.into(), file)].into()).emit();
|
||||
} else {
|
||||
FilesOp::Deleting(old_p.into(), [old_k.into()].into()).emit();
|
||||
FilesOp::Upserting(new_p.into(), [(new_k.into(), file)].into()).emit();
|
||||
FilesOp::Deleting(old_p.into(), [old_n.into()].into()).emit();
|
||||
FilesOp::Upserting(new_p.into(), [(new_n.into(), file)].into()).emit();
|
||||
}
|
||||
|
||||
MgrProxy::reveal(&new);
|
||||
|
|
@ -97,17 +97,6 @@ impl Rename {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_ask(old: &UrlBuf, new: &UrlBuf) -> Result<bool> {
|
||||
let Some(file) = File::maybe_new(&new).await? else {
|
||||
return Ok(true);
|
||||
};
|
||||
|
||||
Ok(
|
||||
engine::must_identical(old, new).await
|
||||
|| ConfirmProxy::show(ConfirmCfg::overwrite(&file)).await,
|
||||
)
|
||||
}
|
||||
|
||||
fn empty_url_part(url: &UrlBuf, by: &str) -> String {
|
||||
if by == "all" {
|
||||
return String::new();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use yazi_fs::{FilesOp, file::File};
|
||||
use yazi_fs::{File, FilesOp};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::RevealForm;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
|
@ -14,7 +14,7 @@ impl Actor for Reveal {
|
|||
const NAME: &str = "reveal";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let Some((parent, child)) = form.target.pair2() else { succ!() };
|
||||
let Some((parent, child)) = form.target.pair() else { succ!() };
|
||||
|
||||
// Cd to the parent directory
|
||||
act!(mgr:cd, cx, (parent, form.source))?;
|
||||
|
|
@ -25,7 +25,7 @@ impl Actor for Reveal {
|
|||
|
||||
// If the child is not hovered, which means it doesn't exist,
|
||||
// create a dummy file
|
||||
if !form.no_dummy && tab.hovered().is_none_or(|f| f.entry_key() != child) {
|
||||
if !form.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {
|
||||
let op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&form.target, None)]);
|
||||
tab.current.update_pub(tab.id, op);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue