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:
required: true
- type: textarea
id: debug
id: env
attributes:
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
label: "`ya env` output"
description: Run `ya env` and paste the environment information here.
render: Shell
validations:
required: true

View file

@ -26,10 +26,10 @@ body:
validations:
required: true
- type: textarea
id: debug
id: env
attributes:
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
label: "`ya env` output"
description: Run `ya env` and paste the environment information here.
render: Shell
validations:
required: true
@ -65,6 +65,6 @@ body:
options:
- label: I tried the [latest nightly build](https://yazi-rs.github.io/docs/installation#binaries), and the issue is still reproducible
required: true
- label: I updated the 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
- 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: []
body:
- type: textarea
id: debug
id: env
attributes:
label: "`yazi --debug` output"
description: Please run `yazi --debug` and paste the debug information here.
label: "`ya env` output"
description: Run `ya env` and paste the environment information here.
render: Shell
validations:
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.
-->
## 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
- Drag and drop ([#4005])
- Bulk create ([#3793])
- Dynamic keymap Lua API ([#4031])
- New `ui.Input` element ([#4040])
- Image preview with Überzug++ on Niri ([#3990])
- New gait for input `backward` and `forward` actions ([#4012])
### Changed
- Rename `<BackTab>` to `<S-Tab>` ([#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]
### 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
[#3989]: https://github.com/sxyazi/yazi/pull/3989
[#3990]: https://github.com/sxyazi/yazi/pull/3990
[#4005]: https://github.com/sxyazi/yazi/pull/4005
[#4012]: https://github.com/sxyazi/yazi/pull/4012
[#4022]: https://github.com/sxyazi/yazi/pull/4022
[#4031]: https://github.com/sxyazi/yazi/pull/4031
[#4040]: https://github.com/sxyazi/yazi/pull/4040
[#4064]: https://github.com/sxyazi/yazi/pull/4064
[#4065]: https://github.com/sxyazi/yazi/pull/4065
[#4067]: https://github.com/sxyazi/yazi/pull/4067
[#4068]: https://github.com/sxyazi/yazi/pull/4068

View file

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

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"
arc-swap = { version = "1.9.1", features = [ "serde" ] }
base64 = "0.22.1"
bitflags = { version = "2.11.1", features = [ "serde" ] }
chrono = "0.4.44"
bitflags = { version = "2.13.0", features = [ "serde" ] }
chrono = "0.4.45"
clap = { version = "4.6.1", features = [ "derive" ] }
core-foundation-sys = "0.8.7"
dirs = "6.0.0"
@ -50,7 +50,9 @@ foldhash = "0.2.0"
futures = "0.3.32"
globset = "0.4.18"
hashbrown = { version = "0.17.1", features = [ "serde" ] }
image = { version = "0.25.10", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp" ] }
indexmap = { version = "2.14.0", features = [ "serde" ] }
inventory = "0.3.24"
libc = "0.2.186"
lru = "0.18.0"
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"
percent-encoding = "2.3.2"
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" ] }
regex = "1.12.3"
russh = { version = "0.61.0", default-features = false, features = [ "ring", "rsa" ] }
ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] }
ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] }
regex = "1.12.4"
russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.150"
serde_with = "3.20.0"
serde_with = "3.21.0"
strum = { version = "0.28.0", features = [ "derive" ] }
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.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
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
- 🔄 Bulk Rename/Create, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
- 🎨 Theme System, Mouse Support, 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!
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": {
"locked": {
"lastModified": 1776329215,
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
"lastModified": 1779877693,
"narHash": "sha256-NOF9NAREhxr50bbBfVcVOq+ArCMSoe8dP79Pk2uyARk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b86751bc4085f48661017fa226dee99fab6c651b",
"rev": "4100e830e085863741bc69b156ec4ccd53ab5be0",
"type": "github"
},
"original": {
@ -48,11 +48,11 @@
]
},
"locked": {
"lastModified": 1776741231,
"narHash": "sha256-k9G98qzn+7npROUaks8VqCFm7cFtEG8ulQLBBo5lItg=",
"lastModified": 1779851998,
"narHash": "sha256-UkkMh3bX9QW4Luqkm98nUaOqKWrU6i65mUnph3WeSSw=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "02061303f7c4c964f7b4584dabd9e985b4cd442b",
"rev": "6cddd512fa2bf7231f098d3a2f92f6e4cff71e0a",
"type": "github"
},
"original": {

View file

@ -28,9 +28,6 @@ rustPlatform.buildRustPackage (finalAttrs: {
cargoLock = {
lockFile = "${src}/Cargo.lock";
#outputHashes = {
# "mlua-0.10.0" = "sha256-Xg6/jc+UP8tbJJ6x1sbAgt8ZHt051xEBBcjmikQqYlw=";
#};
};
env = {
@ -54,6 +51,11 @@ rustPlatform.buildRustPackage (finalAttrs: {
--fish ./yazi-boot/completions/yazi.fish \
--zsh ./yazi-boot/completions/_yazi
installShellCompletion --cmd ya \
--bash ./yazi-cli/completions/ya.bash \
--fish ./yazi-cli/completions/ya.fish \
--zsh ./yazi-cli/completions/_ya
# Resize logo
for RES in 16 24 32 48 64 128 256; do
mkdir -p $out/share/icons/hicolor/"$RES"x"$RES"/apps
@ -75,7 +77,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
hash = "sha256-kEVXejDg4ChFoMNBvKlwdFEyUuTcY2VuK9j0PdafKus=";
};
};
meta = {
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:
- 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.
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:
- 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.
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": {
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
"license": "MIT",
"engines": {
"node": ">=18.17"

View file

@ -133,9 +133,16 @@ parts:
- --prefix=/usr
build-attributes:
- enable-patchelf
organize:
# `libgomp1` installs libgomp into the multiarch directory, but the
# `magick` binary's RPATH only searches `$ORIGIN/../lib` (i.e. usr/lib).
# Move it alongside the Magick libraries so the bundled binary can load
# it at runtime; otherwise magick fails with:
# "libgomp.so.1: cannot open shared object file: No such file or directory"
usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so*: usr/lib/
prime:
- usr/bin/magick
- usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so.1
- usr/lib/libgomp.so*
- usr/lib/libMagickCore-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" }
# External dependencies
anyhow = { workspace = true }
either = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
indexmap = { workspace = true }
mlua = { workspace = true }
paste = { workspace = true }
ratatui = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }
either = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
indexmap = { workspace = true }
mlua = { workspace = true }
paste = { workspace = true }
ratatui-core = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
[target."cfg(unix)".dependencies]
libc = { workspace = true }

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ impl Actor for Trigger {
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
succ!();
} else if form.ticket.is_none() {
cx.cmp.ticket = cx.input.ticket.current();
cx.cmp.ticket = cx.input.lock().map(|g| g.ticket.current()).unwrap_or_default();
}
cx.cmp.handle.take().map(|h| h.abort());
@ -53,7 +53,7 @@ impl Actor for Trigger {
if !cache.is_empty() {
cache
.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), 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 });
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use yazi_macro::{act, render, succ};
use yazi_parser::input::CloseForm;
use yazi_shared::data::Data;
use yazi_parser::{input::CloseForm, spark::SparkKind};
use yazi_shared::{Source, data::Data};
use yazi_widgets::input::InputEvent;
use crate::{Actor, Ctx};
@ -14,16 +14,28 @@ impl Actor for Close {
const NAME: &str = "close";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input;
input.visible = false;
input.ticket.next();
let Some(mut guard) = cx.input.lock_mut() else {
succ!();
};
if let Some(tx) = input.tx.take() {
let value = input.snap().value.clone();
_ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
guard.ticket.next();
if let Some(cb) = guard.cb.take() {
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)?;
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";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input;
if !input.visible || input.ticket.current() != form.ticket {
let Some(mut guard) = cx.input.lock_mut() else {
succ!();
};
if guard.ticket.current() != form.ticket {
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> {
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 {
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::Normal | M::Replace => Ok(().into()),
}?;
act!(escape, cx.input)?;
succ!(render!());
}
}

View file

@ -1,27 +1,34 @@
use std::ops::DerefMut;
use anyhow::Result;
use yazi_config::{THEME, YAZI};
use yazi_macro::{act, render, succ};
use yazi_parser::input::ShowForm;
use yazi_shared::data::Data;
use yazi_widgets::input::InputOpt;
use yazi_shim::ratatui::Padable;
use crate::{Actor, Ctx};
pub struct Show;
impl Actor for Show {
type Form = InputOpt;
type Form = ShowForm;
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)?;
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
let input = &mut cx.input;
input.visible = true;
input.title = form.cfg.title.clone();
input.position = form.cfg.position;
*input.deref_mut() = yazi_widgets::input::Input::new(form)?;
input.main.visible = true;
input.main.title = opt.title.clone();
input.main.position = opt.position;
opt.styles = (&THEME.input).into();
opt.blinking = YAZI.input.cursor_blink;
*input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?;
input.main.repos(area);
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_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 mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataMethods, Value};
use mlua::{AnyUserData, IntoLua, MetaMethod, UserData, UserDataMethods, UserDataRef, Value};
use paste::paste;
use super::{Lives, PtrCell};
pub(super) type CoreRef = UserDataRef<Core>;
pub(super) struct Core {
inner: PtrCell<yazi_core::Core>,
@ -12,8 +14,9 @@ pub(super) struct Core {
c_tabs: Option<Value>,
c_tasks: Option<Value>,
c_yanked: Option<Value>,
c_layer: Option<Value>,
c_input: Option<Value>,
c_which: Option<Value>,
c_layer: Option<Value>,
}
impl Deref for Core {
@ -31,8 +34,9 @@ impl Core {
c_tabs: None,
c_tasks: None,
c_yanked: None,
c_layer: None,
c_input: 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"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),
b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)),
b"layer" => {
reuse!(layer, Ok::<_, mlua::Error>(yazi_binding::Layer::from(me.layer())))
}
b"input" => reuse!(input, super::Input::make(&me.input)),
b"which" => reuse!(which, super::Which::make(&me.which)),
b"layer" => reuse!(layer, Ok::<_, mlua::Error>(me.layer())),
_ => Value::Nil,
})
});

View file

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

View file

@ -1,7 +1,7 @@
use std::ops::{Deref, Range};
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::cached_field;
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{File, Filter, Lives, PtrCell};
@ -9,8 +9,6 @@ pub(super) struct Files {
window: Range<usize>,
folder: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_filter: Option<Value>,
}
impl Deref for Files {
@ -25,13 +23,13 @@ impl Files {
folder: &yazi_core::tab::Folder,
tab: &yazi_core::tab::Tab,
) -> 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 {
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) {

View file

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

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 mlua::{AnyUserData, UserData};
use scopeguard::defer;
use tracing::error;
use yazi_plugin::LUA;
use yazi_shim::cell::RoCell;
use super::{Core, PtrCell};
use crate::lives::MutCell;
static TO_DESTROY: RoCell<RefCell<Vec<AnyUserData>>> = RoCell::new_const(RefCell::new(Vec::new()));
pub(super) static FILE_CACHE: RoCell<RefCell<HashMap<PtrCell<yazi_fs::File>, AnyUserData>>> =
RoCell::new();
pub(super) static TO_DESTROY: MutCell<Vec<AnyUserData>> = MutCell::new(Vec::new());
pub(super) static FILE_CACHE: MutCell<
MaybeUninit<HashMap<PtrCell<yazi_fs::file::File>, AnyUserData>>,
> = MutCell::new(MaybeUninit::uninit());
pub struct 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
F: FnOnce() -> mlua::Result<T>,
F: FnOnce(&mut yazi_core::Core) -> mlua::Result<T>,
{
FILE_CACHE.init(Default::default());
defer! { FILE_CACHE.drop(); }
let result = LUA.scope(|scope| {
scope.add_destructor(|| {
for ud in TO_DESTROY.borrow_mut().drain(..) {
defer! {
unsafe {
(*FILE_CACHE.get()).assume_init_mut().clear();
for ud in (*TO_DESTROY.get()).drain(..) {
ud.destroy().expect("failed to destruct scoped userdata");
}
});
}
}
LUA.set_named_registry_value("cx", scope.create_any_userdata_ref(core)?)?;
LUA.globals().raw_set("cx", Core::make(core)?)?;
f()
});
LUA.set_named_registry_value("cx", Core::make(core)?)?;
LUA.globals().raw_set("cx", Core::make(core)?)?;
let result = f(core);
if let Err(ref e) = result {
error!("{e}");
@ -46,7 +45,7 @@ impl Lives {
T: UserData + 'static,
{
let ud = LUA.create_userdata(data)?;
TO_DESTROY.borrow_mut().push(ud.clone());
unsafe { &mut *TO_DESTROY.get() }.push(ud.clone());
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 mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use yazi_shim::strum::IntoStr;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
use super::{Lives, PtrCell};
pub(super) struct Preference {
inner: PtrCell<yazi_core::tab::Preference>,
v_name: Option<Value>,
v_linemode: Option<Value>,
v_sort_by: Option<Value>,
}
impl Deref for Preference {
@ -23,26 +17,19 @@ impl Deref for Preference {
impl Preference {
pub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self {
inner: inner.into(),
v_name: None,
v_linemode: None,
v_sort_by: None,
})
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Preference {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// Display
cached_field!(fields, name, |lua, me| lua.create_string(&me.name));
cached_field!(fields, linemode, |lua, me| lua.create_string(&*me.linemode));
fields.add_cached_field("name", |lua, me| lua.create_string(&me.name));
fields.add_cached_field("linemode", |lua, me| lua.create_string(&*me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
// Sorting
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_reverse", |_, me| Ok(me.sort_reverse));
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 mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Folder, Lives, PtrCell};
pub(super) struct Preview {
tab: PtrCell<yazi_core::tab::Tab>,
v_folder: Option<Value>,
}
impl Deref for Preview {
@ -22,17 +20,16 @@ impl Deref for Preview {
impl Preview {
pub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { tab: tab.into(), v_folder: None })
Lives::scoped_userdata(Self { tab: tab.into() })
}
}
impl UserData for Preview {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
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));
cached_field!(fields, folder, |_, me| {
fields.add_static_field("folder", |_, me| {
me.tab
.hovered_folder()
.map(|f| {

View file

@ -1,4 +1,5 @@
use mlua::AnyUserData;
use yazi_shared::url::UrlBuf;
use super::Lives;
use crate::lives::PtrCell;
@ -11,7 +12,7 @@ impl Selected {
let inner = PtrCell::from(inner);
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()),
))
}

View file

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

View file

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

View file

@ -1,16 +1,13 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{SER_OPT, cached_field};
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
use yazi_shim::mlua::{SER_OPT, UserDataFieldsExt};
use super::{Lives, PtrCell};
use crate::lives::TaskSnap;
use crate::lives::{Behavior, TaskSnap};
pub(super) struct Tasks {
inner: PtrCell<yazi_core::tasks::Tasks>,
v_snaps: Option<Value>,
v_summary: Option<Value>,
}
impl Deref for Tasks {
@ -21,12 +18,7 @@ impl Deref for Tasks {
impl Tasks {
pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self {
inner: inner.into(),
v_snaps: None,
v_summary: None,
})
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
@ -34,7 +26,9 @@ impl UserData for Tasks {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
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)?;
for snap in &me.snaps {
tbl.raw_push(TaskSnap::make(snap)?)?;
@ -42,6 +36,6 @@ impl UserData for Tasks {
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 mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
pub(super) struct Which {
inner: PtrCell<yazi_core::which::Which>,
v_tx: Option<Value>,
v_cands: Option<Value>,
}
impl Deref for Which {
@ -20,16 +17,16 @@ impl Deref for Which {
impl Which {
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into(), v_tx: None, v_cands: None })
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Which {
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));
cached_field!(fields, cands, |lua, me| {
lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::ChordCow))
fields.add_cached_field("cands", |lua, me| {
lua.create_sequence_from(me.inner.cands.iter().cloned())
});
fields.add_field_method_get("active", |_, me| Ok(me.inner.active));

View file

@ -1,7 +1,9 @@
use std::ops::Deref;
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};
@ -23,7 +25,7 @@ impl Yanked {
Lives::scoped_userdata(Self {
inner,
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()),
))?,
})

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 scopeguard::defer;
use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule};
use yazi_fs::{File, FilesOp, Splatter, provider::{Provider, local::Local}};
use yazi_config::{YAZI, opener::OpenerRuleArc};
use yazi_fs::{FilesOp, Splatter, file::File, provider::{Provider, local::Local}};
use yazi_macro::{succ, writef};
use yazi_parser::VoidForm;
use yazi_proxy::TasksProxy;
use yazi_scheduler::{AppProxy, NotifyProxy};
use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_shim::path::CROSS_SEPARATOR;
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
use yazi_tty::TTY;
use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::{VfsFile, provider};
use yazi_watcher::WATCHER;
@ -106,7 +106,7 @@ impl BulkCreate {
Ok(())
}
fn opener() -> Option<Arc<OpenerRule>> {
fn opener() -> Option<OpenerRuleArc> {
YAZI
.open
.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 hashbrown::HashMap;
use scopeguard::defer;
use tokio::io::AsyncWriteExt;
use yazi_binding::Permit;
use yazi_config::{YAZI, opener::OpenerRule};
use yazi_config::{YAZI, opener::OpenerRuleArc};
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_parser::VoidForm;
use yazi_proxy::TasksProxy;
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_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
use yazi_tty::TTY;
use yazi_term::YIELD_TO_SUBPROCESS;
use yazi_tty::{TTY, sequence::EraseScreen};
use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER;
@ -152,7 +152,7 @@ impl BulkRename {
Ok(())
}
fn opener() -> Option<Arc<OpenerRule>> {
fn opener() -> Option<OpenerRuleArc> {
YAZI
.open
.match_dummy(Path::new("bulk-rename.txt"), "text/plain")

View file

@ -3,10 +3,10 @@ use std::{mem, time::Duration};
use anyhow::Result;
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_config::YAZI;
use yazi_core::mgr::CdSource;
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_parser::mgr::CdForm;
use yazi_proxy::{CmpProxy, MgrProxy};
@ -63,7 +63,7 @@ impl Actor for Cd {
impl Cd {
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 {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -1,6 +1,6 @@
use anyhow::{Result, bail};
use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_fs::{File, FilesOp};
use yazi_config::{YAZI, popup::ConfirmCfg};
use yazi_fs::{FilesOp, file::File};
use yazi_macro::{input, ok_or_not_found, succ};
use yazi_parser::mgr::CreateForm;
use yazi_proxy::{ConfirmProxy, MgrProxy};
@ -20,7 +20,7 @@ impl Actor for Create {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
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 {
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 hashbrown::HashSet;
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_parser::mgr::DownloadForm;
use yazi_proxy::MgrProxy;

View file

@ -3,7 +3,7 @@ use std::time::Duration;
use anyhow::Result;
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_config::YAZI;
use yazi_core::mgr::FilterOpt;
use yazi_macro::{input, succ};
use yazi_parser::mgr::FilterForm;
@ -21,7 +21,7 @@ impl Actor for Filter {
const NAME: &str = "filter";
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 {
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 tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_config::YAZI;
use yazi_core::mgr::FindDoOpt;
use yazi_macro::{input, succ};
use yazi_parser::mgr::FindForm;
@ -21,7 +21,7 @@ impl Actor for Find {
const NAME: &str = "find";
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 {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

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

View file

@ -1,7 +1,8 @@
use anyhow::Result;
use hashbrown::HashMap;
use indexmap::IndexSet;
use yazi_config::{YAZI, popup::PickCfg};
use yazi_config::YAZI;
use yazi_fs::file::File;
use yazi_macro::succ;
use yazi_parser::mgr::OpenDoForm;
use yazi_proxy::{PickProxy, TasksProxy};
@ -40,7 +41,7 @@ impl Actor for OpenDo {
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()]
.into_iter()
.chain(targets.into_iter().map(|(file, _)| file.url.into()))
@ -63,7 +64,7 @@ impl Actor for OpenDo {
impl OpenDo {
// 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();
for (file, mime) in targets {
if let Some(open) = YAZI.open.matches(&file, mime)

View file

@ -1,11 +1,11 @@
use anyhow::Result;
use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_config::{YAZI, popup::ConfirmCfg};
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_parser::mgr::RenameForm;
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_watcher::WATCHER;
use yazi_widgets::input::InputEvent;
@ -42,7 +42,7 @@ impl Actor for Rename {
};
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 {
let Some(InputEvent::Submit(name)) = input.recv().await else { return };

View file

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

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, time::Duration};
use anyhow::Result;
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_config::YAZI;
use yazi_core::mgr::{CdSource, SearchVia};
use yazi_fs::{FilesOp, cha::Cha};
use yazi_macro::{act, input, succ};
@ -28,7 +28,7 @@ impl Actor for Search {
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 {
if let Some(InputEvent::Submit(subject)) = input.recv().await {

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use anyhow::Result;
use yazi_config::popup::InputCfg;
use yazi_config::YAZI;
use yazi_macro::{act, input, succ};
use yazi_parser::mgr::ShellForm;
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 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 {
None
};

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use anyhow::Result;
use yazi_config::popup::InputCfg;
use yazi_config::YAZI;
use yazi_macro::{act, input, render, succ};
use yazi_parser::mgr::TabRenameForm;
use yazi_proxy::MgrProxy;
@ -29,7 +29,7 @@ impl Actor for TabRename {
let mut input = input!(
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 {

View file

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

View file

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

View file

@ -15,6 +15,8 @@ impl Actor for Spawn {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
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),
})
}

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-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
yazi-term = { path = "../yazi-term", version = "26.5.6" }
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
# External dependencies
ansi-to-tui = { workspace = true }
anyhow = { 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" ] }
moxcms = "0.8.1"
palette = { version = "0.7.6", default-features = false }
quantette = { version = "0.6.0", default-features = false }
ratatui = { workspace = true }
strum = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
ansi-to-tui = { workspace = true }
anyhow = { workspace = true }
base64 = { workspace = true }
image = { workspace = true }
inventory = { workspace = true }
moxcms = "0.8.1"
palette = { version = "0.7.6", default-features = false }
quantette = { version = "0.6.0", default-features = false }
ratatui-core = { workspace = true }
strum = { 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 ratatui::layout::Rect;
use strum::{Display, IntoStaticStr};
use tracing::warn;
use yazi_emulator::{Emulator, TMUX};
use yazi_shared::env_exists;
use ratatui_core::layout::Rect;
use yazi_emulator::EMULATOR;
use yazi_shim::cell::SyncCell;
use yazi_widgets::clear::ClearInventory;
use crate::{Adapters, SHOWN, drivers};
use crate::{ADAPTOR, drivers::{Driver, Drivers}};
#[derive(Clone, Copy, Debug, Display, Eq, IntoStaticStr, PartialEq)]
#[strum(serialize_all = "kebab-case")]
pub enum Adapter {
Kgp,
KgpOld,
Iip,
Sixel,
pub struct Adapter {
driver: Driver,
shown: SyncCell<Option<Rect>>,
pub collision: SyncCell<bool>,
}
// Supported by Überzug++
X11,
Wayland,
Chafa,
impl Deref for Adapter {
type Target = Driver;
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 {
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 => 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(super) fn new() -> Self {
Self {
driver: Drivers::matches(&EMULATOR),
shown: SyncCell::new(None),
collision: SyncCell::new(false),
}
}
pub fn image_hide(self) -> Result<()> {
if let Some(area) = SHOWN.replace(None) { self.image_erase(area) } else { Ok(()) }
pub fn image_hide(&self) -> Result<()> {
if let Some(area) = self.shown.replace(None) { self.driver.image_erase(area) } else { Ok(()) }
}
pub fn image_erase(self, area: Rect) -> Result<()> {
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)
}
pub(super) fn shown_store(&self, area: Rect) { self.shown.set(Some(area)); }
}
impl Adapter {
pub fn matches(emulator: &Emulator) -> Self {
let mut adapters: Adapters = emulator.into();
if env_exists("ZELLIJ_SESSION_NAME") {
adapters.retain(|p| *p == Self::Sixel);
} else if TMUX.get() {
adapters.retain(|p| *p != Self::KgpOld);
}
if let Some(p) = adapters.first() {
return *p;
}
inventory::submit! {
ClearInventory {
clear: |area| {
let overlap = area.intersection(ADAPTOR.shown.get()?);
if overlap.area() == 0 {
return None;
}
let supported_compositor = drivers::Ueberzug::supported_compositor();
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() {
"x11" => return Self::X11,
"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
ADAPTOR.driver.image_erase(overlap).ok();
ADAPTOR.collision.set(true);
Some(overlap)
},
}
}

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 anyhow::{Result, anyhow, bail};
use ratatui::layout::Rect;
use ratatui_core::layout::Rect;
use tokio::process::Command;
use yazi_config::THEME;
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 {
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")
.args([
"-f",
@ -57,8 +58,8 @@ impl Chafa {
height: lines.len() as u16,
};
Adapter::Chafa.image_hide()?;
Adapter::shown_store(area);
ADAPTOR.image_hide()?;
ADAPTOR.shown_store(area);
Emulator::move_lock((max.x, max.y), |w| {
for (i, line) in lines.into_iter().enumerate() {
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);
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() {
write!(w, "{}", MoveTo(area.x, y))?;
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 base64::{Engine, engine::{Config, general_purpose::STANDARD}};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
use ratatui::layout::Rect;
use yazi_config::YAZI;
use ratatui_core::layout::Rect;
use yazi_config::{THEME, YAZI};
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 {
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 area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;
Adapter::Iip.image_hide()?;
Adapter::shown_store(area);
ADAPTOR.image_hide()?;
ADAPTOR.shown_store(area);
Emulator::move_lock((max.x, max.y), |w| {
w.write_all(&b)?;
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);
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() {
write!(w, "{}", MoveTo(area.x, y))?;
write!(w, "{s}")?;
}
Ok(())
Ok(write!(w, "{ResetAttrs}")?)
})
}

View file

@ -4,12 +4,13 @@ use std::{io::Write, path::PathBuf};
use anyhow::Result;
use base64::{Engine, engine::general_purpose};
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_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] = [
'\u{0305}',
@ -311,18 +312,18 @@ static DIACRITICS: [char; 297] = [
'\u{1D244}',
];
pub(crate) struct Kgp;
pub(super) struct 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 area = Image::pixel_area((img.width(), img.height()), max);
let b1 = Self::encode(img).await?;
let b2 = Self::place(&area)?;
Adapter::Kgp.image_hide()?;
Adapter::shown_store(area);
ADAPTOR.image_hide()?;
ADAPTOR.shown_store(area);
Emulator::move_lock((area.x, area.y), |w| {
w.write_all(&b1)?;
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);
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() {
write!(w, "{}", MoveTo(area.x, y))?;
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(())
})
}
@ -381,12 +386,16 @@ impl Kgp {
}
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 (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)))?;
if let Some(c) = THEME.app.overall.get().bg {
write!(buf, "{}", SetBg(c))?;
}
for y in 0..area.height {
write!(buf, "{}", MoveTo(area.x, area.y + y))?;
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, "{ResetAttrs}")?;
Ok(buf)
}

View file

@ -4,29 +4,29 @@ use std::{io::Write, path::PathBuf};
use anyhow::Result;
use base64::{Engine, engine::general_purpose};
use image::DynamicImage;
use ratatui::layout::Rect;
use ratatui_core::layout::Rect;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_tty::TTY;
use crate::{Image, adapter::Adapter};
use crate::{ADAPTOR, Image};
pub(crate) struct KgpOld;
pub(super) struct 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 area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;
Adapter::KgpOld.image_hide()?;
Adapter::shown_store(area);
ADAPTOR.image_hide()?;
ADAPTOR.shown_store(area);
Emulator::move_lock((area.x, area.y), |w| {
w.write_all(&b)?;
Ok(area)
})
}
pub(crate) fn image_erase(_: Rect) -> Result<()> {
pub(super) fn image_erase(_: Rect) -> Result<()> {
let mut w = TTY.lockout();
write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
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 palette::{Srgb, cast::ComponentsAs};
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_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> {
indices: Vec<u8>,
@ -18,27 +19,30 @@ struct QuantizeOutput<T> {
}
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 area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?;
Adapter::Sixel.image_hide()?;
Adapter::shown_store(area);
ADAPTOR.image_hide()?;
ADAPTOR.shown_store(area);
Emulator::move_lock((area.x, area.y), |w| {
w.write_all(&b)?;
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);
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() {
write!(w, "{}", MoveTo(area.x, y))?;
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 image::ImageReader;
use ratatui::layout::Rect;
use ratatui_core::layout::Rect;
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
use tracing::{debug, warn};
use yazi_config::YAZI;
@ -10,21 +10,21 @@ use yazi_emulator::Dimension;
use yazi_shared::{LOG_LEVEL, env_exists};
use yazi_shim::{cell::RoCell, strum::IntoStr};
use crate::Adapter;
use crate::{ADAPTOR, drivers::Driver};
type Cmd = Option<(PathBuf, Rect)>;
static DEMON: RoCell<Option<UnboundedSender<Cmd>>> = RoCell::new();
pub(crate) struct Ueberzug;
pub(super) struct Ueberzug;
impl Ueberzug {
pub(crate) fn start(adapter: Adapter) {
if !adapter.needs_ueberzug() {
pub(super) fn start(driver: Driver) {
if !driver.needs_ueberzug() {
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();
tokio::spawn(async move {
@ -34,17 +34,17 @@ impl Ueberzug {
child = None;
}
if child.is_none() {
child = Self::create_demon(adapter).ok();
child = Self::create_demon(driver).ok();
}
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))
}
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 {
bail!("uninitialized ueberzugpp");
};
@ -64,11 +64,11 @@ impl Ueberzug {
.unwrap_or(max);
tx.send(Some((path, area)))?;
Adapter::shown_store(area);
ADAPTOR.shown_store(area);
Ok(area)
}
pub(crate) fn image_erase(_: Rect) -> Result<()> {
pub(super) fn image_erase(_: Rect) -> Result<()> {
if let Some(tx) = &*DEMON {
Ok(tx.send(None)?)
} else {
@ -79,16 +79,16 @@ impl Ueberzug {
// Currently Überzug++'s Wayland output only supports Niri, Sway, Hyprland and
// Wayfire as it requires information from specific compositor socket directly.
// 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("SWAYSOCK")
|| env_exists("HYPRLAND_INSTANCE_SIGNATURE")
|| env_exists("WAYFIRE_SOCKET")
}
fn create_demon(adapter: Adapter) -> Result<Child> {
fn create_demon(driver: Driver) -> Result<Child> {
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" })
.kill_on_drop(true)
.stdin(Stdio::piped())
@ -113,7 +113,7 @@ impl Ueberzug {
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 {
debug!("ueberzugpp rect before adjustment: {:?}", rect);
let rect = Self::adjust_rect(rect);
@ -132,7 +132,7 @@ impl Ueberzug {
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?;
Ok(())

View file

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

View file

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
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_emulator::Dimension;
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_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_shared::in_wsl;
use yazi_shim::cell::SyncCell;
use yazi_shim::cell::{RoCell, SyncCell};
pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa);
// Image state
static SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);
pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// WSL support
pub static WSL: SyncCell<bool> = SyncCell::new(false);
@ -34,7 +31,7 @@ pub fn init() -> anyhow::Result<()> {
EMULATOR.init(emulator);
yazi_config::init_flavor(EMULATOR.light)?;
ADAPTOR.set(Adapter::matches(&EMULATOR));
ADAPTOR.get().start();
ADAPTOR.init(Adapter::new());
ADAPTOR.start();
Ok(())
}

View file

@ -17,30 +17,24 @@ default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies]
yazi-adapter = { path = "../yazi-adapter", 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-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
yazi-term = { path = "../yazi-term", version = "26.5.6" }
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
# External dependencies
ansi-to-tui = { workspace = true }
anyhow = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
mlua = { workspace = true }
paste = { workspace = true }
ratatui = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tracing = { workspace = true }
unicode-width = { workspace = true }
ansi-to-tui = { workspace = true }
anyhow = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
image = { workspace = true }
mlua = { workspace = true }
ratatui-core = { workspace = true }
ratatui-widgets = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
strum = { workspace = true }
tokio = { workspace = true }
unicode-width = { workspace = true }
[target.'cfg(unix)'.dependencies]
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 struct Composer<G, S> {
get: G,
set: S,
parent: Option<(G, S)>,
cache: HashMap<Vec<u8>, Value>,
get: G,
set: S,
cache: HashMap<Vec<u8>, Value>,
}
impl<G, S> Composer<G, S>
@ -16,13 +15,7 @@ where
G: Fn(&Lua, &[u8]) -> 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, 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() }
}
pub fn new(get: G, set: S) -> Self { Self { get, set, cache: Default::default() } }
}
impl<G, S> UserData for Composer<G, S>
@ -37,14 +30,9 @@ where
return Ok(v.clone());
}
let mut value = (me.get)(lua, &key)?;
if value.is_nil()
&& let Some((p_get, _)) = &me.parent
{
value = p_get(lua, &key)?;
}
let value = (me.get)(lua, &key)?;
me.cache.insert(key.to_owned(), value.clone());
Ok(value)
});
@ -56,11 +44,6 @@ where
if value.is_nil() {
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 {
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