mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: plugin system
This commit is contained in:
parent
df42ca799e
commit
f997bfdea0
225 changed files with 4103 additions and 3039 deletions
67
Cargo.lock
generated
67
Cargo.lock
generated
|
|
@ -124,6 +124,12 @@ version = "1.0.75"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.9.0"
|
||||
|
|
@ -911,6 +917,12 @@ dependencies = [
|
|||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.1.0"
|
||||
|
|
@ -1158,7 +1170,9 @@ checksum = "7c81f8ac20188feb5461a73eabb22a34dd09d6d58513535eb587e46bff6ba250"
|
|||
dependencies = [
|
||||
"bstr",
|
||||
"erased-serde",
|
||||
"futures-util",
|
||||
"mlua-sys",
|
||||
"mlua_derive",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
|
|
@ -1179,6 +1193,21 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua_derive"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f359220f24e6452dd82a3f50d7242d4aab822b5594798048e953d7a9e0314c6"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"once_cell",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
|
@ -1981,6 +2010,19 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.8"
|
||||
|
|
@ -2575,6 +2617,7 @@ dependencies = [
|
|||
"base64",
|
||||
"color_quant",
|
||||
"image",
|
||||
"imagesize",
|
||||
"ratatui",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -2587,6 +2630,7 @@ name = "yazi-config"
|
|||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
|
|
@ -2626,11 +2670,12 @@ dependencies = [
|
|||
"syntect",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"unicode-width",
|
||||
"yazi-adaptor",
|
||||
"yazi-config",
|
||||
"yazi-prebuild",
|
||||
"yazi-plugin",
|
||||
"yazi-scheduler",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
|
@ -2646,6 +2691,7 @@ dependencies = [
|
|||
"fdlimit",
|
||||
"futures",
|
||||
"libc",
|
||||
"mlua",
|
||||
"ratatui",
|
||||
"signal-hook-tokio",
|
||||
"tokio",
|
||||
|
|
@ -2657,6 +2703,7 @@ dependencies = [
|
|||
"yazi-config",
|
||||
"yazi-core",
|
||||
"yazi-plugin",
|
||||
"yazi-scheduler",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
||||
|
|
@ -2666,12 +2713,22 @@ version = "0.1.5"
|
|||
dependencies = [
|
||||
"ansi-to-tui",
|
||||
"anyhow",
|
||||
"futures",
|
||||
"libc",
|
||||
"md-5",
|
||||
"mlua",
|
||||
"parking_lot",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syntect",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"unicode-width",
|
||||
"yazi-adaptor",
|
||||
"yazi-config",
|
||||
"yazi-core",
|
||||
"yazi-prebuild",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
||||
|
|
@ -2690,16 +2747,15 @@ dependencies = [
|
|||
"base64",
|
||||
"crossterm",
|
||||
"futures",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"trash",
|
||||
"yazi-adaptor",
|
||||
"yazi-config",
|
||||
"yazi-plugin",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
||||
|
|
@ -2716,6 +2772,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"language":"en","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"],"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","imagesize"],"language":"en","version":"0.2"}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ anyhow = "^1"
|
|||
base64 = "^0"
|
||||
color_quant = "^1"
|
||||
image = "^0"
|
||||
imagesize = "^0"
|
||||
ratatui = "^0"
|
||||
tokio = { version = "^1", features = [ "parking_lot", "io-util", "process" ] }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
use std::{env, path::{Path, PathBuf}, sync::atomic::{AtomicBool, Ordering}};
|
||||
use std::{env, path::Path, sync::atomic::{AtomicBool, Ordering}};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ratatui::prelude::Rect;
|
||||
use tokio::{fs, sync::mpsc::UnboundedSender};
|
||||
use tracing::warn;
|
||||
use yazi_config::PREVIEW;
|
||||
use yazi_shared::{env_exists, RoCell};
|
||||
use yazi_shared::env_exists;
|
||||
|
||||
use super::{Iterm2, Kitty, KittyOld};
|
||||
use crate::{ueberzug::Ueberzug, Sixel, TMUX};
|
||||
|
||||
static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
static UEBERZUG: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCell::new();
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Adaptor {
|
||||
Kitty,
|
||||
|
|
@ -164,16 +159,9 @@ impl ToString for Adaptor {
|
|||
}
|
||||
|
||||
impl Adaptor {
|
||||
pub(super) fn start(self) {
|
||||
UEBERZUG.init(if self.needs_ueberzug() { Ueberzug::start(self).ok() } else { None });
|
||||
}
|
||||
|
||||
pub async fn image_show(self, mut path: &Path, rect: Rect) -> Result<()> {
|
||||
let cache = PREVIEW.cache(path, 0);
|
||||
if fs::symlink_metadata(&cache).await.is_ok() {
|
||||
path = cache.as_path();
|
||||
}
|
||||
pub(super) fn start(self) { Ueberzug::start(self); }
|
||||
|
||||
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
self.image_hide(rect).ok();
|
||||
IMAGE_SHOWN.store(true, Ordering::Relaxed);
|
||||
|
||||
|
|
@ -182,9 +170,7 @@ impl Adaptor {
|
|||
Self::KittyOld => KittyOld::image_show(path, rect).await,
|
||||
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
||||
Self::Sixel => Sixel::image_show(path, rect).await,
|
||||
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
||||
tx.send(Some((path.to_path_buf(), rect)))?;
|
||||
}),
|
||||
_ => Ueberzug::image_show(path, rect).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,9 +184,7 @@ impl Adaptor {
|
|||
Self::Iterm2 => Iterm2::image_hide(rect),
|
||||
Self::KittyOld => KittyOld::image_hide(),
|
||||
Self::Sixel => Sixel::image_hide(rect),
|
||||
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
||||
tx.send(None)?;
|
||||
}),
|
||||
_ => Ueberzug::image_hide(rect),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,51 +2,13 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
|
|||
|
||||
use anyhow::Result;
|
||||
use image::{imageops::FilterType, io::Limits, DynamicImage, ImageFormat};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::{PREVIEW, TASKS};
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
pub struct Image;
|
||||
|
||||
impl Image {
|
||||
fn set_limits(mut r: image::io::Reader<BufReader<File>>) -> image::io::Reader<BufReader<File>> {
|
||||
let mut limits = Limits::no_limits();
|
||||
if TASKS.image_alloc > 0 {
|
||||
limits.max_alloc = Some(TASKS.image_alloc as u64);
|
||||
}
|
||||
if TASKS.image_bound[0] > 0 {
|
||||
limits.max_image_width = Some(TASKS.image_bound[0] as u32);
|
||||
}
|
||||
if TASKS.image_bound[1] > 0 {
|
||||
limits.max_image_height = Some(TASKS.image_bound[1] as u32);
|
||||
}
|
||||
r.limits(limits);
|
||||
r
|
||||
}
|
||||
|
||||
pub(super) async fn downscale(path: &Path, size: (u16, u16)) -> Result<DynamicImage> {
|
||||
let (w, h) = Term::ratio()
|
||||
.map(|(w, h)| {
|
||||
let (w, h) = ((size.0 as f64 * w) as u32, (size.1 as f64 * h) as u32);
|
||||
(w.min(PREVIEW.max_width), h.min(PREVIEW.max_height))
|
||||
})
|
||||
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height));
|
||||
|
||||
let path = path.to_owned();
|
||||
let img = tokio::task::spawn_blocking(move || {
|
||||
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode()
|
||||
})
|
||||
.await??;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
Ok(if img.width() > w || img.height() > h {
|
||||
img.resize(w, h, FilterType::Triangle)
|
||||
} else {
|
||||
img
|
||||
})
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
|
||||
let path = path.to_owned();
|
||||
let mut img = tokio::task::spawn_blocking(move || {
|
||||
|
|
@ -69,21 +31,45 @@ impl Image {
|
|||
.await?
|
||||
}
|
||||
|
||||
pub async fn precache_vec(bin: Vec<u8>, cache: PathBuf) -> Result<()> {
|
||||
let mut img = tokio::task::spawn_blocking(move || image::load_from_memory(&bin)).await??;
|
||||
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
|
||||
let path = path.to_owned();
|
||||
let img = tokio::task::spawn_blocking(move || {
|
||||
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let (w, h) = Self::max_size(rect);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let (w, h) = (PREVIEW.max_width, PREVIEW.max_height);
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, FilterType::Triangle);
|
||||
}
|
||||
|
||||
Ok(match img {
|
||||
DynamicImage::ImageRgb8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
|
||||
DynamicImage::ImageRgba8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
|
||||
buf => buf.into_rgb8().save_with_format(cache, ImageFormat::Jpeg),
|
||||
}?)
|
||||
Ok(if img.width() > w || img.height() > h {
|
||||
img.resize(w, h, FilterType::Triangle)
|
||||
} else {
|
||||
img
|
||||
})
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
pub(super) fn max_size(rect: Rect) -> (u32, u32) {
|
||||
Term::ratio()
|
||||
.map(|(r1, r2)| {
|
||||
let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32);
|
||||
(w.min(PREVIEW.max_width), h.min(PREVIEW.max_height))
|
||||
})
|
||||
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
|
||||
}
|
||||
|
||||
fn set_limits(mut r: image::io::Reader<BufReader<File>>) -> image::io::Reader<BufReader<File>> {
|
||||
let mut limits = Limits::no_limits();
|
||||
if TASKS.image_alloc > 0 {
|
||||
limits.max_alloc = Some(TASKS.image_alloc as u64);
|
||||
}
|
||||
if TASKS.image_bound[0] > 0 {
|
||||
limits.max_image_width = Some(TASKS.image_bound[0] as u32);
|
||||
}
|
||||
if TASKS.image_bound[1] > 0 {
|
||||
limits.max_image_height = Some(TASKS.image_bound[1] as u32);
|
||||
}
|
||||
r.limits(limits);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,16 @@ use crate::{CLOSE, START};
|
|||
pub(super) struct Iterm2;
|
||||
|
||||
impl Iterm2 {
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
let img = Image::downscale(path, rect).await?;
|
||||
let size = (img.width(), img.height());
|
||||
let b = Self::encode(img).await?;
|
||||
|
||||
Self::image_hide(rect)?;
|
||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| Ok(stdout.write_all(&b)?))
|
||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
|
||||
stdout.write_all(&b)?;
|
||||
Ok(size)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn image_hide(rect: Rect) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -312,8 +312,9 @@ static DIACRITICS: [char; 297] = [
|
|||
pub(super) struct Kitty;
|
||||
|
||||
impl Kitty {
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
let img = Image::downscale(path, rect).await?;
|
||||
let size = (img.width(), img.height());
|
||||
let b = Self::encode(img).await?;
|
||||
|
||||
Self::image_hide(rect)?;
|
||||
|
|
@ -335,7 +336,7 @@ impl Kitty {
|
|||
stdout.write_all(buf.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(size)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,16 @@ use crate::{CLOSE, ESCAPE, START};
|
|||
pub(super) struct KittyOld;
|
||||
|
||||
impl KittyOld {
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
let img = Image::downscale(path, rect).await?;
|
||||
let size = (img.width(), img.height());
|
||||
let b = Self::encode(img).await?;
|
||||
|
||||
Self::image_hide()?;
|
||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| Ok(stdout.write_all(&b)?))
|
||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
|
||||
stdout.write_all(&b)?;
|
||||
Ok(size)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -11,12 +11,16 @@ use crate::{Image, CLOSE, ESCAPE, START};
|
|||
pub(super) struct Sixel;
|
||||
|
||||
impl Sixel {
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
let img = Image::downscale(path, rect).await?;
|
||||
let size = (img.width(), img.height());
|
||||
let b = Self::encode(img).await?;
|
||||
|
||||
Self::image_hide(rect)?;
|
||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| Ok(stdout.write_all(&b)?))
|
||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
|
||||
stdout.write_all(&b)?;
|
||||
Ok(size)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn image_hide(rect: Rect) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
use std::{path::PathBuf, process::Stdio};
|
||||
use std::{path::{Path, PathBuf}, process::Stdio};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use imagesize::ImageSize;
|
||||
use ratatui::prelude::Rect;
|
||||
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
|
||||
use tracing::debug;
|
||||
use yazi_config::PREVIEW;
|
||||
use yazi_shared::RoCell;
|
||||
|
||||
use crate::Adaptor;
|
||||
use crate::{Adaptor, Image};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
static DEMON: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCell::new();
|
||||
|
||||
pub(super) struct Ueberzug;
|
||||
|
||||
impl Ueberzug {
|
||||
pub(super) fn start(adaptor: Adaptor) -> Result<UnboundedSender<Option<(PathBuf, Rect)>>> {
|
||||
pub(super) fn start(adaptor: Adaptor) {
|
||||
if !adaptor.needs_ueberzug() {
|
||||
return DEMON.init(None);
|
||||
}
|
||||
|
||||
let mut child = Self::create_demon(adaptor).ok();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
|
|
@ -29,8 +38,35 @@ impl Ueberzug {
|
|||
}
|
||||
}
|
||||
});
|
||||
DEMON.init(Some(tx))
|
||||
}
|
||||
|
||||
Ok(tx)
|
||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
if let Some(tx) = &*DEMON {
|
||||
tx.send(Some((path.to_path_buf(), rect)))?;
|
||||
} else {
|
||||
bail!("uninitialized ueberzug");
|
||||
}
|
||||
|
||||
let path = path.to_owned();
|
||||
let ImageSize { width: w, height: h } =
|
||||
tokio::task::spawn_blocking(move || imagesize::size(path)).await??;
|
||||
|
||||
let (max_w, max_h) = Image::max_size(rect);
|
||||
if w <= max_w as usize && h <= max_h as usize {
|
||||
return Ok((w as u32, h as u32));
|
||||
}
|
||||
|
||||
let ratio = f64::min(max_w as f64 / w as f64, max_h as f64 / h as f64);
|
||||
Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32))
|
||||
}
|
||||
|
||||
pub(super) fn image_hide(_: Rect) -> Result<()> {
|
||||
if let Some(tx) = &*DEMON {
|
||||
Ok(tx.send(None)?)
|
||||
} else {
|
||||
bail!("uninitialized ueberzug");
|
||||
}
|
||||
}
|
||||
|
||||
fn create_demon(adaptor: Adaptor) -> Result<Child> {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
|||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
arc-swap = "^1"
|
||||
clap = { version = "^4", features = [ "derive" ] }
|
||||
crossterm = "^0"
|
||||
dirs = "^5"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#[path = "src/boot/cli.rs"]
|
||||
mod cli;
|
||||
#[path = "src/boot/args.rs"]
|
||||
mod args;
|
||||
|
||||
use std::{env, error::Error, fs};
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let cmd = &mut cli::Args::command();
|
||||
let cmd = &mut args::Args::command();
|
||||
let bin = "yazi";
|
||||
let out = "completions";
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ keymap = [
|
|||
{ on = [ "H" ], exec = "back", desc = "Go back to the previous directory" },
|
||||
{ on = [ "L" ], exec = "forward", desc = "Go forward to the next directory" },
|
||||
|
||||
{ on = [ "<A-k>" ], exec = "peek -5", desc = "Peek up 5 units in the preview" },
|
||||
{ on = [ "<A-j>" ], exec = "peek 5", desc = "Peek down 5 units in the preview" },
|
||||
{ on = [ "<A-PageUp>" ], exec = "peek -5", desc = "Peek up 5 units in the preview" },
|
||||
{ on = [ "<A-PageDown>" ], exec = "peek 5", desc = "Peek down 5 units in the preview" },
|
||||
{ on = [ "<A-k>" ], exec = "seek -5", desc = "Peek up 5 units in the preview" },
|
||||
{ on = [ "<A-j>" ], exec = "seek 5", desc = "Peek down 5 units in the preview" },
|
||||
{ on = [ "<A-PageUp>" ], exec = "seek -5", desc = "Peek up 5 units in the preview" },
|
||||
{ on = [ "<A-PageDown>" ], exec = "seek 5", desc = "Peek down 5 units in the preview" },
|
||||
|
||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ tab_width = 1
|
|||
border_symbol = "│"
|
||||
border_style = { fg = "gray" }
|
||||
|
||||
# Offset
|
||||
folder_offset = [ 1, 0, 1, 0 ]
|
||||
preview_offset = [ 1, 1, 1, 1 ]
|
||||
|
||||
# Highlighting
|
||||
syntect_theme = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ rules = [
|
|||
{ mime = "application/x-bzip2", use = [ "extract", "reveal" ] },
|
||||
{ mime = "application/x-7z-compressed", use = [ "extract", "reveal" ] },
|
||||
{ mime = "application/x-rar", use = [ "extract", "reveal" ] },
|
||||
{ mime = "application/xz", use = [ "extract", "reveal" ] },
|
||||
|
||||
{ mime = "*", use = [ "open", "reveal" ] },
|
||||
]
|
||||
|
|
@ -73,8 +74,44 @@ image_alloc = 536870912 # 512MB
|
|||
image_bound = [ 0, 0 ]
|
||||
suppress_preload = false
|
||||
|
||||
[plugins]
|
||||
preload = []
|
||||
[plugin]
|
||||
|
||||
preloaders = [
|
||||
{ name = "*", cond = "!mime", exec = "mime.lua", multi = true },
|
||||
# Image
|
||||
{ mime = "image/vnd.djvu", exec = "noop.lua" },
|
||||
{ mime = "image/*", exec = "image.lua" },
|
||||
# Video
|
||||
{ mime = "video/*", exec = "video.lua" },
|
||||
# PDF
|
||||
{ mime = "application/pdf", exec = "pdf.lua" },
|
||||
]
|
||||
previewers = [
|
||||
{ name = "*/", exec = "folder.lua", sync = true },
|
||||
# Code
|
||||
{ mime = "text/*", exec = "code.lua" },
|
||||
{ mime = "*/xml", exec = "code.lua" },
|
||||
{ mime = "*/javascript", exec = "code.lua" },
|
||||
{ mime = "*/x-wine-extension-ini", exec = "code.lua" },
|
||||
# JSON
|
||||
{ mime = "application/json", exec = "json.lua" },
|
||||
# Image
|
||||
{ mime = "image/vnd.djvu", exec = "noop.lua" },
|
||||
{ mime = "image/*", exec = "image.lua" },
|
||||
# Video
|
||||
{ mime = "video/*", exec = "video.lua" },
|
||||
# PDF
|
||||
{ mime = "application/pdf", exec = "pdf.lua" },
|
||||
# Archive
|
||||
{ mime = "application/zip", exec = "archive.lua" },
|
||||
{ mime = "application/gzip", exec = "archive.lua" },
|
||||
{ mime = "application/x-tar", exec = "archive.lua" },
|
||||
{ mime = "application/x-bzip", exec = "archive.lua" },
|
||||
{ mime = "application/x-bzip2", exec = "archive.lua" },
|
||||
{ mime = "application/x-7z-compressed", exec = "archive.lua" },
|
||||
{ mime = "application/x-rar", exec = "archive.lua" },
|
||||
{ mime = "application/xz", exec = "archive.lua" },
|
||||
]
|
||||
|
||||
[input]
|
||||
# cd
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use clap::{command, Parser};
|
|||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "yazi")]
|
||||
pub(super) struct Args {
|
||||
pub struct Args {
|
||||
/// Set the current working entry
|
||||
#[arg(index = 1)]
|
||||
pub entry: Option<PathBuf>,
|
||||
|
|
@ -1,24 +1,23 @@
|
|||
use std::{ffi::OsString, fs, path::PathBuf, process};
|
||||
use std::{ffi::OsString, fs, path::{Path, PathBuf}, process};
|
||||
|
||||
use clap::Parser;
|
||||
use yazi_shared::fs::{current_cwd, expand_path};
|
||||
|
||||
use super::cli::Args;
|
||||
use crate::{Xdg, PREVIEW};
|
||||
use super::Args;
|
||||
use crate::{Xdg, ARGS};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Boot {
|
||||
pub cwd: PathBuf,
|
||||
pub file: Option<OsString>,
|
||||
|
||||
pub state_dir: PathBuf,
|
||||
|
||||
pub cwd_file: Option<PathBuf>,
|
||||
pub chooser_file: Option<PathBuf>,
|
||||
pub config_dir: PathBuf,
|
||||
pub plugin_dir: PathBuf,
|
||||
pub state_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Boot {
|
||||
fn parse_entry(entry: Option<PathBuf>) -> (PathBuf, Option<OsString>) {
|
||||
fn parse_entry(entry: Option<&Path>) -> (PathBuf, Option<OsString>) {
|
||||
let entry = match entry {
|
||||
Some(p) => expand_path(p),
|
||||
None => return (current_cwd().unwrap(), None),
|
||||
|
|
@ -35,7 +34,28 @@ impl Boot {
|
|||
|
||||
impl Default for Boot {
|
||||
fn default() -> Self {
|
||||
let args = Args::parse();
|
||||
let (cwd, file) = Self::parse_entry(ARGS.entry.as_deref());
|
||||
let boot = Self {
|
||||
cwd,
|
||||
file,
|
||||
|
||||
config_dir: Xdg::config_dir().unwrap(),
|
||||
plugin_dir: Xdg::plugin_dir().unwrap(),
|
||||
state_dir: Xdg::state_dir().unwrap(),
|
||||
};
|
||||
|
||||
if !boot.state_dir.is_dir() {
|
||||
fs::create_dir_all(&boot.state_dir).unwrap();
|
||||
}
|
||||
|
||||
boot
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Args {
|
||||
fn default() -> Self {
|
||||
let args = Self::parse();
|
||||
|
||||
if args.version {
|
||||
println!(
|
||||
"yazi {} ({} {})",
|
||||
|
|
@ -46,37 +66,6 @@ impl Default for Boot {
|
|||
process::exit(0);
|
||||
}
|
||||
|
||||
let (cwd, file) = Self::parse_entry(args.entry);
|
||||
let boot = Self {
|
||||
cwd,
|
||||
file,
|
||||
|
||||
state_dir: Xdg::state_dir().unwrap(),
|
||||
|
||||
cwd_file: args.cwd_file,
|
||||
chooser_file: args.chooser_file,
|
||||
};
|
||||
|
||||
if !boot.state_dir.is_dir() {
|
||||
fs::create_dir_all(&boot.state_dir).unwrap();
|
||||
}
|
||||
if !PREVIEW.cache_dir.is_dir() {
|
||||
fs::create_dir(&PREVIEW.cache_dir).unwrap();
|
||||
}
|
||||
|
||||
if args.clear_cache {
|
||||
if PREVIEW.cache_dir == Xdg::cache_dir() {
|
||||
println!("Clearing cache directory: \n{:?}", PREVIEW.cache_dir);
|
||||
fs::remove_dir_all(&PREVIEW.cache_dir).unwrap();
|
||||
} else {
|
||||
println!(
|
||||
"You've changed the default cache directory, for your data's safety, please clear it manually: \n{:?}",
|
||||
PREVIEW.cache_dir
|
||||
);
|
||||
}
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
boot
|
||||
args
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
mod args;
|
||||
mod boot;
|
||||
mod cli;
|
||||
|
||||
pub use args::*;
|
||||
pub use boot::*;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ where
|
|||
type Value = Vec<Exec>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a exec string, e.g. `tab_switch 0`")
|
||||
formatter.write_str("a `exec` string or array of strings within [keymap]")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
|
|
@ -45,6 +45,9 @@ where
|
|||
while let Some(value) = &seq.next_element::<String>()? {
|
||||
execs.push(parse(value).map_err(de::Error::custom)?);
|
||||
}
|
||||
if execs.is_empty() {
|
||||
return Err(de::Error::custom("`exec` within [keymap] cannot be empty"));
|
||||
}
|
||||
Ok(execs)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ mod key;
|
|||
mod keymap;
|
||||
|
||||
pub use control::*;
|
||||
#[allow(unused_imports)]
|
||||
pub use exec::*;
|
||||
#[allow(unused_imports)]
|
||||
pub use key::*;
|
||||
pub use keymap::*;
|
||||
|
|
|
|||
12
yazi-config/src/layout.rs
Normal file
12
yazi-config/src/layout.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use ratatui::layout::Rect;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Layout {
|
||||
pub header: Rect,
|
||||
|
||||
pub parent: Rect,
|
||||
pub current: Rect,
|
||||
pub preview: Rect,
|
||||
|
||||
pub status: Rect,
|
||||
}
|
||||
|
|
@ -4,11 +4,12 @@ use yazi_shared::RoCell;
|
|||
|
||||
mod boot;
|
||||
pub mod keymap;
|
||||
mod layout;
|
||||
mod log;
|
||||
pub mod manager;
|
||||
pub mod open;
|
||||
mod pattern;
|
||||
pub mod plugins;
|
||||
pub mod plugin;
|
||||
pub mod popup;
|
||||
mod preset;
|
||||
pub mod preview;
|
||||
|
|
@ -17,10 +18,15 @@ pub mod theme;
|
|||
mod validation;
|
||||
mod xdg;
|
||||
|
||||
pub use layout::*;
|
||||
pub(crate) use pattern::*;
|
||||
pub(crate) use preset::*;
|
||||
pub(crate) use xdg::*;
|
||||
|
||||
pub static ARGS: RoCell<boot::Args> = RoCell::new();
|
||||
pub static BOOT: RoCell<boot::Boot> = RoCell::new();
|
||||
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
|
||||
|
||||
static MERGED_KEYMAP: RoCell<String> = RoCell::new();
|
||||
static MERGED_THEME: RoCell<String> = RoCell::new();
|
||||
static MERGED_YAZI: RoCell<String> = RoCell::new();
|
||||
|
|
@ -29,16 +35,18 @@ pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
|
|||
pub static LOG: RoCell<log::Log> = RoCell::new();
|
||||
pub static MANAGER: RoCell<manager::Manager> = RoCell::new();
|
||||
pub static OPEN: RoCell<open::Open> = RoCell::new();
|
||||
pub static PLUGINS: RoCell<plugins::Plugins> = RoCell::new();
|
||||
pub static PLUGIN: RoCell<plugin::Plugin> = RoCell::new();
|
||||
pub static PREVIEW: RoCell<preview::Preview> = RoCell::new();
|
||||
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
|
||||
pub static THEME: RoCell<theme::Theme> = RoCell::new();
|
||||
pub static INPUT: RoCell<popup::Input> = RoCell::new();
|
||||
pub static SELECT: RoCell<popup::Select> = RoCell::new();
|
||||
|
||||
pub static BOOT: RoCell<boot::Boot> = RoCell::new();
|
||||
|
||||
pub fn init() {
|
||||
ARGS.with(Default::default);
|
||||
BOOT.with(Default::default);
|
||||
LAYOUT.with(Default::default);
|
||||
|
||||
MERGED_KEYMAP.with(Preset::keymap);
|
||||
MERGED_THEME.with(Preset::theme);
|
||||
MERGED_YAZI.with(Preset::yazi);
|
||||
|
|
@ -47,12 +55,10 @@ pub fn init() {
|
|||
LOG.with(Default::default);
|
||||
MANAGER.with(Default::default);
|
||||
OPEN.with(Default::default);
|
||||
PLUGINS.with(Default::default);
|
||||
PLUGIN.with(Default::default);
|
||||
PREVIEW.with(Default::default);
|
||||
TASKS.with(Default::default);
|
||||
THEME.with(Default::default);
|
||||
INPUT.with(Default::default);
|
||||
SELECT.with(Default::default);
|
||||
|
||||
BOOT.with(Default::default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
use anyhow::bail;
|
||||
use crossterm::terminal::WindowSize;
|
||||
use ratatui::{prelude::Rect, widgets::{Block, Padding}};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
use crate::{PREVIEW, THEME};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(try_from = "Vec<u16>")]
|
||||
pub struct ManagerLayout {
|
||||
pub parent: u16,
|
||||
pub current: u16,
|
||||
pub preview: u16,
|
||||
pub all: u16,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u16>> for ManagerLayout {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(ratio: Vec<u16>) -> Result<Self, Self::Error> {
|
||||
if ratio.len() != 3 {
|
||||
bail!("invalid layout ratio: {:?}", ratio);
|
||||
}
|
||||
if ratio.iter().all(|&r| r == 0) {
|
||||
bail!("at least one layout ratio must be non-zero: {:?}", ratio);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
parent: ratio[0],
|
||||
current: ratio[1],
|
||||
preview: ratio[2],
|
||||
all: ratio[0] + ratio[1] + ratio[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagerLayout {
|
||||
pub fn preview_rect(&self) -> Rect {
|
||||
let WindowSize { columns, rows, .. } = Term::size();
|
||||
let (top, right, bottom, left) = THEME.manager.preview_offset;
|
||||
|
||||
let w = (columns * self.preview) as f64 / self.all as f64;
|
||||
let w = if w.fract() > 0.5 { w.ceil() as u16 } else { w.floor() as u16 };
|
||||
|
||||
Rect {
|
||||
x: left.saturating_add(columns - w),
|
||||
y: top,
|
||||
width: w.saturating_sub(left + right),
|
||||
height: rows.saturating_sub(top + bottom),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn preview_height(&self) -> usize { self.preview_rect().height as usize }
|
||||
|
||||
pub fn image_rect(&self) -> Rect {
|
||||
let mut rect = self.preview_rect();
|
||||
if PREVIEW.max_width == 0 || PREVIEW.max_height == 0 {
|
||||
return rect;
|
||||
}
|
||||
if let Some((w, h)) = Term::ratio() {
|
||||
rect.width = rect.width.min((PREVIEW.max_width as f64 / w).ceil() as u16);
|
||||
rect.height = rect.height.min((PREVIEW.max_height as f64 / h).ceil() as u16);
|
||||
}
|
||||
rect
|
||||
}
|
||||
|
||||
pub fn folder_rect(&self) -> Rect {
|
||||
let WindowSize { columns, rows, .. } = Term::size();
|
||||
|
||||
let offset = THEME.manager.folder_offset;
|
||||
Block::default().padding(Padding::new(offset.3, offset.1, offset.0, offset.2)).inner(Rect {
|
||||
x: columns * self.parent / self.all,
|
||||
y: 0,
|
||||
width: columns * self.current / self.all,
|
||||
height: rows,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn folder_height(&self) -> usize { self.folder_rect().height as usize }
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
use super::{ManagerLayout, SortBy};
|
||||
use super::{ManagerRatio, SortBy};
|
||||
use crate::{validation::check_validation, MERGED_YAZI};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Validate)]
|
||||
pub struct Manager {
|
||||
pub layout: ManagerLayout,
|
||||
// FIXME: rename this to "ratio"
|
||||
pub layout: ManagerRatio,
|
||||
|
||||
// Sorting
|
||||
pub sort_by: SortBy,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod layout;
|
||||
mod manager;
|
||||
mod ratio;
|
||||
mod sorting;
|
||||
|
||||
pub use layout::*;
|
||||
pub use manager::*;
|
||||
pub use ratio::*;
|
||||
pub use sorting::*;
|
||||
|
|
|
|||
31
yazi-config/src/manager/ratio.rs
Normal file
31
yazi-config/src/manager/ratio.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(try_from = "Vec<u16>")]
|
||||
pub struct ManagerRatio {
|
||||
pub parent: u16,
|
||||
pub current: u16,
|
||||
pub preview: u16,
|
||||
pub all: u16,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u16>> for ManagerRatio {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(ratio: Vec<u16>) -> Result<Self, Self::Error> {
|
||||
if ratio.len() != 3 {
|
||||
bail!("invalid layout ratio: {:?}", ratio);
|
||||
}
|
||||
if ratio.iter().all(|&r| r == 0) {
|
||||
bail!("at least one layout ratio must be non-zero: {:?}", ratio);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
parent: ratio[0],
|
||||
current: ratio[1],
|
||||
preview: ratio[2],
|
||||
all: ratio[0] + ratio[1] + ratio[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ impl Open {
|
|||
P: AsRef<Path>,
|
||||
M: AsRef<str>,
|
||||
{
|
||||
let is_folder = Some(mime.as_ref() == MIME_DIR);
|
||||
let is_folder = mime.as_ref() == MIME_DIR;
|
||||
self.rules.iter().find_map(|rule| {
|
||||
if rule.mime.as_ref().is_some_and(|m| m.matches(&mime))
|
||||
|| rule.name.as_ref().is_some_and(|n| n.match_path(&path, is_folder))
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ impl Pattern {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn match_path(&self, path: impl AsRef<Path>, is_folder: Option<bool>) -> bool {
|
||||
pub fn match_path(&self, path: impl AsRef<Path>, is_folder: bool) -> bool {
|
||||
let path = path.as_ref();
|
||||
let s = if self.full_path {
|
||||
path.to_str()
|
||||
} else {
|
||||
path.file_name().and_then(|n| n.to_str()).or_else(|| path.to_str())
|
||||
};
|
||||
is_folder.map_or(true, |f| f == self.is_folder) && s.is_some_and(|s| self.matches(s))
|
||||
is_folder == self.is_folder && s.is_some_and(|s| self.matches(s))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
39
yazi-config/src/plugin/exec.rs
Normal file
39
yazi-config/src/plugin/exec.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{de::{self, Visitor}, Deserializer};
|
||||
use yazi_shared::event::Exec;
|
||||
|
||||
pub(super) fn exec_deserialize<'de, D>(deserializer: D) -> Result<Exec, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ExecVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ExecVisitor {
|
||||
type Value = Exec;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a `exec` string or array of strings")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, _: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: de::SeqAccess<'de>,
|
||||
{
|
||||
Err(de::Error::custom("`exec` within [plugin] must be a string"))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
if value.is_empty() {
|
||||
return Err(de::Error::custom("`exec` within [plugin] cannot be empty"));
|
||||
}
|
||||
Ok(Exec { cmd: value.to_owned(), ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ExecVisitor)
|
||||
}
|
||||
7
yazi-config/src/plugin/mod.rs
Normal file
7
yazi-config/src/plugin/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
mod exec;
|
||||
mod plugin;
|
||||
|
||||
pub use exec::*;
|
||||
pub use plugin::*;
|
||||
|
||||
pub const MAX_PRELOADERS: u8 = 32;
|
||||
78
yazi-config/src/plugin/plugin.rs
Normal file
78
yazi-config/src/plugin/plugin.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{event::Exec, Condition, MIME_DIR};
|
||||
|
||||
use crate::{pattern::Pattern, plugin::MAX_PRELOADERS, MERGED_YAZI};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Plugin {
|
||||
pub preloaders: Vec<PluginRule>,
|
||||
pub previewers: Vec<PluginRule>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PluginRule {
|
||||
#[serde(default)]
|
||||
pub id: u8,
|
||||
pub cond: Option<Condition>,
|
||||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
#[serde(deserialize_with = "super::exec_deserialize")]
|
||||
pub exec: Exec,
|
||||
#[serde(default)]
|
||||
pub sync: bool,
|
||||
#[serde(default)]
|
||||
pub multi: bool,
|
||||
}
|
||||
|
||||
impl Default for Plugin {
|
||||
fn default() -> Self {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
plugin: Plugin,
|
||||
}
|
||||
|
||||
let mut plugin = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugin;
|
||||
if plugin.preloaders.len() > MAX_PRELOADERS as usize {
|
||||
panic!("Too many preloaders");
|
||||
}
|
||||
|
||||
for (i, preloader) in plugin.preloaders.iter_mut().enumerate() {
|
||||
if preloader.sync {
|
||||
panic!("Preloaders cannot be synchronous");
|
||||
}
|
||||
preloader.id = i as u8;
|
||||
}
|
||||
|
||||
plugin
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn preloaders(
|
||||
&self,
|
||||
path: &Path,
|
||||
mime: Option<&str>,
|
||||
f: impl Fn(&str) -> bool + Copy,
|
||||
) -> Vec<&PluginRule> {
|
||||
let is_folder = mime == Some(MIME_DIR);
|
||||
self
|
||||
.preloaders
|
||||
.iter()
|
||||
.filter(|&rule| {
|
||||
rule.cond.as_ref().and_then(|c| c.eval(f)) != Some(false)
|
||||
&& (rule.name.as_ref().is_some_and(|n| n.match_path(path, is_folder))
|
||||
|| rule.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&PluginRule> {
|
||||
let is_folder = mime == MIME_DIR;
|
||||
self.previewers.iter().find(|&rule| {
|
||||
rule.mime.as_ref().is_some_and(|m| m.matches(mime))
|
||||
|| rule.name.as_ref().is_some_and(|n| n.match_path(path, is_folder))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
mod plugins;
|
||||
|
||||
pub use plugins::*;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
use yazi_shared::fs::expand_path;
|
||||
|
||||
use crate::MERGED_YAZI;
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub struct Plugins {
|
||||
pub preload: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for Plugins {
|
||||
fn default() -> Self {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
plugins: Plugins,
|
||||
}
|
||||
|
||||
let mut plugins = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugins;
|
||||
|
||||
plugins.preload.iter_mut().for_each(|p| {
|
||||
*p = expand_path(&p);
|
||||
});
|
||||
|
||||
plugins
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ use std::fs;
|
|||
|
||||
use toml::Table;
|
||||
|
||||
use crate::xdg::Xdg;
|
||||
use crate::BOOT;
|
||||
|
||||
pub(crate) struct Preset;
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ impl Preset {
|
|||
}
|
||||
|
||||
fn merge_str(user: &str, base: &str) -> String {
|
||||
let path = Xdg::config_dir().unwrap().join(user);
|
||||
let path = BOOT.config_dir.join(user);
|
||||
let mut user = fs::read_to_string(path).unwrap_or_default().parse::<Table>().unwrap();
|
||||
|
||||
let base = base.parse::<Table>().unwrap();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use std::{path::{Path, PathBuf}, time::{self, SystemTime}};
|
||||
use std::{fs, path::PathBuf, process, time::{self, SystemTime}};
|
||||
|
||||
use md5::{Digest, Md5};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_shared::fs::expand_path;
|
||||
|
||||
use crate::{xdg::Xdg, MERGED_YAZI};
|
||||
use crate::{xdg::Xdg, ARGS, MERGED_YAZI};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Preview {
|
||||
pub tab_size: u8,
|
||||
pub max_width: u32,
|
||||
|
|
@ -37,10 +36,26 @@ impl Default for Preview {
|
|||
}
|
||||
|
||||
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview;
|
||||
|
||||
let cache_dir =
|
||||
preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path);
|
||||
|
||||
if !cache_dir.is_dir() {
|
||||
fs::create_dir(&cache_dir).unwrap();
|
||||
}
|
||||
|
||||
if ARGS.clear_cache {
|
||||
if cache_dir == Xdg::cache_dir() {
|
||||
println!("Clearing cache directory: \n{:?}", cache_dir);
|
||||
fs::remove_dir_all(&cache_dir).unwrap();
|
||||
} else {
|
||||
println!(
|
||||
"You've changed the default cache directory, for your data's safety, please clear it manually: \n{:?}",
|
||||
cache_dir
|
||||
);
|
||||
}
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
Preview {
|
||||
tab_size: preview.tab_size,
|
||||
max_width: preview.max_width,
|
||||
|
|
@ -55,13 +70,6 @@ impl Default for Preview {
|
|||
}
|
||||
|
||||
impl Preview {
|
||||
#[inline]
|
||||
pub fn cache(&self, path: &Path, skip: usize) -> PathBuf {
|
||||
self
|
||||
.cache_dir
|
||||
.join(format!("{:x}", Md5::new_with_prefix(format!("{:?}///{}", path, skip)).finalize()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
|
||||
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use yazi_shared::MIME_DIR;
|
||||
|
||||
use super::{Color, Style, StyleShadow};
|
||||
use crate::Pattern;
|
||||
|
|
@ -12,14 +13,10 @@ pub struct Filetype {
|
|||
}
|
||||
|
||||
impl Filetype {
|
||||
pub fn matches(&self, path: &Path, mime: Option<impl AsRef<str>>, is_dir: bool) -> bool {
|
||||
if self.name.as_ref().is_some_and(|e| e.match_path(path, Some(is_dir))) {
|
||||
return true;
|
||||
}
|
||||
if let Some(mime) = mime {
|
||||
return self.mime.as_ref().is_some_and(|m| m.matches(mime));
|
||||
}
|
||||
false
|
||||
pub fn matches(&self, path: &Path, mime: Option<&str>) -> bool {
|
||||
let is_dir = mime == Some(MIME_DIR);
|
||||
self.name.as_ref().is_some_and(|n| n.match_path(path, is_dir))
|
||||
|| self.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@ pub struct Manager {
|
|||
pub border_symbol: String,
|
||||
pub border_style: Style,
|
||||
|
||||
// Offset
|
||||
pub(crate) folder_offset: (u16, u16, u16, u16),
|
||||
pub(crate) preview_offset: (u16, u16, u16, u16),
|
||||
|
||||
// Highlighting
|
||||
pub syntect_theme: PathBuf,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ impl Xdg {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn plugin_dir() -> Option<PathBuf> { Self::config_dir().map(|p| p.join("plugins")) }
|
||||
|
||||
pub(super) fn state_dir() -> Option<PathBuf> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ repository = "https://github.com/sxyazi/yazi"
|
|||
[dependencies]
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.1.5" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.1.5" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.1.5" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||
|
||||
|
|
@ -30,8 +31,8 @@ serde = "^1"
|
|||
syntect = { version = "^5", default-features = false, features = [ "parsing", "default-themes", "plist-load", "regex-onig" ] }
|
||||
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs", "process", "io-std", "io-util" ] }
|
||||
tokio-stream = "^0"
|
||||
tokio-util = "^0"
|
||||
unicode-width = "^0"
|
||||
yazi-prebuild = "^0"
|
||||
|
||||
# Logging
|
||||
tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mod commands;
|
||||
mod completion;
|
||||
|
||||
pub(super) use completion::*;
|
||||
pub use completion::*;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use ratatui::layout::Rect;
|
||||
use yazi_config::MANAGER;
|
||||
use yazi_shared::{emit, fs::{File, FilesOp}, fs::Url};
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
||||
|
||||
use crate::{files::Files, Step};
|
||||
use crate::{folder::Files, Step};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Folder {
|
||||
|
|
@ -50,7 +50,7 @@ impl Folder {
|
|||
}
|
||||
|
||||
pub fn set_page(&mut self, force: bool) {
|
||||
let limit = MANAGER.layout.folder_height();
|
||||
let limit = LAYOUT.load().current.height as usize;
|
||||
if limit == 0 {
|
||||
return;
|
||||
}
|
||||
|
|
@ -81,8 +81,8 @@ impl Folder {
|
|||
let old = (self.cursor, self.offset);
|
||||
let len = self.files.len();
|
||||
|
||||
let limit = MANAGER.layout.folder_height();
|
||||
self.cursor = step.add(self.cursor, || limit).min(len.saturating_sub(1));
|
||||
let limit = LAYOUT.load().current.height as usize;
|
||||
self.cursor = step.add(self.cursor, limit).min(len.saturating_sub(1));
|
||||
self.offset = if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) {
|
||||
len.saturating_sub(limit).min(self.offset + self.cursor - old.0)
|
||||
} else {
|
||||
|
|
@ -97,7 +97,7 @@ impl Folder {
|
|||
let old = (self.cursor, self.offset);
|
||||
let max = self.files.len().saturating_sub(1);
|
||||
|
||||
self.cursor = step.add(self.cursor, || MANAGER.layout.folder_height()).min(max);
|
||||
self.cursor = step.add(self.cursor, LAYOUT.load().current.height as usize).min(max);
|
||||
self.offset = if self.cursor < self.offset + 5 {
|
||||
self.offset.saturating_sub(old.0 - self.cursor)
|
||||
} else {
|
||||
|
|
@ -133,7 +133,7 @@ impl Folder {
|
|||
|
||||
pub fn paginate(&self, page: usize) -> &[File] {
|
||||
let len = self.files.len();
|
||||
let limit = MANAGER.layout.folder_height();
|
||||
let limit = LAYOUT.load().current.height as usize;
|
||||
|
||||
let start = (page * limit).min(len.saturating_sub(1));
|
||||
let end = (start + limit).min(len);
|
||||
|
|
@ -143,7 +143,7 @@ impl Folder {
|
|||
pub fn rect_current(&self, url: &Url) -> Option<Rect> {
|
||||
let y = self.files.position(url)? - self.offset;
|
||||
|
||||
let mut rect = MANAGER.layout.folder_rect();
|
||||
let mut rect = LAYOUT.load().current;
|
||||
rect.y = rect.y.saturating_sub(1) + y as u16;
|
||||
rect.height = 1;
|
||||
Some(rect)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
mod files;
|
||||
mod folder;
|
||||
mod sorter;
|
||||
|
||||
pub use files::*;
|
||||
pub use folder::*;
|
||||
pub use sorter::*;
|
||||
|
|
@ -68,16 +68,16 @@ impl FilesSorter {
|
|||
let mut entities = Vec::with_capacity(items.len());
|
||||
for (i, file) in items.iter().enumerate() {
|
||||
indices.push(i);
|
||||
entities.push((file.url.to_string_lossy(), file));
|
||||
entities.push(file.url.as_os_str().as_encoded_bytes());
|
||||
}
|
||||
|
||||
indices.sort_unstable_by(|&a, &b| {
|
||||
let promote = self.promote(entities[a].1, entities[b].1);
|
||||
let promote = self.promote(&items[a], &items[b]);
|
||||
if promote != Ordering::Equal {
|
||||
return promote;
|
||||
}
|
||||
|
||||
let ordering = natsort(&entities[a].0, &entities[b].0, !self.sensitive);
|
||||
let ordering = natsort(entities[a], entities[b], !self.sensitive);
|
||||
if self.reverse { ordering.reverse() } else { ordering }
|
||||
});
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
use anyhow::{bail, Result};
|
||||
use syntect::{easy::HighlightLines, util::as_24_bit_terminal_escaped};
|
||||
use yazi_plugin::external::Highlighter;
|
||||
|
||||
use super::Input;
|
||||
use crate::Highlighter;
|
||||
|
||||
impl Input {
|
||||
pub fn value_pretty(&self) -> Result<String> {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,10 @@
|
|||
|
||||
mod clipboard;
|
||||
pub mod completion;
|
||||
mod context;
|
||||
pub mod files;
|
||||
pub mod folder;
|
||||
pub mod help;
|
||||
mod highlighter;
|
||||
pub mod input;
|
||||
pub mod manager;
|
||||
pub mod preview;
|
||||
pub mod select;
|
||||
mod step;
|
||||
pub mod tab;
|
||||
|
|
@ -22,8 +19,6 @@ pub mod tasks;
|
|||
pub mod which;
|
||||
|
||||
pub use clipboard::*;
|
||||
pub use context::*;
|
||||
pub use highlighter::*;
|
||||
pub use step::*;
|
||||
|
||||
pub fn init() {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl Manager {
|
|||
let mut b = self.current_mut().repos(opt.url);
|
||||
|
||||
// Re-peek
|
||||
b |= self.peek(0);
|
||||
b |= self.peek(());
|
||||
|
||||
// Refresh watcher
|
||||
let mut to_watch = BTreeSet::new();
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ mod quit;
|
|||
mod refresh;
|
||||
mod remove;
|
||||
mod rename;
|
||||
mod seek;
|
||||
mod suspend;
|
||||
mod tab_close;
|
||||
mod tab_create;
|
||||
mod tab_swap;
|
||||
mod tab_switch;
|
||||
mod update_mimetype;
|
||||
mod yank;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::ffi::OsString;
|
||||
|
||||
use yazi_config::{popup::SelectCfg, OPEN};
|
||||
use yazi_scheduler::external;
|
||||
use yazi_plugin::external;
|
||||
use yazi_shared::{event::Exec, MIME_DIR};
|
||||
|
||||
use crate::{manager::Manager, select::Select, tasks::Tasks};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use yazi_config::MANAGER;
|
||||
use yazi_shared::{emit, event::Exec, fs::Url, Layer, MIME_DIR};
|
||||
use yazi_shared::{event::Exec, fs::Url, MIME_DIR};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Opt {
|
||||
step: isize,
|
||||
skip: Option<usize>,
|
||||
only_if: Option<Url>,
|
||||
upper_bound: bool,
|
||||
}
|
||||
|
|
@ -13,28 +12,17 @@ pub struct Opt {
|
|||
impl From<&Exec> for Opt {
|
||||
fn from(e: &Exec) -> Self {
|
||||
Self {
|
||||
step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0),
|
||||
skip: e.args.first().and_then(|s| s.parse().ok()),
|
||||
only_if: e.named.get("only-if").map(Url::from),
|
||||
upper_bound: e.named.contains_key("upper-bound"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<isize> for Opt {
|
||||
fn from(step: isize) -> Self { Self { step, only_if: None, upper_bound: false } }
|
||||
impl From<()> for Opt {
|
||||
fn from(_: ()) -> Self { Default::default() }
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
#[inline]
|
||||
pub fn _peek_upper_bound(bound: usize, only_if: &Url) {
|
||||
emit!(Call(
|
||||
Exec::call("peek", vec![bound.to_string()])
|
||||
.with("only-if", only_if.to_string())
|
||||
.with_bool("upper-bound", true)
|
||||
.vec(),
|
||||
Layer::Manager
|
||||
));
|
||||
}
|
||||
|
||||
pub fn peek(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let Some(hovered) = self.hovered() else {
|
||||
return self.active_mut().preview.reset();
|
||||
|
|
@ -45,42 +33,30 @@ impl Manager {
|
|||
return false;
|
||||
}
|
||||
|
||||
if hovered.is_dir() {
|
||||
return self.peek_folder(opt, hovered.url.clone());
|
||||
}
|
||||
|
||||
let Some(mime) = self.mimetype.get(&hovered.url).cloned() else {
|
||||
let mime = if hovered.is_dir() {
|
||||
MIME_DIR.to_owned()
|
||||
} else if let Some(s) = self.mimetype.get(&hovered.url) {
|
||||
s.to_owned()
|
||||
} else {
|
||||
return self.active_mut().preview.reset();
|
||||
};
|
||||
|
||||
let (url, cha) = (hovered.url.clone(), hovered.cha);
|
||||
if opt.upper_bound {
|
||||
self.active_mut().preview.arrow(0, &mime, Some(opt.step as usize));
|
||||
} else if self.active().preview.same_url(&url) {
|
||||
self.active_mut().preview.arrow(opt.step, &mime, None);
|
||||
} else {
|
||||
self.active_mut().preview.arrow(0, &mime, Some(0));
|
||||
let hovered = hovered.clone();
|
||||
if !self.active().preview.same_url(&hovered.url) {
|
||||
self.active_mut().preview.skip = 0;
|
||||
self.active_mut().preview.reset();
|
||||
}
|
||||
|
||||
self.active_mut().preview.go(&url, cha, &mime);
|
||||
if let Some(skip) = opt.skip {
|
||||
let preview = &mut self.active_mut().preview;
|
||||
if opt.upper_bound {
|
||||
preview.skip = preview.skip.min(skip);
|
||||
} else {
|
||||
preview.skip = skip;
|
||||
}
|
||||
}
|
||||
|
||||
self.active_mut().preview.go(hovered, mime);
|
||||
false
|
||||
}
|
||||
|
||||
fn peek_folder(&mut self, opt: Opt, url: Url) -> bool {
|
||||
let folder = self.active().history.get(&url);
|
||||
let (skip, bound) = folder
|
||||
.map(|f| (f.offset, f.files.len().saturating_sub(MANAGER.layout.folder_height())))
|
||||
.unwrap_or_default();
|
||||
|
||||
let in_chunks = folder.is_none();
|
||||
if self.active().preview.same_url(&url) {
|
||||
self.active_mut().preview.arrow(opt.step, MIME_DIR, Some(bound));
|
||||
self.active_mut().preview.sync_skip()
|
||||
} else {
|
||||
self.active_mut().preview.arrow(skip as isize, MIME_DIR, Some(skip));
|
||||
self.active_mut().preview.go_folder(url, in_chunks);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ use std::{collections::BTreeMap, ffi::OsStr, io::{stdout, BufWriter, Write}, pat
|
|||
use anyhow::{anyhow, bail, Result};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
|
||||
use yazi_scheduler::{external::{self, ShellOpt}, BLOCKER};
|
||||
use yazi_plugin::external::{self, ShellOpt};
|
||||
use yazi_scheduler::{Scheduler, BLOCKER};
|
||||
use yazi_shared::{emit, event::Exec, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer};
|
||||
|
||||
use crate::{input::Input, manager::Manager, Ctx};
|
||||
use crate::{input::Input, manager::Manager};
|
||||
|
||||
pub struct Opt {
|
||||
force: bool,
|
||||
|
|
@ -87,10 +88,10 @@ impl Manager {
|
|||
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let _defer = Defer::new(|| {
|
||||
Ctx::resume();
|
||||
Scheduler::app_resume();
|
||||
tokio::spawn(fs::remove_file(tmp.clone()))
|
||||
});
|
||||
Ctx::stop().await;
|
||||
Scheduler::app_stop().await;
|
||||
|
||||
let mut child = external::shell(ShellOpt {
|
||||
cmd: (*opener.exec).into(),
|
||||
|
|
|
|||
40
yazi-core/src/manager/commands/seek.rs
Normal file
40
yazi-core/src/manager/commands/seek.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use yazi_config::PLUGIN;
|
||||
use yazi_plugin::isolate;
|
||||
use yazi_shared::{event::Exec, MIME_DIR};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Opt {
|
||||
units: i16,
|
||||
}
|
||||
|
||||
impl From<&Exec> for Opt {
|
||||
fn from(e: &Exec) -> Self {
|
||||
Self { units: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn seek(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let Some(hovered) = self.hovered() else {
|
||||
return self.active_mut().preview.reset();
|
||||
};
|
||||
|
||||
let mime = if hovered.is_dir() {
|
||||
MIME_DIR
|
||||
} else if let Some(s) = self.mimetype.get(&hovered.url) {
|
||||
s
|
||||
} else {
|
||||
return self.active_mut().preview.reset();
|
||||
};
|
||||
|
||||
let Some(previewer) = PLUGIN.previewer(&hovered.url, mime) else {
|
||||
return self.active_mut().preview.reset();
|
||||
};
|
||||
|
||||
let opt = opt.into() as Opt;
|
||||
isolate::seek_sync(&previewer.exec, hovered.clone(), opt.units);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use yazi_scheduler::Scheduler;
|
||||
use yazi_shared::event::Exec;
|
||||
|
||||
use crate::{manager::Manager, Ctx};
|
||||
use crate::manager::Manager;
|
||||
|
||||
pub struct Opt;
|
||||
impl From<&Exec> for Opt {
|
||||
|
|
@ -11,7 +12,7 @@ impl Manager {
|
|||
pub fn suspend(&mut self, _: impl Into<Opt>) -> bool {
|
||||
#[cfg(unix)]
|
||||
tokio::spawn(async move {
|
||||
Ctx::stop().await;
|
||||
Scheduler::app_stop().await;
|
||||
unsafe { libc::raise(libc::SIGTSTP) };
|
||||
});
|
||||
false
|
||||
|
|
|
|||
43
yazi-core/src/manager/commands/update_mimetype.rs
Normal file
43
yazi-core/src/manager/commands/update_mimetype.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use yazi_plugin::ValueSendable;
|
||||
use yazi_shared::{event::Exec, fs::Url};
|
||||
|
||||
use crate::{manager::Manager, tasks::Tasks};
|
||||
|
||||
pub struct Opt {
|
||||
data: ValueSendable,
|
||||
}
|
||||
|
||||
impl TryFrom<&Exec> for Opt {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(e: &Exec) -> Result<Self, Self::Error> { Ok(Self { data: e.take_data().ok_or(())? }) }
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn update_mimetype(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) -> bool {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let updates: HashMap<_, _> = opt
|
||||
.data
|
||||
.into_table_string()
|
||||
.into_iter()
|
||||
.map(|(url, mime)| (Url::from(url), mime))
|
||||
.filter(|(url, mime)| self.mimetype.get(url) != Some(mime))
|
||||
.collect();
|
||||
|
||||
if updates.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let paged = self.current().paginate(self.current().page);
|
||||
tasks.preload_affected(paged, &updates);
|
||||
|
||||
self.mimetype.extend(updates);
|
||||
self.peek(());
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use yazi_shared::{fs::{File, FilesOp}, fs::Url};
|
||||
use yazi_shared::fs::{File, FilesOp, Url};
|
||||
|
||||
use super::{Tabs, Watcher};
|
||||
use crate::{tab::{Folder, Tab}, tasks::Tasks};
|
||||
use crate::{folder::Folder, tab::Tab};
|
||||
|
||||
pub struct Manager {
|
||||
pub tabs: Tabs,
|
||||
|
|
@ -65,20 +65,6 @@ impl Manager {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_mimetype(&mut self, mut mimes: BTreeMap<Url, String>, tasks: &Tasks) -> bool {
|
||||
mimes.retain(|f, m| self.mimetype.get(f) != Some(m));
|
||||
if mimes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
tasks.precache_image(&mimes);
|
||||
tasks.precache_video(&mimes);
|
||||
tasks.precache_pdf(&mimes);
|
||||
|
||||
self.mimetype.extend(mimes);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ impl Tabs {
|
|||
#[inline]
|
||||
pub(super) fn set_idx(&mut self, idx: usize) {
|
||||
self.idx = idx;
|
||||
self.active_mut().preview.reset_image();
|
||||
// TODO: plugin system
|
||||
// self.active_mut().preview.reset_image();
|
||||
Manager::_refresh();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, R
|
|||
use parking_lot::RwLock;
|
||||
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use yazi_scheduler::external;
|
||||
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
||||
|
||||
use crate::files::Files;
|
||||
use crate::folder::Files;
|
||||
|
||||
pub struct Watcher {
|
||||
watcher: RecommendedWatcher,
|
||||
|
|
@ -164,23 +163,24 @@ impl Watcher {
|
|||
}
|
||||
|
||||
async fn files_changed(urls: &[Url], watched: &IndexMap<Url, Option<Url>>) {
|
||||
let Ok(mut mimes) = external::file(urls).await else {
|
||||
return;
|
||||
};
|
||||
// TODO: plugin system
|
||||
// let Ok(mut mimes) = external::file(urls).await else {
|
||||
// return;
|
||||
// };
|
||||
|
||||
let linked: Vec<_> = watched.iter().filter_map(|(k, v)| v.as_ref().map(|v| (k, v))).fold(
|
||||
Vec::new(),
|
||||
|mut aac, (k, v)| {
|
||||
mimes
|
||||
.iter()
|
||||
.filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
|
||||
.for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()), m.clone())));
|
||||
aac
|
||||
},
|
||||
);
|
||||
// let linked: Vec<_> = watched.iter().filter_map(|(k, v)|
|
||||
// v.as_ref().map(|v| (k, v))).fold( Vec::new(),
|
||||
// |mut aac, (k, v)| {
|
||||
// mimes
|
||||
// .iter()
|
||||
// .filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
|
||||
// .for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()),
|
||||
// m.clone()))); aac
|
||||
// },
|
||||
// );
|
||||
|
||||
mimes.extend(linked);
|
||||
emit!(Mimetype(mimes));
|
||||
// mimes.extend(linked);
|
||||
// emit!(Mimetype(mimes));
|
||||
}
|
||||
|
||||
async fn dir_changed(url: &Url, watched: &IndexMap<Url, Option<Url>>) {
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
mod preview;
|
||||
mod provider;
|
||||
|
||||
pub use preview::*;
|
||||
use provider::*;
|
||||
|
||||
pub static COLLISION: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
use std::{mem, time::Duration};
|
||||
|
||||
use tokio::{pin, task::JoinHandle};
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use yazi_adaptor::ADAPTOR;
|
||||
use yazi_config::MANAGER;
|
||||
use yazi_shared::{emit, event::{PreviewData, PreviewLock}, fs::{Cha, FilesOp, Url}, MimeKind, PeekError};
|
||||
|
||||
use super::Provider;
|
||||
use crate::{files::Files, manager::Manager, Highlighter};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Preview {
|
||||
pub lock: Option<PreviewLock>,
|
||||
skip: usize,
|
||||
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
pub fn go(&mut self, url: &Url, cha: Cha, mime: &str) {
|
||||
if self.content_unchanged(url, &cha) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.abort();
|
||||
let (url, kind, skip) = (url.clone(), MimeKind::new(mime), self.skip);
|
||||
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
match Provider::auto(kind, &url, skip).await {
|
||||
Ok(data) => {
|
||||
emit!(Preview(PreviewLock { url, cha: Some(cha), skip, data }));
|
||||
}
|
||||
Err(PeekError::Exceed(max)) => {
|
||||
Manager::_peek_upper_bound(max, &url);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn go_folder(&mut self, url: Url, in_chunks: bool) {
|
||||
self.abort();
|
||||
self.lock = Some(PreviewLock {
|
||||
url: url.clone(),
|
||||
cha: None,
|
||||
skip: self.skip,
|
||||
data: PreviewData::Folder,
|
||||
});
|
||||
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
let Ok(rx) = Files::from_dir(&url).await else {
|
||||
emit!(Files(FilesOp::IOErr(url.clone())));
|
||||
return;
|
||||
};
|
||||
|
||||
if !in_chunks {
|
||||
emit!(Files(FilesOp::Full(url.clone(), UnboundedReceiverStream::new(rx).collect().await)));
|
||||
return;
|
||||
}
|
||||
|
||||
let stream =
|
||||
UnboundedReceiverStream::new(rx).chunks_timeout(10000, Duration::from_millis(500));
|
||||
pin!(stream);
|
||||
|
||||
let ticket = FilesOp::prepare(&url);
|
||||
while let Some(chunk) = stream.next().await {
|
||||
emit!(Files(FilesOp::Part(url.clone(), ticket, chunk)));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn arrow(&mut self, step: isize, mime: &str, upper: Option<usize>) {
|
||||
let size = Provider::step_size(MimeKind::new(mime), step.unsigned_abs());
|
||||
self.skip = if step < 0 { self.skip.saturating_sub(size) } else { self.skip + size };
|
||||
|
||||
if let Some(upper) = upper {
|
||||
self.skip = self.skip.min(upper);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abort(&mut self) {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
Highlighter::abort();
|
||||
ADAPTOR.image_hide(MANAGER.layout.image_rect()).ok();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) -> bool {
|
||||
self.abort();
|
||||
self.lock.take().map(|l| l.is_image()) == Some(false)
|
||||
}
|
||||
|
||||
pub fn reset_image(&mut self) -> bool {
|
||||
if matches!(self.lock, Some(ref l) if l.is_image()) {
|
||||
self.reset();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn same_url(&self, url: &Url) -> bool {
|
||||
matches!(self.lock, Some(ref lock) if lock.url == *url)
|
||||
}
|
||||
|
||||
pub fn sync_skip(&mut self) -> bool {
|
||||
if let Some(lock) = &mut self.lock {
|
||||
mem::replace(&mut lock.skip, self.skip) != self.skip
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn content_unchanged(&self, url: &Url, cha: &Cha) -> bool {
|
||||
let Some(lock) = &self.lock else {
|
||||
return false;
|
||||
};
|
||||
let Some(cha_) = &lock.cha else {
|
||||
return false;
|
||||
};
|
||||
|
||||
*url == lock.url
|
||||
&& self.skip == lock.skip
|
||||
&& cha.len == cha_.len
|
||||
&& cha.modified == cha_.modified
|
||||
&& cha.kind == cha_.kind
|
||||
&& {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cha.permissions == cha_.permissions
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
use std::path::Path;
|
||||
|
||||
use tokio::fs;
|
||||
use yazi_adaptor::ADAPTOR;
|
||||
use yazi_config::{MANAGER, PREVIEW};
|
||||
use yazi_scheduler::external;
|
||||
use yazi_shared::{event::PreviewData, MimeKind, PeekError};
|
||||
|
||||
use crate::Highlighter;
|
||||
|
||||
pub(super) struct Provider;
|
||||
|
||||
impl Provider {
|
||||
pub(super) async fn auto(
|
||||
kind: MimeKind,
|
||||
path: &Path,
|
||||
skip: usize,
|
||||
) -> Result<PreviewData, PeekError> {
|
||||
match kind {
|
||||
MimeKind::Empty => Err("Empty file".into()),
|
||||
MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::Image => Provider::image(path).await,
|
||||
MimeKind::Video => Provider::video(path, skip).await,
|
||||
MimeKind::JSON => Provider::json(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::PDF => Provider::pdf(path, skip).await,
|
||||
MimeKind::Text => Provider::highlight(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::Others => Err("Unsupported mimetype".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn step_size(kind: MimeKind, step: usize) -> usize {
|
||||
match kind {
|
||||
MimeKind::Empty => 0,
|
||||
MimeKind::Archive => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Image => 0,
|
||||
MimeKind::Video => step,
|
||||
MimeKind::JSON => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::PDF => 1,
|
||||
MimeKind::Text => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Others => step * MANAGER.layout.preview_height() / 10,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn image(path: &Path) -> Result<PreviewData, PeekError> {
|
||||
ADAPTOR.image_show(path, MANAGER.layout.image_rect()).await?;
|
||||
Ok(PreviewData::Image)
|
||||
}
|
||||
|
||||
pub(super) async fn video(path: &Path, skip: usize) -> Result<PreviewData, PeekError> {
|
||||
let cache = PREVIEW.cache(path, skip);
|
||||
if fs::symlink_metadata(&cache).await.is_err() {
|
||||
external::ffmpegthumbnailer(path, &cache, skip).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub(super) async fn pdf(path: &Path, skip: usize) -> Result<PreviewData, PeekError> {
|
||||
let cache = PREVIEW.cache(path, skip);
|
||||
if fs::symlink_metadata(&cache).await.is_err() {
|
||||
external::pdftoppm(path, &cache, skip).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub(super) async fn json(path: &Path, skip: usize) -> Result<String, PeekError> {
|
||||
let result = external::jq(path, skip, MANAGER.layout.preview_height()).await;
|
||||
if let Err(PeekError::Unexpected(_)) = result {
|
||||
return Self::highlight(path, skip).await;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) async fn archive(path: &Path, skip: usize) -> Result<String, PeekError> {
|
||||
Ok(
|
||||
external::lsar(path, skip, MANAGER.layout.preview_height())
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|f| f.name)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) async fn highlight(path: &Path, skip: usize) -> Result<String, PeekError> {
|
||||
let limit = MANAGER.layout.preview_height();
|
||||
let result = Highlighter::new(path.to_owned()).highlight(skip, limit).await?;
|
||||
Ok(result.replace('\t', &" ".repeat(PREVIEW.tab_size as usize)))
|
||||
}
|
||||
}
|
||||
|
|
@ -36,11 +36,11 @@ impl Step {
|
|||
|
||||
impl Step {
|
||||
#[inline]
|
||||
pub fn add<F: FnOnce() -> usize>(self, pos: usize, f: F) -> usize {
|
||||
pub fn add(self, pos: usize, limit: usize) -> usize {
|
||||
let fixed = match self {
|
||||
Self::Fixed(n) => n,
|
||||
Self::Percent(0) => 0,
|
||||
Self::Percent(n) => n as isize * f() as isize / 100,
|
||||
Self::Percent(n) => n as isize * limit as isize / 100,
|
||||
};
|
||||
if fixed > 0 { pos + fixed as usize } else { pos.saturating_sub(fixed.unsigned_abs()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ impl Tab {
|
|||
pub fn cd(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let opt = opt.into() as Opt;
|
||||
if opt.interactive {
|
||||
return self.cd_interactive(opt);
|
||||
return self.cd_interactive();
|
||||
}
|
||||
|
||||
if self.current.cwd == opt.target {
|
||||
|
|
@ -68,11 +68,9 @@ impl Tab {
|
|||
true
|
||||
}
|
||||
|
||||
fn cd_interactive(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let opt = opt.into() as Opt;
|
||||
|
||||
fn cd_interactive(&mut self) -> bool {
|
||||
tokio::spawn(async move {
|
||||
let rx = Input::_show(InputCfg::cd().with_value(opt.target.to_string_lossy()));
|
||||
let rx = Input::_show(InputCfg::cd());
|
||||
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||
pin!(rx);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use yazi_scheduler::{external::{self, FzfOpt, ZoxideOpt}, BLOCKER};
|
||||
use yazi_plugin::external::{self, FzfOpt, ZoxideOpt};
|
||||
use yazi_scheduler::{Scheduler, BLOCKER};
|
||||
use yazi_shared::{event::Exec, fs::ends_with_slash, Defer};
|
||||
|
||||
use crate::{tab::Tab, Ctx};
|
||||
use crate::tab::Tab;
|
||||
|
||||
pub struct Opt {
|
||||
type_: OptType,
|
||||
|
|
@ -36,8 +37,8 @@ impl Tab {
|
|||
let cwd = self.current.cwd.clone();
|
||||
tokio::spawn(async move {
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let _defer = Defer::new(Ctx::resume);
|
||||
Ctx::stop().await;
|
||||
let _defer = Defer::new(Scheduler::app_resume);
|
||||
Scheduler::app_stop().await;
|
||||
|
||||
let result = if opt.type_ == OptType::Fzf {
|
||||
external::fzf(FzfOpt { cwd }).await
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod hidden;
|
|||
mod jump;
|
||||
mod leave;
|
||||
mod linemode;
|
||||
mod preview;
|
||||
mod reveal;
|
||||
mod search;
|
||||
mod select;
|
||||
|
|
|
|||
36
yazi-core/src/tab/commands/preview.rs
Normal file
36
yazi-core/src/tab/commands/preview.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use anyhow::anyhow;
|
||||
use yazi_plugin::utils::PreviewLock;
|
||||
use yazi_shared::event::Exec;
|
||||
|
||||
use crate::tab::Tab;
|
||||
|
||||
pub struct Opt {
|
||||
lock: PreviewLock,
|
||||
}
|
||||
|
||||
impl TryFrom<&Exec> for Opt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(e: &Exec) -> Result<Self, Self::Error> {
|
||||
Ok(Self { lock: e.take_data().ok_or_else(|| anyhow!("invalid data"))? })
|
||||
}
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn preview(&mut self, opt: impl TryInto<Opt>) -> bool {
|
||||
let Some(hovered) = self.current.hovered().map(|h| &h.url) else {
|
||||
return self.preview.reset();
|
||||
};
|
||||
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if hovered != &opt.lock.url {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.preview.lock = Some(opt.lock);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use anyhow::bail;
|
|||
use tokio::pin;
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_scheduler::external;
|
||||
use yazi_plugin::external;
|
||||
use yazi_shared::{emit, event::Exec, fs::FilesOp};
|
||||
|
||||
use crate::{input::Input, manager::Manager, tab::Tab};
|
||||
|
|
@ -78,7 +78,8 @@ impl Tab {
|
|||
handle.abort();
|
||||
}
|
||||
if self.current.cwd.is_search() {
|
||||
self.preview.reset_image();
|
||||
// TODO: plugin system
|
||||
// self.preview.reset_image();
|
||||
|
||||
let rep = self.history_new(&self.current.cwd.to_regular());
|
||||
drop(mem::replace(&mut self.current, rep));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use yazi_config::{manager::SortBy, MANAGER};
|
||||
|
||||
use crate::files::FilesSorter;
|
||||
use crate::folder::FilesSorter;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use regex::bytes::{Regex, RegexBuilder};
|
||||
use yazi_shared::fs::Url;
|
||||
|
||||
use crate::files::Files;
|
||||
use crate::folder::Files;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum FinderCase {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ mod backstack;
|
|||
mod commands;
|
||||
mod config;
|
||||
mod finder;
|
||||
mod folder;
|
||||
mod mode;
|
||||
mod preview;
|
||||
mod tab;
|
||||
|
||||
pub use backstack::*;
|
||||
pub use config::*;
|
||||
pub use finder::*;
|
||||
pub use folder::*;
|
||||
pub use mode::*;
|
||||
pub use preview::*;
|
||||
pub use tab::*;
|
||||
|
|
|
|||
72
yazi-core/src/tab/preview.rs
Normal file
72
yazi-core/src/tab/preview.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use tokio_util::sync::CancellationToken;
|
||||
use yazi_adaptor::ADAPTOR;
|
||||
use yazi_config::{LAYOUT, PLUGIN};
|
||||
use yazi_plugin::{external::Highlighter, utils::PreviewLock};
|
||||
use yazi_shared::fs::{Cha, File, Url};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Preview {
|
||||
pub lock: Option<PreviewLock>,
|
||||
pub skip: usize,
|
||||
|
||||
previewer_ct: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
pub fn go(&mut self, file: File, mime: String) {
|
||||
if self.content_unchanged(&file.url, &file.cha) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.abort();
|
||||
let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if previewer.sync {
|
||||
yazi_plugin::isolate::peek_sync(&previewer.exec, file, self.skip);
|
||||
} else {
|
||||
self.previewer_ct = Some(yazi_plugin::isolate::peek(&previewer.exec, file, self.skip));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abort(&mut self) {
|
||||
self.previewer_ct.take().map(|ct| ct.cancel());
|
||||
Highlighter::abort();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) -> bool {
|
||||
self.abort();
|
||||
ADAPTOR.image_hide(LAYOUT.load().preview).ok();
|
||||
self.lock.take().is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_url(&self, url: &Url) -> bool {
|
||||
matches!(self.lock, Some(ref lock) if lock.url == *url)
|
||||
}
|
||||
|
||||
fn content_unchanged(&self, url: &Url, cha: &Cha) -> bool {
|
||||
let Some(lock) = &self.lock else {
|
||||
return false;
|
||||
};
|
||||
|
||||
*url == lock.url
|
||||
&& self.skip == lock.skip
|
||||
&& cha.len == lock.cha.len
|
||||
&& cha.modified == lock.cha.modified
|
||||
&& cha.kind == lock.cha.kind
|
||||
&& {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cha.permissions == lock.cha.permissions
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ use std::{borrow::Cow, collections::BTreeMap};
|
|||
|
||||
use anyhow::Result;
|
||||
use tokio::task::JoinHandle;
|
||||
use yazi_shared::{event::PreviewLock, fs::File, fs::Url};
|
||||
use yazi_shared::fs::{File, Url};
|
||||
|
||||
use super::{Backstack, Config, Finder, Folder, Mode};
|
||||
use crate::preview::Preview;
|
||||
use super::{Backstack, Config, Finder, Mode, Preview};
|
||||
use crate::folder::Folder;
|
||||
|
||||
pub struct Tab {
|
||||
pub mode: Mode,
|
||||
|
|
@ -46,21 +46,6 @@ impl From<&Url> for Tab {
|
|||
fn from(url: &Url) -> Self { Self::from(url.clone()) }
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn update_preview(&mut self, lock: PreviewLock) -> bool {
|
||||
let Some(hovered) = self.current.hovered().map(|h| &h.url) else {
|
||||
return self.preview.reset();
|
||||
};
|
||||
|
||||
if lock.url != *hovered {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.preview.lock = Some(lock);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
// --- Mode
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use std::io::{stdout, Write};
|
|||
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||
use yazi_scheduler::BLOCKER;
|
||||
use yazi_scheduler::{Scheduler, BLOCKER};
|
||||
use yazi_shared::{event::Exec, term::Term, Defer};
|
||||
|
||||
use crate::{tasks::Tasks, Ctx};
|
||||
use crate::tasks::Tasks;
|
||||
|
||||
pub struct Opt;
|
||||
|
||||
|
|
@ -32,10 +32,10 @@ impl Tasks {
|
|||
task.logs.clone()
|
||||
};
|
||||
|
||||
Ctx::stop().await;
|
||||
Scheduler::app_stop().await;
|
||||
let _defer = Defer::new(|| {
|
||||
disable_raw_mode().ok();
|
||||
Ctx::resume();
|
||||
Scheduler::app_resume();
|
||||
});
|
||||
|
||||
Term::clear(&mut stdout()).ok();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::ffi::OsString;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use yazi_config::{open::Opener, BOOT};
|
||||
use yazi_config::{open::Opener, ARGS};
|
||||
use yazi_shared::{emit, event::Exec, Layer};
|
||||
|
||||
use crate::tasks::Tasks;
|
||||
|
|
@ -29,7 +29,7 @@ impl Tasks {
|
|||
return false;
|
||||
};
|
||||
|
||||
if let Some(p) = &BOOT.chooser_file {
|
||||
if let Some(p) = &ARGS.chooser_file {
|
||||
let paths = opt.targets.into_iter().fold(OsString::new(), |mut s, (p, _)| {
|
||||
s.push(p);
|
||||
s.push("\n");
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::Path, sync::Arc, time::Duration};
|
||||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, path::Path, sync::Arc, time::Duration};
|
||||
|
||||
use tokio::time::sleep;
|
||||
use tracing::debug;
|
||||
use yazi_config::{manager::SortBy, open::Opener, popup::InputCfg, OPEN};
|
||||
use yazi_config::{manager::SortBy, open::Opener, plugin::{PluginRule, MAX_PRELOADERS}, popup::InputCfg, OPEN, PLUGIN};
|
||||
use yazi_scheduler::{Scheduler, TaskSummary};
|
||||
use yazi_shared::{fs::{File, Url}, term::Term, MimeKind};
|
||||
use yazi_shared::{fs::{File, Url}, term::Term, MIME_DIR};
|
||||
|
||||
use super::{TasksProgress, TASKS_PADDING, TASKS_PERCENT};
|
||||
use crate::{files::Files, input::Input};
|
||||
use crate::{folder::Files, input::Input};
|
||||
|
||||
pub struct Tasks {
|
||||
pub(super) scheduler: Arc<Scheduler>,
|
||||
|
|
@ -149,76 +149,94 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn precache_size(&self, targets: &Files) -> bool {
|
||||
pub fn plugin_micro(&self, name: &str) -> bool {
|
||||
self.scheduler.plugin_micro(name.to_owned());
|
||||
false
|
||||
}
|
||||
|
||||
pub fn plugin_macro(&self, name: &str) -> bool {
|
||||
self.scheduler.plugin_macro(name.to_owned());
|
||||
false
|
||||
}
|
||||
|
||||
pub fn preload_paged(&self, paged: &[File], mimetype: &HashMap<Url, String>) {
|
||||
let mut single_tasks = Vec::with_capacity(paged.len());
|
||||
let mut multi_tasks: [Vec<_>; MAX_PRELOADERS as usize] = Default::default();
|
||||
|
||||
let loaded = self.scheduler.preload.rule_loaded.read();
|
||||
for f in paged {
|
||||
let mime = if f.is_dir() { Some(MIME_DIR) } else { mimetype.get(&f.url).map(|s| &**s) };
|
||||
let factors = |s: &str| match s {
|
||||
"mime" => mime.is_some(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
for rule in PLUGIN.preloaders(&f.url, mime, factors) {
|
||||
if loaded.get(&f.url).is_some_and(|x| x & 1 << rule.id != 0) {
|
||||
continue;
|
||||
}
|
||||
if rule.multi {
|
||||
multi_tasks[rule.id as usize].push(f);
|
||||
} else {
|
||||
single_tasks.push((rule, f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(loaded);
|
||||
let mut loaded = self.scheduler.preload.rule_loaded.write();
|
||||
|
||||
let mut go = |rule: &PluginRule, targets: Vec<&File>| {
|
||||
for &f in &targets {
|
||||
*loaded.entry(f.url.clone()).or_default() |= 1 << rule.id;
|
||||
}
|
||||
self.scheduler.preload_paged(rule, targets);
|
||||
};
|
||||
|
||||
for i in 0..PLUGIN.preloaders.len() {
|
||||
if !multi_tasks[i].is_empty() {
|
||||
go(&PLUGIN.preloaders[i], mem::take(&mut multi_tasks[i]));
|
||||
}
|
||||
}
|
||||
for (rule, target) in single_tasks {
|
||||
go(rule, vec![target]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preload_affected(&self, paged: &[File], mimetype: &HashMap<Url, String>) {
|
||||
{
|
||||
let mut loaded = self.scheduler.preload.rule_loaded.write();
|
||||
for u in mimetype.keys() {
|
||||
loaded.remove(u);
|
||||
}
|
||||
}
|
||||
|
||||
self.preload_paged(paged, mimetype);
|
||||
}
|
||||
|
||||
pub fn preload_sorted(&self, targets: &Files) {
|
||||
if targets.sorter().by != SortBy::Size {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
let targets: Vec<_> = targets
|
||||
.iter()
|
||||
.filter(|f| f.is_dir() && !targets.sizes.contains_key(&f.url))
|
||||
.map(|f| &f.url)
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_size(targets);
|
||||
let targets: Vec<_> = {
|
||||
let loading = self.scheduler.preload.size_loading.read();
|
||||
targets
|
||||
.iter()
|
||||
.filter(|f| f.is_dir() && !targets.sizes.contains_key(&f.url) && !loading.contains(&f.url))
|
||||
.map(|f| &f.url)
|
||||
.collect()
|
||||
};
|
||||
if targets.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = targets
|
||||
.iter()
|
||||
.filter(|f| !f.is_dir() && !mimetype.contains_key(&f.url))
|
||||
.map(|f| f.url())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_mime(targets);
|
||||
let mut loading = self.scheduler.preload.size_loading.write();
|
||||
for &target in &targets {
|
||||
loading.insert(target.clone());
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn precache_image(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = mimetype
|
||||
.iter()
|
||||
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Image)
|
||||
.map(|(u, _)| u.clone())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_image(targets);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn precache_video(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = mimetype
|
||||
.iter()
|
||||
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Video)
|
||||
.map(|(u, _)| u.clone())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_video(targets);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn precache_pdf(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = mimetype
|
||||
.iter()
|
||||
.filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF)
|
||||
.map(|(u, _)| u.clone())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_pdf(targets);
|
||||
}
|
||||
false
|
||||
self.scheduler.preload_size(targets);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.1.5" }
|
||||
yazi-core = { path = "../yazi-core", version = "0.1.5" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.1.5" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.1.5" }
|
||||
yazi-core = { path = "../yazi-core", version = "0.1.5" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.1.5" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.1.5" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = "^3"
|
||||
|
|
@ -35,6 +36,12 @@ tracing-subscriber = "^0"
|
|||
libc = "^0"
|
||||
signal-hook-tokio = { version = "^0", features = [ "futures-v0_3" ] }
|
||||
|
||||
[target.'cfg(any(target_arch="riscv64", target_arch="loongarch64"))'.dependencies]
|
||||
mlua = { version = "^0", features = [ "lua52", "vendored" ] }
|
||||
|
||||
[target.'cfg(not(any(target_arch="riscv64", target_arch="loongarch64")))'.dependencies]
|
||||
mlua = { version = "^0", features = [ "luajit52", "vendored" ] }
|
||||
|
||||
[[bin]]
|
||||
name = "yazi"
|
||||
path = "src/main.rs"
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use std::sync::atomic::Ordering;
|
|||
use anyhow::{Ok, Result};
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::{backend::Backend, prelude::Rect};
|
||||
use yazi_config::{keymap::Key, BOOT};
|
||||
use yazi_core::{input::InputMode, preview::COLLISION, Ctx};
|
||||
use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer};
|
||||
use yazi_config::{keymap::Key, ARGS};
|
||||
use yazi_core::input::InputMode;
|
||||
use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer, COLLISION};
|
||||
|
||||
use crate::{Executor, Logs, Panic, Root, Signals};
|
||||
use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Root, Signals};
|
||||
|
||||
pub(crate) struct App {
|
||||
pub(crate) cx: Ctx,
|
||||
|
|
@ -19,11 +19,14 @@ impl App {
|
|||
pub(crate) async fn run() -> Result<()> {
|
||||
Panic::install();
|
||||
let _log = Logs::init()?;
|
||||
let term = Term::start()?;
|
||||
|
||||
let term = Term::start()?;
|
||||
let signals = Signals::start()?;
|
||||
|
||||
Lives::register()?;
|
||||
let mut app = Self { cx: Ctx::make(), term: Some(term), signals };
|
||||
|
||||
app.dispatch_render()?;
|
||||
while let Some(event) = app.signals.recv().await {
|
||||
match event {
|
||||
Event::Quit(no_cwd_file) => {
|
||||
|
|
@ -42,7 +45,7 @@ impl App {
|
|||
}
|
||||
|
||||
fn dispatch_quit(&mut self, no_cwd_file: bool) {
|
||||
if let Some(p) = BOOT.cwd_file.as_ref().filter(|_| !no_cwd_file) {
|
||||
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no_cwd_file) {
|
||||
let cwd = self.cx.manager.cwd().as_os_str();
|
||||
std::fs::write(p, cwd.as_encoded_bytes()).ok();
|
||||
}
|
||||
|
|
@ -72,7 +75,7 @@ impl App {
|
|||
|
||||
let collision = COLLISION.swap(false, Ordering::Relaxed);
|
||||
let frame = term.draw(|f| {
|
||||
yazi_plugin::scope(&self.cx, |_| {
|
||||
Lives::scope(&self.cx, |_| {
|
||||
f.render_widget(Root::new(&self.cx), f.size());
|
||||
});
|
||||
|
||||
|
|
@ -83,8 +86,9 @@ impl App {
|
|||
if !COLLISION.load(Ordering::Relaxed) {
|
||||
if collision {
|
||||
// Reload preview if collision is resolved
|
||||
self.cx.manager.active_mut().preview.reset_image();
|
||||
self.cx.manager.peek(0);
|
||||
// TODO: plugin system
|
||||
// self.cx.manager.active_mut().preview.reset_image();
|
||||
self.cx.manager.peek(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -115,7 +119,7 @@ impl App {
|
|||
|
||||
self.cx.manager.current_mut().set_page(true);
|
||||
self.cx.manager.active_mut().preview.reset();
|
||||
self.cx.manager.peek(0);
|
||||
self.cx.manager.peek(());
|
||||
emit!(Render);
|
||||
}
|
||||
|
||||
|
|
@ -140,23 +144,12 @@ impl App {
|
|||
emit!(Render);
|
||||
}
|
||||
if calc {
|
||||
tasks.precache_size(&manager.current().files);
|
||||
tasks.preload_sorted(&manager.current().files);
|
||||
}
|
||||
}
|
||||
Event::Pages(page) => {
|
||||
let targets = self.cx.manager.current().paginate(page);
|
||||
tasks.precache_mime(targets, &self.cx.manager.mimetype);
|
||||
}
|
||||
Event::Mimetype(mimes) => {
|
||||
if manager.update_mimetype(mimes, tasks) {
|
||||
emit!(Render);
|
||||
manager.peek(0);
|
||||
}
|
||||
}
|
||||
Event::Preview(lock) => {
|
||||
if manager.active_mut().update_preview(lock) {
|
||||
emit!(Render);
|
||||
}
|
||||
tasks.preload_paged(targets, &self.cx.manager.mimetype);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
mod plugin;
|
||||
mod stop;
|
||||
|
|
|
|||
62
yazi-fm/src/app/commands/plugin.rs
Normal file
62
yazi-fm/src/app/commands/plugin.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use mlua::{ExternalError, ExternalResult, IntoLua, Table, TableExt, Value, Variadic};
|
||||
use tracing::error;
|
||||
use yazi_plugin::{LOADED, LUA};
|
||||
use yazi_shared::{emit, event::Exec, Layer};
|
||||
|
||||
use crate::{app::App, lives::Lives};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn plugin(&mut self, opt: impl TryInto<yazi_plugin::Opt>) -> bool {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if !opt.sync {
|
||||
return self.cx.tasks.plugin_micro(&opt.name);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
if LOADED.ensure(&opt.name).await.is_ok() {
|
||||
emit!(Call(Exec::call("plugin_do", vec![opt.name]).with_data(opt.data).vec(), Layer::App));
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn plugin_do(&mut self, opt: impl TryInto<yazi_plugin::Opt>) -> bool {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let args = Variadic::from_iter(opt.data.args.into_iter().filter_map(|v| v.into_lua(&LUA).ok()));
|
||||
let mut ret: mlua::Result<Value> = Err("uninitialized plugin".into_lua_err());
|
||||
|
||||
Lives::scope(&self.cx, |_| {
|
||||
let mut plugin: Option<Table> = None;
|
||||
if let Some(b) = LOADED.read().get(&opt.name) {
|
||||
match LUA.load(b).call(()) {
|
||||
Ok(t) => plugin = Some(t),
|
||||
Err(e) => ret = Err(e),
|
||||
}
|
||||
}
|
||||
if let Some(plugin) = plugin {
|
||||
ret =
|
||||
if let Some(cb) = opt.data.cb { cb(plugin) } else { plugin.call_method("entry", args) };
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = ret {
|
||||
error!("{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(tx) = opt.data.tx else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Ok(v) = ret.and_then(|v| v.try_into().into_lua_err()) {
|
||||
tx.send(v).ok();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,8 @@ impl App {
|
|||
return false;
|
||||
};
|
||||
|
||||
self.cx.manager.active_mut().preview.reset_image();
|
||||
// TODO: plugin system
|
||||
// self.cx.manager.active_mut().preview.reset_image();
|
||||
if opt.state {
|
||||
self.signals.stop_term(true);
|
||||
self.term = None;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ use std::path::MAIN_SEPARATOR;
|
|||
|
||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, List, ListItem, Widget}};
|
||||
use yazi_config::{popup::{Offset, Position}, THEME};
|
||||
use yazi_core::Ctx;
|
||||
|
||||
use crate::widgets;
|
||||
use crate::{widgets, Ctx};
|
||||
|
||||
pub(crate) struct Completion<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
20
yazi-fm/src/components/header.rs
Normal file
20
yazi-fm/src/components/header.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use mlua::{Table, TableExt};
|
||||
use ratatui::{prelude::Buffer, widgets::Widget};
|
||||
use tracing::error;
|
||||
use yazi_plugin::{bindings::Cast, elements::{render_widgets, Rect}, LUA};
|
||||
|
||||
pub(crate) struct Header;
|
||||
|
||||
impl Widget for Header {
|
||||
fn render(self, area: ratatui::layout::Rect, buf: &mut Buffer) {
|
||||
let mut f = || {
|
||||
let area = Rect::cast(&LUA, area)?;
|
||||
let comp: Table = LUA.globals().get("Header")?;
|
||||
render_widgets(comp.call_method("render", area)?, buf);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
yazi-fm/src/components/manager.rs
Normal file
20
yazi-fm/src/components/manager.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use mlua::{Table, TableExt};
|
||||
use ratatui::{prelude::Buffer, widgets::Widget};
|
||||
use tracing::error;
|
||||
use yazi_plugin::{bindings::Cast, elements::{render_widgets, Rect}, LUA};
|
||||
|
||||
pub(crate) struct Manager;
|
||||
|
||||
impl Widget for Manager {
|
||||
fn render(self, area: ratatui::layout::Rect, buf: &mut Buffer) {
|
||||
let mut f = || {
|
||||
let area = Rect::cast(&LUA, area)?;
|
||||
let comp: Table = LUA.globals().get("Manager")?;
|
||||
render_widgets(comp.call_method("render", area)?, buf);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
yazi-fm/src/components/mod.rs
Normal file
11
yazi-fm/src/components/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod header;
|
||||
mod manager;
|
||||
mod preview;
|
||||
mod status;
|
||||
|
||||
pub(super) use header::*;
|
||||
pub(super) use manager::*;
|
||||
pub(super) use preview::*;
|
||||
pub(super) use status::*;
|
||||
25
yazi-fm/src/components/preview.rs
Normal file
25
yazi-fm/src/components/preview.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use ratatui::{prelude::Buffer, widgets::Widget};
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Preview<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Preview<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl Widget for Preview<'_> {
|
||||
fn render(self, _: ratatui::layout::Rect, buf: &mut Buffer) {
|
||||
let preview = &self.cx.manager.active().preview;
|
||||
let Some(lock) = &preview.lock else {
|
||||
return;
|
||||
};
|
||||
|
||||
for w in &lock.data {
|
||||
w.clone_render(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
yazi-fm/src/components/status.rs
Normal file
20
yazi-fm/src/components/status.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use mlua::{Table, TableExt};
|
||||
use ratatui::widgets::Widget;
|
||||
use tracing::error;
|
||||
use yazi_plugin::{bindings::Cast, elements::{render_widgets, Rect}, LUA};
|
||||
|
||||
pub(crate) struct Status;
|
||||
|
||||
impl Widget for Status {
|
||||
fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::prelude::Buffer) {
|
||||
let mut f = || {
|
||||
let area = Rect::cast(&LUA, area)?;
|
||||
let comp: Table = LUA.globals().get("Status")?;
|
||||
render_widgets(comp.call_method("render", area)?, buf);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use ratatui::prelude::Rect;
|
||||
use yazi_config::popup::{Origin, Position};
|
||||
|
||||
use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
|
||||
use yazi_core::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
|
||||
|
||||
pub struct Ctx {
|
||||
pub manager: Manager,
|
||||
|
|
@ -26,12 +25,6 @@ impl Ctx {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn stop() { yazi_scheduler::Scheduler::app_stop().await }
|
||||
|
||||
#[inline]
|
||||
pub fn resume() { yazi_scheduler::Scheduler::app_resume() }
|
||||
|
||||
pub fn area(&self, position: &Position) -> Rect {
|
||||
if position.origin != Origin::Hovered {
|
||||
return position.rect();
|
||||
|
|
@ -84,6 +84,8 @@ impl<'a> Executor<'a> {
|
|||
};
|
||||
}
|
||||
|
||||
on!(plugin);
|
||||
on!(plugin_do);
|
||||
on!(stop);
|
||||
|
||||
false
|
||||
|
|
@ -108,13 +110,16 @@ impl<'a> Executor<'a> {
|
|||
};
|
||||
}
|
||||
|
||||
on!(MANAGER, peek);
|
||||
on!(MANAGER, update_mimetype, &self.app.cx.tasks);
|
||||
on!(MANAGER, hover);
|
||||
on!(MANAGER, peek);
|
||||
on!(MANAGER, seek);
|
||||
on!(MANAGER, refresh);
|
||||
on!(MANAGER, quit, &self.app.cx.tasks);
|
||||
on!(MANAGER, close, &self.app.cx.tasks);
|
||||
on!(MANAGER, suspend);
|
||||
on!(ACTIVE, escape);
|
||||
on!(ACTIVE, preview);
|
||||
|
||||
// Navigation
|
||||
on!(ACTIVE, arrow);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, widgets::{List, ListItem, Widget}};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::Ctx;
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
pub(super) struct Bindings<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, widgets::{Paragraph, Widget}};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::Ctx;
|
||||
|
||||
use super::Bindings;
|
||||
use crate::widgets;
|
||||
use crate::{Ctx, widgets};
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use std::ops::Range;
|
|||
use ansi_to_tui::IntoText;
|
||||
use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Text}, widgets::{Block, BorderType, Borders, Paragraph, Widget}};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::{input::InputMode, Ctx};
|
||||
use yazi_core::input::InputMode;
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
use crate::widgets;
|
||||
use crate::{Ctx, widgets};
|
||||
|
||||
pub(crate) struct Input<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
78
yazi-fm/src/lives/active.rs
Normal file
78
yazi-fm/src/lives/active.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use mlua::{AnyUserData, Lua, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_config::LAYOUT;
|
||||
|
||||
use super::Folder;
|
||||
|
||||
pub struct Active<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
||||
inner: &'a yazi_core::tab::Tab,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Active<'a, 'b> {
|
||||
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.register_userdata_type::<yazi_core::tab::Mode>(|reg| {
|
||||
reg.add_field_method_get("is_select", |_, me| Ok(me.is_select()));
|
||||
reg.add_field_method_get("is_unset", |_, me| Ok(me.is_unset()));
|
||||
reg.add_field_method_get("is_visual", |_, me| Ok(me.is_visual()));
|
||||
reg.add_method("pending", |_, me, (idx, state): (usize, bool)| Ok(me.pending(idx, state)));
|
||||
|
||||
reg.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.to_string()));
|
||||
})?;
|
||||
|
||||
lua.register_userdata_type::<yazi_core::tab::Config>(|reg| {
|
||||
reg.add_field_method_get("sort_by", |_, me| Ok(me.sort_by.to_string()));
|
||||
reg.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
|
||||
reg.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
|
||||
reg.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
|
||||
|
||||
reg.add_field_method_get("linemode", |_, me| Ok(me.linemode.to_owned()));
|
||||
reg.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
|
||||
})?;
|
||||
|
||||
lua.register_userdata_type::<yazi_core::tab::Preview>(|reg| {
|
||||
reg.add_field_method_get("skip", |_, me| Ok(me.skip));
|
||||
reg.add_field_function_get("folder", |_, me| me.named_user_value::<Value>("folder"));
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Active<'a, 'b> {
|
||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a yazi_core::tab::Tab) -> Self {
|
||||
Self { scope, inner }
|
||||
}
|
||||
|
||||
pub(crate) fn make(&self) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = self.scope.create_any_userdata_ref(self.inner)?;
|
||||
ud.set_named_user_value("mode", self.scope.create_any_userdata_ref(&self.inner.mode)?)?;
|
||||
ud.set_named_user_value("conf", self.scope.create_any_userdata_ref(&self.inner.conf)?)?;
|
||||
ud.set_named_user_value(
|
||||
"parent",
|
||||
self.inner.parent.as_ref().and_then(|p| Folder::new(self.scope, p).make(None).ok()),
|
||||
)?;
|
||||
ud.set_named_user_value("current", Folder::new(self.scope, &self.inner.current).make(None)?)?;
|
||||
ud.set_named_user_value("preview", self.preview(self.inner)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
fn preview(&self, tab: &'a yazi_core::tab::Tab) -> mlua::Result<AnyUserData<'a>> {
|
||||
let inner = &tab.preview;
|
||||
let window = || inner.lock.as_ref().map(|l| (l.skip, LAYOUT.load().preview.height as usize));
|
||||
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value(
|
||||
"folder",
|
||||
tab
|
||||
.current
|
||||
.hovered()
|
||||
.filter(|&f| f.is_dir())
|
||||
.and_then(|f| tab.history(&f.url))
|
||||
.and_then(|f| Folder::new(self.scope, f).make(window()).ok()),
|
||||
)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
}
|
||||
195
yazi-fm/src/lives/folder.rs
Normal file
195
yazi-fm/src/lives/folder.rs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_config::{LAYOUT, THEME};
|
||||
use yazi_plugin::{bindings::{Cast, File, Range, Url}, elements::Style};
|
||||
use yazi_shared::MIME_DIR;
|
||||
|
||||
use super::{CtxRef, FolderRef};
|
||||
|
||||
pub struct Folder<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
||||
inner: &'a yazi_core::folder::Folder,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Folder<'a, 'b> {
|
||||
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.register_userdata_type::<yazi_core::folder::Folder>(|reg| {
|
||||
reg.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.cwd.clone()));
|
||||
reg.add_field_method_get("offset", |_, me| Ok(me.offset));
|
||||
reg.add_field_method_get("cursor", |_, me| Ok(me.cursor));
|
||||
|
||||
reg.add_field_function_get("window", |_, me| me.named_user_value::<Value>("window"));
|
||||
reg.add_field_function_get("files", |_, me| me.named_user_value::<AnyUserData>("files"));
|
||||
reg.add_field_function_get("hovered", |_, me| me.named_user_value::<Value>("hovered"));
|
||||
})?;
|
||||
|
||||
lua.register_userdata_type::<yazi_core::folder::Files>(|reg| {
|
||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
|
||||
reg.add_meta_function(MetaMethod::Pairs, |lua, me: AnyUserData| {
|
||||
let iter = lua.create_function(|lua, (me, i): (AnyUserData, usize)| {
|
||||
let files = me.borrow::<yazi_core::folder::Files>()?;
|
||||
let i = i + 1;
|
||||
Ok(if i > files.len() {
|
||||
mlua::Variadic::new()
|
||||
} else {
|
||||
mlua::Variadic::from_iter([
|
||||
i.into_lua(lua)?,
|
||||
File::cast(lua, files[i - 1].clone())?.into_lua(lua)?,
|
||||
])
|
||||
})
|
||||
})?;
|
||||
Ok((iter, me, 0))
|
||||
});
|
||||
})?;
|
||||
|
||||
File::register(lua, |reg| {
|
||||
reg.add_function("size", |_, me: AnyUserData| {
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
if !file.is_dir() {
|
||||
return Ok(Some(file.len));
|
||||
}
|
||||
|
||||
let folder = me.named_user_value::<FolderRef>("folder")?;
|
||||
Ok(folder.files.sizes.get(&file.url).copied())
|
||||
});
|
||||
reg.add_function("mime", |lua, me: AnyUserData| {
|
||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
Ok(cx.manager.mimetype.get(&file.url).cloned())
|
||||
});
|
||||
reg.add_function("prefix", |lua, me: AnyUserData| {
|
||||
let folder = me.named_user_value::<FolderRef>("folder")?;
|
||||
if !folder.cwd.is_search() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
let mut p = file.url.strip_prefix(&folder.cwd).unwrap_or(&file.url).components();
|
||||
p.next_back();
|
||||
Some(lua.create_string(p.as_path().as_os_str().as_encoded_bytes())).transpose()
|
||||
});
|
||||
reg.add_method("icon", |_, me, ()| {
|
||||
Ok(
|
||||
THEME
|
||||
.icons
|
||||
.iter()
|
||||
.find(|&x| x.name.match_path(&me.url, me.is_dir()))
|
||||
.map(|x| x.display.to_string()),
|
||||
)
|
||||
});
|
||||
reg.add_function("style", |lua, me: AnyUserData| {
|
||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
|
||||
let mime = if file.is_dir() {
|
||||
Some(MIME_DIR)
|
||||
} else {
|
||||
cx.manager.mimetype.get(&file.url).map(|x| &**x)
|
||||
};
|
||||
|
||||
Ok(
|
||||
THEME
|
||||
.filetypes
|
||||
.iter()
|
||||
.find(|&x| x.matches(&file.url, mime))
|
||||
.map(|x| Style::from(x.style)),
|
||||
)
|
||||
});
|
||||
reg.add_function("is_hovered", |_, me: AnyUserData| {
|
||||
let folder = me.named_user_value::<FolderRef>("folder")?;
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
Ok(matches!(folder.hovered(), Some(f) if f.url == file.url))
|
||||
});
|
||||
reg.add_function("is_yanked", |lua, me: AnyUserData| {
|
||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
Ok(if !cx.manager.yanked.1.contains(&file.url) {
|
||||
0u8
|
||||
} else if cx.manager.yanked.0 {
|
||||
2u8
|
||||
} else {
|
||||
1u8
|
||||
})
|
||||
});
|
||||
reg.add_function("is_selected", |lua, me: AnyUserData| {
|
||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||
let folder = me.named_user_value::<FolderRef>("folder")?;
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
|
||||
let selected = folder.files.is_selected(&file.url);
|
||||
Ok(if !cx.manager.active().mode.is_visual() {
|
||||
selected
|
||||
} else {
|
||||
let idx: usize = me.named_user_value("idx")?;
|
||||
cx.manager.active().mode.pending(folder.offset + idx, selected)
|
||||
})
|
||||
});
|
||||
reg.add_function("found", |lua, me: AnyUserData| {
|
||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||
let Some(finder) = &cx.manager.active().finder else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
if let Some(idx) = finder.matched_idx(&file.url) {
|
||||
return Some(
|
||||
lua.create_sequence_from([idx.into_lua(lua)?, finder.matched().len().into_lua(lua)?]),
|
||||
)
|
||||
.transpose();
|
||||
}
|
||||
Ok(None)
|
||||
});
|
||||
reg.add_function("highlights", |lua, me: AnyUserData| {
|
||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||
let Some(finder) = &cx.manager.active().finder else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let file = me.borrow::<yazi_shared::fs::File>()?;
|
||||
let Some(h) = file.name().and_then(|n| finder.highlighted(n)) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(h.into_iter().map(Range::from).collect::<Vec<_>>()))
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Folder<'a, 'b> {
|
||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a yazi_core::folder::Folder) -> Self {
|
||||
Self { scope, inner }
|
||||
}
|
||||
|
||||
pub(crate) fn make(&self, window: Option<(usize, usize)>) -> mlua::Result<AnyUserData<'a>> {
|
||||
let window =
|
||||
window.unwrap_or_else(|| (self.inner.offset, LAYOUT.load().current.height as usize));
|
||||
|
||||
let ud = self.scope.create_any_userdata_ref(self.inner)?;
|
||||
ud.set_named_user_value(
|
||||
"window",
|
||||
self
|
||||
.inner
|
||||
.files
|
||||
.iter()
|
||||
.skip(window.0)
|
||||
.take(window.1)
|
||||
.filter_map(|f| self.file(f).ok())
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
ud.set_named_user_value("files", self.scope.create_any_userdata_ref(&self.inner.files)?)?;
|
||||
ud.set_named_user_value("hovered", self.inner.hovered().and_then(|h| self.file(h).ok()))?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
fn file(&self, inner: &'a yazi_shared::fs::File) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value("folder", self.scope.create_any_userdata_ref(self.inner)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
}
|
||||
56
yazi-fm/src/lives/lives.rs
Normal file
56
yazi-fm/src/lives/lives.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use mlua::{Scope, Table};
|
||||
use tracing::error;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_plugin::{elements::RectRef, LUA};
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Lives;
|
||||
|
||||
impl Lives {
|
||||
pub(crate) fn register() -> mlua::Result<()> {
|
||||
yazi_plugin::bindings::Cha::register(&LUA)?;
|
||||
yazi_plugin::bindings::Url::register(&LUA)?;
|
||||
|
||||
super::Active::register(&LUA)?;
|
||||
super::Folder::register(&LUA)?;
|
||||
super::Tabs::register(&LUA)?;
|
||||
super::Tasks::register(&LUA)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn scope<'a>(cx: &'a Ctx, f: impl FnOnce(&Scope<'a, 'a>)) {
|
||||
let result = LUA.scope(|scope| {
|
||||
LUA.set_named_registry_value("cx", scope.create_any_userdata_ref(cx)?)?;
|
||||
|
||||
let global = LUA.globals();
|
||||
global.set(
|
||||
"cx",
|
||||
LUA.create_table_from([
|
||||
("active", super::Active::new(scope, cx.manager.active()).make()?),
|
||||
("tabs", super::Tabs::new(scope, &cx.manager.tabs).make()?),
|
||||
("tasks", super::Tasks::new(scope, &cx.tasks).make()?),
|
||||
])?,
|
||||
)?;
|
||||
|
||||
f(scope);
|
||||
|
||||
LAYOUT.store(Arc::new(yazi_config::Layout {
|
||||
header: *global.get::<_, Table>("Header")?.get::<_, RectRef>("area")?,
|
||||
parent: *global.get::<_, Table>("Parent")?.get::<_, RectRef>("area")?,
|
||||
current: *global.get::<_, Table>("Current")?.get::<_, RectRef>("area")?,
|
||||
preview: *global.get::<_, Table>("Preview")?.get::<_, RectRef>("area")?,
|
||||
status: *global.get::<_, Table>("Status")?.get::<_, RectRef>("area")?,
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(e) = result {
|
||||
error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
yazi-fm/src/lives/mod.rs
Normal file
16
yazi-fm/src/lives/mod.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod active;
|
||||
mod folder;
|
||||
mod lives;
|
||||
mod tabs;
|
||||
mod tasks;
|
||||
|
||||
pub(super) use active::*;
|
||||
pub(super) use folder::*;
|
||||
pub(super) use lives::*;
|
||||
pub(super) use tabs::*;
|
||||
pub(super) use tasks::*;
|
||||
|
||||
type CtxRef<'lua> = mlua::UserDataRef<'lua, crate::Ctx>;
|
||||
type FolderRef<'lua> = mlua::UserDataRef<'lua, yazi_core::folder::Folder>;
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
use mlua::{AnyUserData, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||
|
||||
use crate::LUA;
|
||||
use mlua::{AnyUserData, Lua, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||
|
||||
pub struct Tabs<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
|
@ -9,8 +7,8 @@ pub struct Tabs<'a, 'b> {
|
|||
}
|
||||
|
||||
impl<'a, 'b> Tabs<'a, 'b> {
|
||||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
LUA.register_userdata_type::<yazi_core::manager::Tabs>(|reg| {
|
||||
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.register_userdata_type::<yazi_core::manager::Tabs>(|reg| {
|
||||
reg.add_field_method_get("idx", |_, me| Ok(me.idx));
|
||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
reg.add_meta_function(MetaMethod::Index, |_, (me, index): (AnyUserData, usize)| {
|
||||
|
|
@ -19,17 +17,15 @@ impl<'a, 'b> Tabs<'a, 'b> {
|
|||
});
|
||||
})?;
|
||||
|
||||
LUA.register_userdata_type::<yazi_core::tab::Tab>(|reg| {
|
||||
reg.add_method("name", |_, me, ()| {
|
||||
Ok(
|
||||
me.current
|
||||
.cwd
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy())
|
||||
.or_else(|| Some(me.current.cwd.to_string_lossy()))
|
||||
.unwrap_or_default()
|
||||
.into_owned(),
|
||||
)
|
||||
lua.register_userdata_type::<yazi_core::tab::Tab>(|reg| {
|
||||
reg.add_method("name", |lua, me, ()| {
|
||||
Some(lua.create_string(
|
||||
me.current.cwd.file_name().map_or_else(
|
||||
|| me.current.cwd.as_os_str().as_encoded_bytes(),
|
||||
|n| n.as_encoded_bytes(),
|
||||
),
|
||||
))
|
||||
.transpose()
|
||||
});
|
||||
|
||||
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
||||
|
|
@ -41,7 +37,9 @@ impl<'a, 'b> Tabs<'a, 'b> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Tabs<'a, 'b> {
|
||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a yazi_core::manager::Tabs) -> Self {
|
||||
Self { scope, inner }
|
||||
}
|
||||
|
|
@ -62,35 +60,16 @@ impl<'a, 'b> Tabs<'a, 'b> {
|
|||
|
||||
ud.set_named_user_value("parent", inner.parent.as_ref().and_then(|p| self.folder(p).ok()))?;
|
||||
ud.set_named_user_value("current", self.folder(&inner.current)?)?;
|
||||
ud.set_named_user_value("preview", self.preview(inner)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
pub(crate) fn folder(&self, inner: &'a yazi_core::tab::Folder) -> mlua::Result<AnyUserData<'a>> {
|
||||
pub(crate) fn folder(
|
||||
&self,
|
||||
inner: &'a yazi_core::folder::Folder,
|
||||
) -> mlua::Result<AnyUserData<'a>> {
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value("files", self.files(&inner.files)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
||||
fn files(&self, inner: &'a yazi_core::files::Files) -> mlua::Result<AnyUserData<'a>> {
|
||||
self.scope.create_any_userdata_ref(inner)
|
||||
}
|
||||
|
||||
fn preview(&self, tab: &'a yazi_core::tab::Tab) -> mlua::Result<AnyUserData<'a>> {
|
||||
let inner = &tab.preview;
|
||||
|
||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||
ud.set_named_user_value(
|
||||
"folder",
|
||||
inner
|
||||
.lock
|
||||
.as_ref()
|
||||
.filter(|l| l.is_folder())
|
||||
.and_then(|l| tab.history(&l.url))
|
||||
.and_then(|f| self.folder(f).ok()),
|
||||
)?;
|
||||
ud.set_named_user_value("files", self.scope.create_any_userdata_ref(&inner.files)?)?;
|
||||
|
||||
Ok(ud)
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
use mlua::{AnyUserData, LuaSerdeExt, UserDataFields};
|
||||
|
||||
use crate::LUA;
|
||||
use mlua::{AnyUserData, Lua, LuaSerdeExt, UserDataFields};
|
||||
|
||||
pub struct Tasks<'a, 'b> {
|
||||
scope: &'b mlua::Scope<'a, 'a>,
|
||||
|
|
@ -9,8 +7,8 @@ pub struct Tasks<'a, 'b> {
|
|||
}
|
||||
|
||||
impl<'a, 'b> Tasks<'a, 'b> {
|
||||
pub(crate) fn init() -> mlua::Result<()> {
|
||||
LUA.register_userdata_type::<yazi_core::tasks::Tasks>(|reg| {
|
||||
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.register_userdata_type::<yazi_core::tasks::Tasks>(|reg| {
|
||||
reg.add_field_method_get("progress", |lua, me| lua.to_value(&me.progress))
|
||||
})?;
|
||||
|
||||
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
mod app;
|
||||
mod completion;
|
||||
mod components;
|
||||
mod context;
|
||||
mod executor;
|
||||
mod help;
|
||||
mod input;
|
||||
mod lives;
|
||||
mod logs;
|
||||
mod panic;
|
||||
mod root;
|
||||
|
|
@ -14,6 +17,7 @@ mod tasks;
|
|||
mod which;
|
||||
mod widgets;
|
||||
|
||||
use context::*;
|
||||
use executor::*;
|
||||
use logs::*;
|
||||
use panic::*;
|
||||
|
|
@ -29,6 +33,8 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
yazi_core::init();
|
||||
|
||||
yazi_scheduler::init();
|
||||
|
||||
yazi_plugin::init();
|
||||
|
||||
yazi_adaptor::init();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget};
|
||||
use yazi_core::Ctx;
|
||||
use yazi_plugin::components;
|
||||
|
||||
use super::{completion, input, select, tasks, which};
|
||||
use crate::help;
|
||||
use crate::{components, help, Ctx};
|
||||
|
||||
pub(super) struct Root<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -20,9 +18,10 @@ impl<'a> Widget for Root<'a> {
|
|||
.constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)])
|
||||
.split(area);
|
||||
|
||||
components::Header::new(self.cx).render(chunks[0], buf);
|
||||
components::Manager::new(self.cx).render(chunks[1], buf);
|
||||
components::Status::new(self.cx).render(chunks[2], buf);
|
||||
components::Header.render(chunks[0], buf);
|
||||
components::Manager.render(chunks[1], buf);
|
||||
components::Status.render(chunks[2], buf);
|
||||
components::Preview::new(self.cx).render(chunks[2], buf);
|
||||
|
||||
if self.cx.tasks.visible {
|
||||
tasks::Layout::new(self.cx).render(area, buf);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, List, ListItem, Widget}};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::Ctx;
|
||||
|
||||
use crate::widgets;
|
||||
use crate::{Ctx, widgets};
|
||||
|
||||
pub(crate) struct Select<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ impl Signals {
|
|||
#[cfg(unix)]
|
||||
fn spawn_system_task(&self) -> Result<JoinHandle<()>> {
|
||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM};
|
||||
use yazi_core::Ctx;
|
||||
use yazi_scheduler::Scheduler;
|
||||
|
||||
let tx = self.tx.clone();
|
||||
let mut signals = signal_hook_tokio::Signals::new([
|
||||
|
|
@ -68,7 +68,7 @@ impl Signals {
|
|||
break;
|
||||
}
|
||||
}
|
||||
SIGCONT => Ctx::resume(),
|
||||
SIGCONT => Scheduler::app_resume(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue