Merge remote-tracking branch 'upstream/main' into improve-rg-search

This commit is contained in:
Jed 2026-06-21 19:11:28 +02:00
commit 963b277dbd
623 changed files with 8017 additions and 6527 deletions

View file

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

View file

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

View file

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

View file

@ -15,3 +15,7 @@ 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. 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)

View file

@ -14,14 +14,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added ### Added
- Drag and drop ([#4005])
- Bulk create ([#3793]) - Bulk create ([#3793])
- Dynamic keymap Lua API ([#4031])
- New `ui.Input` element ([#4040])
- Image preview with Überzug++ on Niri ([#3990]) - Image preview with Überzug++ on Niri ([#3990])
- New gait for input `backward` and `forward` actions ([#4012])
### Changed ### Changed
- Rename `<BackTab>` to `<S-Tab>` ([#3989]) - Rename `<BackTab>` to `<S-Tab>` ([#3989])
- Remove Legacy Console Mode on Windows ([#3989]) - Remove Legacy Console Mode on Windows ([#3989])
### Deprecated
- Deprecate `backward --far` and `forward --far` in favor of `backward wide` and `forward wide`, respectively ([#4012])
### 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] ## [v26.5.6]
### Added ### Added
@ -1729,3 +1748,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3943]: https://github.com/sxyazi/yazi/pull/3943 [#3943]: https://github.com/sxyazi/yazi/pull/3943
[#3989]: https://github.com/sxyazi/yazi/pull/3989 [#3989]: https://github.com/sxyazi/yazi/pull/3989
[#3990]: https://github.com/sxyazi/yazi/pull/3990 [#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

View file

@ -11,6 +11,7 @@ This guide will help you understand how to contribute to the project.
3. [Development Setup](#development-setup) 3. [Development Setup](#development-setup)
4. [How to Contribute](#how-to-contribute) 4. [How to Contribute](#how-to-contribute)
5. [Pull Requests](#pull-requests) 5. [Pull Requests](#pull-requests)
6. [AI Policy](#ai-policy)
## Getting Started ## Getting Started
@ -164,3 +165,10 @@ We want you to succeed, and it can be discouraging to find that a lot of re-work
- Your code passes all tests and lints. - Your code passes all tests and lints.
- Your pull request description clearly explains the changes and why they are needed. - 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. 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.

1763
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -39,8 +39,8 @@ ansi-to-tui = "8.0.1"
anyhow = "1.0.102" anyhow = "1.0.102"
arc-swap = { version = "1.9.1", features = [ "serde" ] } arc-swap = { version = "1.9.1", features = [ "serde" ] }
base64 = "0.22.1" base64 = "0.22.1"
bitflags = { version = "2.11.1", features = [ "serde" ] } bitflags = { version = "2.13.0", features = [ "serde" ] }
chrono = "0.4.44" chrono = "0.4.45"
clap = { version = "4.6.1", features = [ "derive" ] } clap = { version = "4.6.1", features = [ "derive" ] }
core-foundation-sys = "0.8.7" core-foundation-sys = "0.8.7"
dirs = "6.0.0" dirs = "6.0.0"
@ -50,7 +50,9 @@ foldhash = "0.2.0"
futures = "0.3.32" futures = "0.3.32"
globset = "0.4.18" globset = "0.4.18"
hashbrown = { version = "0.17.1", features = [ "serde" ] } hashbrown = { version = "0.17.1", features = [ "serde" ] }
image = { version = "0.25.10", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp" ] }
indexmap = { version = "2.14.0", features = [ "serde" ] } indexmap = { version = "2.14.0", features = [ "serde" ] }
inventory = "0.3.24"
libc = "0.2.186" libc = "0.2.186"
lru = "0.18.0" lru = "0.18.0"
mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] } mlua = { version = "0.11.6", features = [ "anyhow", "async", "error-send", "lua55", "macros", "serde" ] }
@ -60,13 +62,14 @@ parking_lot = "0.12.5"
paste = "1.0.15" paste = "1.0.15"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] } rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
ratatui = { version = "0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] } ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] }
regex = "1.12.3" ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] }
russh = { version = "0.61.0", default-features = false, features = [ "ring", "rsa" ] } regex = "1.12.4"
russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] } serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.150" serde_json = "1.0.150"
serde_with = "3.20.0" serde_with = "3.21.0"
strum = { version = "0.28.0", features = [ "derive" ] } strum = { version = "0.28.0", features = [ "derive" ] }
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.18" thiserror = "2.0.18"

View file

@ -24,7 +24,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths - 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.) - 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
- 🔄 Bulk 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) - 🔄 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, Trash Bin, Custom Layouts, CSI u, OSC 52 - 🎨 Theme System, Mouse Support, Drag and Drop, Trash Bin, Custom Layouts, CSI u, OSC 52
- ... and more! - ... and more!
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7 https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7

View file

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

12
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1776329215, "lastModified": 1779877693,
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", "narHash": "sha256-NOF9NAREhxr50bbBfVcVOq+ArCMSoe8dP79Pk2uyARk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b86751bc4085f48661017fa226dee99fab6c651b", "rev": "4100e830e085863741bc69b156ec4ccd53ab5be0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -48,11 +48,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1776741231, "lastModified": 1779851998,
"narHash": "sha256-k9G98qzn+7npROUaks8VqCFm7cFtEG8ulQLBBo5lItg=", "narHash": "sha256-UkkMh3bX9QW4Luqkm98nUaOqKWrU6i65mUnph3WeSSw=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "02061303f7c4c964f7b4584dabd9e985b4cd442b", "rev": "6cddd512fa2bf7231f098d3a2f92f6e4cff71e0a",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

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

View file

@ -13,7 +13,7 @@ function bugReportBody(creator, content, hash) {
I noticed that you did not correctly follow the issue template. Please ensure that: 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 bug can still be reproduced on the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).
- The debug information (\`yazi --debug\`) is updated for the newest nightly. - The environment info (\`ya env\`) is updated for the newest nightly.
- The non-optional items in the checklist are checked. - 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. Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
@ -32,7 +32,7 @@ function featureRequestBody(creator, content) {
I noticed that you did not correctly follow the issue template. Please ensure that: 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 requested feature does not exist in the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).
- The debug information (\`yazi --debug\`) is updated for the newest nightly. - The environment info (\`ya env\`) is updated for the newest nightly.
- The non-optional items in the checklist are checked. - 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. Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.

View file

@ -238,9 +238,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.25.0", "version": "6.27.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"

View file

@ -133,9 +133,16 @@ parts:
- --prefix=/usr - --prefix=/usr
build-attributes: build-attributes:
- enable-patchelf - 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: prime:
- usr/bin/magick - usr/bin/magick
- usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so.1 - usr/lib/libgomp.so*
- usr/lib/libMagickCore-7.Q16HDRI.so* - usr/lib/libMagickCore-7.Q16HDRI.so*
- usr/lib/libMagickWand-7.Q16HDRI.so* - usr/lib/libMagickWand-7.Q16HDRI.so*

View file

@ -40,18 +40,18 @@ yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" } yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
either = { workspace = true } either = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
ratatui = { workspace = true } ratatui-core = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-stream = { workspace = true } tokio-stream = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[target."cfg(unix)".dependencies] [target."cfg(unix)".dependencies]
libc = { workspace = true } libc = { workspace = true }

View file

@ -29,7 +29,7 @@ impl Actor for AcceptPayload {
drop(lock); drop(lock);
let kind = kind.to_owned(); let kind = kind.to_owned();
succ!(Lives::scope(cx.core, || { succ!(Lives::scope(cx.core, |_| {
let body = payload.body.into_lua(&LUA)?; let body = payload.body.into_lua(&LUA)?;
for (id, cb) in handlers { for (id, cb) in handlers {
if let Err(e) = runtime_scope!(LUA, &id, cb.call::<()>(body.clone())) { if let Err(e) = runtime_scope!(LUA, &id, cb.call::<()>(body.clone())) {

43
yazi-actor/src/app/dnd.rs Normal file
View file

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

View file

@ -1,10 +1,9 @@
use anyhow::Result; use anyhow::Result;
use yazi_binding::runtime_scope; use yazi_binding::runtime_scope;
use yazi_dds::Sendable;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::app::LuaForm; use yazi_parser::app::LuaForm;
use yazi_plugin::LUA; use yazi_plugin::LUA;
use yazi_shared::data::Data; use yazi_shared::data::{Data, Sendable};
use crate::{Actor, Ctx, lives::Lives}; use crate::{Actor, Ctx, lives::Lives};
@ -17,7 +16,7 @@ impl Actor for Lua {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let chunk = LUA.load(&*form.code).set_name("anonymous"); 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()?)) runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
}); });
succ!(result?); succ!(result?);

View file

@ -2,6 +2,7 @@ yazi_macro::mod_flat!(
accept_payload accept_payload
bootstrap bootstrap
deprecate deprecate
dnd
focus focus
lua lua
mouse mouse

View file

@ -19,31 +19,29 @@ impl Actor for Mouse {
const NAME: &str = "mouse"; const NAME: &str = "mouse";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { 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 Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
let area = yazi_binding::elements::Rect::from(size); let area = yazi_binding::elements::Rect::from(size);
let result = Lives::scope(cx.core, move || { let result = Lives::scope(cx.core, move |_| {
let root = runtime_scope!(LUA, "root", { runtime_scope!(LUA, "root", {
LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area) let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
})?;
match event.kind { match form.event.kind {
MouseEventKind::Down(_) => root.call_method("click", (event, false))?, MouseEventKind::Down(_) => root.call_method("click", (form.event, false))?,
MouseEventKind::Up(_) => root.call_method("click", (event, true))?, MouseEventKind::Up(_) => root.call_method("click", (form.event, true))?,
MouseEventKind::ScrollDown => root.call_method("scroll", (event, 1))?, MouseEventKind::ScrollDown => root.call_method("scroll", (form.event, 1))?,
MouseEventKind::ScrollUp => root.call_method("scroll", (event, -1))?, MouseEventKind::ScrollUp => root.call_method("scroll", (form.event, -1))?,
MouseEventKind::ScrollRight => root.call_method("touch", (event, 1))?, MouseEventKind::ScrollRight => root.call_method("touch", (form.event, 1))?,
MouseEventKind::ScrollLeft => root.call_method("touch", (event, -1))?, MouseEventKind::ScrollLeft => root.call_method("touch", (form.event, -1))?,
MouseEventKind::Moved => root.call_method("move", event)?, MouseEventKind::Moved => root.call_method("move", form.event)?,
MouseEventKind::Drag(_) => root.call_method("drag", event)?, MouseEventKind::Drag(_) => root.call_method("drag", form.event)?,
} }
Ok(()) Ok(())
})
}); });
if let Err(ref e) = result { if let Err(ref e) = result {

View file

@ -43,7 +43,7 @@ impl Actor for PluginDo {
}; };
drop(loader); drop(loader);
let result = Lives::scope(cx.core, || { let result = Lives::scope(cx.core, |_| {
if let Some(cb) = opt.callback { if let Some(cb) = opt.callback {
cb(&LUA, plugin) cb(&LUA, plugin)
} else { } else {

View file

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use mlua::Value; use mlua::Value;
use ratatui::layout::Position; use ratatui_core::layout::Position;
use tracing::error; use tracing::error;
use yazi_actor::lives::Lives; use yazi_actor::lives::Lives;
use yazi_config::LAYOUT; 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 Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
let mut layout = LAYOUT.get(); 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())?; let comps = (form.reflow)((Position::ORIGIN, size).into())?;
for v in comps.sequence_values::<Value>() { for v in comps.sequence_values::<Value>() {

View file

@ -3,8 +3,7 @@ use yazi_actor::Ctx;
use yazi_macro::{succ, writef}; use yazi_macro::{succ, writef};
use yazi_parser::{app::TitleForm, spark::SparkKind}; use yazi_parser::{app::TitleForm, spark::SparkKind};
use yazi_shared::{Source, data::Data}; use yazi_shared::{Source, data::Data};
use yazi_term::sequence::SetTitle; use yazi_tty::{TTY, sequence::SetTitle};
use yazi_tty::TTY;
use yazi_tui::RatermState; use yazi_tui::RatermState;
use crate::Actor; use crate::Actor;

View file

@ -22,7 +22,7 @@ impl Actor for Trigger {
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) { if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
succ!(); succ!();
} else if form.ticket.is_none() { } else if form.ticket.is_none() {
cx.cmp.ticket = cx.input.ticket.current(); cx.cmp.ticket = cx.input.lock().map(|g| g.ticket.current()).unwrap_or_default();
} }
cx.cmp.handle.take().map(|h| h.abort()); cx.cmp.handle.take().map(|h| h.abort());
@ -53,7 +53,7 @@ impl Actor for Trigger {
if !cache.is_empty() { if !cache.is_empty() {
cache cache
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false)); .sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), true));
CmpProxy::show(CmpOpt { cache, cache_name: parent, word, ticket }); CmpProxy::show(CmpOpt { cache, cache_name: parent, word, ticket });
} }

View file

@ -2,8 +2,8 @@ use std::ops::{Deref, DerefMut};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}}; use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}};
use yazi_fs::File; use yazi_fs::file::File;
use yazi_shared::{Id, Source, event::Action, url::UrlBuf}; use yazi_shared::{Source, event::Action, id::Id, url::UrlBuf};
use yazi_tui::Raterm; use yazi_tui::Raterm;
pub struct Ctx<'a> { pub struct Ctx<'a> {

View file

@ -16,7 +16,7 @@ impl Preflight {
return Ok(opt.1); return Ok(opt.1);
}; };
Ok(Lives::scope(cx.core, || { Ok(Lives::scope(cx.core, |_| {
let mut body = opt.1.into_lua(&LUA)?; let mut body = opt.1.into_lua(&LUA)?;
for (id, cb) in handlers { for (id, cb) in handlers {
match runtime_scope!(LUA, &id, cb.call::<Value>(&body)) { match runtime_scope!(LUA, &id, cb.call::<Value>(&body)) {

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::input::CloseForm; use yazi_parser::{input::CloseForm, spark::SparkKind};
use yazi_shared::data::Data; use yazi_shared::{Source, data::Data};
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -14,16 +14,28 @@ impl Actor for Close {
const NAME: &str = "close"; const NAME: &str = "close";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input; let Some(mut guard) = cx.input.lock_mut() else {
input.visible = false; succ!();
input.ticket.next(); };
if let Some(tx) = input.tx.take() { guard.ticket.next();
let value = input.snap().value.clone(); if let Some(cb) = guard.cb.take() {
_ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) }); let value = guard.snap().value.clone();
cb(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
} }
drop(guard);
cx.input.main.visible = false;
act!(cmp:close, cx)?; act!(cmp:close, cx)?;
succ!(render!()); succ!(render!());
} }
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Key => Some(SparkKind::KeyInputClose),
Source::Ind => Some(SparkKind::IndInputClose),
_ => None,
}
}
} }

View file

@ -13,11 +13,14 @@ impl Actor for Complete {
const NAME: &str = "complete"; const NAME: &str = "complete";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input; let Some(mut guard) = cx.input.lock_mut() else {
if !input.visible || input.ticket.current() != form.ticket { succ!();
};
if guard.ticket.current() != form.ticket {
succ!(); succ!();
} }
act!(complete, input, form) act!(complete, guard, form)
} }
} }

View file

@ -15,16 +15,20 @@ impl Actor for Escape {
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
use yazi_widgets::input::InputMode as M; use yazi_widgets::input::InputMode as M;
let input = &mut cx.input; let Some(mut guard) = cx.input.lock_mut() else {
succ!();
};
let mode = input.snap().mode; let (mode, op) = (guard.snap().mode, guard.snap().op);
act!(escape, guard)?;
drop(guard);
match mode { match mode {
M::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx), M::Normal if op == InputOp::None => act!(input:close, cx),
M::Insert => act!(cmp:close, cx), M::Insert => act!(cmp:close, cx),
M::Normal | M::Replace => Ok(().into()), M::Normal | M::Replace => Ok(().into()),
}?; }?;
act!(escape, cx.input)?;
succ!(render!()); succ!(render!());
} }
} }

View file

@ -1,27 +1,34 @@
use std::ops::DerefMut; use std::ops::DerefMut;
use anyhow::Result; use anyhow::Result;
use yazi_config::{THEME, YAZI};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::input::ShowForm;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_widgets::input::InputOpt; use yazi_shim::ratatui::Padable;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
pub struct Show; pub struct Show;
impl Actor for Show { impl Actor for Show {
type Form = InputOpt; type Form = ShowForm;
const NAME: &str = "show"; const NAME: &str = "show";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
act!(input:close, cx)?; act!(input:close, cx)?;
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
let input = &mut cx.input; let input = &mut cx.input;
input.visible = true; input.main.visible = true;
input.title = form.cfg.title.clone(); input.main.title = opt.title.clone();
input.position = form.cfg.position; input.main.position = opt.position;
*input.deref_mut() = yazi_widgets::input::Input::new(form)?;
opt.styles = (&THEME.input).into();
opt.blinking = YAZI.input.cursor_blink;
*input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?;
input.main.repos(area);
succ!(render!()); succ!(render!());
} }

View file

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

View file

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

View file

@ -1,10 +1,12 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataMethods, Value}; use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataMethods, UserDataRef, Value};
use paste::paste; use paste::paste;
use super::{Lives, PtrCell}; use super::{Lives, PtrCell};
pub(super) type CoreRef = UserDataRef<Core>;
pub(super) struct Core { pub(super) struct Core {
inner: PtrCell<yazi_core::Core>, inner: PtrCell<yazi_core::Core>,
@ -12,8 +14,9 @@ pub(super) struct Core {
c_tabs: Option<Value>, c_tabs: Option<Value>,
c_tasks: Option<Value>, c_tasks: Option<Value>,
c_yanked: Option<Value>, c_yanked: Option<Value>,
c_layer: Option<Value>, c_input: Option<Value>,
c_which: Option<Value>, c_which: Option<Value>,
c_layer: Option<Value>,
} }
impl Deref for Core { impl Deref for Core {
@ -31,8 +34,9 @@ impl Core {
c_tabs: None, c_tabs: None,
c_tasks: None, c_tasks: None,
c_yanked: None, c_yanked: None,
c_layer: None, c_input: None,
c_which: None, c_which: None,
c_layer: None,
}) })
} }
} }
@ -57,10 +61,9 @@ impl UserData for Core {
b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)), b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)), b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),
b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)), b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)),
b"layer" => { b"input" => reuse!(input, super::Input::make(&me.input)),
reuse!(layer, Ok::<_, mlua::Error>(yazi_binding::Layer::from(me.layer())))
}
b"which" => reuse!(which, super::Which::make(&me.which)), b"which" => reuse!(which, super::Which::make(&me.which)),
b"layer" => reuse!(layer, Ok::<_, mlua::Error>(me.layer())),
_ => Value::Nil, _ => Value::Nil,
}) })
}); });

View file

@ -1,37 +1,27 @@
use std::{ops::Deref, ptr}; use std::{ops::Deref, ptr};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods};
use yazi_binding::{Range, Style, cached_field}; use yazi_binding::{Range, style::Style};
use yazi_config::THEME; use yazi_config::THEME;
use yazi_shared::{path::AsPath, url::UrlLike}; use yazi_shared::{path::AsPath, url::UrlLike};
use super::Lives; use super::{FILE_CACHE, Lives};
use crate::lives::PtrCell; use crate::lives::{CoreRef, PtrCell};
pub(super) struct File { pub(super) struct File {
idx: usize, idx: usize,
folder: PtrCell<yazi_core::tab::Folder>, folder: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>, 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 { impl Deref for File {
type Target = yazi_fs::File; type Target = yazi_fs::file::File;
fn deref(&self) -> &Self::Target { &self.folder.files[self.idx] } fn deref(&self) -> &Self::Target { &self.folder.files[self.idx] }
} }
impl AsRef<yazi_fs::File> for File { impl AsRef<yazi_fs::file::File> for File {
fn as_ref(&self) -> &yazi_fs::File { self } fn as_ref(&self) -> &yazi_fs::file::File { self }
} }
impl File { impl File {
@ -42,24 +32,10 @@ impl File {
) -> mlua::Result<AnyUserData> { ) -> mlua::Result<AnyUserData> {
use hashbrown::hash_map::Entry; use hashbrown::hash_map::Entry;
Ok(match super::FILE_CACHE.borrow_mut().entry(PtrCell(&folder.files[idx])) { Ok(match unsafe { (*FILE_CACHE.get()).assume_init_mut() }.entry(PtrCell(&folder.files[idx])) {
Entry::Occupied(oe) => oe.into_mut().clone(), Entry::Occupied(oe) => oe.into_mut().clone(),
Entry::Vacant(ve) => { Entry::Vacant(ve) => {
let ud = Lives::scoped_userdata(Self { let ud = Lives::scoped_userdata(Self { idx, folder: folder.into(), tab: tab.into() })?;
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()); ve.insert(ud.clone());
ud ud
} }
@ -73,7 +49,7 @@ impl File {
impl UserData for File { impl UserData for File {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
yazi_binding::impl_file_fields!(fields); yazi_binding::impl_file_fields!(fields);
cached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me))); fields.add_cached_field("bare", |_, me| Ok(yazi_fs::file::File::from(&**me)));
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1)); fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
fields.add_field_method_get("is_hovered", |_, me| Ok(me.is_hovered())); fields.add_field_method_get("is_hovered", |_, me| Ok(me.is_hovered()));
@ -87,17 +63,15 @@ impl UserData for File {
yazi_binding::impl_file_methods!(methods); yazi_binding::impl_file_methods!(methods);
methods.add_method("icon", |_, me, ()| { methods.add_method("icon", |_, me, ()| {
use yazi_binding::Icon;
// TODO: use a cache // TODO: use a cache
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()).map(Icon::from)) Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
}); });
methods.add_method("size", |_, me, ()| { methods.add_method("size", |_, me, ()| {
Ok(if me.is_dir() { me.folder.files.sizes.get(&me.urn()).copied() } else { Some(me.len) }) Ok(if me.is_dir() { me.folder.files.sizes.get(&me.urn()).copied() } else { Some(me.len) })
}); });
methods.add_method("mime", |lua, me, ()| { methods.add_method("mime", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { let core: CoreRef = lua.named_registry_value("cx")?;
core.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose() core.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose()
})?
}); });
methods.add_method("prefix", |lua, me, ()| { methods.add_method("prefix", |lua, me, ()| {
if !me.url.has_trail() { if !me.url.has_trail() {
@ -109,20 +83,18 @@ impl UserData for File {
Some(lua.create_string(comp.as_path().encoded_bytes())).transpose() Some(lua.create_string(comp.as_path().encoded_bytes())).transpose()
}); });
methods.add_method("style", |lua, me, ()| { methods.add_method("style", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { let core: CoreRef = lua.named_registry_value("cx")?;
let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default(); let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();
THEME.filetype.match_style(me, mime).map(Style::from) Ok(THEME.filetype.match_style(me, mime).map(Style::from))
})
}); });
methods.add_method("is_yanked", |lua, me, ()| { methods.add_method("is_yanked", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { let core: CoreRef = lua.named_registry_value("cx")?;
if !core.mgr.yanked.contains(&me.url) { Ok(if !core.mgr.yanked.contains(&me.url) {
0u8 0u8
} else if core.mgr.yanked.cut { } else if core.mgr.yanked.cut {
2u8 2u8
} else { } else {
1u8 1u8
}
}) })
}); });
methods.add_method("is_marked", |_, me, ()| { methods.add_method("is_marked", |_, me, ()| {
@ -139,31 +111,30 @@ impl UserData for File {
}); });
methods.add_method("is_selected", |_, me, ()| Ok(me.tab.selected.contains(&me.url))); methods.add_method("is_selected", |_, me, ()| Ok(me.tab.selected.contains(&me.url)));
methods.add_method("found", |lua, me, ()| { methods.add_method("found", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { let core: CoreRef = lua.named_registry_value("cx")?;
let Some(finder) = &core.active().finder else { let Some(finder) = &core.active().finder else {
return Ok(None); return Ok(None);
}; };
let Some(idx) = finder.matched_idx(&me.folder, me.urn()) else { let Some(idx) = finder.matched_idx(&me.folder, me.urn()) else {
return Ok(None); return Ok(None);
}; };
Some(lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?])) lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?]).map(Some)
.transpose()
})
}); });
methods.add_method("highlights", |lua, me, ()| { methods.add_method("highlights", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { let core: CoreRef = lua.named_registry_value("cx")?;
let Some(finder) = &core.active().finder else { let Some(finder) = &core.active().finder else {
return None; return Ok(None);
}; };
if me.folder.url != me.tab.current.url { if me.folder.url != me.tab.current.url {
return None; return Ok(None);
} }
let Some(Some(h)) = me.url.name().map(|s| finder.filter.highlighted(s)) else {
return Ok(None);
};
let h = finder.filter.highlighted(me.url.name()?)?; lua.create_sequence_from(h.into_iter().map(Range::from)).map(Some)
Some(h.into_iter().map(Range::from).collect::<Vec<_>>())
})
}); });
} }
} }

View file

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

View file

@ -1,8 +1,8 @@
use std::ops::{Deref, Range}; use std::ops::{Deref, Range};
use mlua::{AnyUserData, UserData, UserDataFields, Value}; use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_binding::{FolderStage, Url, cached_field};
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{File, Files, Lives, PtrCell}; use super::{File, Files, Lives, PtrCell};
@ -10,12 +10,6 @@ pub(super) struct Folder {
window: Range<usize>, window: Range<usize>,
inner: PtrCell<yazi_core::tab::Folder>, inner: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>, 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 { impl Deref for Folder {
@ -38,30 +32,20 @@ impl Folder {
} }
}; };
Lives::scoped_userdata(Self { Lives::scoped_userdata(Self { window, inner: inner.into(), tab: tab.into() })
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 { impl UserData for Folder {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cwd, |_, me| Ok(Url::new(&me.url))); fields.add_cached_field("cwd", |_, me| Ok(me.url.clone()));
cached_field!(fields, files, |_, me| Files::make(0..me.files.len(), me, &me.tab)); fields.add_static_field("files", |_, me| Files::make(0..me.files.len(), me, &me.tab));
cached_field!(fields, stage, |_, me| Ok(FolderStage::new(me.stage.clone()))); fields.add_cached_field("stage", |_, me| Ok(me.stage.clone()));
cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab)); fields.add_static_field("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("offset", |_, me| Ok(me.offset));
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor)); fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
cached_field!(fields, hovered, |_, me| { fields.add_static_field("hovered", |_, me| {
me.hovered().map(|_| File::make(me.cursor, me, &me.tab)).transpose() me.hovered().map(|_| File::make(me.cursor, me, &me.tab)).transpose()
}); });
} }

View file

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

View file

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

View file

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

View file

@ -1 +1,5 @@
yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked); yazi_macro::mod_flat!(behavior core file files 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())) };
}

View file

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

View file

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

View file

@ -1,15 +1,13 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, Value}; use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_binding::cached_field;
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Folder, Lives, PtrCell}; use super::{Folder, Lives, PtrCell};
pub(super) struct Preview { pub(super) struct Preview {
tab: PtrCell<yazi_core::tab::Tab>, tab: PtrCell<yazi_core::tab::Tab>,
v_folder: Option<Value>,
} }
impl Deref for Preview { impl Deref for Preview {
@ -22,17 +20,16 @@ impl Deref for Preview {
impl Preview { impl Preview {
pub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> { pub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { tab: tab.into(), v_folder: None }) Lives::scoped_userdata(Self { tab: tab.into() })
} }
} }
impl UserData for Preview { impl UserData for Preview {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("skip", |_, me| Ok(me.skip)); fields.add_field_method_get("skip", |_, me| Ok(me.skip));
// TODO: Make sure we need this ?
fields.add_field_method_get("search_idx", |_, me| Ok(me.search_idx)); fields.add_field_method_get("search_idx", |_, me| Ok(me.search_idx));
cached_field!(fields, folder, |_, me| { fields.add_static_field("folder", |_, me| {
me.tab me.tab
.hovered_folder() .hovered_folder()
.map(|f| { .map(|f| {

View file

@ -1,4 +1,5 @@
use mlua::AnyUserData; use mlua::AnyUserData;
use yazi_shared::url::UrlBuf;
use super::Lives; use super::Lives;
use crate::lives::PtrCell; use crate::lives::PtrCell;
@ -11,7 +12,7 @@ impl Selected {
let inner = PtrCell::from(inner); let inner = PtrCell::from(inner);
Lives::scoped_userdata(yazi_binding::Iter::new( Lives::scoped_userdata(yazi_binding::Iter::new(
inner.as_static().values().map(yazi_binding::Url::new), inner.as_static().values().map(UrlBuf::from),
Some(inner.len()), Some(inner.len()),
)) ))
} }

View file

@ -1,21 +1,13 @@
use std::{borrow::Cow, ops::Deref}; use std::{borrow::Cow, ops::Deref};
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods};
use yazi_binding::{Id, UrlRef, cached_field}; use yazi_shared::url::UrlRef;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected}; use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};
pub(super) struct Tab { pub(super) struct Tab {
inner: PtrCell<yazi_core::tab::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 { impl Deref for Tab {
@ -26,42 +18,29 @@ impl Deref for Tab {
impl Tab { impl Tab {
pub(super) fn make(inner: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> { pub(super) fn make(inner: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { Lives::scoped_userdata(Self { inner: inner.into() })
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 { impl UserData for Tab {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id))); fields.add_field_method_get("id", |_, me| Ok(me.id));
cached_field!(fields, name, |lua, me| { fields.add_cached_field("name", |lua, me| match me.name() {
match me.name() { Cow::Borrowed(s) => lua.create_string(s),
Cow::Borrowed(s) => lua.create_string(s), Cow::Owned(s) => lua.create_external_string(s),
Cow::Owned(s) => lua.create_external_string(s),
}
}); });
cached_field!(fields, mode, |_, me| Mode::make(&me.mode)); fields.add_static_field("mode", |_, me| Mode::make(&me.mode));
cached_field!(fields, pref, |_, me| Preference::make(&me.pref)); fields.add_static_field("pref", |_, me| Preference::make(&me.pref));
cached_field!(fields, current, |_, me| Folder::make(None, &me.current, me)); fields.add_static_field("current", |_, me| Folder::make(None, &me.current, me));
cached_field!(fields, parent, |_, me| { fields.add_static_field("parent", |_, me| {
me.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose() me.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose()
}); });
cached_field!(fields, selected, |_, me| Selected::make(&me.selected)); fields.add_static_field("selected", |_, me| Selected::make(&me.selected));
cached_field!(fields, preview, |_, me| Preview::make(me)); fields.add_static_field("preview", |_, me| Preview::make(me));
cached_field!(fields, finder, |_, me| me.finder.as_ref().map(Finder::make).transpose()); fields.add_static_field("finder", |_, me| me.finder.as_ref().map(Finder::make).transpose());
} }
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

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

View file

@ -1,16 +1,13 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value}; use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
use yazi_binding::{SER_OPT, cached_field}; use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
use super::{Lives, PtrCell}; use super::{Lives, PtrCell};
use crate::lives::TaskSnap; use crate::lives::{Behavior, TaskSnap};
pub(super) struct Tasks { pub(super) struct Tasks {
inner: PtrCell<yazi_core::tasks::Tasks>, inner: PtrCell<yazi_core::tasks::Tasks>,
v_snaps: Option<Value>,
v_summary: Option<Value>,
} }
impl Deref for Tasks { impl Deref for Tasks {
@ -21,12 +18,7 @@ impl Deref for Tasks {
impl Tasks { impl Tasks {
pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> { pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { Lives::scoped_userdata(Self { inner: inner.into() })
inner: inner.into(),
v_snaps: None,
v_summary: None,
})
} }
} }
@ -34,7 +26,9 @@ impl UserData for Tasks {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor)); fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
cached_field!(fields, snaps, |lua, me| { fields.add_static_field("behavior", |_, me| Behavior::make(&me.scheduler.behavior));
fields.add_static_field("snaps", |lua, me| {
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?; let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
for snap in &me.snaps { for snap in &me.snaps {
tbl.raw_push(TaskSnap::make(snap)?)?; tbl.raw_push(TaskSnap::make(snap)?)?;
@ -42,6 +36,6 @@ impl UserData for Tasks {
Ok(tbl) Ok(tbl)
}); });
cached_field!(fields, summary, |lua, me| lua.to_value_with(&me.summary, SER_OPT)); fields.add_cached_field("summary", |lua, me| lua.to_value_with(&me.summary, SER_OPT));
} }
} }

View file

@ -1,15 +1,12 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, Value}; use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_binding::cached_field; use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell}; use super::{Lives, PtrCell};
pub(super) struct Which { pub(super) struct Which {
inner: PtrCell<yazi_core::which::Which>, inner: PtrCell<yazi_core::which::Which>,
v_tx: Option<Value>,
v_cands: Option<Value>,
} }
impl Deref for Which { impl Deref for Which {
@ -20,16 +17,16 @@ impl Deref for Which {
impl Which { impl Which {
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> { pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into(), v_tx: None, v_cands: None }) Lives::scoped_userdata(Self { inner: inner.into() })
} }
} }
impl UserData for Which { impl UserData for Which {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, tx, |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx))); fields.add_cached_field("tx", |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
fields.add_field_method_get("times", |_, me| Ok(me.inner.times)); fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
cached_field!(fields, cands, |lua, me| { fields.add_cached_field("cands", |lua, me| {
lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::ChordCow)) lua.create_sequence_from(me.inner.cands.iter().cloned())
}); });
fields.add_field_method_get("active", |_, me| Ok(me.inner.active)); fields.add_field_method_get("active", |_, me| Ok(me.inner.active));

View file

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

View file

@ -1,18 +1,18 @@
use std::{fmt::{self, Display}, io::{self, Read, Write}, path::{MAIN_SEPARATOR, Path}, sync::Arc}; use std::{fmt::{self, Display}, io::{self, Read, Write}, path::{MAIN_SEPARATOR, Path}};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use scopeguard::defer; use scopeguard::defer;
use yazi_binding::Permit; use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule}; use yazi_config::{YAZI, opener::OpenerRuleArc};
use yazi_fs::{File, FilesOp, Splatter, provider::{Provider, local::Local}}; use yazi_fs::{FilesOp, Splatter, file::File, provider::{Provider, local::Local}};
use yazi_macro::{succ, writef}; use yazi_macro::{succ, writef};
use yazi_parser::VoidForm; use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy}; use yazi_scheduler::{AppProxy, NotifyProxy};
use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_shim::path::CROSS_SEPARATOR; use yazi_shim::path::CROSS_SEPARATOR;
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen}; use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::TTY; use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::{VfsFile, provider}; use yazi_vfs::{VfsFile, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -106,7 +106,7 @@ impl BulkCreate {
Ok(()) Ok(())
} }
fn opener() -> Option<Arc<OpenerRule>> { fn opener() -> Option<OpenerRuleArc> {
YAZI YAZI
.open .open
.match_dummy(Path::new("bulk-create.txt"), "text/plain") .match_dummy(Path::new("bulk-create.txt"), "text/plain")

View file

@ -1,20 +1,20 @@
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path, sync::Arc}; use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use hashbrown::HashMap; use hashbrown::HashMap;
use scopeguard::defer; use scopeguard::defer;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use yazi_binding::Permit; use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule}; use yazi_config::{YAZI, opener::OpenerRuleArc};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}}; use yazi_fs::{FilesOp, Splatter, file::File, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
use yazi_macro::{err, succ, writef}; use yazi_macro::{err, succ, writef};
use yazi_parser::VoidForm; use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy}; use yazi_scheduler::{AppProxy, NotifyProxy};
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen}; use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::TTY; use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -152,7 +152,7 @@ impl BulkRename {
Ok(()) Ok(())
} }
fn opener() -> Option<Arc<OpenerRule>> { fn opener() -> Option<OpenerRuleArc> {
YAZI YAZI
.open .open
.match_dummy(Path::new("bulk-rename.txt"), "text/plain") .match_dummy(Path::new("bulk-rename.txt"), "text/plain")

View file

@ -3,10 +3,10 @@ use std::{mem, time::Duration};
use anyhow::Result; use anyhow::Result;
use tokio::pin; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::YAZI;
use yazi_core::mgr::CdSource; use yazi_core::mgr::CdSource;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}}; use yazi_fs::{FilesOp, file::File, path::{clean_url, expand_url}};
use yazi_macro::{act, err, input, render, succ}; use yazi_macro::{act, err, input, render, succ};
use yazi_parser::mgr::CdForm; use yazi_parser::mgr::CdForm;
use yazi_proxy::{CmpProxy, MgrProxy}; use yazi_proxy::{CmpProxy, MgrProxy};
@ -63,7 +63,7 @@ impl Actor for Cd {
impl Cd { impl Cd {
fn cd_interactive(cx: &mut Ctx) -> Result<Data> { fn cd_interactive(cx: &mut Ctx) -> Result<Data> {
let input = input!(cx, InputCfg::cd(cx.cwd().as_url()))?; let input = input!(cx, YAZI.input.cd(cx.cwd().as_url()))?;
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -1,6 +1,6 @@
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::{YAZI, popup::ConfirmCfg};
use yazi_fs::{File, FilesOp}; use yazi_fs::{FilesOp, file::File};
use yazi_macro::{input, ok_or_not_found, succ}; use yazi_macro::{input, ok_or_not_found, succ};
use yazi_parser::mgr::CreateForm; use yazi_parser::mgr::CreateForm;
use yazi_proxy::{ConfirmProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, MgrProxy};
@ -20,7 +20,7 @@ impl Actor for Create {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let cwd = cx.cwd().to_owned(); let cwd = cx.cwd().to_owned();
let mut input = input!(cx, InputCfg::create(form.dir))?; let mut input = input!(cx, YAZI.input.create(form.dir))?;
tokio::spawn(async move { tokio::spawn(async move {
let Some(InputEvent::Submit(name)) = input.recv().await else { return }; let Some(InputEvent::Submit(name)) = input.recv().await else { return };

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use futures::{StreamExt, stream::FuturesUnordered}; use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::HashSet; use hashbrown::HashSet;
use yazi_core::mgr::OpenOpt; use yazi_core::mgr::OpenOpt;
use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}}; use yazi_fs::{FsScheme, file::File, provider::{Provider, local::Local}};
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::DownloadForm; use yazi_parser::mgr::DownloadForm;
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;

View file

@ -3,7 +3,7 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use tokio::pin; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::YAZI;
use yazi_core::mgr::FilterOpt; use yazi_core::mgr::FilterOpt;
use yazi_macro::{input, succ}; use yazi_macro::{input, succ};
use yazi_parser::mgr::FilterForm; use yazi_parser::mgr::FilterForm;
@ -21,7 +21,7 @@ impl Actor for Filter {
const NAME: &str = "filter"; const NAME: &str = "filter";
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
let input = input!(cx, InputCfg::filter())?; let input = input!(cx, YAZI.input.filter())?;
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -3,7 +3,7 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use tokio::pin; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::YAZI;
use yazi_core::mgr::FindDoOpt; use yazi_core::mgr::FindDoOpt;
use yazi_macro::{input, succ}; use yazi_macro::{input, succ};
use yazi_parser::mgr::FindForm; use yazi_parser::mgr::FindForm;
@ -21,7 +21,7 @@ impl Actor for Find {
const NAME: &str = "find"; const NAME: &str = "find";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = input!(cx, InputCfg::find(form.prev))?; let input = input!(cx, YAZI.input.find(form.prev))?;
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -3,7 +3,7 @@ use futures::StreamExt;
use hashbrown::HashSet; use hashbrown::HashSet;
use yazi_boot::ARGS; use yazi_boot::ARGS;
use yazi_core::mgr::OpenDoOpt; use yazi_core::mgr::OpenDoOpt;
use yazi_fs::File; use yazi_fs::file::File;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_parser::mgr::OpenForm; use yazi_parser::mgr::OpenForm;
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;

View file

@ -1,7 +1,8 @@
use anyhow::Result; use anyhow::Result;
use hashbrown::HashMap; use hashbrown::HashMap;
use indexmap::IndexSet; use indexmap::IndexSet;
use yazi_config::{YAZI, popup::PickCfg}; use yazi_config::YAZI;
use yazi_fs::file::File;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::OpenDoForm; use yazi_parser::mgr::OpenDoForm;
use yazi_proxy::{PickProxy, TasksProxy}; use yazi_proxy::{PickProxy, TasksProxy};
@ -40,7 +41,7 @@ impl Actor for OpenDo {
succ!(); succ!();
} }
let pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect())); let pick = PickProxy::show(YAZI.pick.open(openers.iter().map(|o| o.desc()).collect()));
let urls: Vec<_> = [UrlCow::default()] let urls: Vec<_> = [UrlCow::default()]
.into_iter() .into_iter()
.chain(targets.into_iter().map(|(file, _)| file.url.into())) .chain(targets.into_iter().map(|(file, _)| file.url.into()))
@ -63,7 +64,7 @@ impl Actor for OpenDo {
impl OpenDo { impl OpenDo {
// TODO: remove // TODO: remove
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(yazi_fs::File, &str)>) { fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(File, &str)>) {
let mut openers = HashMap::new(); let mut openers = HashMap::new();
for (file, mime) in targets { for (file, mime) in targets {
if let Some(open) = YAZI.open.matches(&file, mime) if let Some(open) = YAZI.open.matches(&file, mime)

View file

@ -1,11 +1,11 @@
use anyhow::Result; use anyhow::Result;
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::{YAZI, popup::ConfirmCfg};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp}; use yazi_fs::{FilesOp, file::File};
use yazi_macro::{act, err, input, ok_or_not_found, succ}; use yazi_macro::{act, err, input, ok_or_not_found, succ};
use yazi_parser::mgr::RenameForm; use yazi_parser::mgr::RenameForm;
use yazi_proxy::{ConfirmProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, MgrProxy};
use yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}}; use yazi_shared::{data::Data, id::Id, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -42,7 +42,7 @@ impl Actor for Rename {
}; };
let (tab, old) = (cx.tab().id, hovered.url_owned()); let (tab, old) = (cx.tab().id, hovered.url_owned());
let mut input = input!(cx, InputCfg::rename().with_value(name).with_cursor(cursor))?; let mut input = input!(cx, YAZI.input.rename().with_value(name).with_cursor(cursor))?;
tokio::spawn(async move { tokio::spawn(async move {
let Some(InputEvent::Submit(name)) = input.recv().await else { return }; let Some(InputEvent::Submit(name)) = input.recv().await else { return };

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use yazi_fs::{File, FilesOp}; use yazi_fs::{FilesOp, file::File};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::mgr::RevealForm; use yazi_parser::mgr::RevealForm;
use yazi_shared::{data::Data, url::UrlLike}; use yazi_shared::{data::Data, url::UrlLike};

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, time::Duration};
use anyhow::Result; use anyhow::Result;
use tokio::pin; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::YAZI;
use yazi_core::mgr::{CdSource, SearchVia}; use yazi_core::mgr::{CdSource, SearchVia};
use yazi_fs::{FilesOp, cha::Cha}; use yazi_fs::{FilesOp, cha::Cha};
use yazi_macro::{act, input, succ}; use yazi_macro::{act, input, succ};
@ -28,7 +28,7 @@ impl Actor for Search {
handle.abort(); handle.abort();
} }
let mut input = input!(cx, InputCfg::search(opt.via.into()).with_value(&*opt.subject))?; let mut input = input!(cx, YAZI.input.search(opt.via.into()).with_value(&*opt.subject))?;
tokio::spawn(async move { tokio::spawn(async move {
if let Some(InputEvent::Submit(subject)) = input.recv().await { if let Some(InputEvent::Submit(subject)) = input.recv().await {

View file

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use yazi_config::popup::InputCfg; use yazi_config::YAZI;
use yazi_macro::{act, input, succ}; use yazi_macro::{act, input, succ};
use yazi_parser::mgr::ShellForm; use yazi_parser::mgr::ShellForm;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
@ -25,7 +25,10 @@ impl Actor for Shell {
let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect(); let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect();
let input = if form.interactive { let input = if form.interactive {
Some(input!(cx, InputCfg::shell(form.block).with_value(&*form.run).with_cursor(form.cursor))?) Some(input!(
cx,
YAZI.input.shell(form.block).with_value(&*form.run).with_cursor(form.cursor)
)?)
} else { } else {
None None
}; };

View file

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use yazi_config::popup::InputCfg; use yazi_config::YAZI;
use yazi_macro::{act, input, render, succ}; use yazi_macro::{act, input, render, succ};
use yazi_parser::mgr::TabRenameForm; use yazi_parser::mgr::TabRenameForm;
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
@ -29,7 +29,7 @@ impl Actor for TabRename {
let mut input = input!( let mut input = input!(
cx, cx,
InputCfg::tab_rename().with_value(form.name.unwrap_or(Cow::Borrowed(&pref.name))) YAZI.input.tab_rename().with_value(form.name.unwrap_or(Cow::Borrowed(&pref.name)))
)?; )?;
tokio::spawn(async move { tokio::spawn(async move {

View file

@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use yazi_core::notify::Notify; use yazi_core::notify::Notify;
use yazi_macro::{render, render_partial, succ}; use yazi_macro::{render, render_partial, succ};
use yazi_parser::notify::TickForm; use yazi_parser::notify::TickForm;

View file

@ -4,13 +4,12 @@ use anyhow::Result;
use scopeguard::defer; use scopeguard::defer;
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time}; use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
use yazi_binding::Permit; use yazi_binding::Permit;
use yazi_term::TERM;
use yazi_macro::{succ, writef}; use yazi_macro::{succ, writef};
use yazi_parser::VoidForm; use yazi_parser::VoidForm;
use yazi_scheduler::AppProxy; use yazi_scheduler::AppProxy;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen}; use yazi_term::{TERM, YIELD_TO_SUBPROCESS};
use yazi_tty::TTY; use yazi_tty::{TTY, sequence::EraseScreen};
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};

View file

@ -15,6 +15,8 @@ impl Actor for Spawn {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
succ!(match form.opt { succ!(match form.opt {
TaskOpt::Cut(r#in) => cx.tasks.scheduler.file_cut(r#in),
TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in), TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in),
}) })
} }

View file

@ -19,18 +19,19 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-shared = { path = "../yazi-shared", version = "26.5.6" } yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", 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-tty = { path = "../yazi-tty", version = "26.5.9" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
image = { version = "0.25.10", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp" ] } image = { workspace = true }
moxcms = "0.8.1" inventory = { workspace = true }
palette = { version = "0.7.6", default-features = false } moxcms = "0.8.1"
quantette = { version = "0.6.0", default-features = false } palette = { version = "0.7.6", default-features = false }
ratatui = { workspace = true } quantette = { version = "0.6.0", default-features = false }
strum = { workspace = true } ratatui-core = { workspace = true }
tokio = { workspace = true } strum = { workspace = true }
tracing = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true }

View file

@ -1,105 +1,56 @@
use std::{env, path::PathBuf}; use std::{fmt::{self, Debug}, ops::Deref};
use anyhow::Result; use anyhow::Result;
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use strum::{Display, IntoStaticStr}; use yazi_emulator::EMULATOR;
use tracing::warn; use yazi_shim::cell::SyncCell;
use yazi_emulator::{Emulator, TMUX}; use yazi_widgets::clear::ClearInventory;
use yazi_shared::env_exists;
use crate::{Adapters, SHOWN, drivers}; use crate::{ADAPTOR, drivers::{Driver, Drivers}};
#[derive(Clone, Copy, Debug, Display, Eq, IntoStaticStr, PartialEq)] pub struct Adapter {
#[strum(serialize_all = "kebab-case")] driver: Driver,
pub enum Adapter { shown: SyncCell<Option<Rect>>,
Kgp, pub collision: SyncCell<bool>,
KgpOld, }
Iip,
Sixel,
// Supported by Überzug++ impl Deref for Adapter {
X11, type Target = Driver;
Wayland,
Chafa, fn deref(&self) -> &Self::Target { &self.driver }
}
impl Debug for Adapter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.driver.fmt(f) }
} }
impl Adapter { impl Adapter {
pub async fn image_show<P>(self, path: P, max: Rect) -> Result<Rect> pub(super) fn new() -> Self {
where Self {
P: Into<PathBuf>, driver: Drivers::matches(&EMULATOR),
{ shown: SyncCell::new(None),
if max.is_empty() { collision: SyncCell::new(false),
return Ok(Rect::default());
}
let path = path.into();
match self {
Self::Kgp => drivers::Kgp::image_show(path, max).await,
Self::KgpOld => drivers::KgpOld::image_show(path, max).await,
Self::Iip => drivers::Iip::image_show(path, max).await,
Self::Sixel => drivers::Sixel::image_show(path, max).await,
Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max).await,
Self::Chafa => drivers::Chafa::image_show(path, max).await,
} }
} }
pub fn image_hide(self) -> Result<()> { pub fn image_hide(&self) -> Result<()> {
if let Some(area) = SHOWN.replace(None) { self.image_erase(area) } else { Ok(()) } if let Some(area) = self.shown.replace(None) { self.driver.image_erase(area) } else { Ok(()) }
} }
pub fn image_erase(self, area: Rect) -> Result<()> { pub(super) fn shown_store(&self, area: Rect) { self.shown.set(Some(area)); }
match self {
Self::Kgp => drivers::Kgp::image_erase(area),
Self::KgpOld => drivers::KgpOld::image_erase(area),
Self::Iip => drivers::Iip::image_erase(area),
Self::Sixel => drivers::Sixel::image_erase(area),
Self::X11 | Self::Wayland => drivers::Ueberzug::image_erase(area),
Self::Chafa => drivers::Chafa::image_erase(area),
}
}
#[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.get() }
#[inline]
pub(super) fn shown_store(area: Rect) { SHOWN.set(Some(area)); }
pub(super) fn start(self) { drivers::Ueberzug::start(self); }
#[inline]
pub(super) fn needs_ueberzug(self) -> bool {
!matches!(self, Self::Kgp | Self::KgpOld | Self::Iip | Self::Sixel)
}
} }
impl Adapter { inventory::submit! {
pub fn matches(emulator: &Emulator) -> Self { ClearInventory {
let mut adapters: Adapters = emulator.into(); clear: |area| {
if env_exists("ZELLIJ_SESSION_NAME") { let overlap = area.intersection(ADAPTOR.shown.get()?);
adapters.retain(|p| *p == Self::Sixel); if overlap.area() == 0 {
} else if TMUX.get() { return None;
adapters.retain(|p| *p != Self::KgpOld); }
}
if let Some(p) = adapters.first() {
return *p;
}
let supported_compositor = drivers::Ueberzug::supported_compositor(); ADAPTOR.driver.image_erase(overlap).ok();
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() { ADAPTOR.collision.set(true);
"x11" => return Self::X11, Some(overlap)
"wayland" if supported_compositor => return Self::Wayland, },
"wayland" if !supported_compositor => return Self::Chafa,
_ => warn!("[Adapter] Could not identify XDG_SESSION_TYPE"),
}
if env_exists("WAYLAND_DISPLAY") {
return if supported_compositor { Self::Wayland } else { Self::Chafa };
}
match env::var("DISPLAY").unwrap_or_default().as_str() {
s if !s.is_empty() && !s.contains("/org.xquartz") => return Self::X11,
_ => {}
}
warn!("[Adapter] Falling back to chafa");
Self::Chafa
} }
} }

View file

@ -1,62 +0,0 @@
use std::ops::{Deref, DerefMut};
use crate::Adapter;
pub(super) struct Adapters(Vec<Adapter>);
impl Deref for Adapters {
type Target = Vec<Adapter>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for Adapters {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl From<&yazi_emulator::Emulator> for Adapters {
fn from(value: &yazi_emulator::Emulator) -> Self { value.kind.either_into() }
}
impl From<yazi_emulator::Brand> for Adapters {
fn from(value: yazi_emulator::Brand) -> Self {
use yazi_emulator::Brand as B;
use crate::Adapter as A;
Self(match value {
B::Kitty => vec![A::Kgp],
B::Konsole => vec![A::KgpOld],
B::Iterm2 => vec![A::Iip, A::Sixel],
B::WezTerm => vec![A::Iip, A::Sixel],
B::Foot => vec![A::Sixel],
B::Ghostty => vec![A::Kgp],
B::Microsoft => vec![A::Sixel],
B::Warp => vec![A::Iip, A::KgpOld],
B::Rio => vec![A::Kgp],
B::BlackBox => vec![A::Sixel],
B::VSCode => vec![A::Iip, A::Sixel],
B::Tabby => vec![A::Iip, A::Sixel],
B::Hyper => vec![A::Iip, A::Sixel],
B::Mintty => vec![A::Iip],
B::Tmux => vec![],
B::VTerm => vec![],
B::Apple => vec![],
B::Urxvt => vec![],
B::Bobcat => vec![A::Iip, A::Sixel],
})
}
}
impl From<yazi_emulator::Unknown> for Adapters {
fn from(value: yazi_emulator::Unknown) -> Self {
use Adapter as A;
Self(match (value.kgp, value.sixel) {
(true, true) => vec![A::Sixel, A::KgpOld],
(true, false) => vec![A::KgpOld],
(false, true) => vec![A::Sixel],
(false, false) => vec![],
})
}
}

View file

@ -2,17 +2,18 @@ use std::{io::Write, path::PathBuf, process::Stdio};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use anyhow::{Result, anyhow, bail}; use anyhow::{Result, anyhow, bail};
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use tokio::process::Command; use tokio::process::Command;
use yazi_config::THEME;
use yazi_emulator::Emulator; use yazi_emulator::Emulator;
use yazi_term::sequence::MoveTo; use yazi_tty::sequence::{MoveTo, ResetAttrs, SetBg};
use crate::Adapter; use crate::ADAPTOR;
pub(crate) struct Chafa; pub(super) struct Chafa;
impl Chafa { impl Chafa {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(super) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let child = Command::new("chafa") let child = Command::new("chafa")
.args([ .args([
"-f", "-f",
@ -57,8 +58,8 @@ impl Chafa {
height: lines.len() as u16, height: lines.len() as u16,
}; };
Adapter::Chafa.image_hide()?; ADAPTOR.image_hide()?;
Adapter::shown_store(area); ADAPTOR.shown_store(area);
Emulator::move_lock((max.x, max.y), |w| { Emulator::move_lock((max.x, max.y), |w| {
for (i, line) in lines.into_iter().enumerate() { for (i, line) in lines.into_iter().enumerate() {
w.write_all(line)?; w.write_all(line)?;
@ -68,14 +69,17 @@ impl Chafa {
}) })
} }
pub(crate) fn image_erase(area: Rect) -> Result<()> { pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| { Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?; write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?; write!(w, "{s}")?;
} }
Ok(()) Ok(write!(w, "{ResetAttrs}")?)
}) })
} }
} }

View file

@ -0,0 +1,59 @@
use std::path::PathBuf;
use anyhow::Result;
use ratatui_core::layout::Rect;
use strum::{Display, IntoStaticStr};
use crate::drivers::{Chafa, Iip, Kgp, KgpOld, Sixel, Ueberzug};
#[derive(Clone, Copy, Debug, Display, Eq, IntoStaticStr, PartialEq)]
#[strum(serialize_all = "kebab-case")]
pub enum Driver {
Kgp,
KgpOld,
Iip,
Sixel,
// Supported by Überzug++
X11,
Wayland,
Chafa,
}
impl Driver {
pub async fn image_show<P>(self, path: P, max: Rect) -> Result<Rect>
where
P: Into<PathBuf>,
{
if max.is_empty() {
return Ok(Rect::default());
}
let path = path.into();
match self {
Self::Kgp => Kgp::image_show(path, max).await,
Self::KgpOld => KgpOld::image_show(path, max).await,
Self::Iip => Iip::image_show(path, max).await,
Self::Sixel => Sixel::image_show(path, max).await,
Self::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
Self::Chafa => Chafa::image_show(path, max).await,
}
}
pub fn image_erase(self, area: Rect) -> Result<()> {
match self {
Self::Kgp => Kgp::image_erase(area),
Self::KgpOld => KgpOld::image_erase(area),
Self::Iip => Iip::image_erase(area),
Self::Sixel => Sixel::image_erase(area),
Self::X11 | Self::Wayland => Ueberzug::image_erase(area),
Self::Chafa => Chafa::image_erase(area),
}
}
pub(crate) fn start(self) { Ueberzug::start(self); }
pub(crate) fn needs_ueberzug(self) -> bool {
!matches!(self, Self::Kgp | Self::KgpOld | Self::Iip | Self::Sixel)
}
}

View file

@ -0,0 +1,94 @@
use std::{env, ops::{Deref, DerefMut}};
use tracing::warn;
use yazi_emulator::{Emulator, TMUX};
use yazi_shared::env_exists;
use crate::drivers::{Driver as D, Ueberzug};
pub(crate) struct Drivers(Vec<D>);
impl Deref for Drivers {
type Target = Vec<D>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for Drivers {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl From<&yazi_emulator::Emulator> for Drivers {
fn from(value: &yazi_emulator::Emulator) -> Self { value.kind.either_into() }
}
impl From<yazi_emulator::Brand> for Drivers {
fn from(value: yazi_emulator::Brand) -> Self {
use yazi_emulator::Brand as B;
Self(match value {
B::Kitty => vec![D::Kgp],
B::Konsole => vec![D::KgpOld],
B::Iterm2 => vec![D::Iip, D::Sixel],
B::WezTerm => vec![D::Iip, D::Sixel],
B::Foot => vec![D::Sixel],
B::Ghostty => vec![D::Kgp],
B::Microsoft => vec![D::Sixel],
B::Warp => vec![D::Iip, D::KgpOld],
B::Rio => vec![D::Kgp],
B::BlackBox => vec![D::Sixel],
B::VSCode => vec![D::Iip, D::Sixel],
B::Tabby => vec![D::Iip, D::Sixel],
B::Hyper => vec![D::Iip, D::Sixel],
B::Mintty => vec![D::Iip],
B::Tmux => vec![],
B::VTerm => vec![],
B::Apple => vec![],
B::Urxvt => vec![],
B::Bobcat => vec![D::Iip, D::Sixel],
})
}
}
impl From<yazi_emulator::Unknown> for Drivers {
fn from(value: yazi_emulator::Unknown) -> Self {
Self(match (value.kgp, value.sixel) {
(true, true) => vec![D::Sixel, D::KgpOld],
(true, false) => vec![D::KgpOld],
(false, true) => vec![D::Sixel],
(false, false) => vec![],
})
}
}
impl Drivers {
pub fn matches(emulator: &Emulator) -> D {
let mut adapters: Self = emulator.into();
if env_exists("ZELLIJ_SESSION_NAME") {
adapters.retain(|p| *p == D::Sixel);
} else if TMUX.get() {
adapters.retain(|p| *p != D::KgpOld);
}
if let Some(p) = adapters.first() {
return *p;
}
let supported_compositor = Ueberzug::supported_compositor();
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() {
"x11" => return D::X11,
"wayland" if supported_compositor => return D::Wayland,
"wayland" if !supported_compositor => return D::Chafa,
_ => warn!("[Adapter] Could not identify XDG_SESSION_TYPE"),
}
if env_exists("WAYLAND_DISPLAY") {
return if supported_compositor { D::Wayland } else { D::Chafa };
}
match env::var("DISPLAY").unwrap_or_default().as_str() {
s if !s.is_empty() && !s.contains("/org.xquartz") => return D::X11,
_ => {}
}
warn!("[Adapter] Falling back to chafa");
D::Chafa
}
}

View file

@ -1,39 +1,42 @@
use std::{fmt::Write, io::Write as ioWrite, path::PathBuf}; use std::{fmt::Write as _, io::Write as _, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use base64::{Engine, engine::{Config, general_purpose::STANDARD}}; use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}}; use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use yazi_config::YAZI; use yazi_config::{THEME, YAZI};
use yazi_emulator::{CLOSE, Emulator, START}; use yazi_emulator::{CLOSE, Emulator, START};
use yazi_term::sequence::MoveTo; use yazi_tty::sequence::{MoveTo, ResetAttrs, SetBg};
use crate::{Image, adapter::Adapter}; use crate::{ADAPTOR, Image};
pub(crate) struct Iip; pub(super) struct Iip;
impl Iip { impl Iip {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(super) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Adapter::Iip.image_hide()?; ADAPTOR.image_hide()?;
Adapter::shown_store(area); ADAPTOR.shown_store(area);
Emulator::move_lock((max.x, max.y), |w| { Emulator::move_lock((max.x, max.y), |w| {
w.write_all(&b)?; w.write_all(&b)?;
Ok(area) Ok(area)
}) })
} }
pub(crate) fn image_erase(area: Rect) -> Result<()> { pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| { Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?; write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?; write!(w, "{s}")?;
} }
Ok(()) Ok(write!(w, "{ResetAttrs}")?)
}) })
} }

View file

@ -4,12 +4,13 @@ use std::{io::Write, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use image::DynamicImage; use image::DynamicImage;
use ratatui::{layout::Rect, style::Color}; use ratatui_core::{layout::Rect, style::Color};
use yazi_config::THEME;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START}; use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_shim::cell::SyncCell; use yazi_shim::cell::SyncCell;
use yazi_term::sequence::{MoveTo, SetFg}; use yazi_tty::sequence::{MoveTo, ResetAttrs, SetBg, SetFg};
use crate::{adapter::Adapter, image::Image}; use crate::{ADAPTOR, image::Image};
static DIACRITICS: [char; 297] = [ static DIACRITICS: [char; 297] = [
'\u{0305}', '\u{0305}',
@ -311,18 +312,18 @@ static DIACRITICS: [char; 297] = [
'\u{1D244}', '\u{1D244}',
]; ];
pub(crate) struct Kgp; pub(super) struct Kgp;
impl Kgp { impl Kgp {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(super) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b1 = Self::encode(img).await?; let b1 = Self::encode(img).await?;
let b2 = Self::place(&area)?; let b2 = Self::place(&area)?;
Adapter::Kgp.image_hide()?; ADAPTOR.image_hide()?;
Adapter::shown_store(area); ADAPTOR.shown_store(area);
Emulator::move_lock((area.x, area.y), |w| { Emulator::move_lock((area.x, area.y), |w| {
w.write_all(&b1)?; w.write_all(&b1)?;
w.write_all(&b2)?; w.write_all(&b2)?;
@ -330,15 +331,19 @@ impl Kgp {
}) })
} }
pub(crate) fn image_erase(area: Rect) -> Result<()> { pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| { Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?; write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?; write!(w, "{s}")?;
} }
write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?; write!(w, "{ResetAttrs}{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
Ok(()) Ok(())
}) })
} }
@ -381,12 +386,16 @@ impl Kgp {
} }
fn place(area: &Rect) -> Result<Vec<u8>> { fn place(area: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 50); let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 500);
let id = Self::image_id(); let id = Self::image_id();
let (r, g, b) = ((id >> 16) & 0xff, (id >> 8) & 0xff, id & 0xff); let (r, g, b) = ((id >> 16) & 0xff, (id >> 8) & 0xff, id & 0xff);
write!(buf, "{}", SetFg(Color::Rgb(r as u8, g as u8, b as u8)))?; write!(buf, "{}", SetFg(Color::Rgb(r as u8, g as u8, b as u8)))?;
if let Some(c) = THEME.app.overall.get().bg {
write!(buf, "{}", SetBg(c))?;
}
for y in 0..area.height { for y in 0..area.height {
write!(buf, "{}", MoveTo(area.x, area.y + y))?; write!(buf, "{}", MoveTo(area.x, area.y + y))?;
for x in 0..area.width { for x in 0..area.width {
@ -395,6 +404,8 @@ impl Kgp {
write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?; write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;
} }
} }
write!(buf, "{ResetAttrs}")?;
Ok(buf) Ok(buf)
} }

View file

@ -4,29 +4,29 @@ use std::{io::Write, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START}; use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_tty::TTY; use yazi_tty::TTY;
use crate::{Image, adapter::Adapter}; use crate::{ADAPTOR, Image};
pub(crate) struct KgpOld; pub(super) struct KgpOld;
impl KgpOld { impl KgpOld {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(super) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Adapter::KgpOld.image_hide()?; ADAPTOR.image_hide()?;
Adapter::shown_store(area); ADAPTOR.shown_store(area);
Emulator::move_lock((area.x, area.y), |w| { Emulator::move_lock((area.x, area.y), |w| {
w.write_all(&b)?; w.write_all(&b)?;
Ok(area) Ok(area)
}) })
} }
pub(crate) fn image_erase(_: Rect) -> Result<()> { pub(super) fn image_erase(_: Rect) -> Result<()> {
let mut w = TTY.lockout(); let mut w = TTY.lockout();
write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?; write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
w.flush()?; w.flush()?;

View file

@ -1 +1 @@
yazi_macro::mod_flat!(chafa iip kgp kgp_old sixel ueberzug); yazi_macro::mod_flat!(chafa driver drivers iip kgp kgp_old sixel ueberzug);

View file

@ -4,13 +4,14 @@ use anyhow::{Result, bail};
use image::{DynamicImage, GenericImageView, RgbImage}; use image::{DynamicImage, GenericImageView, RgbImage};
use palette::{Srgb, cast::ComponentsAs}; use palette::{Srgb, cast::ComponentsAs};
use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}}; use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use yazi_config::THEME;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START}; use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_term::sequence::MoveTo; use yazi_tty::sequence::{MoveTo, ResetAttrs, SetBg};
use crate::{Image, adapter::Adapter}; use crate::{ADAPTOR, Image};
pub(crate) struct Sixel; pub(super) struct Sixel;
struct QuantizeOutput<T> { struct QuantizeOutput<T> {
indices: Vec<u8>, indices: Vec<u8>,
@ -18,27 +19,30 @@ struct QuantizeOutput<T> {
} }
impl Sixel { impl Sixel {
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(super) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(path, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Adapter::Sixel.image_hide()?; ADAPTOR.image_hide()?;
Adapter::shown_store(area); ADAPTOR.shown_store(area);
Emulator::move_lock((area.x, area.y), |w| { Emulator::move_lock((area.x, area.y), |w| {
w.write_all(&b)?; w.write_all(&b)?;
Ok(area) Ok(area)
}) })
} }
pub(crate) fn image_erase(area: Rect) -> Result<()> { pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize); let s = " ".repeat(area.width as usize);
Emulator::move_lock((0, 0), |w| { Emulator::move_lock((0, 0), |w| {
if let Some(c) = THEME.app.overall.get().bg {
write!(w, "{}", SetBg(c))?;
}
for y in area.top()..area.bottom() { for y in area.top()..area.bottom() {
write!(w, "{}", MoveTo(area.x, y))?; write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?; write!(w, "{s}")?;
} }
Ok(()) Ok(write!(w, "{ResetAttrs}")?)
}) })
} }

View file

@ -2,7 +2,7 @@ use std::{path::PathBuf, process::Stdio};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use image::ImageReader; use image::ImageReader;
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}}; use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
use tracing::{debug, warn}; use tracing::{debug, warn};
use yazi_config::YAZI; use yazi_config::YAZI;
@ -10,21 +10,21 @@ use yazi_emulator::Dimension;
use yazi_shared::{LOG_LEVEL, env_exists}; use yazi_shared::{LOG_LEVEL, env_exists};
use yazi_shim::{cell::RoCell, strum::IntoStr}; use yazi_shim::{cell::RoCell, strum::IntoStr};
use crate::Adapter; use crate::{ADAPTOR, drivers::Driver};
type Cmd = Option<(PathBuf, Rect)>; type Cmd = Option<(PathBuf, Rect)>;
static DEMON: RoCell<Option<UnboundedSender<Cmd>>> = RoCell::new(); static DEMON: RoCell<Option<UnboundedSender<Cmd>>> = RoCell::new();
pub(crate) struct Ueberzug; pub(super) struct Ueberzug;
impl Ueberzug { impl Ueberzug {
pub(crate) fn start(adapter: Adapter) { pub(super) fn start(driver: Driver) {
if !adapter.needs_ueberzug() { if !driver.needs_ueberzug() {
return DEMON.init(None); return DEMON.init(None);
} }
let mut child = Self::create_demon(adapter).ok(); let mut child = Self::create_demon(driver).ok();
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
tokio::spawn(async move { tokio::spawn(async move {
@ -34,17 +34,17 @@ impl Ueberzug {
child = None; child = None;
} }
if child.is_none() { if child.is_none() {
child = Self::create_demon(adapter).ok(); child = Self::create_demon(driver).ok();
} }
if let Some(c) = &mut child { if let Some(c) = &mut child {
Self::send_command(adapter, c, cmd).await.ok(); Self::send_command(driver, c, cmd).await.ok();
} }
} }
}); });
DEMON.init(Some(tx)) DEMON.init(Some(tx))
} }
pub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> { pub(super) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {
let Some(tx) = &*DEMON else { let Some(tx) = &*DEMON else {
bail!("uninitialized ueberzugpp"); bail!("uninitialized ueberzugpp");
}; };
@ -64,11 +64,11 @@ impl Ueberzug {
.unwrap_or(max); .unwrap_or(max);
tx.send(Some((path, area)))?; tx.send(Some((path, area)))?;
Adapter::shown_store(area); ADAPTOR.shown_store(area);
Ok(area) Ok(area)
} }
pub(crate) fn image_erase(_: Rect) -> Result<()> { pub(super) fn image_erase(_: Rect) -> Result<()> {
if let Some(tx) = &*DEMON { if let Some(tx) = &*DEMON {
Ok(tx.send(None)?) Ok(tx.send(None)?)
} else { } else {
@ -79,16 +79,16 @@ impl Ueberzug {
// Currently Überzug++'s Wayland output only supports Niri, Sway, Hyprland and // Currently Überzug++'s Wayland output only supports Niri, Sway, Hyprland and
// Wayfire as it requires information from specific compositor socket directly. // Wayfire as it requires information from specific compositor socket directly.
// These environment variables are from ueberzugpp src/canvas/wayland/config.cpp // These environment variables are from ueberzugpp src/canvas/wayland/config.cpp
pub(crate) fn supported_compositor() -> bool { pub(super) fn supported_compositor() -> bool {
env_exists("NIRI_SOCKET") env_exists("NIRI_SOCKET")
|| env_exists("SWAYSOCK") || env_exists("SWAYSOCK")
|| env_exists("HYPRLAND_INSTANCE_SIGNATURE") || env_exists("HYPRLAND_INSTANCE_SIGNATURE")
|| env_exists("WAYFIRE_SOCKET") || env_exists("WAYFIRE_SOCKET")
} }
fn create_demon(adapter: Adapter) -> Result<Child> { fn create_demon(driver: Driver) -> Result<Child> {
let result = Command::new("ueberzugpp") let result = Command::new("ueberzugpp")
.args(["layer", "-so", adapter.into_str()]) .args(["layer", "-so", driver.into_str()])
.env("SPDLOG_LEVEL", if LOG_LEVEL.get().is_none() { "" } else { "debug" }) .env("SPDLOG_LEVEL", if LOG_LEVEL.get().is_none() { "" } else { "debug" })
.kill_on_drop(true) .kill_on_drop(true)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@ -113,7 +113,7 @@ impl Ueberzug {
rect rect
} }
async fn send_command(adapter: Adapter, child: &mut Child, cmd: Cmd) -> Result<()> { async fn send_command(driver: Driver, child: &mut Child, cmd: Cmd) -> Result<()> {
let s = if let Some((path, rect)) = cmd { let s = if let Some((path, rect)) = cmd {
debug!("ueberzugpp rect before adjustment: {:?}", rect); debug!("ueberzugpp rect before adjustment: {:?}", rect);
let rect = Self::adjust_rect(rect); let rect = Self::adjust_rect(rect);
@ -132,7 +132,7 @@ impl Ueberzug {
format!(r#"{{"action":"remove","identifier":"yazi"}}{}"#, '\n') format!(r#"{{"action":"remove","identifier":"yazi"}}{}"#, '\n')
}; };
debug!("`ueberzugpp layer -so {adapter}` command: {s}"); debug!("`ueberzugpp layer -so {driver}` command: {s}");
child.stdin.as_mut().unwrap().write_all(s.as_bytes()).await?; child.stdin.as_mut().unwrap().write_all(s.as_bytes()).await?;
Ok(()) Ok(())

View file

@ -3,6 +3,7 @@ use image::{ColorType, DynamicImage, GrayAlphaImage, GrayImage, ImageDecoder, Rg
use moxcms::{CicpColorPrimaries, ColorProfile, DataColorSpace, Layout, TransferCharacteristics, TransformOptions}; use moxcms::{CicpColorPrimaries, ColorProfile, DataColorSpace, Layout, TransferCharacteristics, TransformOptions};
pub(super) struct Icc; pub(super) struct Icc;
impl Icc { impl Icc {
pub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result<DynamicImage> { pub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result<DynamicImage> {
if let Some(layout) = Self::color_type_to_layout(decoder.color_type()) if let Some(layout) = Self::color_type_to_layout(decoder.color_type())

View file

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::Result;
use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation}; use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
use ratatui::layout::Rect; use ratatui_core::layout::Rect;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_emulator::Dimension; use yazi_emulator::Dimension;
use yazi_fs::provider::{Provider, local::Local}; use yazi_fs::provider::{Provider, local::Local};

View file

@ -1,43 +0,0 @@
use std::path::PathBuf;
use image::{ImageDecoder, ImageError};
pub type ImageFormat = image::ImageFormat;
pub type ImageColor = image::ColorType;
pub type ImageOrientation = image::metadata::Orientation;
#[derive(Clone, Copy)]
pub struct ImageInfo {
pub format: ImageFormat,
pub width: u32,
pub height: u32,
pub color: ImageColor,
pub orientation: Option<ImageOrientation>,
}
impl ImageInfo {
pub async fn new(path: PathBuf) -> image::ImageResult<Self> {
tokio::task::spawn_blocking(move || {
let reader = image::ImageReader::open(path)?.with_guessed_format()?;
let Some(format) = reader.format() else {
return Err(ImageError::IoError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"unknown image format",
)));
};
let mut decoder = reader.into_decoder()?;
let (width, height) = decoder.dimensions();
Ok(Self {
format,
width,
height,
color: decoder.color_type(),
orientation: decoder.orientation().ok(),
})
})
.await
.map_err(|e| ImageError::IoError(e.into()))?
}
}

View file

@ -1,15 +1,12 @@
yazi_macro::mod_pub!(drivers); yazi_macro::mod_pub!(drivers);
yazi_macro::mod_flat!(adapter adapters icc image info); yazi_macro::mod_flat!(adapter icc image);
use yazi_emulator::{Brand, CLOSE, EMULATOR, ESCAPE, Emulator, Mux, START, TMUX}; use yazi_emulator::{Brand, CLOSE, EMULATOR, ESCAPE, Emulator, Mux, START, TMUX};
use yazi_shared::in_wsl; use yazi_shared::in_wsl;
use yazi_shim::cell::SyncCell; use yazi_shim::cell::{RoCell, SyncCell};
pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa); pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// Image state
static SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);
// WSL support // WSL support
pub static WSL: SyncCell<bool> = SyncCell::new(false); pub static WSL: SyncCell<bool> = SyncCell::new(false);
@ -34,7 +31,7 @@ pub fn init() -> anyhow::Result<()> {
EMULATOR.init(emulator); EMULATOR.init(emulator);
yazi_config::init_flavor(EMULATOR.light)?; yazi_config::init_flavor(EMULATOR.light)?;
ADAPTOR.set(Adapter::matches(&EMULATOR)); ADAPTOR.init(Adapter::new());
ADAPTOR.get().start(); ADAPTOR.start();
Ok(()) Ok(())
} }

View file

@ -17,30 +17,24 @@ default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ] vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-adapter = { path = "../yazi-adapter", version = "26.5.6" }
yazi-codegen = { path = "../yazi-codegen", version = "26.5.6" } yazi-codegen = { path = "../yazi-codegen", version = "26.5.6" }
yazi-config = { path = "../yazi-config", version = "26.5.6" }
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", version = "26.5.6" } yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
yazi-term = { path = "../yazi-term", version = "26.5.6" }
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
mlua = { workspace = true } image = { workspace = true }
paste = { workspace = true } mlua = { workspace = true }
ratatui = { workspace = true } ratatui-core = { workspace = true }
serde_json = { workspace = true } ratatui-widgets = { workspace = true }
tokio = { workspace = true } serde = { workspace = true }
tokio-stream = { workspace = true } serde_json = { workspace = true }
tracing = { workspace = true } strum = { workspace = true }
unicode-width = { workspace = true } tokio = { workspace = true }
unicode-width = { workspace = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = { workspace = true } libc = { workspace = true }

View file

@ -1,42 +0,0 @@
use mlua::{AnyUserData, IntoLuaMulti, UserData, UserDataMethods, Value};
use yazi_fs::provider::FileBuilder;
use crate::{Error, Fd, UrlRef};
#[derive(Default)]
pub struct Access(yazi_vfs::provider::Gate);
impl UserData for Access {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("append", |_, (ud, append): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.0.append(append);
Ok(ud)
});
methods.add_function("create", |_, (ud, create): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.0.create(create);
Ok(ud)
});
methods.add_function("create_new", |_, (ud, create_new): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.0.create_new(create_new);
Ok(ud)
});
methods.add_async_method("open", |lua, me, url: UrlRef| async move {
match me.0.open(&*url).await {
Ok(fd) => Fd(fd).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}
});
methods.add_function("read", |_, (ud, read): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.0.read(read);
Ok(ud)
});
methods.add_function("truncate", |_, (ud, truncate): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.0.truncate(truncate);
Ok(ud)
});
methods.add_function("write", |_, (ud, write): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.0.write(write);
Ok(ud)
});
}
}

View file

@ -1,15 +0,0 @@
use mlua::UserData;
use yazi_codegen::FromLuaOwned;
#[derive(Clone, FromLuaOwned)]
pub struct ChordCow(pub yazi_config::keymap::ChordCow);
impl From<yazi_config::keymap::ChordCow> for ChordCow {
fn from(value: yazi_config::keymap::ChordCow) -> Self { Self(value) }
}
impl From<ChordCow> for yazi_config::keymap::ChordCow {
fn from(value: ChordCow) -> Self { value.0 }
}
impl UserData for ChordCow {}

View file

@ -5,10 +5,9 @@ pub type ComposerGet = fn(&Lua, &[u8]) -> mlua::Result<Value>;
pub type ComposerSet = fn(&Lua, &[u8], Value) -> mlua::Result<Value>; pub type ComposerSet = fn(&Lua, &[u8], Value) -> mlua::Result<Value>;
pub struct Composer<G, S> { pub struct Composer<G, S> {
get: G, get: G,
set: S, set: S,
parent: Option<(G, S)>, cache: HashMap<Vec<u8>, Value>,
cache: HashMap<Vec<u8>, Value>,
} }
impl<G, S> Composer<G, S> impl<G, S> Composer<G, S>
@ -16,13 +15,7 @@ where
G: Fn(&Lua, &[u8]) -> mlua::Result<Value> + 'static, G: Fn(&Lua, &[u8]) -> mlua::Result<Value> + 'static,
S: Fn(&Lua, &[u8], Value) -> mlua::Result<Value> + 'static, S: Fn(&Lua, &[u8], Value) -> mlua::Result<Value> + 'static,
{ {
#[inline] pub fn new(get: G, set: S) -> Self { Self { get, set, cache: Default::default() } }
pub fn new(get: G, set: S) -> Self { Self { get, set, parent: None, cache: Default::default() } }
#[inline]
pub fn with_parent(get: G, set: S, p_get: G, p_set: S) -> Self {
Self { get, set, parent: Some((p_get, p_set)), cache: Default::default() }
}
} }
impl<G, S> UserData for Composer<G, S> impl<G, S> UserData for Composer<G, S>
@ -37,14 +30,9 @@ where
return Ok(v.clone()); return Ok(v.clone());
} }
let mut value = (me.get)(lua, &key)?; let value = (me.get)(lua, &key)?;
if value.is_nil()
&& let Some((p_get, _)) = &me.parent
{
value = p_get(lua, &key)?;
}
me.cache.insert(key.to_owned(), value.clone()); me.cache.insert(key.to_owned(), value.clone());
Ok(value) Ok(value)
}); });
@ -56,11 +44,6 @@ where
if value.is_nil() { if value.is_nil() {
me.cache.remove(key.as_ref()); me.cache.remove(key.as_ref());
} else if let Some((_, p_set)) = &me.parent {
match p_set(lua, key.as_ref(), value)? {
Value::Nil => me.cache.remove(key.as_ref()),
v => me.cache.insert(key.to_owned(), v),
};
} else { } else {
me.cache.insert(key.to_owned(), value); me.cache.insert(key.to_owned(), value);
} }

View file

@ -1,75 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use crate::{FileRef, Id, Iter, cached_field};
#[derive(Clone)]
pub struct Fetcher {
inner: Arc<yazi_config::plugin::Fetcher>,
v_name: Option<Value>,
}
impl Deref for Fetcher {
type Target = yazi_config::plugin::Fetcher;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Fetcher {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Fetcher>>) -> Self {
Self { inner: inner.into(), v_name: None }
}
}
impl UserData for Fetcher {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
}
}
// --- Matcher
pub struct FetcherMatcher(yazi_config::plugin::FetcherMatcher<'static>);
impl FetcherMatcher {
pub fn new(inner: impl Into<yazi_config::plugin::FetcherMatcher<'static>>) -> Self {
Self(inner.into())
}
}
impl TryFrom<Table> for FetcherMatcher {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
let id: Id = value.raw_get("id").unwrap_or_default();
let file: Option<FileRef> = value.raw_get("file")?;
let mime: Option<String> = value.raw_get("mime")?;
Ok(Self(yazi_config::plugin::FetcherMatcher {
fetchers: YAZI.plugin.fetchers.load_full(),
id: id.0,
file: file.map(|f| f.inner.clone().into()),
mime: mime.map(Into::into),
..Default::default()
}))
}
}
impl FromLua for FetcherMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {
Value::Table(t) => t.try_into(),
_ => Err("expected a table of FetcherMatcher".into_lua_err()),
}
}
}
impl IntoLua for FetcherMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.map(Fetcher::new), None).into_lua(lua)
}
}

View file

@ -1,17 +0,0 @@
use mlua::{IntoLua, MetaMethod, UserData, UserDataMethods};
use yazi_config::YAZI;
use crate::config::FetcherMatcher;
pub struct Fetchers;
impl UserData for Fetchers {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("match", |lua, _, matcher: Option<FetcherMatcher>| match matcher {
Some(matcher) => matcher.into_lua(lua),
None => FetcherMatcher::new(&YAZI.plugin.fetchers).into_lua(lua),
});
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.fetchers.load().len()));
}
}

View file

@ -1 +0,0 @@
yazi_macro::mod_flat!(fetcher fetchers open_rule open_rules opener opener_rule opener_rules preloader preloaders previewer previewers spotter spotters);

View file

@ -1,85 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use crate::{FileRef, Id, Iter, cached_field};
pub struct OpenRule {
inner: Arc<yazi_config::open::OpenRule>,
v_use: Option<Value>,
}
impl Deref for OpenRule {
type Target = Arc<yazi_config::open::OpenRule>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<OpenRule> for Arc<yazi_config::open::OpenRule> {
fn from(value: OpenRule) -> Self { value.inner }
}
impl OpenRule {
pub fn new(inner: impl Into<Arc<yazi_config::open::OpenRule>>) -> Self {
Self { inner: inner.into(), v_use: None }
}
}
impl FromLua for OpenRule {
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
Ok(Self::new(lua.from_value::<yazi_config::open::OpenRule>(value)?))
}
}
impl UserData for OpenRule {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, use, |lua, me| {
lua.create_sequence_from(me.r#use.iter().map(|s| s.as_str()))
});
}
}
// --- Matcher
pub struct OpenRuleMatcher(pub(super) yazi_config::open::OpenRuleMatcher<'static>);
impl OpenRuleMatcher {
pub fn new(inner: impl Into<yazi_config::open::OpenRuleMatcher<'static>>) -> Self {
Self(inner.into())
}
}
impl TryFrom<Table> for OpenRuleMatcher {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
let id: Id = value.raw_get("id").unwrap_or_default();
let file: Option<FileRef> = value.raw_get("file")?;
let mime: Option<String> = value.raw_get("mime")?;
Ok(Self(yazi_config::open::OpenRuleMatcher {
rules: YAZI.open.load_full(),
id: id.0,
file: file.map(|f| f.inner.clone().into()),
mime: mime.map(Into::into),
..Default::default()
}))
}
}
impl FromLua for OpenRuleMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {
Value::Table(t) => t.try_into(),
_ => Err("expected a table of OpenRuleMatcher".into_lua_err()),
}
}
}
impl IntoLua for OpenRuleMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.map(OpenRule::new), None).into_lua(lua)
}
}

View file

@ -1,39 +0,0 @@
use mlua::{ExternalError, ExternalResult, IntoLua, MetaMethod, Table, UserData, UserDataMethods};
use yazi_config::YAZI;
use yazi_shim::mlua::DeserializeOverLua;
use crate::config::{OpenRule, OpenRuleMatcher};
pub struct OpenRules;
impl UserData for OpenRules {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("match", |lua, _, matcher: Option<OpenRuleMatcher>| match matcher {
Some(matcher) => matcher.into_lua(lua),
None => OpenRuleMatcher::new(&*YAZI.open).into_lua(lua),
});
methods.add_method("insert", |_, _, (index, rule): (isize, OpenRule)| {
let index = match index {
1.. => index - 1,
0 => return Err("index must be 1-based or negative".into_lua_err()),
_ => index,
};
YAZI.open.insert(index, rule.clone()).into_lua_err()?;
Ok(rule)
});
methods.add_method("remove", |_, _, matcher: OpenRuleMatcher| {
YAZI.open.remove(matcher.0);
Ok(())
});
methods.add_method("update", |_, _, (matcher, table): (OpenRuleMatcher, Table)| {
YAZI.open.update(matcher.0, |rule| rule.deserialize_over_lua(&table))?;
Ok(())
});
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.open.load().len()));
}
}

View file

@ -1,45 +0,0 @@
use mlua::{ExternalError, FromLua, IntoLua, IntoLuaMulti, MetaMethod, UserData, UserDataMethods, Value};
use yazi_config::{YAZI, opener::OpenerRulesMatcher};
use crate::config::OpenerRules;
pub struct Opener;
impl UserData for Opener {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Index, |lua, _, key: mlua::String| {
let key = key.to_str()?;
match YAZI.opener.load().get(&*key) {
Some(rules) => OpenerRules::new(rules.clone()).into_lua(lua),
None => Ok(Value::Nil),
}
});
methods.add_meta_method(MetaMethod::NewIndex, |lua, _, (key, value): (mlua::String, Value)| {
let key = key.to_str()?;
match value {
t @ Value::Table(_) => {
YAZI.opener.insert(&key, &*OpenerRules::from_lua(t, lua)?);
}
Value::Nil => {
YAZI.opener.remove(&key);
}
_ => return Err("expected a table or nil".into_lua_err()),
}
Ok(())
});
methods.add_meta_method(MetaMethod::Pairs, |lua, _, ()| {
let mut matcher = OpenerRulesMatcher::from(&YAZI.opener);
let iter = lua.create_function_mut(move |lua, ()| {
if let Some((name, rules)) = matcher.next() {
(name, OpenerRules::new(rules)).into_lua_multi(lua)
} else {
().into_lua_multi(lua)
}
})?;
Ok(iter)
});
}
}

View file

@ -1,81 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use crate::{Id, Iter, cached_field};
pub struct OpenerRule {
inner: Arc<yazi_config::opener::OpenerRule>,
v_run: Option<Value>,
v_desc: Option<Value>,
}
impl Deref for OpenerRule {
type Target = Arc<yazi_config::opener::OpenerRule>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<OpenerRule> for Arc<yazi_config::opener::OpenerRule> {
fn from(value: OpenerRule) -> Self { value.inner }
}
impl OpenerRule {
pub fn new(inner: impl Into<Arc<yazi_config::opener::OpenerRule>>) -> Self {
Self { inner: inner.into(), v_run: None, v_desc: None }
}
}
impl FromLua for OpenerRule {
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
let mut inner: yazi_config::opener::OpenerRule = lua.from_value(value)?;
inner.fill();
Ok(Self::new(inner))
}
}
impl UserData for OpenerRule {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, run, |lua, me| lua.create_string(&*me.run));
fields.add_field_method_get("block", |_, me| Ok(me.block));
fields.add_field_method_get("orphan", |_, me| Ok(me.orphan));
cached_field!(fields, desc, |lua, me| lua.create_string(&*me.desc));
}
}
// --- Matcher
pub struct OpenerRuleMatcher(pub(super) yazi_config::opener::OpenerRuleMatcher);
impl OpenerRuleMatcher {
pub fn new(inner: impl Into<yazi_config::opener::OpenerRuleMatcher>) -> Self {
Self(inner.into())
}
}
impl TryFrom<Table> for OpenerRuleMatcher {
type Error = mlua::Error;
fn try_from(t: Table) -> Result<Self, Self::Error> {
let id: Id = t.raw_get("id").unwrap_or_default();
Ok(Self(yazi_config::opener::OpenerRuleMatcher { id: id.0, ..Default::default() }))
}
}
impl FromLua for OpenerRuleMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {
Value::Table(t) => t.try_into(),
_ => Err("expected a table of OpenerRuleMatcher".into_lua_err()),
}
}
}
impl IntoLua for OpenerRuleMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.map(OpenerRule::new), None).into_lua(lua)
}
}

View file

@ -1,57 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, LuaSerdeExt, MetaMethod, UserData, UserDataMethods, Value};
use yazi_shim::toml::DeserializeOverHook;
use crate::config::{OpenerRule, OpenerRuleMatcher};
pub struct OpenerRules {
inner: Arc<yazi_config::opener::OpenerRules>,
}
impl Deref for OpenerRules {
type Target = Arc<yazi_config::opener::OpenerRules>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl OpenerRules {
pub fn new(inner: impl Into<Arc<yazi_config::opener::OpenerRules>>) -> Self {
Self { inner: inner.into() }
}
}
impl FromLua for OpenerRules {
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
let inner: yazi_config::opener::OpenerRules = lua.from_value(value)?;
Ok(Self::new(inner.deserialize_over_hook().into_lua_err()?))
}
}
impl UserData for OpenerRules {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("match", |lua, me, matcher: Option<OpenerRuleMatcher>| match matcher {
Some(matcher) => matcher.into_lua(lua),
None => OpenerRuleMatcher::new(&*me.inner).into_lua(lua),
});
methods.add_method("insert", |_, me, (index, rule): (isize, OpenerRule)| {
let index = match index {
1.. => index - 1,
0 => return Err("index must be 1-based or negative".into_lua_err()),
_ => index,
};
me.inner.insert(index, rule.clone()).into_lua_err()?;
Ok(rule)
});
methods.add_method("remove", |_, me, matcher: OpenerRuleMatcher| {
me.inner.remove(matcher.0);
Ok(())
});
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.inner.load().len()));
}
}

View file

@ -1,75 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use crate::{FileRef, Id, Iter, cached_field};
#[derive(Clone)]
pub struct Preloader {
inner: Arc<yazi_config::plugin::Preloader>,
v_name: Option<Value>,
}
impl Deref for Preloader {
type Target = yazi_config::plugin::Preloader;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Preloader {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Preloader>>) -> Self {
Self { inner: inner.into(), v_name: None }
}
}
impl UserData for Preloader {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
}
}
// --- Matcher
pub struct PreloaderMatcher(pub(super) yazi_config::plugin::PreloaderMatcher<'static>);
impl PreloaderMatcher {
pub fn new(inner: impl Into<yazi_config::plugin::PreloaderMatcher<'static>>) -> Self {
Self(inner.into())
}
}
impl TryFrom<Table> for PreloaderMatcher {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
let id: Id = value.raw_get("id").unwrap_or_default();
let file: Option<FileRef> = value.raw_get("file")?;
let mime: Option<String> = value.raw_get("mime")?;
Ok(Self(yazi_config::plugin::PreloaderMatcher {
preloaders: YAZI.plugin.preloaders.load_full(),
id: id.0,
file: file.map(|f| f.inner.clone().into()),
mime: mime.map(Into::into),
..Default::default()
}))
}
}
impl FromLua for PreloaderMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {
Value::Table(t) => t.try_into(),
_ => Err("expected a table of PreloaderMatcher".into_lua_err()),
}
}
}
impl IntoLua for PreloaderMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.map(Preloader::new), None).into_lua(lua)
}
}

View file

@ -1,17 +0,0 @@
use mlua::{IntoLua, MetaMethod, UserData, UserDataMethods};
use yazi_config::YAZI;
use crate::config::PreloaderMatcher;
pub struct Preloaders;
impl UserData for Preloaders {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("match", |lua, _, matcher: Option<PreloaderMatcher>| match matcher {
Some(matcher) => matcher.into_lua(lua),
None => PreloaderMatcher::new(&YAZI.plugin.preloaders).into_lua(lua),
});
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.preloaders.load().len()));
}
}

View file

@ -1,85 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use crate::{FileRef, Id, Iter, cached_field};
#[derive(Clone)]
pub struct Previewer {
inner: Arc<yazi_config::plugin::Previewer>,
v_name: Option<Value>,
}
impl Deref for Previewer {
type Target = yazi_config::plugin::Previewer;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<Previewer> for Arc<yazi_config::plugin::Previewer> {
fn from(value: Previewer) -> Self { value.inner }
}
impl Previewer {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Previewer>>) -> Self {
Self { inner: inner.into(), v_name: None }
}
}
impl FromLua for Previewer {
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
Ok(Self::new(lua.from_value::<yazi_config::plugin::Previewer>(value)?))
}
}
impl UserData for Previewer {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
}
}
// --- Matcher
pub struct PreviewerMatcher(pub(super) yazi_config::plugin::PreviewerMatcher<'static>);
impl PreviewerMatcher {
pub fn new(inner: impl Into<yazi_config::plugin::PreviewerMatcher<'static>>) -> Self {
Self(inner.into())
}
}
impl TryFrom<Table> for PreviewerMatcher {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
let id: Id = value.raw_get("id").unwrap_or_default();
let file: Option<FileRef> = value.raw_get("file")?;
let mime: Option<String> = value.raw_get("mime")?;
Ok(Self(yazi_config::plugin::PreviewerMatcher {
previewers: YAZI.plugin.previewers.load_full(),
id: id.0,
file: file.map(|f| f.inner.clone().into()),
mime: mime.map(Into::into),
..Default::default()
}))
}
}
impl FromLua for PreviewerMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {
Value::Table(t) => t.try_into(),
_ => Err("expected a table of PreviewerMatcher".into_lua_err()),
}
}
}
impl IntoLua for PreviewerMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.map(Previewer::new), None).into_lua(lua)
}
}

View file

@ -1,33 +0,0 @@
use mlua::{ExternalError, ExternalResult, IntoLua, MetaMethod, UserData, UserDataMethods};
use yazi_config::YAZI;
use crate::config::{Previewer, PreviewerMatcher};
pub struct Previewers;
impl UserData for Previewers {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("match", |lua, _, matcher: Option<PreviewerMatcher>| match matcher {
Some(matcher) => matcher.into_lua(lua),
None => PreviewerMatcher::new(&YAZI.plugin.previewers).into_lua(lua),
});
methods.add_method("insert", |_, _, (index, previewer): (isize, Previewer)| {
let index = match index {
1.. => index - 1,
0 => return Err("index must be 1-based or negative".into_lua_err()),
_ => index,
};
YAZI.plugin.previewers.insert(index, previewer.clone().into()).into_lua_err()?;
Ok(previewer)
});
methods.add_method("remove", |_, _, matcher: PreviewerMatcher| {
YAZI.plugin.previewers.remove(matcher.0);
Ok(())
});
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.previewers.load().len()));
}
}

View file

@ -1,75 +0,0 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use crate::{FileRef, Id, Iter, cached_field};
#[derive(Clone)]
pub struct Spotter {
inner: Arc<yazi_config::plugin::Spotter>,
v_name: Option<Value>,
}
impl Deref for Spotter {
type Target = yazi_config::plugin::Spotter;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Spotter {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Spotter>>) -> Self {
Self { inner: inner.into(), v_name: None }
}
}
impl UserData for Spotter {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
}
}
// --- Matcher
pub struct SpotterMatcher(pub(super) yazi_config::plugin::SpotterMatcher<'static>);
impl SpotterMatcher {
pub fn new(inner: impl Into<yazi_config::plugin::SpotterMatcher<'static>>) -> Self {
Self(inner.into())
}
}
impl TryFrom<Table> for SpotterMatcher {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
let id: Id = value.raw_get("id").unwrap_or_default();
let file: Option<FileRef> = value.raw_get("file")?;
let mime: Option<String> = value.raw_get("mime")?;
Ok(Self(yazi_config::plugin::SpotterMatcher {
spotters: YAZI.plugin.spotters.load_full(),
id: id.0,
file: file.map(|f| f.inner.clone().into()),
mime: mime.map(Into::into),
..Default::default()
}))
}
}
impl FromLua for SpotterMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {
Value::Table(t) => t.try_into(),
_ => Err("expected a table of SpotterMatcher".into_lua_err()),
}
}
}
impl IntoLua for SpotterMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.map(Spotter::new), None).into_lua(lua)
}
}

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