diff --git a/Cargo.lock b/Cargo.lock
index 7c8669dc..06652881 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -211,6 +211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
dependencies = [
"rustversion",
+ "serde",
]
[[package]]
@@ -2130,7 +2131,7 @@ checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8"
dependencies = [
"num-integer",
"num-traits",
- "rand 0.10.0",
+ "rand 0.10.1",
"rand_core 0.10.0",
]
@@ -3431,9 +3432,9 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
@@ -3441,9 +3442,9 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.10.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
+checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
"chacha20 0.10.0",
"getrandom 0.4.2",
@@ -3623,7 +3624,7 @@ dependencies = [
"num-traits",
"paste",
"profiling",
- "rand 0.9.2",
+ "rand 0.9.3",
"rand_chacha 0.9.0",
"simd_helpers",
"thiserror 2.0.18",
@@ -3828,7 +3829,7 @@ dependencies = [
"pkcs5",
"pkcs8",
"polyval 0.7.1",
- "rand 0.10.0",
+ "rand 0.10.1",
"rand_core 0.10.0",
"ring",
"rsa",
@@ -4864,7 +4865,7 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
dependencies = [
- "rand 0.9.2",
+ "rand 0.9.3",
]
[[package]]
@@ -5908,6 +5909,7 @@ name = "yazi-config"
version = "26.2.2"
dependencies = [
"anyhow",
+ "arc-swap",
"bitflags 2.11.0",
"crossterm 0.29.0",
"globset",
@@ -5916,6 +5918,7 @@ dependencies = [
"ratatui",
"regex",
"serde",
+ "serde_with",
"strum 0.28.0",
"tokio",
"toml",
@@ -5924,6 +5927,7 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
+ "yazi-shim",
"yazi-tty",
]
@@ -6076,7 +6080,7 @@ dependencies = [
"objc2",
"parking_lot",
"percent-encoding",
- "rand 0.10.0",
+ "rand 0.10.1",
"regex",
"scopeguard",
"serde",
@@ -6278,8 +6282,12 @@ dependencies = [
name = "yazi-shim"
version = "26.2.2"
dependencies = [
+ "arc-swap",
"crossterm 0.29.0",
"ratatui",
+ "serde",
+ "thiserror 2.0.18",
+ "toml",
"twox-hash",
"unicode-segmentation",
"unicode-width",
diff --git a/Cargo.toml b/Cargo.toml
index 6acc8042..6cd9f1ac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,7 +37,7 @@ debug = false
[workspace.dependencies]
ansi-to-tui = "8.0.1"
anyhow = "1.0.102"
-arc-swap = "1.9.1"
+arc-swap = { version = "1.9.1", features = [ "serde" ] }
base64 = "0.22.1"
bitflags = { version = "2.11.0", features = [ "serde" ] }
chrono = "0.4.44"
@@ -60,7 +60,7 @@ ordered-float = { version = "5.3.0", features = [ "serde" ] }
parking_lot = "0.12.5"
paste = "1.0.15"
percent-encoding = "2.3.2"
-rand = { version = "0.10.0", default-features = false, features = [ "std", "sys_rng" ] }
+rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.3"
russh = { version = "0.60.0", default-features = false, features = [ "ring", "rsa" ] }
diff --git a/README.md b/README.md
index e1f46cea..63e6513a 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
@@ -47,24 +47,24 @@ Yazi is currently in heavy development, expect breaking changes.
## Image Preview
-| Platform | Protocol | Support |
-| ---------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |
-| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
-| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
-| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
-| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
-| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
-| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
-| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
-| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
-| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
-| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
-| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
-| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio renders images at incorrect sizes |
-| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
-| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
-| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
-| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required (>= 1.16.0) |
+| Platform | Protocol | Support |
+| ---------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------- |
+| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
+| [iTerm2](https://iterm2.com) | [Inline images protocol][iip] | ✅ Built-in |
+| [WezTerm](https://github.com/wez/wezterm) | [Inline images protocol][iip] | ✅ Built-in |
+| [Konsole](https://invent.kde.org/utilities/konsole) | [Kitty old protocol][kgp-old] | ✅ Built-in |
+| [foot](https://codeberg.org/dnkl/foot) | [Sixel graphics format][sixel] | ✅ Built-in |
+| [Ghostty](https://github.com/ghostty-org/ghostty) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
+| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel] | ✅ Built-in |
+| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch) | [Sixel graphics format][sixel] | ✅ Built-in |
+| [Warp](https://www.warp.dev) (macOS/Linux only) | [Inline images protocol][iip] | ✅ Built-in |
+| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in |
+| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in |
+| [Rio](https://github.com/raphamorim/rio) (>= 0.3.9) | [Kitty unicode placeholders][kgp] | ✅ Built-in |
+| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in |
+| [Bobcat](https://github.com/ismail-yilmaz/Bobcat) | [Inline images protocol][iip] | ✅ Built-in |
+| X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required |
+| Fallback | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required (>= 1.16.0) |
See https://yazi-rs.github.io/docs/image-preview for details.
diff --git a/cspell.json b/cspell.json
index 3fc3d927..e5d69993 100644
--- a/cspell.json
+++ b/cspell.json
@@ -1 +1 @@
-{"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"],"flagWords":[],"language":"en","version":"0.2"}
\ No newline at end of file
+{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU","fellback","watchee","Textlike","sstr","pointee"],"version":"0.2","language":"en","flagWords":[]}
\ No newline at end of file
diff --git a/yazi-actor/src/mgr/seek.rs b/yazi-actor/src/mgr/seek.rs
index 6e0ed78d..fde0477a 100644
--- a/yazi-actor/src/mgr/seek.rs
+++ b/yazi-actor/src/mgr/seek.rs
@@ -25,12 +25,12 @@ impl Actor for Seek {
succ!(cx.tab_mut().preview.reset());
};
- let Some(previewer) = YAZI.plugin.previewer(hovered, mime) else {
+ let Some(previewer) = YAZI.plugin.previewers.matches(hovered, mime) else {
succ!(cx.tab_mut().preview.reset());
};
let job = SeekJob { file: hovered.clone(), units: form.units };
- let opt = PluginOpt::new_callback(&*previewer.run.name, move |_, plugin| {
+ let opt = PluginOpt::new_callback(previewer.name.clone(), move |_, plugin| {
plugin.call_method("seek", job)
});
act!(app:plugin, cx, opt)
diff --git a/yazi-adapter/src/adapters.rs b/yazi-adapter/src/adapters.rs
index 36b50e71..77912c41 100644
--- a/yazi-adapter/src/adapters.rs
+++ b/yazi-adapter/src/adapters.rs
@@ -33,7 +33,7 @@ impl From for Adapters {
B::Ghostty => vec![A::Kgp],
B::Microsoft => vec![A::Sixel],
B::Warp => vec![A::Iip, A::KgpOld],
- B::Rio => vec![A::Iip, A::Sixel],
+ B::Rio => vec![A::Kgp],
B::BlackBox => vec![A::Sixel],
B::VSCode => vec![A::Iip, A::Sixel],
B::Tabby => vec![A::Iip, A::Sixel],
diff --git a/yazi-binding/src/access.rs b/yazi-binding/src/access.rs
index bb8a1d85..1134d982 100644
--- a/yazi-binding/src/access.rs
+++ b/yazi-binding/src/access.rs
@@ -8,15 +8,15 @@ pub struct Access(yazi_vfs::provider::Gate);
impl UserData for Access {
fn add_methods>(methods: &mut M) {
- methods.add_function_mut("append", |_, (ud, append): (AnyUserData, bool)| {
+ methods.add_function("append", |_, (ud, append): (AnyUserData, bool)| {
ud.borrow_mut::()?.0.append(append);
Ok(ud)
});
- methods.add_function_mut("create", |_, (ud, create): (AnyUserData, bool)| {
+ methods.add_function("create", |_, (ud, create): (AnyUserData, bool)| {
ud.borrow_mut::()?.0.create(create);
Ok(ud)
});
- methods.add_function_mut("create_new", |_, (ud, create_new): (AnyUserData, bool)| {
+ methods.add_function("create_new", |_, (ud, create_new): (AnyUserData, bool)| {
ud.borrow_mut::()?.0.create_new(create_new);
Ok(ud)
});
@@ -26,15 +26,15 @@ impl UserData for Access {
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}
});
- methods.add_function_mut("read", |_, (ud, read): (AnyUserData, bool)| {
+ methods.add_function("read", |_, (ud, read): (AnyUserData, bool)| {
ud.borrow_mut::()?.0.read(read);
Ok(ud)
});
- methods.add_function_mut("truncate", |_, (ud, truncate): (AnyUserData, bool)| {
+ methods.add_function("truncate", |_, (ud, truncate): (AnyUserData, bool)| {
ud.borrow_mut::()?.0.truncate(truncate);
Ok(ud)
});
- methods.add_function_mut("write", |_, (ud, write): (AnyUserData, bool)| {
+ methods.add_function("write", |_, (ud, write): (AnyUserData, bool)| {
ud.borrow_mut::()?.0.write(write);
Ok(ud)
});
diff --git a/yazi-binding/src/config/fetcher.rs b/yazi-binding/src/config/fetcher.rs
new file mode 100644
index 00000000..eb2d799e
--- /dev/null
+++ b/yazi-binding/src/config/fetcher.rs
@@ -0,0 +1,32 @@
+use std::{ops::Deref, sync::Arc};
+
+use mlua::{UserData, UserDataFields, Value};
+
+use crate::{Id, cached_field};
+
+#[derive(Clone)]
+pub struct Fetcher {
+ inner: Arc,
+
+ v_name: Option,
+}
+
+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>) -> Self {
+ Self { inner: inner.into(), v_name: None }
+ }
+}
+
+impl UserData for Fetcher {
+ fn add_fields>(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));
+ }
+}
diff --git a/yazi-binding/src/config/fetchers.rs b/yazi-binding/src/config/fetchers.rs
new file mode 100644
index 00000000..7b8f6885
--- /dev/null
+++ b/yazi-binding/src/config/fetchers.rs
@@ -0,0 +1,51 @@
+use mlua::{ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
+use yazi_config::YAZI;
+
+use crate::{FileRef, Id, Iter, config::Fetcher};
+
+pub struct Fetchers;
+
+impl UserData for Fetchers {
+ fn add_methods>(methods: &mut M) {
+ methods.add_method("match", |_, _, matcher: FetcherMatcher| Ok(matcher));
+
+ methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.fetchers.load().len()));
+ }
+}
+
+// --- Matcher
+pub struct FetcherMatcher(yazi_config::plugin::FetcherMatcher<'static>);
+
+impl TryFrom for FetcherMatcher {
+ type Error = mlua::Error;
+
+ fn try_from(value: Table) -> Result {
+ let id: Id = value.raw_get("id").unwrap_or_default();
+ let file: Option = value.raw_get("file")?;
+ let mime: Option = 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),
+ offset: 0,
+ seen: Default::default(),
+ }))
+ }
+}
+
+impl FromLua for FetcherMatcher {
+ fn from_lua(value: Value, _: &Lua) -> mlua::Result {
+ match value {
+ Value::Table(t) => t.try_into(),
+ _ => return Err("expected a table of FetcherMatcher".into_lua_err()),
+ }
+ }
+}
+
+impl IntoLua for FetcherMatcher {
+ fn into_lua(self, lua: &Lua) -> mlua::Result {
+ Iter::new(self.0.into_iter().map(Fetcher::new), None).into_lua(lua)
+ }
+}
diff --git a/yazi-binding/src/config/mod.rs b/yazi-binding/src/config/mod.rs
new file mode 100644
index 00000000..5d106eb4
--- /dev/null
+++ b/yazi-binding/src/config/mod.rs
@@ -0,0 +1 @@
+yazi_macro::mod_flat!(fetcher fetchers preloader preloaders previewer previewers spotter spotters);
diff --git a/yazi-binding/src/config/preloader.rs b/yazi-binding/src/config/preloader.rs
new file mode 100644
index 00000000..08403d5a
--- /dev/null
+++ b/yazi-binding/src/config/preloader.rs
@@ -0,0 +1,32 @@
+use std::{ops::Deref, sync::Arc};
+
+use mlua::{UserData, UserDataFields, Value};
+
+use crate::{Id, cached_field};
+
+#[derive(Clone)]
+pub struct Preloader {
+ inner: Arc,
+
+ v_name: Option,
+}
+
+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>) -> Self {
+ Self { inner: inner.into(), v_name: None }
+ }
+}
+
+impl UserData for Preloader {
+ fn add_fields>(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));
+ }
+}
diff --git a/yazi-binding/src/config/preloaders.rs b/yazi-binding/src/config/preloaders.rs
new file mode 100644
index 00000000..7e9348a3
--- /dev/null
+++ b/yazi-binding/src/config/preloaders.rs
@@ -0,0 +1,51 @@
+use mlua::{ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
+use yazi_config::YAZI;
+
+use crate::{FileRef, Id, Iter, config::Preloader};
+
+pub struct Preloaders;
+
+impl UserData for Preloaders {
+ fn add_methods>(methods: &mut M) {
+ methods.add_method("match", |_, _, matcher: PreloaderMatcher| Ok(matcher));
+
+ methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.preloaders.load().len()));
+ }
+}
+
+// --- Matcher
+pub struct PreloaderMatcher(yazi_config::plugin::PreloaderMatcher<'static>);
+
+impl TryFrom for PreloaderMatcher {
+ type Error = mlua::Error;
+
+ fn try_from(value: Table) -> Result {
+ let id: Id = value.raw_get("id").unwrap_or_default();
+ let file: Option = value.raw_get("file")?;
+ let mime: Option = 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),
+ offset: 0,
+ next: true,
+ }))
+ }
+}
+
+impl FromLua for PreloaderMatcher {
+ fn from_lua(value: Value, _: &Lua) -> mlua::Result {
+ match value {
+ Value::Table(t) => t.try_into(),
+ _ => return Err("expected a table of PreloaderMatcher".into_lua_err()),
+ }
+ }
+}
+
+impl IntoLua for PreloaderMatcher {
+ fn into_lua(self, lua: &Lua) -> mlua::Result {
+ Iter::new(self.0.into_iter().map(Preloader::new), None).into_lua(lua)
+ }
+}
diff --git a/yazi-binding/src/config/previewer.rs b/yazi-binding/src/config/previewer.rs
new file mode 100644
index 00000000..07190a66
--- /dev/null
+++ b/yazi-binding/src/config/previewer.rs
@@ -0,0 +1,70 @@
+use std::{ops::Deref, sync::Arc};
+
+use mlua::{AnyUserData, ExternalError, FromLua, Lua, UserData, UserDataFields, Value};
+use yazi_config::plugin::previewer_id;
+
+use crate::{Id, cached_field};
+
+const EXPECTED: &str = "expected a table or Previewer";
+
+#[derive(Clone)]
+pub struct Previewer {
+ inner: Arc,
+
+ v_name: Option,
+}
+
+impl Deref for Previewer {
+ type Target = yazi_config::plugin::Previewer;
+
+ fn deref(&self) -> &Self::Target { &self.inner }
+}
+
+impl From for Arc {
+ fn from(value: Previewer) -> Self { value.inner }
+}
+
+impl Previewer {
+ pub fn new(inner: impl Into>) -> Self {
+ Self { inner: inner.into(), v_name: None }
+ }
+}
+
+impl TryFrom for Previewer {
+ type Error = mlua::Error;
+
+ fn try_from(t: mlua::Table) -> Result {
+ Ok(Self::new(yazi_config::plugin::Previewer {
+ id: previewer_id(),
+ url: t.raw_get::