mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: spotter (#1802)
This commit is contained in:
parent
af565fbc40
commit
1f32601dc4
115 changed files with 1739 additions and 587 deletions
45
Cargo.lock
generated
45
Cargo.lock
generated
|
|
@ -266,9 +266,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
|
||||
checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
|
|
@ -327,9 +327,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
|
||||
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
|
@ -366,9 +366,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -376,9 +376,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -388,9 +388,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.37"
|
||||
version = "4.5.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11611dca53440593f38e6b25ec629de50b14cdfa63adc0fb856115a2c6d97595"
|
||||
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
|
@ -429,9 +429,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
|
|
@ -819,9 +819,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.34"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
|
|
@ -1237,12 +1237,6 @@ dependencies = [
|
|||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.11.0"
|
||||
|
|
@ -1402,9 +1396,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.162"
|
||||
version = "0.2.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
|
|
@ -2292,9 +2286,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.132"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
|
@ -3100,7 +3094,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3363,7 +3357,6 @@ dependencies = [
|
|||
"crossterm",
|
||||
"futures",
|
||||
"image",
|
||||
"imagesize",
|
||||
"ratatui",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -14,20 +14,20 @@ ansi-to-tui = "7.0.0"
|
|||
anyhow = "1.0.93"
|
||||
base64 = "0.22.1"
|
||||
bitflags = "2.6.0"
|
||||
clap = { version = "4.5.20", features = [ "derive" ] }
|
||||
clap = { version = "4.5.21", features = [ "derive" ] }
|
||||
crossterm = { version = "0.28.1", features = [ "event-stream" ] }
|
||||
dirs = "5.0.1"
|
||||
futures = "0.3.31"
|
||||
globset = "0.4.15"
|
||||
libc = "0.2.162"
|
||||
libc = "0.2.164"
|
||||
md-5 = "0.10.6"
|
||||
mlua = { version = "0.10.1", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serialize" ] }
|
||||
parking_lot = "0.12.3"
|
||||
ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] }
|
||||
regex = "1.11.1"
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.214", features = [ "derive" ] }
|
||||
serde_json = "1.0.132"
|
||||
serde = { version = "1.0.215", features = [ "derive" ] }
|
||||
serde_json = "1.0.133"
|
||||
shell-words = "1.1.0"
|
||||
tokio = { version = "1.41.1", features = [ "full" ] }
|
||||
tokio-stream = "0.1.16"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","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","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","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","imagesize","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","Sysinfo"],"language":"en","version":"0.2"}
|
||||
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","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","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","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","Sysinfo"],"language":"en","version":"0.2"}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ color_quant = "1.1.0"
|
|||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
image = { version = "0.25.5", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
imagesize = "0.13.0"
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
44
yazi-adapter/src/info.rs
Normal file
44
yazi-adapter/src/info.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use std::path::Path;
|
||||
|
||||
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: &Path) -> image::ImageResult<Self> {
|
||||
let path = path.to_owned();
|
||||
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()))?
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(clippy::unit_arg)]
|
||||
|
||||
yazi_macro::mod_flat!(
|
||||
adapter chafa dimension emulator iip image kgp kgp_old mux sixel ueberzug
|
||||
adapter chafa dimension emulator iip image info kgp kgp_old mux sixel ueberzug
|
||||
);
|
||||
|
||||
use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::{path::{Path, PathBuf}, process::Stdio};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use imagesize::ImageSize;
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
|
||||
use tracing::{debug, warn};
|
||||
|
|
@ -48,8 +47,7 @@ impl Ueberzug {
|
|||
};
|
||||
|
||||
let p = path.to_owned();
|
||||
let ImageSize { width: w, height: h } =
|
||||
tokio::task::spawn_blocking(move || imagesize::size(p)).await??;
|
||||
let (w, h) = tokio::task::spawn_blocking(move || image::image_dimensions(p)).await??;
|
||||
|
||||
let area = Dimension::ratio()
|
||||
.map(|(r1, r2)| Rect {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ serde = { workspace = true }
|
|||
|
||||
[build-dependencies]
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.5.37"
|
||||
clap_complete = "4.5.38"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.5.4"
|
||||
vergen-gitcl = { version = "1.0.1", features = [ "build", "rustc" ] }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ toml_edit = "0.22.22"
|
|||
# External build dependencies
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.5.37"
|
||||
clap_complete = "4.5.38"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.5.4"
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -42,10 +42,6 @@ keymap = [
|
|||
{ on = "H", run = "back", desc = "Go back to the previous directory" },
|
||||
{ on = "L", run = "forward", desc = "Go forward to the next directory" },
|
||||
|
||||
# Seeking
|
||||
{ on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" },
|
||||
{ on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" },
|
||||
|
||||
# Toggle
|
||||
{ on = "<Space>", run = [ "toggle", "arrow 1" ], desc = "Toggle the current selection state" },
|
||||
{ on = "<C-a>", run = "toggle_all on", desc = "Select all files" },
|
||||
|
|
@ -55,6 +51,13 @@ keymap = [
|
|||
{ on = "v", run = "visual_mode", desc = "Enter visual mode (selection mode)" },
|
||||
{ on = "V", run = "visual_mode --unset", desc = "Enter visual mode (unset mode)" },
|
||||
|
||||
# Seeking
|
||||
{ on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" },
|
||||
{ on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" },
|
||||
|
||||
# Spotting
|
||||
{ on = "<Tab>", run = "spot", desc = "Spot hovered file" },
|
||||
|
||||
# Operation
|
||||
{ on = "o", run = "open", desc = "Open selected files" },
|
||||
{ on = "O", run = "open --interactive", desc = "Open selected files interactively" },
|
||||
|
|
@ -175,6 +178,32 @@ keymap = [
|
|||
{ on = "<F1>", run = "help", desc = "Open help" },
|
||||
]
|
||||
|
||||
[spot]
|
||||
|
||||
keymap = [
|
||||
{ on = "<Esc>", run = "close", desc = "Close the spot" },
|
||||
{ on = "<C-[>", run = "close", desc = "Close the spot" },
|
||||
{ on = "<C-c>", run = "close", desc = "Close the spot" },
|
||||
{ on = "<Tab>", run = "close", desc = "Close the spot" },
|
||||
|
||||
{ on = "k", run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = "j", run = "arrow 1", desc = "Move cursor down" },
|
||||
{ on = "h", run = "swipe -1", desc = "Swipe to the next file" },
|
||||
{ on = "l", run = "swipe 1", desc = "Swipe to the previous file" },
|
||||
|
||||
{ on = "<Up>", run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
|
||||
{ on = "<Left>", run = "swipe -1", desc = "Swipe to the next file" },
|
||||
{ on = "<Right>", run = "swipe 1", desc = "Swipe to the previous file" },
|
||||
|
||||
# Copy
|
||||
{ on = [ "c", "c" ], run = "copy cell", desc = "Copy selected cell" },
|
||||
|
||||
# Help
|
||||
{ on = "~", run = "help", desc = "Open help" },
|
||||
{ on = "<F1>", run = "help", desc = "Open help" },
|
||||
]
|
||||
|
||||
[pick]
|
||||
|
||||
keymap = [
|
||||
|
|
|
|||
|
|
@ -92,6 +92,19 @@ fetchers = [
|
|||
# Mimetype
|
||||
{ id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" },
|
||||
]
|
||||
spotters = [
|
||||
{ name = "*/", run = "folder" },
|
||||
# Code
|
||||
{ mime = "text/*", run = "code" },
|
||||
{ mime = "*/{xml,javascript,x-wine-extension-ini}", run = "code" },
|
||||
# Image
|
||||
{ mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
|
||||
{ mime = "image/*", run = "image" },
|
||||
# Video
|
||||
{ mime = "video/*", run = "video" },
|
||||
# Fallback
|
||||
{ name = "*", run = "file" },
|
||||
]
|
||||
preloaders = [
|
||||
# Image
|
||||
{ mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use crate::Preset;
|
|||
pub struct Keymap {
|
||||
pub manager: Vec<Chord>,
|
||||
pub tasks: Vec<Chord>,
|
||||
pub spot: Vec<Chord>,
|
||||
pub pick: Vec<Chord>,
|
||||
pub input: Vec<Chord>,
|
||||
pub confirm: Vec<Chord>,
|
||||
|
|
@ -26,6 +27,7 @@ impl Keymap {
|
|||
Layer::App => unreachable!(),
|
||||
Layer::Manager => &self.manager,
|
||||
Layer::Tasks => &self.tasks,
|
||||
Layer::Spot => &self.spot,
|
||||
Layer::Pick => &self.pick,
|
||||
Layer::Input => &self.input,
|
||||
Layer::Confirm => &self.confirm,
|
||||
|
|
@ -53,6 +55,7 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
struct Shadow {
|
||||
manager: Inner,
|
||||
tasks: Inner,
|
||||
spot: Inner,
|
||||
pick: Inner,
|
||||
input: Inner,
|
||||
confirm: Inner,
|
||||
|
|
@ -90,7 +93,9 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
#[rustfmt::skip]
|
||||
tasks: mix(shadow.tasks.prepend_keymap, shadow.tasks.keymap, shadow.tasks.append_keymap),
|
||||
#[rustfmt::skip]
|
||||
pick: mix(shadow.pick.prepend_keymap, shadow.pick.keymap, shadow.pick.append_keymap),
|
||||
spot: mix(shadow.spot.prepend_keymap, shadow.spot.keymap, shadow.spot.append_keymap),
|
||||
#[rustfmt::skip]
|
||||
pick: mix(shadow.pick.prepend_keymap, shadow.pick.keymap, shadow.pick.append_keymap),
|
||||
#[rustfmt::skip]
|
||||
input: mix(shadow.input.prepend_keymap, shadow.input.keymap, shadow.input.append_keymap),
|
||||
#[rustfmt::skip]
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ impl Fetcher {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FetcherProps {
|
||||
pub id: u8,
|
||||
pub name: String,
|
||||
pub name: &'static str,
|
||||
pub prio: Priority,
|
||||
}
|
||||
|
||||
impl From<&Fetcher> for FetcherProps {
|
||||
fn from(fetcher: &Fetcher) -> Self {
|
||||
Self { id: fetcher.idx, name: fetcher.run.name.to_owned(), prio: fetcher.prio }
|
||||
impl From<&'static Fetcher> for FetcherProps {
|
||||
fn from(fetcher: &'static Fetcher) -> Self {
|
||||
Self { id: fetcher.idx, name: &fetcher.run.name, prio: fetcher.prio }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
yazi_macro::mod_flat!(fetcher plugin preloader previewer);
|
||||
yazi_macro::mod_flat!(fetcher plugin preloader previewer spotter);
|
||||
|
||||
pub const MAX_PREWORKERS: u8 = 32;
|
||||
|
|
|
|||
|
|
@ -3,22 +3,23 @@ use std::{collections::HashSet, path::Path, str::FromStr};
|
|||
use anyhow::Context;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use super::{Fetcher, Preloader, Previewer};
|
||||
use super::{Fetcher, Preloader, Previewer, Spotter};
|
||||
use crate::{Preset, plugin::MAX_PREWORKERS};
|
||||
|
||||
pub struct Plugin {
|
||||
pub fetchers: Vec<Fetcher>,
|
||||
pub spotters: Vec<Spotter>,
|
||||
pub preloaders: Vec<Preloader>,
|
||||
pub previewers: Vec<Previewer>,
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn fetchers<'a>(
|
||||
&'a self,
|
||||
pub fn fetchers<'a, 'b: 'a>(
|
||||
&'b self,
|
||||
path: &'a Path,
|
||||
mime: &'a str,
|
||||
factor: impl Fn(&str) -> bool + Copy,
|
||||
) -> impl Iterator<Item = &'a Fetcher> {
|
||||
factor: impl Fn(&str) -> bool + Copy + 'a,
|
||||
) -> impl Iterator<Item = &'b Fetcher> + 'a {
|
||||
let mut seen = HashSet::new();
|
||||
self.fetchers.iter().filter(move |&f| {
|
||||
if seen.contains(&f.id) || !f.matches(path, mime, factor) {
|
||||
|
|
@ -34,11 +35,15 @@ impl Plugin {
|
|||
self.fetchers.iter().fold(0, |n, f| if f.mime.is_some() { n } else { n | 1 << f.idx as u32 })
|
||||
}
|
||||
|
||||
pub fn preloaders<'a>(
|
||||
&'a self,
|
||||
pub fn spotter(&self, path: &Path, mime: &str) -> Option<&Spotter> {
|
||||
self.spotters.iter().find(|&p| p.matches(path, mime))
|
||||
}
|
||||
|
||||
pub fn preloaders<'a, 'b: 'a>(
|
||||
&'b self,
|
||||
path: &'a Path,
|
||||
mime: &'a str,
|
||||
) -> impl Iterator<Item = &'a Preloader> {
|
||||
) -> impl Iterator<Item = &'b Preloader> + 'a {
|
||||
let mut next = true;
|
||||
self.preloaders.iter().filter(move |&p| {
|
||||
if !next || !p.matches(path, mime) {
|
||||
|
|
@ -80,6 +85,12 @@ impl<'de> Deserialize<'de> for Plugin {
|
|||
#[serde(default)]
|
||||
append_fetchers: Vec<Fetcher>,
|
||||
|
||||
spotters: Vec<Spotter>,
|
||||
#[serde(default)]
|
||||
prepend_spotters: Vec<Spotter>,
|
||||
#[serde(default)]
|
||||
append_spotters: Vec<Spotter>,
|
||||
|
||||
preloaders: Vec<Preloader>,
|
||||
#[serde(default)]
|
||||
prepend_preloaders: Vec<Preloader>,
|
||||
|
|
@ -94,6 +105,12 @@ impl<'de> Deserialize<'de> for Plugin {
|
|||
}
|
||||
|
||||
let mut shadow = Outer::deserialize(deserializer)?.plugin;
|
||||
if shadow.append_spotters.iter().any(|r| r.any_file()) {
|
||||
shadow.spotters.retain(|r| !r.any_file());
|
||||
}
|
||||
if shadow.append_spotters.iter().any(|r| r.any_dir()) {
|
||||
shadow.spotters.retain(|r| !r.any_dir());
|
||||
}
|
||||
if shadow.append_previewers.iter().any(|r| r.any_file()) {
|
||||
shadow.previewers.retain(|r| !r.any_file());
|
||||
}
|
||||
|
|
@ -103,6 +120,8 @@ impl<'de> Deserialize<'de> for Plugin {
|
|||
|
||||
shadow.fetchers =
|
||||
Preset::mix(shadow.prepend_fetchers, shadow.fetchers, shadow.append_fetchers).collect();
|
||||
shadow.spotters =
|
||||
Preset::mix(shadow.prepend_spotters, shadow.spotters, shadow.append_spotters).collect();
|
||||
shadow.preloaders =
|
||||
Preset::mix(shadow.prepend_preloaders, shadow.preloaders, shadow.append_preloaders).collect();
|
||||
shadow.previewers =
|
||||
|
|
@ -121,6 +140,7 @@ impl<'de> Deserialize<'de> for Plugin {
|
|||
|
||||
Ok(Self {
|
||||
fetchers: shadow.fetchers,
|
||||
spotters: shadow.spotters,
|
||||
preloaders: shadow.preloaders,
|
||||
previewers: shadow.previewers,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ impl Preloader {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct PreloaderProps {
|
||||
pub id: u8,
|
||||
pub name: String,
|
||||
pub name: &'static str,
|
||||
pub prio: Priority,
|
||||
}
|
||||
|
||||
impl From<&Preloader> for PreloaderProps {
|
||||
fn from(preloader: &Preloader) -> Self {
|
||||
Self { id: preloader.idx, name: preloader.run.name.to_owned(), prio: preloader.prio }
|
||||
impl From<&'static Preloader> for PreloaderProps {
|
||||
fn from(preloader: &'static Preloader) -> Self {
|
||||
Self { id: preloader.idx, name: &preloader.run.name, prio: preloader.prio }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
yazi-config/src/plugin/spotter.rs
Normal file
27
yazi-config/src/plugin/spotter.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{MIME_DIR, event::Cmd};
|
||||
|
||||
use crate::Pattern;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Spotter {
|
||||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
pub run: Cmd,
|
||||
}
|
||||
|
||||
impl Spotter {
|
||||
#[inline]
|
||||
pub fn matches(&self, path: &Path, mime: &str) -> bool {
|
||||
self.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|
||||
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
|
||||
|
||||
#[inline]
|
||||
pub fn any_dir(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_dir()) }
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
clippy::unit_arg
|
||||
)]
|
||||
|
||||
yazi_macro::mod_pub!(completion confirm help input manager notify pick tab tasks which);
|
||||
yazi_macro::mod_pub!(completion confirm help input manager notify pick spot tab tasks which);
|
||||
|
||||
pub fn init() {
|
||||
manager::WATCHED.with(<_>::default);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ yazi_macro::mod_flat!(
|
|||
remove
|
||||
rename
|
||||
seek
|
||||
spot
|
||||
suspend
|
||||
tab_close
|
||||
tab_create
|
||||
|
|
|
|||
40
yazi-core/src/manager/commands/spot.rs
Normal file
40
yazi-core/src/manager/commands/spot.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use yazi_shared::{MIME_DIR, event::{Cmd, Data}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
struct Opt {
|
||||
skip: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self { Self { skip: c.get("skip").and_then(Data::as_usize) } }
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
#[yazi_codegen::command]
|
||||
pub fn spot(&mut self, opt: Opt) {
|
||||
let Some(hovered) = self.hovered().cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mime = if hovered.is_dir() {
|
||||
Cow::Borrowed(MIME_DIR)
|
||||
} else {
|
||||
Cow::Owned(self.mimetype.get_owned(&hovered.url).unwrap_or_default())
|
||||
};
|
||||
|
||||
if !self.active().spot.same_file(&hovered, &mime) {
|
||||
// self.active_mut().spot.reset();
|
||||
}
|
||||
|
||||
if let Some(skip) = opt.skip {
|
||||
self.active_mut().spot.skip = skip;
|
||||
} else if !self.active().spot.same_url(&hovered.url) {
|
||||
self.active_mut().spot.skip = 0;
|
||||
}
|
||||
|
||||
self.active_mut().spot.go(hovered, mime);
|
||||
}
|
||||
}
|
||||
|
|
@ -40,11 +40,11 @@ impl Tabs {
|
|||
if !opt.current {
|
||||
tab.cd(opt.url);
|
||||
} else if let Some(h) = self.active().hovered() {
|
||||
tab.conf = self.active().conf.clone();
|
||||
tab.pref = self.active().pref.clone();
|
||||
tab.apply_files_attrs();
|
||||
tab.reveal(h.url.to_regular());
|
||||
} else {
|
||||
tab.conf = self.active().conf.clone();
|
||||
tab.pref = self.active().pref.clone();
|
||||
tab.apply_files_attrs();
|
||||
tab.cd(self.active().cwd().to_regular());
|
||||
}
|
||||
|
|
|
|||
31
yazi-core/src/spot/commands/arrow.rs
Normal file
31
yazi-core/src/spot/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use yazi_macro::render;
|
||||
use yazi_proxy::ManagerProxy;
|
||||
use yazi_shared::event::{Cmd, Data};
|
||||
|
||||
use crate::spot::Spot;
|
||||
|
||||
struct Opt {
|
||||
step: isize,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self { Self { step: c.first().and_then(Data::as_isize).unwrap_or(0) } }
|
||||
}
|
||||
|
||||
impl Spot {
|
||||
#[yazi_codegen::command]
|
||||
pub fn arrow(&mut self, opt: Opt) {
|
||||
let Some(lock) = &mut self.lock else { return };
|
||||
|
||||
let Some(old) = lock.selected() else {
|
||||
let new = self.skip.saturating_add_signed(opt.step);
|
||||
return ManagerProxy::spot(Some(new));
|
||||
};
|
||||
|
||||
lock.select(Some(old.saturating_add_signed(opt.step)));
|
||||
let new = lock.selected().unwrap();
|
||||
|
||||
self.skip = new;
|
||||
render!(new != old);
|
||||
}
|
||||
}
|
||||
21
yazi-core/src/spot/commands/close.rs
Normal file
21
yazi-core/src/spot/commands/close.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use yazi_macro::render;
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::spot::Spot;
|
||||
|
||||
struct Opt;
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(_: Cmd) -> Self { Self }
|
||||
}
|
||||
impl From<()> for Opt {
|
||||
fn from(_: ()) -> Self { Self }
|
||||
}
|
||||
|
||||
impl Spot {
|
||||
#[yazi_codegen::command]
|
||||
pub fn close(&mut self, _: Opt) {
|
||||
self.ct.take().map(|h| h.cancel());
|
||||
render!(self.lock.take().is_some());
|
||||
}
|
||||
}
|
||||
34
yazi-core/src/spot/commands/copy.rs
Normal file
34
yazi-core/src/spot/commands/copy.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use yazi_plugin::CLIPBOARD;
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::spot::Spot;
|
||||
|
||||
struct Opt {
|
||||
type_: String,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(mut c: Cmd) -> Self { Self { type_: c.take_first_str().unwrap_or_default() } }
|
||||
}
|
||||
|
||||
impl Spot {
|
||||
#[yazi_codegen::command]
|
||||
pub fn copy(&mut self, opt: Opt) {
|
||||
let Some(lock) = &self.lock else { return };
|
||||
let Some(table) = lock.table() else { return };
|
||||
|
||||
let mut s = String::new();
|
||||
match opt.type_.as_str() {
|
||||
"cell" => {
|
||||
let Some(cell) = table.selected_cell() else { return };
|
||||
s = cell.to_string();
|
||||
}
|
||||
"line" => {
|
||||
// TODO
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
futures::executor::block_on(CLIPBOARD.set(s));
|
||||
}
|
||||
}
|
||||
1
yazi-core/src/spot/commands/mod.rs
Normal file
1
yazi-core/src/spot/commands/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(arrow close copy swipe);
|
||||
20
yazi-core/src/spot/commands/swipe.rs
Normal file
20
yazi-core/src/spot/commands/swipe.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use yazi_proxy::{ManagerProxy, TabProxy};
|
||||
use yazi_shared::event::{Cmd, Data};
|
||||
|
||||
use crate::spot::Spot;
|
||||
|
||||
struct Opt {
|
||||
step: isize,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self { Self { step: c.first().and_then(Data::as_isize).unwrap_or(0) } }
|
||||
}
|
||||
|
||||
impl Spot {
|
||||
#[yazi_codegen::command]
|
||||
pub fn swipe(&mut self, opt: Opt) {
|
||||
TabProxy::arrow(opt.step);
|
||||
ManagerProxy::spot(None);
|
||||
}
|
||||
}
|
||||
3
yazi-core/src/spot/mod.rs
Normal file
3
yazi-core/src/spot/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
yazi_macro::mod_pub!(commands);
|
||||
|
||||
yazi_macro::mod_flat!(spot);
|
||||
54
yazi-core/src/spot/spot.rs
Normal file
54
yazi-core/src/spot/spot.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use yazi_config::PLUGIN;
|
||||
use yazi_macro::render;
|
||||
use yazi_plugin::{isolate, utils::SpotLock};
|
||||
use yazi_shared::fs::{File, Url};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Spot {
|
||||
pub lock: Option<SpotLock>,
|
||||
pub skip: usize,
|
||||
|
||||
pub(super) ct: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl Spot {
|
||||
pub fn go(&mut self, file: File, mime: Cow<'static, str>) {
|
||||
if mime.is_empty() {
|
||||
return; // Wait till mimetype is resolved to avoid flickering
|
||||
} else if self.same_lock(&file, &mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(spotter) = PLUGIN.spotter(&file.url, &mime) else {
|
||||
return self.reset();
|
||||
};
|
||||
|
||||
self.ct = Some(isolate::spot(&spotter.run, file, mime, self.skip));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn visible(&self) -> bool { self.lock.is_some() }
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.ct.take().map(|ct| ct.cancel());
|
||||
render!(self.lock.take().is_some());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_url(&self, url: &Url) -> bool { self.lock.as_ref().is_some_and(|l| *url == l.url) }
|
||||
|
||||
#[inline]
|
||||
pub fn same_file(&self, file: &File, mime: &str) -> bool {
|
||||
self.same_url(&file.url)
|
||||
&& self.lock.as_ref().is_some_and(|l| file.cha.hits(l.cha) && mime == l.mime)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_lock(&self, file: &File, mime: &str) -> bool {
|
||||
self.same_file(file, mime) && self.lock.as_ref().is_some_and(|l| self.skip == l.skip)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@ use crate::tab::Tab;
|
|||
|
||||
impl Tab {
|
||||
pub fn hidden(&mut self, mut c: Cmd) {
|
||||
self.conf.show_hidden = match c.take_first_str().as_deref() {
|
||||
self.pref.show_hidden = match c.take_first_str().as_deref() {
|
||||
Some("show") => true,
|
||||
Some("hide") => false,
|
||||
_ => !self.conf.show_hidden,
|
||||
_ => !self.pref.show_hidden,
|
||||
};
|
||||
|
||||
let hovered = self.hovered().map(|f| f.url_owned());
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tab::Tab;
|
|||
|
||||
impl Tab {
|
||||
pub fn linemode(&mut self, mut c: Cmd) {
|
||||
render!(self.conf.patch(|new| {
|
||||
render!(self.pref.patch(|new| {
|
||||
let Some(mode) = c.take_first_str() else {
|
||||
return;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ yazi_macro::mod_flat!(
|
|||
select_all
|
||||
shell
|
||||
sort
|
||||
spot
|
||||
toggle
|
||||
toggle_all
|
||||
update_peeked
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl Tab {
|
|||
}
|
||||
|
||||
let cwd = self.cwd().to_search(&opt.subject);
|
||||
let hidden = self.conf.show_hidden;
|
||||
let hidden = self.pref.show_hidden;
|
||||
|
||||
self.search = Some(tokio::spawn(async move {
|
||||
let rx = if opt.via == SearchOptVia::Rg {
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ use crate::{tab::Tab, tasks::Tasks};
|
|||
|
||||
impl Tab {
|
||||
pub fn sort(&mut self, mut c: Cmd, tasks: &Tasks) {
|
||||
let conf = &mut self.conf;
|
||||
let pref = &mut self.pref;
|
||||
if let Some(by) = c.take_first_str() {
|
||||
conf.sort_by = SortBy::from_str(&by).unwrap_or_default();
|
||||
pref.sort_by = SortBy::from_str(&by).unwrap_or_default();
|
||||
}
|
||||
|
||||
conf.sort_reverse = c.maybe_bool("reverse").unwrap_or(conf.sort_reverse);
|
||||
conf.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(conf.sort_dir_first);
|
||||
conf.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(conf.sort_sensitive);
|
||||
conf.sort_translit = c.maybe_bool("translit").unwrap_or(conf.sort_translit);
|
||||
pref.sort_reverse = c.maybe_bool("reverse").unwrap_or(pref.sort_reverse);
|
||||
pref.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(pref.sort_dir_first);
|
||||
pref.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(pref.sort_sensitive);
|
||||
pref.sort_translit = c.maybe_bool("translit").unwrap_or(pref.sort_translit);
|
||||
|
||||
self.apply_files_attrs();
|
||||
ManagerProxy::update_paged();
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
use yazi_shared::event::{Cmd, Data};
|
||||
|
||||
use crate::tab::Tab;
|
||||
|
||||
struct Opt {
|
||||
skip: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self { Self { skip: c.first().and_then(Data::as_usize) } }
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
#[yazi_codegen::command]
|
||||
pub fn spot(&mut self, c: Cmd) {
|
||||
let Some(hovered) = self.hovered().cloned() else {
|
||||
return self.preview.reset();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
use yazi_plugin::utils::PreviewLock;
|
||||
use yazi_macro::render;
|
||||
use yazi_plugin::utils::SpotLock;
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::tab::Tab;
|
||||
|
||||
pub struct Opt {
|
||||
lock: PreviewLock,
|
||||
lock: SpotLock,
|
||||
}
|
||||
|
||||
impl TryFrom<Cmd> for Opt {
|
||||
|
|
@ -17,6 +18,20 @@ impl TryFrom<Cmd> for Opt {
|
|||
|
||||
impl Tab {
|
||||
pub fn update_spotted(&mut self, opt: impl TryInto<Opt>) {
|
||||
todo!();
|
||||
let Some(hovered) = self.hovered().map(|h| &h.url) else {
|
||||
return self.preview.reset();
|
||||
};
|
||||
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if opt.lock.url != *hovered {
|
||||
return;
|
||||
}
|
||||
|
||||
self.spot.skip = opt.lock.selected().unwrap_or_default();
|
||||
self.spot.lock = Some(opt.lock);
|
||||
render!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
yazi_macro::mod_pub!(commands);
|
||||
|
||||
yazi_macro::mod_flat!(backstack config finder history mode preview selected tab);
|
||||
yazi_macro::mod_flat!(backstack preference finder history mode preview selected tab);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use yazi_config::{MANAGER, manager::SortBy};
|
|||
use yazi_fs::FilesSorter;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
pub struct Preference {
|
||||
// Sorting
|
||||
pub sort_by: SortBy,
|
||||
pub sort_sensitive: bool,
|
||||
|
|
@ -15,7 +15,7 @@ pub struct Config {
|
|||
pub show_hidden: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
impl Default for Preference {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Sorting
|
||||
|
|
@ -32,7 +32,7 @@ impl Default for Config {
|
|||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
impl Preference {
|
||||
pub(super) fn patch<F: FnOnce(&mut Self)>(&mut self, f: F) -> bool {
|
||||
let old = self.clone();
|
||||
f(self);
|
||||
|
|
@ -9,13 +9,13 @@ use yazi_fs::{Folder, FolderStage};
|
|||
use yazi_macro::render;
|
||||
use yazi_shared::{Id, Ids, fs::{File, Url}};
|
||||
|
||||
use super::{Backstack, Config, Finder, History, Mode, Preview};
|
||||
use crate::tab::Selected;
|
||||
use super::{Backstack, Finder, History, Mode, Preference, Preview};
|
||||
use crate::{spot::Spot, tab::Selected};
|
||||
|
||||
pub struct Tab {
|
||||
pub id: Id,
|
||||
pub mode: Mode,
|
||||
pub conf: Config,
|
||||
pub pref: Preference,
|
||||
pub current: Folder,
|
||||
pub parent: Option<Folder>,
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ pub struct Tab {
|
|||
pub history: History,
|
||||
pub selected: Selected,
|
||||
|
||||
pub spot: Spot,
|
||||
pub preview: Preview,
|
||||
pub finder: Option<Finder>,
|
||||
pub search: Option<JoinHandle<Result<()>>>,
|
||||
|
|
@ -35,7 +36,7 @@ impl Default for Tab {
|
|||
Self {
|
||||
id: IDS.next(),
|
||||
mode: Default::default(),
|
||||
conf: Default::default(),
|
||||
pref: Default::default(),
|
||||
current: Default::default(),
|
||||
parent: Default::default(),
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ impl Default for Tab {
|
|||
history: Default::default(),
|
||||
selected: Default::default(),
|
||||
|
||||
spot: Default::default(),
|
||||
preview: Default::default(),
|
||||
finder: Default::default(),
|
||||
search: Default::default(),
|
||||
|
|
@ -123,8 +125,8 @@ impl Tab {
|
|||
}
|
||||
|
||||
let hovered = f.hovered().filter(|_| f.tracing).map(|h| h.urn_owned());
|
||||
f.files.set_show_hidden(self.conf.show_hidden);
|
||||
f.files.set_sorter(self.conf.sorter());
|
||||
f.files.set_show_hidden(self.pref.show_hidden);
|
||||
f.files.set_sorter(self.pref.sorter());
|
||||
|
||||
render!(f.files.catchup_revision());
|
||||
render!(f.repos(hovered.as_ref().map(|u| u.as_urn())));
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl App {
|
|||
let frame = term
|
||||
.draw_partial(|f| {
|
||||
_ = Lives::scope(&self.cx, || {
|
||||
f.render_widget(crate::tasks::Progress, f.area());
|
||||
f.render_widget(crate::tasks::Progress::new(&self.cx), f.area());
|
||||
f.render_widget(crate::notify::Notify::new(&self.cx), f.area());
|
||||
Ok(())
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ impl From<()> for Opt {
|
|||
impl App {
|
||||
#[yazi_codegen::command]
|
||||
pub fn resize(&mut self, _: Opt) {
|
||||
self.cx.manager.active_mut().preview.reset();
|
||||
self.cx.active_mut().preview.reset();
|
||||
self.reflow(());
|
||||
|
||||
self.cx.manager.current_mut().sync_page(true);
|
||||
self.cx.current_mut().sync_page(true);
|
||||
self.cx.manager.hover(None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{Term, app::App};
|
|||
|
||||
impl App {
|
||||
pub(crate) fn resume(&mut self, _: Cmd) {
|
||||
self.cx.manager.active_mut().preview.reset_image();
|
||||
self.cx.active_mut().preview.reset_image();
|
||||
self.term = Some(Term::start().unwrap());
|
||||
|
||||
// While the app resumes, it's possible that the terminal size has changed.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ impl From<Cmd> for Opt {
|
|||
impl App {
|
||||
#[yazi_codegen::command]
|
||||
pub fn stop(&mut self, opt: Opt) {
|
||||
self.cx.manager.active_mut().preview.reset_image();
|
||||
self.cx.active_mut().preview.reset_image();
|
||||
|
||||
// We need to destroy the `term` first before stopping the `signals`
|
||||
// to prevent any signal from triggering the term to render again
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use ratatui::layout::Rect;
|
||||
use yazi_core::{completion::Completion, confirm::Confirm, help::Help, input::Input, manager::Manager, notify::Notify, pick::Pick, tasks::Tasks, which::Which};
|
||||
use yazi_core::{completion::Completion, confirm::Confirm, help::Help, input::Input, manager::Manager, notify::Notify, pick::Pick, tab::Tab, tasks::Tasks, which::Which};
|
||||
use yazi_fs::Folder;
|
||||
|
||||
pub struct Ctx {
|
||||
pub manager: Manager,
|
||||
|
|
@ -40,3 +41,17 @@ impl Ctx {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
#[inline]
|
||||
pub fn active(&self) -> &Tab { self.manager.active() }
|
||||
|
||||
#[inline]
|
||||
pub fn active_mut(&mut self) -> &mut Tab { self.manager.active_mut() }
|
||||
|
||||
#[inline]
|
||||
pub fn current(&self) -> &Folder { self.manager.current() }
|
||||
|
||||
#[inline]
|
||||
pub fn current_mut(&mut self) -> &mut Folder { self.manager.current_mut() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ impl<'a> Executor<'a> {
|
|||
Layer::App => self.app(cmd),
|
||||
Layer::Manager => self.manager(cmd),
|
||||
Layer::Tasks => self.tasks(cmd),
|
||||
Layer::Spot => self.spot(cmd),
|
||||
Layer::Pick => self.pick(cmd),
|
||||
Layer::Input => self.input(cmd),
|
||||
Layer::Confirm => self.confirm(cmd),
|
||||
|
|
@ -79,7 +80,7 @@ impl<'a> Executor<'a> {
|
|||
on!(MANAGER, hover);
|
||||
on!(MANAGER, peek);
|
||||
on!(MANAGER, seek);
|
||||
on!(ACTIVE, spot);
|
||||
on!(MANAGER, spot);
|
||||
on!(MANAGER, refresh, &self.app.cx.tasks);
|
||||
on!(MANAGER, quit, &self.app.cx.tasks);
|
||||
on!(MANAGER, close, &self.app.cx.tasks);
|
||||
|
|
@ -182,6 +183,29 @@ impl<'a> Executor<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn spot(&mut self, cmd: Cmd) {
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
if cmd.name == stringify!($name) {
|
||||
return self.app.cx.active_mut().spot.$name(cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
on!(arrow);
|
||||
on!(close);
|
||||
on!(swipe);
|
||||
on!(copy);
|
||||
|
||||
match cmd.name.as_str() {
|
||||
// Help
|
||||
"help" => self.app.cx.help.toggle(Layer::Spot),
|
||||
// Plugin
|
||||
"plugin" => self.app.plugin(cmd),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn pick(&mut self, cmd: Cmd) {
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ impl UserData for File {
|
|||
});
|
||||
methods.add_method("found", |lua, me, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|cx: &Ctx| {
|
||||
let Some(finder) = &cx.manager.active().finder else {
|
||||
let Some(finder) = &cx.active().finder else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ impl UserData for File {
|
|||
});
|
||||
methods.add_method("highlights", |lua, me, ()| {
|
||||
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|cx: &Ctx| {
|
||||
let Some(finder) = &cx.manager.active().finder else {
|
||||
let Some(finder) = &cx.active().finder else {
|
||||
return None;
|
||||
};
|
||||
if me.folder().url != me.tab().current.url {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl Lives {
|
|||
LUA.globals().raw_set(
|
||||
"cx",
|
||||
LUA.create_table_from([
|
||||
("active", super::Tab::make(cx.manager.active())?),
|
||||
("active", super::Tab::make(cx.active())?),
|
||||
("tabs", super::Tabs::make(&cx.manager.tabs)?),
|
||||
("tasks", super::Tasks::make(&cx.tasks)?),
|
||||
("yanked", super::Yanked::make(&cx.manager.yanked)?),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(
|
||||
config file files filter finder folder iter lives mode preview selected tab tabs
|
||||
preference file files filter finder folder iter lives mode preview selected tab tabs
|
||||
tasks yanked
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,24 +4,24 @@ use mlua::{AnyUserData, UserData, UserDataFields};
|
|||
|
||||
use super::Lives;
|
||||
|
||||
pub(super) struct Config {
|
||||
inner: *const yazi_core::tab::Config,
|
||||
pub(super) struct Preference {
|
||||
inner: *const yazi_core::tab::Preference,
|
||||
}
|
||||
|
||||
impl Deref for Config {
|
||||
type Target = yazi_core::tab::Config;
|
||||
impl Deref for Preference {
|
||||
type Target = yazi_core::tab::Preference;
|
||||
|
||||
fn deref(&self) -> &Self::Target { unsafe { &*self.inner } }
|
||||
}
|
||||
|
||||
impl Config {
|
||||
impl Preference {
|
||||
#[inline]
|
||||
pub(super) fn make(inner: &yazi_core::tab::Config) -> mlua::Result<AnyUserData> {
|
||||
pub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {
|
||||
Lives::scoped_userdata(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Config {
|
||||
impl UserData for Preference {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("sort_by", |_, me| Ok(me.sort_by.to_string()));
|
||||
fields.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
|
||||
|
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
|||
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods};
|
||||
use yazi_plugin::url::UrlRef;
|
||||
|
||||
use super::{Config, Finder, Folder, Lives, Mode, Preview, Selected};
|
||||
use super::{Finder, Folder, Lives, Mode, Preference, Preview, Selected};
|
||||
|
||||
pub(super) struct Tab {
|
||||
inner: *const yazi_core::tab::Tab,
|
||||
|
|
@ -26,7 +26,29 @@ impl UserData for Tab {
|
|||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, me| Ok(me.id.get()));
|
||||
fields.add_field_method_get("mode", |_, me| Mode::make(&me.mode));
|
||||
fields.add_field_method_get("conf", |_, me| Config::make(&me.conf));
|
||||
// TODO: remove `conf` once v0.4 is released
|
||||
fields.add_field_method_get("conf", |lua, me| {
|
||||
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||||
let id = match lua.named_registry_value::<yazi_plugin::RtRef>("rt")?.current() {
|
||||
Some(id) => format!("`{id}.yazi` plugin"),
|
||||
None => "`init.lua` config".to_owned(),
|
||||
};
|
||||
let s = "The `conf` property of `tab::Tab` has been deprecated in Yazi v0.4.
|
||||
|
||||
Please use `pref` instead, e.g. `cx.active.conf` => `cx.active.pref`, in your {id}.";
|
||||
yazi_proxy::AppProxy::notify(yazi_proxy::options::NotifyOpt {
|
||||
title: "Deprecated API".to_owned(),
|
||||
content: s.replace("{id}", &id),
|
||||
level: yazi_proxy::options::NotifyLevel::Warn,
|
||||
timeout: std::time::Duration::from_secs(20),
|
||||
});
|
||||
}
|
||||
|
||||
Preference::make(&me.pref)
|
||||
});
|
||||
fields.add_field_method_get("pref", |_, me| Preference::make(&me.pref));
|
||||
fields.add_field_method_get("current", |_, me| Folder::make(None, &me.current, me));
|
||||
fields.add_field_method_get("parent", |_, me| {
|
||||
me.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#[global_allocator]
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
yazi_macro::mod_pub!(app completion confirm help input lives manager notify pick tasks which);
|
||||
yazi_macro::mod_pub!(app completion confirm help input lives manager notify pick spot tasks which);
|
||||
|
||||
yazi_macro::mod_flat!(context executor logs panic root router signals term);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use ratatui::{buffer::Buffer, widgets::Widget};
|
||||
use yazi_config::LAYOUT;
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
|
|
@ -12,18 +13,17 @@ impl<'a> Preview<'a> {
|
|||
}
|
||||
|
||||
impl Widget for Preview<'_> {
|
||||
fn render(self, area: ratatui::layout::Rect, buf: &mut Buffer) {
|
||||
let preview = &self.cx.manager.active().preview;
|
||||
let Some(lock) = &preview.lock else {
|
||||
fn render(self, _: ratatui::layout::Rect, buf: &mut Buffer) {
|
||||
let Some(lock) = &self.cx.active().preview.lock else {
|
||||
return;
|
||||
};
|
||||
|
||||
if (lock.window.rows, lock.window.cols) != (area.height, area.width) {
|
||||
if *lock.area != LAYOUT.get().preview {
|
||||
return;
|
||||
}
|
||||
|
||||
for w in &lock.data {
|
||||
w.clone_render(buf);
|
||||
w.clone().render(buf, |p| self.cx.manager.area(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use mlua::{ObjectLike, Table};
|
||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||
use tracing::error;
|
||||
use yazi_plugin::{LUA, elements::render_widgets};
|
||||
use yazi_plugin::{LUA, elements::render_once};
|
||||
|
||||
use super::{completion, confirm, help, input, manager, pick, tasks, which};
|
||||
use super::{completion, confirm, help, input, manager, pick, spot, tasks, which};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(super) struct Root<'a> {
|
||||
|
|
@ -26,7 +26,7 @@ impl Widget for Root<'_> {
|
|||
let area = yazi_plugin::elements::Rect::from(area);
|
||||
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
|
||||
|
||||
render_widgets(root.call_method("redraw", ())?, buf);
|
||||
render_once(root.call_method("redraw", ())?, buf, |p| self.cx.manager.area(p));
|
||||
Ok::<_, mlua::Error>(())
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
|
|
@ -39,6 +39,10 @@ impl Widget for Root<'_> {
|
|||
tasks::Tasks::new(self.cx).render(area, buf);
|
||||
}
|
||||
|
||||
if self.cx.active().spot.visible() {
|
||||
spot::Spot::new(self.cx).render(area, buf);
|
||||
}
|
||||
|
||||
if self.cx.pick.visible {
|
||||
pick::Pick::new(self.cx).render(area, buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ impl<'a> Router<'a> {
|
|||
self.matches(Layer::Confirm, key)
|
||||
} else if cx.pick.visible {
|
||||
self.matches(Layer::Pick, key)
|
||||
} else if cx.active().spot.visible() {
|
||||
self.matches(Layer::Spot, key)
|
||||
} else if cx.tasks.visible {
|
||||
self.matches(Layer::Tasks, key)
|
||||
} else {
|
||||
|
|
|
|||
1
yazi-fm/src/spot/mod.rs
Normal file
1
yazi-fm/src/spot/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(spot);
|
||||
23
yazi-fm/src/spot/spot.rs
Normal file
23
yazi-fm/src/spot/spot.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Spot<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Spot<'a> {
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl Widget for Spot<'_> {
|
||||
fn render(self, _: Rect, buf: &mut Buffer) {
|
||||
let Some(lock) = &self.cx.active().spot.lock else {
|
||||
return;
|
||||
};
|
||||
|
||||
for w in &lock.data {
|
||||
w.clone().render(buf, |p| self.cx.manager.area(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,26 @@ use mlua::{ObjectLike, Table};
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||
use tracing::error;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_plugin::{LUA, elements::render_widgets};
|
||||
use yazi_plugin::{LUA, elements::render_once};
|
||||
|
||||
pub(crate) struct Progress;
|
||||
use crate::Ctx;
|
||||
|
||||
impl Widget for Progress {
|
||||
pub(crate) struct Progress<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Progress<'a> {
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl Widget for Progress<'_> {
|
||||
fn render(self, _: Rect, buf: &mut Buffer) {
|
||||
let mut f = || {
|
||||
let area = yazi_plugin::elements::Rect::from(LAYOUT.get().progress);
|
||||
let progress =
|
||||
LUA.globals().raw_get::<Table>("Progress")?.call_method::<Table>("use", area)?;
|
||||
|
||||
render_widgets(progress.call_method("redraw", ())?, buf);
|
||||
render_once(progress.call_method("redraw", ())?, buf, |p| self.cx.manager.area(p));
|
||||
Ok::<_, mlua::Error>(())
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function Linemode:new(file) return setmetatable({ _file = file }, { __index = se
|
|||
function Linemode:space() return ui.Line(" ") end
|
||||
|
||||
function Linemode:solo()
|
||||
local mode = cx.active.conf.linemode
|
||||
local mode = cx.active.pref.linemode
|
||||
if mode == "none" or mode == "solo" then
|
||||
return ui.Line("")
|
||||
elseif not self[mode] then
|
||||
|
|
|
|||
|
|
@ -47,17 +47,17 @@ end
|
|||
|
||||
-- Mouse events
|
||||
function Root:click(event, up)
|
||||
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self._children)
|
||||
return c and c:click(event, up)
|
||||
end
|
||||
|
||||
function Root:scroll(event, step)
|
||||
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self._children)
|
||||
return c and c:scroll(event, step)
|
||||
end
|
||||
|
||||
function Root:touch(event, step)
|
||||
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self._children)
|
||||
return c and c:touch(event, step)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function Status:name()
|
|||
return ui.Line {}
|
||||
end
|
||||
|
||||
return ui.Line(" " .. h.name)
|
||||
return ui.Line(" " .. h.name:gsub("\r", "?", 1))
|
||||
end
|
||||
|
||||
function Status:perm()
|
||||
|
|
|
|||
|
|
@ -47,16 +47,16 @@ end
|
|||
|
||||
-- Mouse events
|
||||
function Tab:click(event, up)
|
||||
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self._children)
|
||||
return c and c:click(event, up)
|
||||
end
|
||||
|
||||
function Tab:scroll(event, step)
|
||||
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self._children)
|
||||
return c and c:scroll(event, step)
|
||||
end
|
||||
|
||||
function Tab:touch(event, step)
|
||||
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self._children)
|
||||
return c and c:touch(event, step)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@ function M:seek(units)
|
|||
})
|
||||
end
|
||||
|
||||
function M:spot(args) require("file"):spot(args) end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -16,13 +16,63 @@ end
|
|||
|
||||
function M:seek() end
|
||||
|
||||
function M:spot(skip)
|
||||
local rect = ui.Rect { x = 10, y = 10, w = 20, h = 20 }
|
||||
-- TODO: remove this
|
||||
local hovered_mime = ya.sync(function()
|
||||
local h = cx.active.current.hovered
|
||||
if not h then
|
||||
return nil
|
||||
elseif h.cha.is_dir then
|
||||
return "inode/directory"
|
||||
else
|
||||
return h:mime()
|
||||
end
|
||||
end)
|
||||
|
||||
ya.spot_widgets(self, {
|
||||
ui.Clear(rect),
|
||||
ui.Table(rect),
|
||||
})
|
||||
function M:spot(args)
|
||||
local mime = hovered_mime()
|
||||
if not mime then
|
||||
return
|
||||
end
|
||||
|
||||
local file = args.file
|
||||
local spotter = PLUGIN.spotter(file.url, mime)
|
||||
local previewer = PLUGIN.previewer(file.url, mime)
|
||||
local fetchers = PLUGIN.fetchers(file.url, mime)
|
||||
local preloaders = PLUGIN.preloaders(file.url, mime)
|
||||
|
||||
for i, v in ipairs(fetchers) do
|
||||
fetchers[i] = v.cmd
|
||||
end
|
||||
for i, v in ipairs(preloaders) do
|
||||
preloaders[i] = v.cmd
|
||||
end
|
||||
|
||||
local rows = {}
|
||||
local row = function(key, value)
|
||||
local h = type(value) == "table" and #value or 1
|
||||
rows[#rows + 1] = ui.Row({ key, value }):height(h)
|
||||
end
|
||||
|
||||
rows[#rows + 1] = ui.Row({ "Metadata", "" }):style(ui.Style():fg("red"))
|
||||
row(" Created:", file.cha.btime and os.date("%y/%m/%d %H:%M", math.floor(file.cha.btime)) or "-")
|
||||
row(" Modified:", file.cha.mtime and os.date("%y/%m/%d %H:%M", math.floor(file.cha.mtime)) or "-")
|
||||
row(" Mimetype:", mime)
|
||||
rows[#rows + 1] = ui.Row({ { "", "Plugins" }, "" }):height(2):style(ui.Style():fg("red"))
|
||||
row(" Spotter:", spotter and spotter.cmd or "-")
|
||||
row(" Previewer:", previewer and previewer.cmd or "-")
|
||||
row(" Fetchers:", #fetchers ~= 0 and fetchers or "-")
|
||||
row(" Preloaders:", #preloaders ~= 0 and preloaders or "-")
|
||||
|
||||
ya.spot_table(
|
||||
args,
|
||||
ui.Table(rows)
|
||||
:area(ui.Pos { "center", w = 60, h = 20 })
|
||||
:row(1)
|
||||
:col(1)
|
||||
:col_style(ui.Style():fg("blue"))
|
||||
:cell_style(ui.Style():fg("yellow"):reverse())
|
||||
:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -40,4 +40,6 @@ function M:seek(units)
|
|||
end
|
||||
end
|
||||
|
||||
function M:spot(args) require("file"):spot(args) end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -22,4 +22,30 @@ function M:preload()
|
|||
return ya.image_precache(self.file.url, cache) and 1 or 2
|
||||
end
|
||||
|
||||
function M:spot(args)
|
||||
local info = ya.image_info(args.file.url)
|
||||
|
||||
local rows = {}
|
||||
local row = function(key, value)
|
||||
local h = type(value) == "table" and #value or 1
|
||||
rows[#rows + 1] = ui.Row({ key, value }):height(h)
|
||||
end
|
||||
|
||||
row("Format:", tostring(info.format))
|
||||
row("Width:", string.format("%dpx", info.w))
|
||||
row("Height:", string.format("%dpx", info.h))
|
||||
row("Color:", tostring(info.color))
|
||||
|
||||
ya.spot_table(
|
||||
args,
|
||||
ui.Table(rows)
|
||||
:area(ui.Pos { "center", w = 60, h = 20 })
|
||||
:row(args.skip)
|
||||
:col(1)
|
||||
:col_style(ui.Style():fg("blue"))
|
||||
:cell_style(ui.Style():fg("yellow"):reverse())
|
||||
:widths { ui.Constraint.Length(12), ui.Constraint.Fill(1) }
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -41,4 +41,6 @@ function M:preload()
|
|||
return status and status.success and 1 or 2
|
||||
end
|
||||
|
||||
function M:spot(args) require("file"):spot(args) end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ local function match_mimetype(s)
|
|||
end
|
||||
end
|
||||
|
||||
function M:fetch()
|
||||
function M:fetch(args)
|
||||
local urls = {}
|
||||
for _, file in ipairs(self.files) do
|
||||
for _, file in ipairs(args.files) do
|
||||
urls[#urls + 1] = tostring(file.url)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -62,4 +62,6 @@ function M:preload()
|
|||
return status and status.success and 1 or 2
|
||||
end
|
||||
|
||||
function M:spot(args) require("file"):spot(args) end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ function ya.readable_path(path)
|
|||
end
|
||||
end
|
||||
|
||||
function ya.child_at(position, children)
|
||||
function ya.child_at(pos, children)
|
||||
for i = #children, 1, -1 do
|
||||
if children[i]._area:contains(position) then
|
||||
if children[i]._area:contains(pos) then
|
||||
return children[i]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
83
yazi-plugin/src/bindings/image.rs
Normal file
83
yazi-plugin/src/bindings/image.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{MetaMethod, UserData};
|
||||
|
||||
pub struct ImageInfo(yazi_adapter::ImageInfo);
|
||||
|
||||
impl Deref for ImageInfo {
|
||||
type Target = yazi_adapter::ImageInfo;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<yazi_adapter::ImageInfo> for ImageInfo {
|
||||
fn from(value: yazi_adapter::ImageInfo) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl UserData for ImageInfo {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("w", |_, me| Ok(me.width));
|
||||
fields.add_field_method_get("h", |_, me| Ok(me.height));
|
||||
fields.add_field_method_get("ori", |_, me| Ok(me.orientation.map(|o| o.to_exif())));
|
||||
fields.add_field_method_get("format", |_, me| Ok(ImageFormat(me.format)));
|
||||
fields.add_field_method_get("color", |_, me| Ok(ImageColor(me.color)));
|
||||
}
|
||||
}
|
||||
|
||||
// --- ImageFormat
|
||||
struct ImageFormat(yazi_adapter::ImageFormat);
|
||||
|
||||
impl UserData for ImageFormat {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, me, ()| {
|
||||
use yazi_adapter::ImageFormat as F;
|
||||
|
||||
Ok(match me.0 {
|
||||
F::Png => "PNG",
|
||||
F::Jpeg => "JPEG",
|
||||
F::Gif => "GIF",
|
||||
F::WebP => "WEBP",
|
||||
F::Pnm => "PNM",
|
||||
F::Tiff => "TIFF",
|
||||
F::Tga => "TGA",
|
||||
F::Dds => "DDS",
|
||||
F::Bmp => "BMP",
|
||||
F::Ico => "ICO",
|
||||
F::Hdr => "HDR",
|
||||
F::OpenExr => "OpenEXR",
|
||||
F::Farbfeld => "Farbfeld",
|
||||
F::Avif => "AVIF",
|
||||
F::Qoi => "QOI",
|
||||
F::Pcx => "PCX",
|
||||
_ => "Unknown",
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- ImageColor
|
||||
struct ImageColor(yazi_adapter::ImageColor);
|
||||
|
||||
impl UserData for ImageColor {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, me, ()| {
|
||||
use yazi_adapter::ImageColor as C;
|
||||
|
||||
Ok(match me.0 {
|
||||
C::L8 => "L8",
|
||||
C::La8 => "La8",
|
||||
C::Rgb8 => "Rgb8",
|
||||
C::Rgba8 => "Rgba8",
|
||||
|
||||
C::L16 => "L16",
|
||||
C::La16 => "La16",
|
||||
C::Rgb16 => "Rgb16",
|
||||
C::Rgba16 => "Rgba16",
|
||||
|
||||
C::Rgb32F => "Rgb32F",
|
||||
C::Rgba32F => "Rgba32F",
|
||||
_ => "Unknown",
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(bindings cha icon input mouse permit position range window);
|
||||
yazi_macro::mod_flat!(bindings cha icon image input mouse permit range window);
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
use std::{ops::Deref, str::FromStr};
|
||||
|
||||
use mlua::{ExternalResult, IntoLua, Lua};
|
||||
|
||||
pub struct Position(yazi_config::popup::Position);
|
||||
|
||||
impl Deref for Position {
|
||||
type Target = yazi_config::popup::Position;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<Position> for yazi_config::popup::Position {
|
||||
fn from(value: Position) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl TryFrom<mlua::Table> for Position {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(t: mlua::Table) -> Result<Self, Self::Error> {
|
||||
use yazi_config::popup::{Offset, Origin, Position};
|
||||
|
||||
Ok(Self(Position {
|
||||
origin: Origin::from_str(&t.raw_get::<mlua::String>(1)?.to_str()?).into_lua_err()?,
|
||||
offset: Offset {
|
||||
x: t.raw_get("x").unwrap_or_default(),
|
||||
y: t.raw_get("y").unwrap_or_default(),
|
||||
width: t.raw_get("w")?,
|
||||
height: t.raw_get("h").unwrap_or(3),
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for Position {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<mlua::Value> {
|
||||
lua
|
||||
.create_table_from([
|
||||
(1.into_lua(lua)?, self.origin.to_string().into_lua(lua)?),
|
||||
("x".into_lua(lua)?, self.offset.x.into_lua(lua)?),
|
||||
("y".into_lua(lua)?, self.offset.y.into_lua(lua)?),
|
||||
("w".into_lua(lua)?, self.offset.width.into_lua(lua)?),
|
||||
("h".into_lua(lua)?, self.offset.height.into_lua(lua)?),
|
||||
])?
|
||||
.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,13 +9,24 @@ pub struct Window {
|
|||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Default for Window {
|
||||
fn default() -> Self {
|
||||
impl Window {
|
||||
pub fn get() -> Self {
|
||||
let ws = Dimension::available();
|
||||
Self { rows: ws.rows, cols: ws.columns, width: ws.width, height: ws.height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Window> for crossterm::terminal::WindowSize {
|
||||
fn from(value: Window) -> Self {
|
||||
crossterm::terminal::WindowSize {
|
||||
rows: value.rows,
|
||||
columns: value.cols,
|
||||
width: value.width,
|
||||
height: value.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Window {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("rows", |_, me| Ok(me.rows));
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
use mlua::AnyUserData;
|
||||
|
||||
use crate::elements::Renderable;
|
||||
|
||||
pub fn cast_to_renderable(ud: &AnyUserData) -> Option<Box<dyn Renderable + Send>> {
|
||||
if let Ok(c) = ud.take::<crate::elements::Text>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::List>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Bar>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Clear>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Border>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Gauge>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Table>() {
|
||||
Some(Box::new(c))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
use mlua::{Lua, LuaSerdeExt, SerializeOptions};
|
||||
use mlua::{IntoLua, Lua, LuaSerdeExt, SerializeOptions, Table, Value};
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_config::{MANAGER, PREVIEW, THEME};
|
||||
|
||||
use super::Plugin;
|
||||
|
||||
const OPTIONS: SerializeOptions =
|
||||
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
|
||||
|
||||
|
|
@ -31,4 +33,26 @@ impl<'a> Config<'a> {
|
|||
self.lua.globals().raw_set("PREVIEW", self.lua.to_value_with(&*PREVIEW, OPTIONS)?)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn install_plugin(self) -> mlua::Result<Self> {
|
||||
let index = self.lua.create_function(|lua, (ts, key): (Table, mlua::String)| {
|
||||
let value = match key.as_bytes().as_ref() {
|
||||
b"fetchers" => Plugin::fetchers(lua)?,
|
||||
b"spotter" => Plugin::spotter(lua)?,
|
||||
b"preloaders" => Plugin::preloaders(lua)?,
|
||||
b"previewer" => Plugin::previewer(lua)?,
|
||||
_ => return Ok(Value::Nil),
|
||||
}
|
||||
.into_lua(lua)?;
|
||||
|
||||
ts.raw_set(key, value.clone())?;
|
||||
Ok(value)
|
||||
})?;
|
||||
|
||||
let fetcher = self.lua.create_table()?;
|
||||
fetcher.set_metatable(Some(self.lua.create_table_from([("__index", index)])?));
|
||||
|
||||
self.lua.globals().raw_set("PLUGIN", fetcher)?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
1
yazi-plugin/src/config/mod.rs
Normal file
1
yazi-plugin/src/config/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(config plugin);
|
||||
72
yazi-plugin/src/config/plugin.rs
Normal file
72
yazi-plugin/src/config/plugin.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use mlua::{Function, Lua, UserData};
|
||||
use yazi_config::PLUGIN;
|
||||
|
||||
use crate::url::UrlRef;
|
||||
|
||||
pub(super) struct Plugin;
|
||||
|
||||
impl Plugin {
|
||||
pub(super) fn fetchers(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, (url, mime): (UrlRef, mlua::String)| {
|
||||
let factors = |s: &str| match s {
|
||||
"mime" => !mime.as_bytes().is_empty(),
|
||||
_ => false,
|
||||
};
|
||||
lua.create_sequence_from(PLUGIN.fetchers(&url, &mime.to_str()?, factors).map(Fetcher))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn spotter(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, (url, mime): (UrlRef, mlua::String)| {
|
||||
Ok(PLUGIN.spotter(&url, &mime.to_str()?).map(Spotter))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn preloaders(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, (url, mime): (UrlRef, mlua::String)| {
|
||||
lua.create_sequence_from(PLUGIN.preloaders(&url, &mime.to_str()?).map(Preloader))
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn previewer(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, (url, mime): (UrlRef, mlua::String)| {
|
||||
Ok(PLUGIN.previewer(&url, &mime.to_str()?).map(Previewer))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fetcher
|
||||
struct Fetcher(&'static yazi_config::plugin::Fetcher);
|
||||
|
||||
impl UserData for Fetcher {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("cmd", |lua, me| lua.create_string(&me.0.run.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Spotter
|
||||
struct Spotter(&'static yazi_config::plugin::Spotter);
|
||||
|
||||
impl UserData for Spotter {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("cmd", |lua, me| lua.create_string(&me.0.run.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Preloader
|
||||
struct Preloader(&'static yazi_config::plugin::Preloader);
|
||||
|
||||
impl UserData for Preloader {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("cmd", |lua, me| lua.create_string(&me.0.run.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Previewer
|
||||
struct Previewer(&'static yazi_config::plugin::Previewer);
|
||||
|
||||
impl UserData for Previewer {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("cmd", |lua, me| lua.create_string(&me.0.run.name));
|
||||
}
|
||||
}
|
||||
92
yazi-plugin/src/elements/area.rs
Normal file
92
yazi-plugin/src/elements/area.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Value};
|
||||
|
||||
use super::{Pos, Rect};
|
||||
|
||||
const EXPECTED: &str = "expected a Pos or Rect";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Area {
|
||||
Pos(Pos),
|
||||
Rect(Rect),
|
||||
}
|
||||
|
||||
impl Default for Area {
|
||||
fn default() -> Self { Self::Rect(Default::default()) }
|
||||
}
|
||||
|
||||
impl Area {
|
||||
#[inline]
|
||||
pub fn size(self) -> ratatui::layout::Size {
|
||||
match self {
|
||||
Self::Pos(pos) => {
|
||||
ratatui::layout::Size { width: pos.offset.width, height: pos.offset.height }
|
||||
}
|
||||
Self::Rect(rect) => (*rect).into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner(self, padding: ratatui::widgets::Padding) -> Self {
|
||||
match self {
|
||||
Self::Pos(mut pos) => {
|
||||
pos.pad += padding;
|
||||
Self::Pos(pos)
|
||||
}
|
||||
Self::Rect(rect) => Self::Rect(rect.pad(padding.into())),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transform(
|
||||
self,
|
||||
f: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) -> ratatui::layout::Rect {
|
||||
match self {
|
||||
Self::Pos(pos) => *Rect(f(*pos)).pad(pos.pad),
|
||||
Self::Rect(rect) => *rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyUserData> for Area {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> {
|
||||
Ok(if let Ok(rect) = value.borrow::<Rect>() {
|
||||
Self::Rect(*rect)
|
||||
} else if let Ok(pos) = value.borrow::<Pos>() {
|
||||
Self::Pos(*pos)
|
||||
} else {
|
||||
return Err(EXPECTED.into_lua_err());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for Area {
|
||||
fn from_lua(value: Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Self::try_from(ud),
|
||||
_ => Err(EXPECTED.into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for Area {
|
||||
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
||||
match self {
|
||||
Self::Pos(pos) => pos.into_lua(lua),
|
||||
Self::Rect(rect) => rect.into_lua(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Area {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Pos(pos) => write!(f, "{:?}", **pos),
|
||||
Self::Rect(rect) => write!(f, "{:?}", **rect),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
use mlua::{AnyUserData, Lua, Table, UserData};
|
||||
use mlua::{Lua, Table, UserData};
|
||||
use ratatui::widgets::Borders;
|
||||
|
||||
use super::{Rect, Renderable};
|
||||
use super::Area;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Bar {
|
||||
area: Rect,
|
||||
area: Area,
|
||||
|
||||
direction: ratatui::widgets::Borders,
|
||||
symbol: String,
|
||||
|
|
@ -31,6 +31,50 @@ impl Bar {
|
|||
bar.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(bar)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
let rect = self.area.transform(trans);
|
||||
if rect.area() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let symbol = if !self.symbol.is_empty() {
|
||||
&self.symbol
|
||||
} else if self.direction.intersects(Borders::TOP | Borders::BOTTOM) {
|
||||
"─"
|
||||
} else if self.direction.intersects(Borders::LEFT | Borders::RIGHT) {
|
||||
"│"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
|
||||
if self.direction.contains(Borders::LEFT) {
|
||||
for y in rect.top()..rect.bottom() {
|
||||
buf[(rect.left(), y)].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
if self.direction.contains(Borders::TOP) {
|
||||
for x in rect.left()..rect.right() {
|
||||
buf[(x, rect.top())].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
if self.direction.contains(Borders::RIGHT) {
|
||||
let x = rect.right() - 1;
|
||||
for y in rect.top()..rect.bottom() {
|
||||
buf[(x, y)].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
if self.direction.contains(Borders::BOTTOM) {
|
||||
let y = rect.bottom() - 1;
|
||||
for x in rect.left()..rect.right() {
|
||||
buf[(x, y)].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Bar {
|
||||
|
|
@ -48,48 +92,3 @@ impl UserData for Bar {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Bar {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
if self.area.area() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let symbol = if !self.symbol.is_empty() {
|
||||
&self.symbol
|
||||
} else if self.direction.intersects(Borders::TOP | Borders::BOTTOM) {
|
||||
"─"
|
||||
} else if self.direction.intersects(Borders::LEFT | Borders::RIGHT) {
|
||||
"│"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
|
||||
if self.direction.contains(Borders::LEFT) {
|
||||
for y in self.area.top()..self.area.bottom() {
|
||||
buf[(self.area.left(), y)].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
if self.direction.contains(Borders::TOP) {
|
||||
for x in self.area.left()..self.area.right() {
|
||||
buf[(x, self.area.top())].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
if self.direction.contains(Borders::RIGHT) {
|
||||
let x = self.area.right() - 1;
|
||||
for y in self.area.top()..self.area.bottom() {
|
||||
buf[(x, y)].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
if self.direction.contains(Borders::BOTTOM) {
|
||||
let y = self.area.bottom() - 1;
|
||||
for x in self.area.left()..self.area.right() {
|
||||
buf[(x, y)].set_style(self.style).set_symbol(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(self.clone()).render(buf) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use mlua::{AnyUserData, Lua, Table, UserData};
|
||||
use mlua::{Lua, Table, UserData, Value};
|
||||
use ratatui::widgets::{Borders, Widget};
|
||||
|
||||
use super::{Rect, Renderable};
|
||||
use super::Area;
|
||||
use crate::elements::Line;
|
||||
|
||||
// Type
|
||||
const PLAIN: u8 = 0;
|
||||
|
|
@ -13,11 +14,13 @@ const QUADRANT_OUTSIDE: u8 = 5;
|
|||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Border {
|
||||
area: Rect,
|
||||
pub(crate) area: Area,
|
||||
|
||||
position: ratatui::widgets::Borders,
|
||||
type_: ratatui::widgets::BorderType,
|
||||
style: ratatui::style::Style,
|
||||
pub(crate) position: ratatui::widgets::Borders,
|
||||
pub(crate) type_: ratatui::widgets::BorderType,
|
||||
pub(crate) style: ratatui::style::Style,
|
||||
|
||||
pub(crate) titles: Vec<(ratatui::widgets::block::Position, ratatui::text::Line<'static>)>,
|
||||
}
|
||||
|
||||
impl Border {
|
||||
|
|
@ -49,6 +52,26 @@ impl Border {
|
|||
border.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(border)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
let mut block = ratatui::widgets::Block::default()
|
||||
.borders(self.position)
|
||||
.border_type(self.type_)
|
||||
.border_style(self.style);
|
||||
|
||||
for title in self.titles {
|
||||
block = match title {
|
||||
(ratatui::widgets::block::Position::Top, line) => block.title(line),
|
||||
(ratatui::widgets::block::Position::Bottom, line) => block.title(line),
|
||||
};
|
||||
}
|
||||
|
||||
block.render(self.area.transform(trans), buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Border {
|
||||
|
|
@ -56,10 +79,6 @@ impl UserData for Border {
|
|||
crate::impl_area_method!(methods);
|
||||
crate::impl_style_method!(methods, style);
|
||||
|
||||
methods.add_function_mut("position", |_, (ud, position): (AnyUserData, u8)| {
|
||||
ud.borrow_mut::<Self>()?.position = ratatui::widgets::Borders::from_bits_truncate(position);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("type", |_, (ud, value): (AnyUserData, u8)| {
|
||||
ud.borrow_mut::<Self>()?.type_ = match value {
|
||||
ROUNDED => ratatui::widgets::BorderType::Rounded,
|
||||
|
|
@ -71,19 +90,22 @@ impl UserData for Border {
|
|||
};
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut(
|
||||
"title",
|
||||
|_, (ud, line, position): (AnyUserData, Value, Option<u8>)| {
|
||||
let position = if position == Some(Borders::BOTTOM.bits()) {
|
||||
ratatui::widgets::block::Position::Bottom
|
||||
} else {
|
||||
ratatui::widgets::block::Position::Top
|
||||
};
|
||||
|
||||
ud.borrow_mut::<Self>()?.titles.push((position, Line::try_from(line)?.0));
|
||||
Ok(ud)
|
||||
},
|
||||
);
|
||||
methods.add_function_mut("position", |_, (ud, position): (AnyUserData, u8)| {
|
||||
ud.borrow_mut::<Self>()?.position = ratatui::widgets::Borders::from_bits_truncate(position);
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Border {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
ratatui::widgets::Block::default()
|
||||
.borders(self.position)
|
||||
.border_type(self.type_)
|
||||
.border_style(self.style)
|
||||
.render(*self.area, buf);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(self.clone()).render(buf); }
|
||||
}
|
||||
|
|
|
|||
20
yazi-plugin/src/elements/cell.rs
Normal file
20
yazi-plugin/src/elements/cell.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use mlua::{ExternalError, FromLua};
|
||||
|
||||
use super::Text;
|
||||
|
||||
const EXPECTED: &str = "expected a table of strings, Texts, Lines or Spans";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cell {
|
||||
pub(super) text: ratatui::text::Text<'static>,
|
||||
}
|
||||
|
||||
impl From<Cell> for ratatui::widgets::Cell<'static> {
|
||||
fn from(value: Cell) -> Self { Self::new(value.text) }
|
||||
}
|
||||
|
||||
impl FromLua for Cell {
|
||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||
Ok(Self { text: Text::try_from(value).map_err(|_| EXPECTED.into_lua_err())?.inner })
|
||||
}
|
||||
}
|
||||
|
|
@ -3,24 +3,32 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use mlua::{Lua, Table, UserData};
|
||||
use yazi_adapter::ADAPTOR;
|
||||
|
||||
use super::{Rect, Renderable};
|
||||
use super::Area;
|
||||
|
||||
pub static COLLISION: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Clear {
|
||||
pub area: Rect,
|
||||
pub area: Area,
|
||||
}
|
||||
|
||||
impl Clear {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let new = lua.create_function(|_, (_, area): (Table, Rect)| Ok(Clear { area }))?;
|
||||
let new = lua.create_function(|_, (_, area): (Table, Area)| Ok(Clear { area }))?;
|
||||
|
||||
let clear = lua.create_table()?;
|
||||
clear.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
Ok(clear)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
<Self as ratatui::widgets::Widget>::render(Default::default(), self.area.transform(trans), buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl ratatui::widgets::Widget for Clear {
|
||||
|
|
@ -44,16 +52,6 @@ impl ratatui::widgets::Widget for Clear {
|
|||
}
|
||||
}
|
||||
|
||||
impl Renderable for Clear {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
<Self as ratatui::widgets::Widget>::render(Default::default(), *self.area, buf);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(*self).render(buf); }
|
||||
}
|
||||
|
||||
impl UserData for Clear {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
crate::impl_area_method!(methods);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::{AnyUserData, IntoLua, Lua, Table, Value};
|
||||
use tracing::error;
|
||||
|
||||
use crate::cast_to_renderable;
|
||||
use super::Renderable;
|
||||
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let index = lua.create_function(|lua, (ts, key): (Table, mlua::String)| {
|
||||
|
|
@ -14,14 +14,16 @@ pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
|||
b"Layout" => super::Layout::compose(lua)?,
|
||||
b"Line" => super::Line::compose(lua)?,
|
||||
b"List" => super::List::compose(lua)?,
|
||||
b"Padding" => super::Padding::compose(lua)?,
|
||||
b"Pad" => super::Pad::compose(lua, true)?,
|
||||
// TODO: deprecate this
|
||||
b"Padding" => super::Pad::compose(lua, false)?,
|
||||
b"Paragraph" => super::Paragraph::compose(lua)?,
|
||||
b"Position" => super::Position::compose(lua)?,
|
||||
b"Pos" => super::Pos::compose(lua)?,
|
||||
b"Rect" => super::Rect::compose(lua)?,
|
||||
b"Row" => super::Row::compose(lua)?,
|
||||
b"Span" => super::Span::compose(lua)?,
|
||||
b"Style" => super::Style::compose(lua)?,
|
||||
b"Table" => super::Table::compose(lua)?,
|
||||
b"TableRow" => super::TableRow::compose(lua)?,
|
||||
b"Text" => super::Text::compose(lua)?,
|
||||
_ => return Ok(Value::Nil),
|
||||
}
|
||||
|
|
@ -37,24 +39,19 @@ pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
|||
Ok(ui)
|
||||
}
|
||||
|
||||
pub trait Renderable {
|
||||
fn area(&self) -> ratatui::layout::Rect;
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer);
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer);
|
||||
}
|
||||
|
||||
pub fn render_widgets(widgets: Table, buf: &mut ratatui::buffer::Buffer) {
|
||||
pub fn render_once<F>(widgets: Table, buf: &mut ratatui::buffer::Buffer, trans: F)
|
||||
where
|
||||
F: Fn(yazi_config::popup::Position) -> ratatui::layout::Rect + Copy,
|
||||
{
|
||||
for widget in widgets.sequence_values::<AnyUserData>() {
|
||||
let Ok(widget) = widget else {
|
||||
error!("Failed to convert to renderable UserData: {}", widget.unwrap_err());
|
||||
continue;
|
||||
};
|
||||
|
||||
match cast_to_renderable(&widget) {
|
||||
Some(w) => w.render(buf),
|
||||
None => error!("Only the UserData of renderable element is accepted: {widget:#?}"),
|
||||
match Renderable::try_from(widget) {
|
||||
Ok(w) => w.render(buf, trans),
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use mlua::{AnyUserData, ExternalError, Lua, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{ExternalError, Lua, Table, UserData, UserDataMethods, Value};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
use super::{Rect, Renderable, Span};
|
||||
use super::{Area, Span};
|
||||
use crate::elements::Style;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Gauge {
|
||||
area: Rect,
|
||||
area: Area,
|
||||
|
||||
ratio: f64,
|
||||
label: Option<ratatui::text::Span<'static>>,
|
||||
|
|
@ -23,6 +23,23 @@ impl Gauge {
|
|||
|
||||
Ok(gauge)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
let mut gauge = ratatui::widgets::Gauge::default()
|
||||
.ratio(self.ratio)
|
||||
.style(self.style)
|
||||
.gauge_style(self.gauge_style);
|
||||
|
||||
if let Some(s) = self.label {
|
||||
gauge = gauge.label(s)
|
||||
}
|
||||
|
||||
gauge.render(self.area.transform(trans), buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Gauge {
|
||||
|
|
@ -59,22 +76,3 @@ impl UserData for Gauge {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Gauge {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
let mut gauge = ratatui::widgets::Gauge::default()
|
||||
.ratio(self.ratio)
|
||||
.style(self.style)
|
||||
.gauge_style(self.gauge_style);
|
||||
|
||||
if let Some(s) = self.label {
|
||||
gauge = gauge.label(s)
|
||||
}
|
||||
|
||||
gauge.render(*self.area, buf);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(self.clone()).render(buf) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const LEFT: u8 = 0;
|
|||
const CENTER: u8 = 1;
|
||||
const RIGHT: u8 = 2;
|
||||
|
||||
const EXPECTED: &str = "expected a string, ui.Span, ui.Line, or a table of them";
|
||||
const EXPECTED: &str = "expected a string, Span, Line, or a table of them";
|
||||
|
||||
#[derive(Clone, FromLua)]
|
||||
pub struct Line(pub(super) ratatui::text::Line<'static>);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use mlua::{ExternalError, Lua, Table, UserData, Value};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
use super::{Rect, Renderable, Text};
|
||||
use super::{Area, Text};
|
||||
|
||||
const EXPECTED: &str = "expected a table of strings, Texts, Lines or Spans";
|
||||
|
||||
// --- List
|
||||
#[derive(Clone, Default)]
|
||||
pub struct List {
|
||||
area: Rect,
|
||||
area: Area,
|
||||
|
||||
inner: ratatui::widgets::List<'static>,
|
||||
}
|
||||
|
|
@ -16,10 +18,7 @@ impl List {
|
|||
let new = lua.create_function(|_, (_, seq): (Table, Table)| {
|
||||
let mut items = Vec::with_capacity(seq.raw_len());
|
||||
for v in seq.sequence_values::<Value>() {
|
||||
match v? {
|
||||
Value::Table(_) => Err("Nested table not supported".into_lua_err())?,
|
||||
v => items.push(Text::try_from(v)?),
|
||||
}
|
||||
items.push(Text::try_from(v?).map_err(|_| EXPECTED.into_lua_err())?);
|
||||
}
|
||||
|
||||
Ok(Self { inner: ratatui::widgets::List::new(items), ..Default::default() })
|
||||
|
|
@ -30,6 +29,14 @@ impl List {
|
|||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
self.inner.render(self.area.transform(trans), buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for List {
|
||||
|
|
@ -37,13 +44,3 @@ impl UserData for List {
|
|||
crate::impl_area_method!(methods);
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for List {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
self.inner.render(*self.area, buf);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(self.clone()).render(buf) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(bar border clear constraint elements gauge layout line list padding paragraph position rect span style table text);
|
||||
yazi_macro::mod_flat!(area bar border cell clear constraint elements gauge layout line list pad paragraph pos rect renderable row span style table text);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
use std::ops::Deref;
|
||||
use std::ops::{Add, AddAssign, Deref};
|
||||
|
||||
use mlua::{FromLua, Lua, Table, UserData};
|
||||
|
||||
#[derive(Clone, Copy, FromLua)]
|
||||
pub struct Padding(ratatui::widgets::Padding);
|
||||
#[derive(Clone, Copy, Default, FromLua)]
|
||||
pub struct Pad(ratatui::widgets::Padding);
|
||||
|
||||
impl Deref for Padding {
|
||||
impl Deref for Pad {
|
||||
type Target = ratatui::widgets::Padding;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let new =
|
||||
impl From<ratatui::widgets::Padding> for Pad {
|
||||
fn from(pad: ratatui::widgets::Padding) -> Self { Self(pad) }
|
||||
}
|
||||
|
||||
impl Pad {
|
||||
pub fn compose(lua: &Lua, v4: bool) -> mlua::Result<Table> {
|
||||
let new = if v4 {
|
||||
lua.create_function(|_, (_, top, right, bottom, left): (Table, u16, u16, u16, u16)| {
|
||||
Ok(Self(ratatui::widgets::Padding::new(left, right, top, bottom)))
|
||||
})?
|
||||
} else {
|
||||
lua.create_function(|_, (_, left, right, top, bottom): (Table, u16, u16, u16, u16)| {
|
||||
Ok(Self(ratatui::widgets::Padding::new(left, right, top, bottom)))
|
||||
})?;
|
||||
})?
|
||||
};
|
||||
|
||||
let padding = lua.create_table_from([
|
||||
let pad = lua.create_table_from([
|
||||
(
|
||||
"left",
|
||||
lua.create_function(|_, left: u16| Ok(Self(ratatui::widgets::Padding::left(left))))?,
|
||||
|
|
@ -42,12 +51,12 @@ impl Padding {
|
|||
),
|
||||
])?;
|
||||
|
||||
padding.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(padding)
|
||||
pad.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(pad)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Padding {
|
||||
impl UserData for Pad {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("left", |_, me| Ok(me.left));
|
||||
fields.add_field_method_get("right", |_, me| Ok(me.right));
|
||||
|
|
@ -55,3 +64,20 @@ impl UserData for Padding {
|
|||
fields.add_field_method_get("bottom", |_, me| Ok(me.bottom));
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<ratatui::widgets::Padding> for Pad {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: ratatui::widgets::Padding) -> Self::Output {
|
||||
Self(ratatui::widgets::Padding::new(
|
||||
self.left.saturating_add(rhs.left),
|
||||
self.right.saturating_add(rhs.right),
|
||||
self.top.saturating_add(rhs.top),
|
||||
self.bottom.saturating_add(rhs.bottom),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<ratatui::widgets::Padding> for Pad {
|
||||
fn add_assign(&mut self, rhs: ratatui::widgets::Padding) { *self = *self + rhs; }
|
||||
}
|
||||
80
yazi-plugin/src/elements/pos.rs
Normal file
80
yazi-plugin/src/elements/pos.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use std::{ops::Deref, str::FromStr};
|
||||
|
||||
use mlua::{AnyUserData, ExternalResult, Lua, Table, UserData};
|
||||
|
||||
use super::Pad;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Pos {
|
||||
inner: yazi_config::popup::Position,
|
||||
|
||||
pub(super) pad: Pad,
|
||||
}
|
||||
|
||||
impl Deref for Pos {
|
||||
type Target = yazi_config::popup::Position;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl From<yazi_config::popup::Position> for Pos {
|
||||
fn from(value: yazi_config::popup::Position) -> Self {
|
||||
Self { inner: value, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pos> for yazi_config::popup::Position {
|
||||
fn from(value: Pos) -> Self { value.inner }
|
||||
}
|
||||
|
||||
impl TryFrom<mlua::Table> for Pos {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(t: mlua::Table) -> Result<Self, Self::Error> {
|
||||
use yazi_config::popup::{Offset, Origin, Position};
|
||||
|
||||
Ok(Self::from(Position {
|
||||
origin: Origin::from_str(&t.raw_get::<mlua::String>(1)?.to_str()?).into_lua_err()?,
|
||||
offset: Offset {
|
||||
x: t.raw_get("x").unwrap_or_default(),
|
||||
y: t.raw_get("y").unwrap_or_default(),
|
||||
width: t.raw_get("w").unwrap_or_default(),
|
||||
height: t.raw_get("h").unwrap_or_default(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pos {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let new = lua.create_function(|_, (_, t): (Table, Table)| Self::try_from(t))?;
|
||||
|
||||
let position = lua.create_table()?;
|
||||
position.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
Ok(position)
|
||||
}
|
||||
|
||||
pub fn new_input(t: mlua::Table) -> mlua::Result<Self> {
|
||||
let mut p = Self::try_from(t)?;
|
||||
p.inner.offset.height = 3;
|
||||
Ok(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Pos {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get(1, |_, me| Ok(me.origin.to_string()));
|
||||
fields.add_field_method_get("x", |_, me| Ok(me.offset.x));
|
||||
fields.add_field_method_get("y", |_, me| Ok(me.offset.y));
|
||||
fields.add_field_method_get("w", |_, me| Ok(me.offset.width));
|
||||
fields.add_field_method_get("h", |_, me| Ok(me.offset.height));
|
||||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_function_mut("pad", |_, (ud, pad): (AnyUserData, Pad)| {
|
||||
ud.borrow_mut::<Self>()?.pad = pad;
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{FromLua, Lua, Table, UserData, UserDataFields};
|
||||
|
||||
#[derive(Clone, Copy, FromLua)]
|
||||
pub struct Position(ratatui::layout::Position);
|
||||
|
||||
impl Deref for Position {
|
||||
type Target = ratatui::layout::Position;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<ratatui::layout::Rect> for Position {
|
||||
fn from(rect: ratatui::layout::Rect) -> Self { Self(rect.as_position()) }
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let new = lua.create_function(|_, (_, args): (Table, Table)| {
|
||||
Ok(Self(ratatui::layout::Position { x: args.raw_get("x")?, y: args.raw_get("y")? }))
|
||||
})?;
|
||||
|
||||
let position = lua.create_table_from([("default", Self(Default::default()))])?;
|
||||
|
||||
position.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(position)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Position {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("x", |_, me| Ok(me.x));
|
||||
fields.add_field_method_get("y", |_, me| Ok(me.y));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ use std::ops::Deref;
|
|||
|
||||
use mlua::{FromLua, Lua, Table, UserData};
|
||||
|
||||
use super::{Padding, Position};
|
||||
use super::Pad;
|
||||
|
||||
#[derive(Clone, Copy, Default, FromLua)]
|
||||
pub struct Rect(ratatui::layout::Rect);
|
||||
pub struct Rect(pub(super) ratatui::layout::Rect);
|
||||
|
||||
impl Deref for Rect {
|
||||
type Target = ratatui::layout::Rect;
|
||||
|
|
@ -27,10 +27,10 @@ impl Rect {
|
|||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let new = lua.create_function(|_, (_, args): (Table, Table)| {
|
||||
Ok(Self(ratatui::layout::Rect {
|
||||
x: args.raw_get("x")?,
|
||||
y: args.raw_get("y")?,
|
||||
width: args.raw_get("w")?,
|
||||
height: args.raw_get("h")?,
|
||||
x: args.raw_get("x").unwrap_or_default(),
|
||||
y: args.raw_get("y").unwrap_or_default(),
|
||||
width: args.raw_get("w").unwrap_or_default(),
|
||||
height: args.raw_get("h").unwrap_or_default(),
|
||||
}))
|
||||
})?;
|
||||
|
||||
|
|
@ -39,6 +39,16 @@ impl Rect {
|
|||
rect.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(rect)
|
||||
}
|
||||
|
||||
pub(super) fn pad(self, pad: Pad) -> Self {
|
||||
let mut r = *self;
|
||||
r.x = r.x.saturating_add(pad.left);
|
||||
r.y = r.y.saturating_add(pad.top);
|
||||
|
||||
r.width = r.width.saturating_sub(pad.left + pad.right);
|
||||
r.height = r.height.saturating_sub(pad.top + pad.bottom);
|
||||
Self(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Rect {
|
||||
|
|
@ -55,16 +65,9 @@ impl UserData for Rect {
|
|||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("padding", |_, me, padding: Padding| {
|
||||
let mut r = **me;
|
||||
r.x = r.x.saturating_add(padding.left);
|
||||
r.y = r.y.saturating_add(padding.top);
|
||||
|
||||
r.width = r.width.saturating_sub(padding.left + padding.right);
|
||||
r.height = r.height.saturating_sub(padding.top + padding.bottom);
|
||||
Ok(Self(r))
|
||||
});
|
||||
methods.add_method("position", |_, me, ()| Ok(Position::from(**me)));
|
||||
methods.add_method("contains", |_, me, position: Position| Ok(me.contains(*position)));
|
||||
methods.add_method("pad", |_, me, pad: Pad| Ok(me.pad(pad)));
|
||||
// TODO: deprecate this
|
||||
methods.add_method("padding", |_, me, pad: Pad| Ok(me.pad(pad)));
|
||||
methods.add_method("contains", |_, me, Rect(rect)| Ok(me.contains(rect.into())));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
yazi-plugin/src/elements/renderable.rs
Normal file
58
yazi-plugin/src/elements/renderable.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use mlua::{AnyUserData, ExternalError};
|
||||
|
||||
use super::{Bar, Border, Clear, Gauge, List, Table, Text};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Renderable {
|
||||
Text(Text),
|
||||
List(List),
|
||||
Bar(Bar),
|
||||
Clear(Clear),
|
||||
Border(Border),
|
||||
Gauge(Gauge),
|
||||
Table(Table),
|
||||
}
|
||||
|
||||
impl Renderable {
|
||||
pub fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl Fn(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
match self {
|
||||
Self::Text(text) => text.render(buf, trans),
|
||||
Self::List(list) => list.render(buf, trans),
|
||||
Self::Bar(bar) => bar.render(buf, trans),
|
||||
Self::Clear(clear) => clear.render(buf, trans),
|
||||
Self::Border(border) => border.render(buf, trans),
|
||||
Self::Gauge(gauge) => gauge.render(buf, trans),
|
||||
Self::Table(table) => table.render(buf, trans),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyUserData> for Renderable {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(ud: AnyUserData) -> Result<Self, Self::Error> {
|
||||
Ok(if let Ok(c) = ud.take::<crate::elements::Text>() {
|
||||
Self::Text(c)
|
||||
} else if let Ok(c) = ud.take::<crate::elements::List>() {
|
||||
Self::List(c)
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Bar>() {
|
||||
Self::Bar(c)
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Clear>() {
|
||||
Self::Clear(c)
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Border>() {
|
||||
Self::Border(c)
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Gauge>() {
|
||||
Self::Gauge(c)
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Table>() {
|
||||
Self::Table(c)
|
||||
} else {
|
||||
return Err(
|
||||
format!("expected a UserData of renderable element, not: {ud:#?}").into_lua_err(),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
54
yazi-plugin/src/elements/row.rs
Normal file
54
yazi-plugin/src/elements/row.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData};
|
||||
|
||||
use super::Cell;
|
||||
|
||||
#[derive(Clone, Default, FromLua)]
|
||||
pub struct Row {
|
||||
pub(super) cells: Vec<Cell>,
|
||||
height: u16,
|
||||
top_margin: u16,
|
||||
bottom_margin: u16,
|
||||
style: ratatui::style::Style,
|
||||
}
|
||||
|
||||
impl Row {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<Table> {
|
||||
let new = lua.create_function(|_, (_, cells): (Table, Vec<Cell>)| {
|
||||
Ok(Self { cells, ..Default::default() })
|
||||
})?;
|
||||
|
||||
let row = lua.create_table()?;
|
||||
row.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
Ok(row)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Row> for ratatui::widgets::Row<'static> {
|
||||
fn from(value: Row) -> Self {
|
||||
Self::new(value.cells)
|
||||
.height(value.height.max(1))
|
||||
.top_margin(value.top_margin)
|
||||
.bottom_margin(value.bottom_margin)
|
||||
.style(value.style)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Row {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
crate::impl_style_method!(methods, style);
|
||||
|
||||
methods.add_function_mut("height", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.height = value;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("margin_t", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.top_margin = value;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("margin_b", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.bottom_margin = value;
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::{ExternalError, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
const EXPECTED: &str = "expected a string or ui.Span";
|
||||
const EXPECTED: &str = "expected a string or Span";
|
||||
|
||||
#[derive(Clone, FromLua)]
|
||||
pub struct Span(pub(super) ratatui::text::Span<'static>);
|
||||
|
|
|
|||
|
|
@ -1,33 +1,38 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, UserData};
|
||||
use mlua::{AnyUserData, Lua, UserData, Value};
|
||||
use ratatui::widgets::StatefulWidget;
|
||||
|
||||
use super::{Rect, Renderable, Text};
|
||||
use crate::elements::Constraint;
|
||||
use super::{Area, Row};
|
||||
use crate::elements::{Constraint, Style};
|
||||
|
||||
// --- Table
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Table {
|
||||
area: Rect,
|
||||
pub(crate) area: Area,
|
||||
|
||||
rows: Vec<ratatui::widgets::Row<'static>>,
|
||||
header: Option<ratatui::widgets::Row<'static>>,
|
||||
footer: Option<ratatui::widgets::Row<'static>>,
|
||||
widths: Vec<ratatui::layout::Constraint>,
|
||||
column_spacing: u16,
|
||||
block: Option<ratatui::widgets::Block<'static>>,
|
||||
style: ratatui::style::Style,
|
||||
row_highlight_style: ratatui::style::Style,
|
||||
highlight_symbol: ratatui::text::Text<'static>,
|
||||
highlight_spacing: ratatui::widgets::HighlightSpacing,
|
||||
flex: ratatui::layout::Flex,
|
||||
rows: Vec<Row>,
|
||||
header: Option<ratatui::widgets::Row<'static>>,
|
||||
footer: Option<ratatui::widgets::Row<'static>>,
|
||||
widths: Vec<ratatui::layout::Constraint>,
|
||||
column_spacing: u16,
|
||||
block: Option<ratatui::widgets::Block<'static>>, // TODO
|
||||
|
||||
style: ratatui::style::Style,
|
||||
row_highlight_style: ratatui::style::Style,
|
||||
column_highlight_style: ratatui::style::Style,
|
||||
cell_highlight_style: ratatui::style::Style,
|
||||
|
||||
highlight_symbol: ratatui::text::Text<'static>, // TODO
|
||||
highlight_spacing: ratatui::widgets::HighlightSpacing, // TODO
|
||||
|
||||
flex: ratatui::layout::Flex,
|
||||
|
||||
state: ratatui::widgets::TableState,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<mlua::Table> {
|
||||
let new = lua.create_function(|_, (_, area, rows): (mlua::Table, Rect, Vec<TableRow>)| {
|
||||
Ok(Self { area, rows: rows.into_iter().map(Into::into).collect(), ..Default::default() })
|
||||
let new = lua.create_function(|_, (_, rows): (mlua::Table, Vec<Row>)| {
|
||||
Ok(Self { rows, ..Default::default() })
|
||||
})?;
|
||||
|
||||
let table = lua.create_table()?;
|
||||
|
|
@ -35,35 +40,24 @@ impl Table {
|
|||
|
||||
Ok(table)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Table {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
crate::impl_area_method!(methods);
|
||||
|
||||
methods.add_function_mut("header", |_, (ud, row): (AnyUserData, TableRow)| {
|
||||
ud.borrow_mut::<Self>()?.header = Some(row.into());
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("footer", |_, (ud, row): (AnyUserData, TableRow)| {
|
||||
ud.borrow_mut::<Self>()?.footer = Some(row.into());
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("widths", |_, (ud, widths): (AnyUserData, Vec<Constraint>)| {
|
||||
ud.borrow_mut::<Self>()?.widths = widths.into_iter().map(Into::into).collect();
|
||||
Ok(ud)
|
||||
});
|
||||
pub fn selected_cell(&self) -> Option<&ratatui::text::Text> {
|
||||
let row = &self.rows[self.selected()?];
|
||||
let col = self.state.selected_column()?;
|
||||
if row.cells.is_empty() { None } else { Some(&row.cells[col.min(row.cells.len() - 1)].text) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Table {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(mut self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
pub(super) fn render(
|
||||
mut self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
let mut table = ratatui::widgets::Table::new(self.rows, self.widths)
|
||||
.column_spacing(self.column_spacing)
|
||||
.style(self.style)
|
||||
.row_highlight_style(self.row_highlight_style)
|
||||
.column_highlight_style(self.column_highlight_style)
|
||||
.cell_highlight_style(self.cell_highlight_style)
|
||||
.highlight_symbol(self.highlight_symbol)
|
||||
.highlight_spacing(self.highlight_spacing)
|
||||
.flex(self.flex);
|
||||
|
|
@ -78,59 +72,70 @@ impl Renderable for Table {
|
|||
table = table.block(block);
|
||||
}
|
||||
|
||||
table.render(*self.area, buf, &mut self.state);
|
||||
table.render(self.area.transform(trans), buf, &mut self.state);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(self.clone()).render(buf) }
|
||||
}
|
||||
pub(crate) fn select(&mut self, idx: Option<usize>) {
|
||||
self
|
||||
.state
|
||||
.select(idx.map(|i| if self.rows.is_empty() { 0 } else { i.min(self.rows.len() - 1) }));
|
||||
}
|
||||
|
||||
// --- TableRow
|
||||
#[derive(Clone, Default, FromLua)]
|
||||
pub struct TableRow {
|
||||
cells: Vec<ratatui::widgets::Cell<'static>>,
|
||||
height: u16,
|
||||
top_margin: u16,
|
||||
bottom_margin: u16,
|
||||
style: ratatui::style::Style,
|
||||
}
|
||||
|
||||
impl TableRow {
|
||||
pub fn compose(lua: &Lua) -> mlua::Result<mlua::Table> {
|
||||
let new = lua.create_function(|_, (_, cols): (mlua::Table, Vec<Text>)| {
|
||||
Ok(Self { cells: cols.into_iter().map(Into::into).collect(), ..Default::default() })
|
||||
})?;
|
||||
|
||||
let row = lua.create_table()?;
|
||||
row.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
Ok(row)
|
||||
pub(crate) fn selected(&self) -> Option<usize> {
|
||||
if self.rows.is_empty() { None } else { Some(self.state.selected()?.min(self.rows.len() - 1)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TableRow> for ratatui::widgets::Row<'static> {
|
||||
fn from(value: TableRow) -> Self {
|
||||
Self::new(value.cells)
|
||||
.height(value.height)
|
||||
.top_margin(value.top_margin)
|
||||
.bottom_margin(value.bottom_margin)
|
||||
.style(value.style)
|
||||
}
|
||||
impl TryFrom<AnyUserData> for Table {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> { value.take() }
|
||||
}
|
||||
|
||||
impl UserData for TableRow {
|
||||
impl UserData for Table {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
crate::impl_style_method!(methods, style);
|
||||
crate::impl_area_method!(methods);
|
||||
|
||||
methods.add_function_mut("height", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.height = value;
|
||||
methods.add_function_mut("header", |_, (ud, row): (AnyUserData, Row)| {
|
||||
ud.borrow_mut::<Self>()?.header = Some(row.into());
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("margin_t", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.top_margin = value;
|
||||
methods.add_function_mut("footer", |_, (ud, row): (AnyUserData, Row)| {
|
||||
ud.borrow_mut::<Self>()?.footer = Some(row.into());
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("margin_b", |_, (ud, value): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.bottom_margin = value;
|
||||
methods.add_function_mut("widths", |_, (ud, widths): (AnyUserData, Vec<Constraint>)| {
|
||||
ud.borrow_mut::<Self>()?.widths = widths.into_iter().map(Into::into).collect();
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("spacing", |_, (ud, spacing): (AnyUserData, u16)| {
|
||||
ud.borrow_mut::<Self>()?.column_spacing = spacing;
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function_mut("row", |_, (ud, idx): (AnyUserData, Option<usize>)| {
|
||||
ud.borrow_mut::<Self>()?.state.select(idx);
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("col", |_, (ud, idx): (AnyUserData, Option<usize>)| {
|
||||
ud.borrow_mut::<Self>()?.state.select_column(idx);
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function_mut("style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.style = Style::try_from(value)?.0;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("row_style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.row_highlight_style = Style::try_from(value)?.0;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("col_style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.column_highlight_style = Style::try_from(value)?.0;
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("cell_style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.cell_highlight_style = Style::try_from(value)?.0;
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use ansi_to_tui::IntoText;
|
||||
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, Table, UserData, Value};
|
||||
use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, Table, UserData, Value};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
use super::{Line, Rect, Renderable, Span};
|
||||
use super::{Area, Line, Span};
|
||||
|
||||
// Alignment
|
||||
pub(super) const LEFT: u8 = 0;
|
||||
|
|
@ -14,11 +14,11 @@ pub const WRAP_NO: u8 = 0;
|
|||
pub const WRAP: u8 = 1;
|
||||
pub const WRAP_TRIM: u8 = 2;
|
||||
|
||||
const EXPECTED: &str = "expected a string, ui.Line, ui.Span or a table of them";
|
||||
const EXPECTED: &str = "expected a string, Line, Span, or a table of them";
|
||||
|
||||
#[derive(Clone, Default, FromLua)]
|
||||
pub struct Text {
|
||||
pub area: Rect,
|
||||
pub area: Area,
|
||||
|
||||
// TODO: block
|
||||
pub inner: ratatui::text::Text<'static>,
|
||||
|
|
@ -49,6 +49,16 @@ impl Text {
|
|||
text.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
self,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
trans: impl Fn(yazi_config::popup::Position) -> ratatui::layout::Rect,
|
||||
) {
|
||||
let rect = self.area.transform(trans);
|
||||
let p: ratatui::widgets::Paragraph = self.into();
|
||||
p.render(rect, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for Text {
|
||||
|
|
@ -63,6 +73,8 @@ impl TryFrom<Value> for Text {
|
|||
line.into()
|
||||
} else if let Ok(Span(span)) = ud.take() {
|
||||
span.into()
|
||||
} else if let Ok(text) = ud.take() {
|
||||
return Ok(text);
|
||||
} else {
|
||||
Err(EXPECTED.into_lua_err())?
|
||||
}
|
||||
|
|
@ -131,24 +143,12 @@ impl UserData for Text {
|
|||
methods.add_function_mut("wrap", |_, (ud, wrap): (AnyUserData, u8)| {
|
||||
ud.borrow_mut::<Self>()?.wrap = match wrap {
|
||||
w @ (WRAP | WRAP_TRIM | WRAP_NO) => w,
|
||||
_ => return Err("expected a WRAP or WRAP_TRIM or WRAP_OFF".into_lua_err()),
|
||||
_ => return Err("expected a WRAP, WRAP_TRIM or WRAP_NO".into_lua_err()),
|
||||
};
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_method("max_width", |_, me, ()| {
|
||||
Ok(me.inner.lines.iter().take(me.area.height as usize).map(|l| l.width()).max())
|
||||
Ok(me.inner.lines.iter().take(me.area.size().height as usize).map(|l| l.width()).max())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Text {
|
||||
fn area(&self) -> ratatui::layout::Rect { *self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
let area = *self.area;
|
||||
let p: ratatui::widgets::Paragraph = (*self).into();
|
||||
p.render(area, buf);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(self.clone()).render(buf) }
|
||||
}
|
||||
|
|
|
|||
18
yazi-plugin/src/external/highlighter.rs
vendored
18
yazi-plugin/src/external/highlighter.rs
vendored
|
|
@ -1,7 +1,7 @@
|
|||
use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}, sync::OnceLock};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use ratatui::{layout::Rect, text::{Line, Span, Text}};
|
||||
use ratatui::{layout::Size, text::{Line, Span, Text}};
|
||||
use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
|
||||
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}};
|
||||
use yazi_config::{PREVIEW, THEME, preview::PreviewWrap};
|
||||
|
|
@ -36,14 +36,14 @@ impl Highlighter {
|
|||
#[inline]
|
||||
pub fn abort() { INCR.next(); }
|
||||
|
||||
pub async fn highlight(&self, skip: usize, area: Rect) -> Result<Text<'static>, PeekError> {
|
||||
pub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, PeekError> {
|
||||
let mut reader = BufReader::new(File::open(&self.path).await?);
|
||||
|
||||
let syntax = Self::find_syntax(&self.path).await;
|
||||
let mut plain = syntax.is_err();
|
||||
|
||||
let mut before = Vec::with_capacity(if plain { 0 } else { skip });
|
||||
let mut after = Vec::with_capacity(area.height as _);
|
||||
let mut after = Vec::with_capacity(size.height as _);
|
||||
|
||||
let mut i = 0;
|
||||
let mut buf = vec![];
|
||||
|
|
@ -67,24 +67,24 @@ impl Highlighter {
|
|||
i += if i >= skip {
|
||||
buf.iter_mut().for_each(Self::carriage_return_to_line_feed);
|
||||
after.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
Self::line_height(&after[after.len() - 1], area.width)
|
||||
Self::line_height(&after[after.len() - 1], size.width)
|
||||
} else if !plain {
|
||||
before.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
Self::line_height(&before[before.len() - 1], area.width)
|
||||
Self::line_height(&before[before.len() - 1], size.width)
|
||||
} else if PREVIEW.wrap == PreviewWrap::Yes {
|
||||
Self::line_height(&String::from_utf8_lossy(&buf), area.width)
|
||||
Self::line_height(&String::from_utf8_lossy(&buf), size.width)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
buf.clear();
|
||||
if i > skip + area.height as usize {
|
||||
if i > skip + size.height as usize {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if skip > 0 && i < skip + area.height as usize {
|
||||
return Err(PeekError::Exceed(i.saturating_sub(area.height as _)));
|
||||
if skip > 0 && i < skip + size.height as usize {
|
||||
return Err(PeekError::Exceed(i.saturating_sub(size.height as _)));
|
||||
}
|
||||
|
||||
Ok(if plain {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{ExternalError, ExternalResult, ObjectLike, Table};
|
||||
use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table};
|
||||
use tokio::runtime::Handle;
|
||||
use yazi_config::LAYOUT;
|
||||
|
||||
|
|
@ -20,16 +20,20 @@ pub async fn fetch(name: &str, files: Vec<yazi_shared::fs::File>) -> mlua::Resul
|
|||
return Err("unloaded plugin".into_lua_err());
|
||||
};
|
||||
|
||||
let files = files.into_iter().filter_map(|f| File::cast(&lua, f).ok()).collect::<Vec<_>>();
|
||||
if files.is_empty() {
|
||||
return Err("no files".into_lua_err());
|
||||
let files =
|
||||
lua.create_sequence_from(files.into_iter().filter_map(|f| File::cast(&lua, f).ok()))?;
|
||||
|
||||
if files.raw_len() == 0 {
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
plugin.raw_set("skip", 0)?;
|
||||
plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
|
||||
plugin.raw_set("files", files)?;
|
||||
|
||||
Handle::current().block_on(plugin.call_async_method("fetch", ()))
|
||||
Handle::current().block_on(plugin.call_async_method(
|
||||
"fetch",
|
||||
lua.create_table_from([
|
||||
("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),
|
||||
("files", files.into_lua(&lua)?),
|
||||
])?,
|
||||
))
|
||||
})
|
||||
.await
|
||||
.into_lua_err()?
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::runtime::Runtime;
|
|||
pub fn slim_lua(name: &str) -> mlua::Result<Lua> {
|
||||
let lua = Lua::new();
|
||||
lua.set_named_registry_value("rt", Runtime::new(name))?;
|
||||
crate::Config::new(&lua).install_preview()?;
|
||||
crate::config::Config::new(&lua).install_preview()?.install_plugin()?;
|
||||
|
||||
// Base
|
||||
let globals = lua.globals();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
yazi_macro::mod_flat!(entry fetch isolate peek preload seek);
|
||||
yazi_macro::mod_flat!(entry fetch isolate peek preload seek spot);
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ pub fn peek(
|
|||
skip: usize,
|
||||
) -> CancellationToken {
|
||||
let ct = CancellationToken::new();
|
||||
let (ct1, ct2) = (ct.clone(), ct.clone());
|
||||
|
||||
let name = cmd.name.to_owned();
|
||||
let (ct1, ct2) = (ct.clone(), ct.clone());
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let future = async {
|
||||
LOADER.ensure(&name).await.into_lua_err()?;
|
||||
|
|
@ -43,10 +43,12 @@ pub fn peek(
|
|||
return Err("unloaded plugin".into_lua_err());
|
||||
};
|
||||
plugin.raw_set("file", File::cast(&lua, file)?)?;
|
||||
plugin.raw_set("_mime", mime)?;
|
||||
plugin.raw_set("mime", mime)?;
|
||||
plugin.raw_set("skip", skip)?;
|
||||
plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
|
||||
plugin.raw_set("window", Window::default())?;
|
||||
// TODO: remove this as it's not safe in async context,
|
||||
// there may be race conditions between the `window` and `area`
|
||||
plugin.raw_set("window", Window::get())?;
|
||||
|
||||
if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", ()).await }
|
||||
};
|
||||
|
|
@ -71,10 +73,12 @@ pub fn peek(
|
|||
pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>, skip: usize) {
|
||||
let cb: PluginCallback = Box::new(move |_, plugin| {
|
||||
plugin.raw_set("file", File::cast(&LUA, file)?)?;
|
||||
plugin.raw_set("_mime", mime)?;
|
||||
plugin.raw_set("mime", mime)?;
|
||||
plugin.raw_set("skip", skip)?;
|
||||
plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
|
||||
plugin.raw_set("window", Window::default())?;
|
||||
// TODO: remove this as it's not safe in async context,
|
||||
// there may be race conditions between the `window` and `area`
|
||||
plugin.raw_set("window", Window::get())?;
|
||||
plugin.call_method("peek", ())
|
||||
});
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue