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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
|
@ -911,6 +917,12 @@ dependencies = [
|
||||||
"tiff",
|
"tiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imagesize"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|
@ -1158,7 +1170,9 @@ checksum = "7c81f8ac20188feb5461a73eabb22a34dd09d6d58513535eb587e46bff6ba250"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bstr",
|
"bstr",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
|
"futures-util",
|
||||||
"mlua-sys",
|
"mlua-sys",
|
||||||
|
"mlua_derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
|
@ -1179,6 +1193,21 @@ dependencies = [
|
||||||
"pkg-config",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
|
@ -1981,6 +2010,19 @@ dependencies = [
|
||||||
"tokio",
|
"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]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.8"
|
version = "0.8.8"
|
||||||
|
|
@ -2575,6 +2617,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"color_quant",
|
"color_quant",
|
||||||
"image",
|
"image",
|
||||||
|
"imagesize",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -2587,6 +2630,7 @@ name = "yazi-config"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"arc-swap",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"clap_complete_fig",
|
"clap_complete_fig",
|
||||||
|
|
@ -2626,11 +2670,12 @@ dependencies = [
|
||||||
"syntect",
|
"syntect",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"yazi-adaptor",
|
"yazi-adaptor",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
"yazi-prebuild",
|
"yazi-plugin",
|
||||||
"yazi-scheduler",
|
"yazi-scheduler",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
]
|
]
|
||||||
|
|
@ -2646,6 +2691,7 @@ dependencies = [
|
||||||
"fdlimit",
|
"fdlimit",
|
||||||
"futures",
|
"futures",
|
||||||
"libc",
|
"libc",
|
||||||
|
"mlua",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"signal-hook-tokio",
|
"signal-hook-tokio",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -2657,6 +2703,7 @@ dependencies = [
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
"yazi-core",
|
"yazi-core",
|
||||||
"yazi-plugin",
|
"yazi-plugin",
|
||||||
|
"yazi-scheduler",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2666,12 +2713,22 @@ version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-to-tui",
|
"ansi-to-tui",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"futures",
|
||||||
|
"libc",
|
||||||
|
"md-5",
|
||||||
"mlua",
|
"mlua",
|
||||||
|
"parking_lot",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"syntect",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
"yazi-adaptor",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
"yazi-core",
|
"yazi-prebuild",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2690,16 +2747,15 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"futures",
|
"futures",
|
||||||
"libc",
|
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
"trash",
|
"trash",
|
||||||
"yazi-adaptor",
|
"yazi-adaptor",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
|
"yazi-plugin",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2716,6 +2772,7 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"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"
|
base64 = "^0"
|
||||||
color_quant = "^1"
|
color_quant = "^1"
|
||||||
image = "^0"
|
image = "^0"
|
||||||
|
imagesize = "^0"
|
||||||
ratatui = "^0"
|
ratatui = "^0"
|
||||||
tokio = { version = "^1", features = [ "parking_lot", "io-util", "process" ] }
|
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 anyhow::{anyhow, Result};
|
||||||
use ratatui::prelude::Rect;
|
use ratatui::prelude::Rect;
|
||||||
use tokio::{fs, sync::mpsc::UnboundedSender};
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use yazi_config::PREVIEW;
|
use yazi_shared::env_exists;
|
||||||
use yazi_shared::{env_exists, RoCell};
|
|
||||||
|
|
||||||
use super::{Iterm2, Kitty, KittyOld};
|
use super::{Iterm2, Kitty, KittyOld};
|
||||||
use crate::{ueberzug::Ueberzug, Sixel, TMUX};
|
use crate::{ueberzug::Ueberzug, Sixel, TMUX};
|
||||||
|
|
||||||
static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false);
|
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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum Adaptor {
|
pub enum Adaptor {
|
||||||
Kitty,
|
Kitty,
|
||||||
|
|
@ -164,16 +159,9 @@ impl ToString for Adaptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Adaptor {
|
impl Adaptor {
|
||||||
pub(super) fn start(self) {
|
pub(super) fn start(self) { Ueberzug::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 async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||||
self.image_hide(rect).ok();
|
self.image_hide(rect).ok();
|
||||||
IMAGE_SHOWN.store(true, Ordering::Relaxed);
|
IMAGE_SHOWN.store(true, Ordering::Relaxed);
|
||||||
|
|
||||||
|
|
@ -182,9 +170,7 @@ impl Adaptor {
|
||||||
Self::KittyOld => KittyOld::image_show(path, rect).await,
|
Self::KittyOld => KittyOld::image_show(path, rect).await,
|
||||||
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
||||||
Self::Sixel => Sixel::image_show(path, rect).await,
|
Self::Sixel => Sixel::image_show(path, rect).await,
|
||||||
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
_ => Ueberzug::image_show(path, rect).await,
|
||||||
tx.send(Some((path.to_path_buf(), rect)))?;
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,9 +184,7 @@ impl Adaptor {
|
||||||
Self::Iterm2 => Iterm2::image_hide(rect),
|
Self::Iterm2 => Iterm2::image_hide(rect),
|
||||||
Self::KittyOld => KittyOld::image_hide(),
|
Self::KittyOld => KittyOld::image_hide(),
|
||||||
Self::Sixel => Sixel::image_hide(rect),
|
Self::Sixel => Sixel::image_hide(rect),
|
||||||
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
_ => Ueberzug::image_hide(rect),
|
||||||
tx.send(None)?;
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,51 +2,13 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use image::{imageops::FilterType, io::Limits, DynamicImage, ImageFormat};
|
use image::{imageops::FilterType, io::Limits, DynamicImage, ImageFormat};
|
||||||
|
use ratatui::layout::Rect;
|
||||||
use yazi_config::{PREVIEW, TASKS};
|
use yazi_config::{PREVIEW, TASKS};
|
||||||
use yazi_shared::term::Term;
|
use yazi_shared::term::Term;
|
||||||
|
|
||||||
pub struct Image;
|
pub struct Image;
|
||||||
|
|
||||||
impl 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<()> {
|
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
|
||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
let mut img = tokio::task::spawn_blocking(move || {
|
let mut img = tokio::task::spawn_blocking(move || {
|
||||||
|
|
@ -69,21 +31,45 @@ impl Image {
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn precache_vec(bin: Vec<u8>, cache: PathBuf) -> Result<()> {
|
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
|
||||||
let mut img = tokio::task::spawn_blocking(move || image::load_from_memory(&bin)).await??;
|
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 || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let (w, h) = (PREVIEW.max_width, PREVIEW.max_height);
|
Ok(if img.width() > w || img.height() > h {
|
||||||
if img.width() > w || img.height() > h {
|
img.resize(w, h, FilterType::Triangle)
|
||||||
img = img.resize(w, h, FilterType::Triangle);
|
} else {
|
||||||
}
|
img
|
||||||
|
})
|
||||||
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),
|
|
||||||
}?)
|
|
||||||
})
|
})
|
||||||
.await?
|
.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;
|
pub(super) struct Iterm2;
|
||||||
|
|
||||||
impl Iterm2 {
|
impl Iterm2 {
|
||||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
let img = Image::downscale(path, rect).await?;
|
||||||
|
let size = (img.width(), img.height());
|
||||||
let b = Self::encode(img).await?;
|
let b = Self::encode(img).await?;
|
||||||
|
|
||||||
Self::image_hide(rect)?;
|
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<()> {
|
pub(super) fn image_hide(rect: Rect) -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -312,8 +312,9 @@ static DIACRITICS: [char; 297] = [
|
||||||
pub(super) struct Kitty;
|
pub(super) struct Kitty;
|
||||||
|
|
||||||
impl Kitty {
|
impl Kitty {
|
||||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
let img = Image::downscale(path, rect).await?;
|
||||||
|
let size = (img.width(), img.height());
|
||||||
let b = Self::encode(img).await?;
|
let b = Self::encode(img).await?;
|
||||||
|
|
||||||
Self::image_hide(rect)?;
|
Self::image_hide(rect)?;
|
||||||
|
|
@ -335,7 +336,7 @@ impl Kitty {
|
||||||
stdout.write_all(buf.as_bytes())?;
|
stdout.write_all(buf.as_bytes())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(size)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,16 @@ use crate::{CLOSE, ESCAPE, START};
|
||||||
pub(super) struct KittyOld;
|
pub(super) struct KittyOld;
|
||||||
|
|
||||||
impl KittyOld {
|
impl KittyOld {
|
||||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
let img = Image::downscale(path, rect).await?;
|
||||||
|
let size = (img.width(), img.height());
|
||||||
let b = Self::encode(img).await?;
|
let b = Self::encode(img).await?;
|
||||||
|
|
||||||
Self::image_hide()?;
|
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]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,16 @@ use crate::{Image, CLOSE, ESCAPE, START};
|
||||||
pub(super) struct Sixel;
|
pub(super) struct Sixel;
|
||||||
|
|
||||||
impl Sixel {
|
impl Sixel {
|
||||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
let img = Image::downscale(path, rect).await?;
|
||||||
|
let size = (img.width(), img.height());
|
||||||
let b = Self::encode(img).await?;
|
let b = Self::encode(img).await?;
|
||||||
|
|
||||||
Self::image_hide(rect)?;
|
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<()> {
|
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 ratatui::prelude::Rect;
|
||||||
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
|
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use yazi_config::PREVIEW;
|
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;
|
pub(super) struct Ueberzug;
|
||||||
|
|
||||||
impl 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 mut child = Self::create_demon(adaptor).ok();
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
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> {
|
fn create_demon(adaptor: Adaptor) -> Result<Child> {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = "^1"
|
anyhow = "^1"
|
||||||
|
arc-swap = "^1"
|
||||||
clap = { version = "^4", features = [ "derive" ] }
|
clap = { version = "^4", features = [ "derive" ] }
|
||||||
crossterm = "^0"
|
crossterm = "^0"
|
||||||
dirs = "^5"
|
dirs = "^5"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#[path = "src/boot/cli.rs"]
|
#[path = "src/boot/args.rs"]
|
||||||
mod cli;
|
mod args;
|
||||||
|
|
||||||
use std::{env, error::Error, fs};
|
use std::{env, error::Error, fs};
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd = &mut cli::Args::command();
|
let cmd = &mut args::Args::command();
|
||||||
let bin = "yazi";
|
let bin = "yazi";
|
||||||
let out = "completions";
|
let out = "completions";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ keymap = [
|
||||||
{ on = [ "H" ], exec = "back", desc = "Go back to the previous directory" },
|
{ on = [ "H" ], exec = "back", desc = "Go back to the previous directory" },
|
||||||
{ on = [ "L" ], exec = "forward", desc = "Go forward to the next 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-k>" ], exec = "seek -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-j>" ], exec = "seek 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-PageUp>" ], exec = "seek -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-PageDown>" ], exec = "seek 5", desc = "Peek down 5 units in the preview" },
|
||||||
|
|
||||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,6 @@ tab_width = 1
|
||||||
border_symbol = "│"
|
border_symbol = "│"
|
||||||
border_style = { fg = "gray" }
|
border_style = { fg = "gray" }
|
||||||
|
|
||||||
# Offset
|
|
||||||
folder_offset = [ 1, 0, 1, 0 ]
|
|
||||||
preview_offset = [ 1, 1, 1, 1 ]
|
|
||||||
|
|
||||||
# Highlighting
|
# Highlighting
|
||||||
syntect_theme = ""
|
syntect_theme = ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ rules = [
|
||||||
{ mime = "application/x-bzip2", use = [ "extract", "reveal" ] },
|
{ mime = "application/x-bzip2", use = [ "extract", "reveal" ] },
|
||||||
{ mime = "application/x-7z-compressed", use = [ "extract", "reveal" ] },
|
{ mime = "application/x-7z-compressed", use = [ "extract", "reveal" ] },
|
||||||
{ mime = "application/x-rar", use = [ "extract", "reveal" ] },
|
{ mime = "application/x-rar", use = [ "extract", "reveal" ] },
|
||||||
|
{ mime = "application/xz", use = [ "extract", "reveal" ] },
|
||||||
|
|
||||||
{ mime = "*", use = [ "open", "reveal" ] },
|
{ mime = "*", use = [ "open", "reveal" ] },
|
||||||
]
|
]
|
||||||
|
|
@ -73,8 +74,44 @@ image_alloc = 536870912 # 512MB
|
||||||
image_bound = [ 0, 0 ]
|
image_bound = [ 0, 0 ]
|
||||||
suppress_preload = false
|
suppress_preload = false
|
||||||
|
|
||||||
[plugins]
|
[plugin]
|
||||||
preload = []
|
|
||||||
|
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]
|
[input]
|
||||||
# cd
|
# cd
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use clap::{command, Parser};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "yazi")]
|
#[command(name = "yazi")]
|
||||||
pub(super) struct Args {
|
pub struct Args {
|
||||||
/// Set the current working entry
|
/// Set the current working entry
|
||||||
#[arg(index = 1)]
|
#[arg(index = 1)]
|
||||||
pub entry: Option<PathBuf>,
|
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 clap::Parser;
|
||||||
use yazi_shared::fs::{current_cwd, expand_path};
|
use yazi_shared::fs::{current_cwd, expand_path};
|
||||||
|
|
||||||
use super::cli::Args;
|
use super::Args;
|
||||||
use crate::{Xdg, PREVIEW};
|
use crate::{Xdg, ARGS};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Boot {
|
pub struct Boot {
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
pub file: Option<OsString>,
|
pub file: Option<OsString>,
|
||||||
|
|
||||||
pub state_dir: PathBuf,
|
pub config_dir: PathBuf,
|
||||||
|
pub plugin_dir: PathBuf,
|
||||||
pub cwd_file: Option<PathBuf>,
|
pub state_dir: PathBuf,
|
||||||
pub chooser_file: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Boot {
|
impl Boot {
|
||||||
fn parse_entry(entry: Option<PathBuf>) -> (PathBuf, Option<OsString>) {
|
fn parse_entry(entry: Option<&Path>) -> (PathBuf, Option<OsString>) {
|
||||||
let entry = match entry {
|
let entry = match entry {
|
||||||
Some(p) => expand_path(p),
|
Some(p) => expand_path(p),
|
||||||
None => return (current_cwd().unwrap(), None),
|
None => return (current_cwd().unwrap(), None),
|
||||||
|
|
@ -35,7 +34,28 @@ impl Boot {
|
||||||
|
|
||||||
impl Default for Boot {
|
impl Default for Boot {
|
||||||
fn default() -> Self {
|
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 {
|
if args.version {
|
||||||
println!(
|
println!(
|
||||||
"yazi {} ({} {})",
|
"yazi {} ({} {})",
|
||||||
|
|
@ -46,37 +66,6 @@ impl Default for Boot {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (cwd, file) = Self::parse_entry(args.entry);
|
args
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
|
mod args;
|
||||||
mod boot;
|
mod boot;
|
||||||
mod cli;
|
|
||||||
|
|
||||||
|
pub use args::*;
|
||||||
pub use boot::*;
|
pub use boot::*;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ where
|
||||||
type Value = Vec<Exec>;
|
type Value = Vec<Exec>;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
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>
|
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>()? {
|
while let Some(value) = &seq.next_element::<String>()? {
|
||||||
execs.push(parse(value).map_err(de::Error::custom)?);
|
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)
|
Ok(execs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ mod key;
|
||||||
mod keymap;
|
mod keymap;
|
||||||
|
|
||||||
pub use control::*;
|
pub use control::*;
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use exec::*;
|
pub use exec::*;
|
||||||
|
#[allow(unused_imports)]
|
||||||
pub use key::*;
|
pub use key::*;
|
||||||
pub use keymap::*;
|
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;
|
mod boot;
|
||||||
pub mod keymap;
|
pub mod keymap;
|
||||||
|
mod layout;
|
||||||
mod log;
|
mod log;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
pub mod open;
|
pub mod open;
|
||||||
mod pattern;
|
mod pattern;
|
||||||
pub mod plugins;
|
pub mod plugin;
|
||||||
pub mod popup;
|
pub mod popup;
|
||||||
mod preset;
|
mod preset;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
|
|
@ -17,10 +18,15 @@ pub mod theme;
|
||||||
mod validation;
|
mod validation;
|
||||||
mod xdg;
|
mod xdg;
|
||||||
|
|
||||||
|
pub use layout::*;
|
||||||
pub(crate) use pattern::*;
|
pub(crate) use pattern::*;
|
||||||
pub(crate) use preset::*;
|
pub(crate) use preset::*;
|
||||||
pub(crate) use xdg::*;
|
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_KEYMAP: RoCell<String> = RoCell::new();
|
||||||
static MERGED_THEME: RoCell<String> = RoCell::new();
|
static MERGED_THEME: RoCell<String> = RoCell::new();
|
||||||
static MERGED_YAZI: 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 LOG: RoCell<log::Log> = RoCell::new();
|
||||||
pub static MANAGER: RoCell<manager::Manager> = RoCell::new();
|
pub static MANAGER: RoCell<manager::Manager> = RoCell::new();
|
||||||
pub static OPEN: RoCell<open::Open> = 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 PREVIEW: RoCell<preview::Preview> = RoCell::new();
|
||||||
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
|
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
|
||||||
pub static THEME: RoCell<theme::Theme> = RoCell::new();
|
pub static THEME: RoCell<theme::Theme> = RoCell::new();
|
||||||
pub static INPUT: RoCell<popup::Input> = RoCell::new();
|
pub static INPUT: RoCell<popup::Input> = RoCell::new();
|
||||||
pub static SELECT: RoCell<popup::Select> = RoCell::new();
|
pub static SELECT: RoCell<popup::Select> = RoCell::new();
|
||||||
|
|
||||||
pub static BOOT: RoCell<boot::Boot> = RoCell::new();
|
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
|
ARGS.with(Default::default);
|
||||||
|
BOOT.with(Default::default);
|
||||||
|
LAYOUT.with(Default::default);
|
||||||
|
|
||||||
MERGED_KEYMAP.with(Preset::keymap);
|
MERGED_KEYMAP.with(Preset::keymap);
|
||||||
MERGED_THEME.with(Preset::theme);
|
MERGED_THEME.with(Preset::theme);
|
||||||
MERGED_YAZI.with(Preset::yazi);
|
MERGED_YAZI.with(Preset::yazi);
|
||||||
|
|
@ -47,12 +55,10 @@ pub fn init() {
|
||||||
LOG.with(Default::default);
|
LOG.with(Default::default);
|
||||||
MANAGER.with(Default::default);
|
MANAGER.with(Default::default);
|
||||||
OPEN.with(Default::default);
|
OPEN.with(Default::default);
|
||||||
PLUGINS.with(Default::default);
|
PLUGIN.with(Default::default);
|
||||||
PREVIEW.with(Default::default);
|
PREVIEW.with(Default::default);
|
||||||
TASKS.with(Default::default);
|
TASKS.with(Default::default);
|
||||||
THEME.with(Default::default);
|
THEME.with(Default::default);
|
||||||
INPUT.with(Default::default);
|
INPUT.with(Default::default);
|
||||||
SELECT.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 serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use super::{ManagerLayout, SortBy};
|
use super::{ManagerRatio, SortBy};
|
||||||
use crate::{validation::check_validation, MERGED_YAZI};
|
use crate::{validation::check_validation, MERGED_YAZI};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Validate)]
|
#[derive(Debug, Deserialize, Serialize, Validate)]
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
pub layout: ManagerLayout,
|
// FIXME: rename this to "ratio"
|
||||||
|
pub layout: ManagerRatio,
|
||||||
|
|
||||||
// Sorting
|
// Sorting
|
||||||
pub sort_by: SortBy,
|
pub sort_by: SortBy,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
mod layout;
|
|
||||||
mod manager;
|
mod manager;
|
||||||
|
mod ratio;
|
||||||
mod sorting;
|
mod sorting;
|
||||||
|
|
||||||
pub use layout::*;
|
|
||||||
pub use manager::*;
|
pub use manager::*;
|
||||||
|
pub use ratio::*;
|
||||||
pub use sorting::*;
|
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>,
|
P: AsRef<Path>,
|
||||||
M: AsRef<str>,
|
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| {
|
self.rules.iter().find_map(|rule| {
|
||||||
if rule.mime.as_ref().is_some_and(|m| m.matches(&mime))
|
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))
|
|| rule.name.as_ref().is_some_and(|n| n.match_path(&path, is_folder))
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ impl Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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 path = path.as_ref();
|
||||||
let s = if self.full_path {
|
let s = if self.full_path {
|
||||||
path.to_str()
|
path.to_str()
|
||||||
} else {
|
} else {
|
||||||
path.file_name().and_then(|n| n.to_str()).or_else(|| path.to_str())
|
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 toml::Table;
|
||||||
|
|
||||||
use crate::xdg::Xdg;
|
use crate::BOOT;
|
||||||
|
|
||||||
pub(crate) struct Preset;
|
pub(crate) struct Preset;
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ impl Preset {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_str(user: &str, base: &str) -> String {
|
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 mut user = fs::read_to_string(path).unwrap_or_default().parse::<Table>().unwrap();
|
||||||
|
|
||||||
let base = base.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, Serialize};
|
||||||
use serde::Deserialize;
|
|
||||||
use yazi_shared::fs::expand_path;
|
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 struct Preview {
|
||||||
pub tab_size: u8,
|
pub tab_size: u8,
|
||||||
pub max_width: u32,
|
pub max_width: u32,
|
||||||
|
|
@ -37,10 +36,26 @@ impl Default for Preview {
|
||||||
}
|
}
|
||||||
|
|
||||||
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview;
|
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview;
|
||||||
|
|
||||||
let cache_dir =
|
let cache_dir =
|
||||||
preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path);
|
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 {
|
Preview {
|
||||||
tab_size: preview.tab_size,
|
tab_size: preview.tab_size,
|
||||||
max_width: preview.max_width,
|
max_width: preview.max_width,
|
||||||
|
|
@ -55,13 +70,6 @@ impl Default for Preview {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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]
|
#[inline]
|
||||||
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
|
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
|
||||||
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
|
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use yazi_shared::MIME_DIR;
|
||||||
|
|
||||||
use super::{Color, Style, StyleShadow};
|
use super::{Color, Style, StyleShadow};
|
||||||
use crate::Pattern;
|
use crate::Pattern;
|
||||||
|
|
@ -12,14 +13,10 @@ pub struct Filetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filetype {
|
impl Filetype {
|
||||||
pub fn matches(&self, path: &Path, mime: Option<impl AsRef<str>>, is_dir: bool) -> bool {
|
pub fn matches(&self, path: &Path, mime: Option<&str>) -> bool {
|
||||||
if self.name.as_ref().is_some_and(|e| e.match_path(path, Some(is_dir))) {
|
let is_dir = mime == Some(MIME_DIR);
|
||||||
return true;
|
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))
|
||||||
if let Some(mime) = mime {
|
|
||||||
return self.mime.as_ref().is_some_and(|m| m.matches(mime));
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,6 @@ pub struct Manager {
|
||||||
pub border_symbol: String,
|
pub border_symbol: String,
|
||||||
pub border_style: Style,
|
pub border_style: Style,
|
||||||
|
|
||||||
// Offset
|
|
||||||
pub(crate) folder_offset: (u16, u16, u16, u16),
|
|
||||||
pub(crate) preview_offset: (u16, u16, u16, u16),
|
|
||||||
|
|
||||||
// Highlighting
|
// Highlighting
|
||||||
pub syntect_theme: PathBuf,
|
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> {
|
pub(super) fn state_dir() -> Option<PathBuf> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ repository = "https://github.com/sxyazi/yazi"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
||||||
yazi-config = { path = "../yazi-config", 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-scheduler = { path = "../yazi-scheduler", version = "0.1.5" }
|
||||||
yazi-shared = { path = "../yazi-shared", 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" ] }
|
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 = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs", "process", "io-std", "io-util" ] }
|
||||||
tokio-stream = "^0"
|
tokio-stream = "^0"
|
||||||
|
tokio-util = "^0"
|
||||||
unicode-width = "^0"
|
unicode-width = "^0"
|
||||||
yazi-prebuild = "^0"
|
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] }
|
tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
mod completion;
|
mod completion;
|
||||||
|
|
||||||
pub(super) use completion::*;
|
pub use completion::*;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use yazi_config::MANAGER;
|
use yazi_config::LAYOUT;
|
||||||
use yazi_shared::{emit, fs::{File, FilesOp}, fs::Url};
|
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
||||||
|
|
||||||
use crate::{files::Files, Step};
|
use crate::{folder::Files, Step};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Folder {
|
pub struct Folder {
|
||||||
|
|
@ -50,7 +50,7 @@ impl Folder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_page(&mut self, force: bool) {
|
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 {
|
if limit == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -81,8 +81,8 @@ impl Folder {
|
||||||
let old = (self.cursor, self.offset);
|
let old = (self.cursor, self.offset);
|
||||||
let len = self.files.len();
|
let len = self.files.len();
|
||||||
|
|
||||||
let limit = MANAGER.layout.folder_height();
|
let limit = LAYOUT.load().current.height as usize;
|
||||||
self.cursor = step.add(self.cursor, || limit).min(len.saturating_sub(1));
|
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) {
|
self.offset = if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) {
|
||||||
len.saturating_sub(limit).min(self.offset + self.cursor - old.0)
|
len.saturating_sub(limit).min(self.offset + self.cursor - old.0)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -97,7 +97,7 @@ impl Folder {
|
||||||
let old = (self.cursor, self.offset);
|
let old = (self.cursor, self.offset);
|
||||||
let max = self.files.len().saturating_sub(1);
|
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 = if self.cursor < self.offset + 5 {
|
||||||
self.offset.saturating_sub(old.0 - self.cursor)
|
self.offset.saturating_sub(old.0 - self.cursor)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -133,7 +133,7 @@ impl Folder {
|
||||||
|
|
||||||
pub fn paginate(&self, page: usize) -> &[File] {
|
pub fn paginate(&self, page: usize) -> &[File] {
|
||||||
let len = self.files.len();
|
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 start = (page * limit).min(len.saturating_sub(1));
|
||||||
let end = (start + limit).min(len);
|
let end = (start + limit).min(len);
|
||||||
|
|
@ -143,7 +143,7 @@ impl Folder {
|
||||||
pub fn rect_current(&self, url: &Url) -> Option<Rect> {
|
pub fn rect_current(&self, url: &Url) -> Option<Rect> {
|
||||||
let y = self.files.position(url)? - self.offset;
|
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.y = rect.y.saturating_sub(1) + y as u16;
|
||||||
rect.height = 1;
|
rect.height = 1;
|
||||||
Some(rect)
|
Some(rect)
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
mod files;
|
mod files;
|
||||||
|
mod folder;
|
||||||
mod sorter;
|
mod sorter;
|
||||||
|
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
pub use folder::*;
|
||||||
pub use sorter::*;
|
pub use sorter::*;
|
||||||
|
|
@ -68,16 +68,16 @@ impl FilesSorter {
|
||||||
let mut entities = Vec::with_capacity(items.len());
|
let mut entities = Vec::with_capacity(items.len());
|
||||||
for (i, file) in items.iter().enumerate() {
|
for (i, file) in items.iter().enumerate() {
|
||||||
indices.push(i);
|
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| {
|
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 {
|
if promote != Ordering::Equal {
|
||||||
return promote;
|
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 }
|
if self.reverse { ordering.reverse() } else { ordering }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use syntect::{easy::HighlightLines, util::as_24_bit_terminal_escaped};
|
use syntect::{easy::HighlightLines, util::as_24_bit_terminal_escaped};
|
||||||
|
use yazi_plugin::external::Highlighter;
|
||||||
|
|
||||||
use super::Input;
|
use super::Input;
|
||||||
use crate::Highlighter;
|
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn value_pretty(&self) -> Result<String> {
|
pub fn value_pretty(&self) -> Result<String> {
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,10 @@
|
||||||
|
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
pub mod completion;
|
pub mod completion;
|
||||||
mod context;
|
pub mod folder;
|
||||||
pub mod files;
|
|
||||||
pub mod help;
|
pub mod help;
|
||||||
mod highlighter;
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
pub mod preview;
|
|
||||||
pub mod select;
|
pub mod select;
|
||||||
mod step;
|
mod step;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
@ -22,8 +19,6 @@ pub mod tasks;
|
||||||
pub mod which;
|
pub mod which;
|
||||||
|
|
||||||
pub use clipboard::*;
|
pub use clipboard::*;
|
||||||
pub use context::*;
|
|
||||||
pub use highlighter::*;
|
|
||||||
pub use step::*;
|
pub use step::*;
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl Manager {
|
||||||
let mut b = self.current_mut().repos(opt.url);
|
let mut b = self.current_mut().repos(opt.url);
|
||||||
|
|
||||||
// Re-peek
|
// Re-peek
|
||||||
b |= self.peek(0);
|
b |= self.peek(());
|
||||||
|
|
||||||
// Refresh watcher
|
// Refresh watcher
|
||||||
let mut to_watch = BTreeSet::new();
|
let mut to_watch = BTreeSet::new();
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ mod quit;
|
||||||
mod refresh;
|
mod refresh;
|
||||||
mod remove;
|
mod remove;
|
||||||
mod rename;
|
mod rename;
|
||||||
|
mod seek;
|
||||||
mod suspend;
|
mod suspend;
|
||||||
mod tab_close;
|
mod tab_close;
|
||||||
mod tab_create;
|
mod tab_create;
|
||||||
mod tab_swap;
|
mod tab_swap;
|
||||||
mod tab_switch;
|
mod tab_switch;
|
||||||
|
mod update_mimetype;
|
||||||
mod yank;
|
mod yank;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use yazi_config::{popup::SelectCfg, OPEN};
|
use yazi_config::{popup::SelectCfg, OPEN};
|
||||||
use yazi_scheduler::external;
|
use yazi_plugin::external;
|
||||||
use yazi_shared::{event::Exec, MIME_DIR};
|
use yazi_shared::{event::Exec, MIME_DIR};
|
||||||
|
|
||||||
use crate::{manager::Manager, select::Select, tasks::Tasks};
|
use crate::{manager::Manager, select::Select, tasks::Tasks};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use yazi_config::MANAGER;
|
use yazi_shared::{event::Exec, fs::Url, MIME_DIR};
|
||||||
use yazi_shared::{emit, event::Exec, fs::Url, Layer, MIME_DIR};
|
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
step: isize,
|
skip: Option<usize>,
|
||||||
only_if: Option<Url>,
|
only_if: Option<Url>,
|
||||||
upper_bound: bool,
|
upper_bound: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -13,28 +12,17 @@ pub struct Opt {
|
||||||
impl From<&Exec> for Opt {
|
impl From<&Exec> for Opt {
|
||||||
fn from(e: &Exec) -> Self {
|
fn from(e: &Exec) -> Self {
|
||||||
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),
|
only_if: e.named.get("only-if").map(Url::from),
|
||||||
upper_bound: e.named.contains_key("upper-bound"),
|
upper_bound: e.named.contains_key("upper-bound"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<isize> for Opt {
|
impl From<()> for Opt {
|
||||||
fn from(step: isize) -> Self { Self { step, only_if: None, upper_bound: false } }
|
fn from(_: ()) -> Self { Default::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manager {
|
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 {
|
pub fn peek(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let Some(hovered) = self.hovered() else {
|
let Some(hovered) = self.hovered() else {
|
||||||
return self.active_mut().preview.reset();
|
return self.active_mut().preview.reset();
|
||||||
|
|
@ -45,42 +33,30 @@ impl Manager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if hovered.is_dir() {
|
let mime = if hovered.is_dir() {
|
||||||
return self.peek_folder(opt, hovered.url.clone());
|
MIME_DIR.to_owned()
|
||||||
}
|
} else if let Some(s) = self.mimetype.get(&hovered.url) {
|
||||||
|
s.to_owned()
|
||||||
let Some(mime) = self.mimetype.get(&hovered.url).cloned() else {
|
} else {
|
||||||
return self.active_mut().preview.reset();
|
return self.active_mut().preview.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
let (url, cha) = (hovered.url.clone(), hovered.cha);
|
let hovered = hovered.clone();
|
||||||
if opt.upper_bound {
|
if !self.active().preview.same_url(&hovered.url) {
|
||||||
self.active_mut().preview.arrow(0, &mime, Some(opt.step as usize));
|
self.active_mut().preview.skip = 0;
|
||||||
} 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));
|
|
||||||
self.active_mut().preview.reset();
|
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
|
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 anyhow::{anyhow, bail, Result};
|
||||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||||
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
|
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 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 {
|
pub struct Opt {
|
||||||
force: bool,
|
force: bool,
|
||||||
|
|
@ -87,10 +88,10 @@ impl Manager {
|
||||||
|
|
||||||
let _guard = BLOCKER.acquire().await.unwrap();
|
let _guard = BLOCKER.acquire().await.unwrap();
|
||||||
let _defer = Defer::new(|| {
|
let _defer = Defer::new(|| {
|
||||||
Ctx::resume();
|
Scheduler::app_resume();
|
||||||
tokio::spawn(fs::remove_file(tmp.clone()))
|
tokio::spawn(fs::remove_file(tmp.clone()))
|
||||||
});
|
});
|
||||||
Ctx::stop().await;
|
Scheduler::app_stop().await;
|
||||||
|
|
||||||
let mut child = external::shell(ShellOpt {
|
let mut child = external::shell(ShellOpt {
|
||||||
cmd: (*opener.exec).into(),
|
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 yazi_shared::event::Exec;
|
||||||
|
|
||||||
use crate::{manager::Manager, Ctx};
|
use crate::manager::Manager;
|
||||||
|
|
||||||
pub struct Opt;
|
pub struct Opt;
|
||||||
impl From<&Exec> for Opt {
|
impl From<&Exec> for Opt {
|
||||||
|
|
@ -11,7 +12,7 @@ impl Manager {
|
||||||
pub fn suspend(&mut self, _: impl Into<Opt>) -> bool {
|
pub fn suspend(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
Ctx::stop().await;
|
Scheduler::app_stop().await;
|
||||||
unsafe { libc::raise(libc::SIGTSTP) };
|
unsafe { libc::raise(libc::SIGTSTP) };
|
||||||
});
|
});
|
||||||
false
|
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 super::{Tabs, Watcher};
|
||||||
use crate::{tab::{Folder, Tab}, tasks::Tasks};
|
use crate::{folder::Folder, tab::Tab};
|
||||||
|
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
pub tabs: Tabs,
|
pub tabs: Tabs,
|
||||||
|
|
@ -65,20 +65,6 @@ impl Manager {
|
||||||
false
|
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 {
|
impl Manager {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ impl Tabs {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn set_idx(&mut self, idx: usize) {
|
pub(super) fn set_idx(&mut self, idx: usize) {
|
||||||
self.idx = idx;
|
self.idx = idx;
|
||||||
self.active_mut().preview.reset_image();
|
// TODO: plugin system
|
||||||
|
// self.active_mut().preview.reset_image();
|
||||||
Manager::_refresh();
|
Manager::_refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,9 @@ use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, R
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
use yazi_scheduler::external;
|
|
||||||
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
||||||
|
|
||||||
use crate::files::Files;
|
use crate::folder::Files;
|
||||||
|
|
||||||
pub struct Watcher {
|
pub struct Watcher {
|
||||||
watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
|
|
@ -164,23 +163,24 @@ impl Watcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn files_changed(urls: &[Url], watched: &IndexMap<Url, Option<Url>>) {
|
async fn files_changed(urls: &[Url], watched: &IndexMap<Url, Option<Url>>) {
|
||||||
let Ok(mut mimes) = external::file(urls).await else {
|
// TODO: plugin system
|
||||||
return;
|
// 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(
|
// let linked: Vec<_> = watched.iter().filter_map(|(k, v)|
|
||||||
Vec::new(),
|
// v.as_ref().map(|v| (k, v))).fold( Vec::new(),
|
||||||
|mut aac, (k, v)| {
|
// |mut aac, (k, v)| {
|
||||||
mimes
|
// mimes
|
||||||
.iter()
|
// .iter()
|
||||||
.filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
|
// .filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
|
||||||
.for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()), m.clone())));
|
// .for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()),
|
||||||
aac
|
// m.clone()))); aac
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
mimes.extend(linked);
|
// mimes.extend(linked);
|
||||||
emit!(Mimetype(mimes));
|
// emit!(Mimetype(mimes));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dir_changed(url: &Url, watched: &IndexMap<Url, Option<Url>>) {
|
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 {
|
impl Step {
|
||||||
#[inline]
|
#[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 {
|
let fixed = match self {
|
||||||
Self::Fixed(n) => n,
|
Self::Fixed(n) => n,
|
||||||
Self::Percent(0) => 0,
|
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()) }
|
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 {
|
pub fn cd(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
if opt.interactive {
|
if opt.interactive {
|
||||||
return self.cd_interactive(opt);
|
return self.cd_interactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.current.cwd == opt.target {
|
if self.current.cwd == opt.target {
|
||||||
|
|
@ -68,11 +68,9 @@ impl Tab {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cd_interactive(&mut self, opt: impl Into<Opt>) -> bool {
|
fn cd_interactive(&mut self) -> bool {
|
||||||
let opt = opt.into() as Opt;
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
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));
|
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||||
pin!(rx);
|
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 yazi_shared::{event::Exec, fs::ends_with_slash, Defer};
|
||||||
|
|
||||||
use crate::{tab::Tab, Ctx};
|
use crate::tab::Tab;
|
||||||
|
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
type_: OptType,
|
type_: OptType,
|
||||||
|
|
@ -36,8 +37,8 @@ impl Tab {
|
||||||
let cwd = self.current.cwd.clone();
|
let cwd = self.current.cwd.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _guard = BLOCKER.acquire().await.unwrap();
|
let _guard = BLOCKER.acquire().await.unwrap();
|
||||||
let _defer = Defer::new(Ctx::resume);
|
let _defer = Defer::new(Scheduler::app_resume);
|
||||||
Ctx::stop().await;
|
Scheduler::app_stop().await;
|
||||||
|
|
||||||
let result = if opt.type_ == OptType::Fzf {
|
let result = if opt.type_ == OptType::Fzf {
|
||||||
external::fzf(FzfOpt { cwd }).await
|
external::fzf(FzfOpt { cwd }).await
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ mod hidden;
|
||||||
mod jump;
|
mod jump;
|
||||||
mod leave;
|
mod leave;
|
||||||
mod linemode;
|
mod linemode;
|
||||||
|
mod preview;
|
||||||
mod reveal;
|
mod reveal;
|
||||||
mod search;
|
mod search;
|
||||||
mod select;
|
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::pin;
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
use yazi_config::popup::InputCfg;
|
use yazi_config::popup::InputCfg;
|
||||||
use yazi_scheduler::external;
|
use yazi_plugin::external;
|
||||||
use yazi_shared::{emit, event::Exec, fs::FilesOp};
|
use yazi_shared::{emit, event::Exec, fs::FilesOp};
|
||||||
|
|
||||||
use crate::{input::Input, manager::Manager, tab::Tab};
|
use crate::{input::Input, manager::Manager, tab::Tab};
|
||||||
|
|
@ -78,7 +78,8 @@ impl Tab {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
if self.current.cwd.is_search() {
|
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());
|
let rep = self.history_new(&self.current.cwd.to_regular());
|
||||||
drop(mem::replace(&mut self.current, rep));
|
drop(mem::replace(&mut self.current, rep));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use yazi_config::{manager::SortBy, MANAGER};
|
use yazi_config::{manager::SortBy, MANAGER};
|
||||||
|
|
||||||
use crate::files::FilesSorter;
|
use crate::folder::FilesSorter;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||||
use regex::bytes::{Regex, RegexBuilder};
|
use regex::bytes::{Regex, RegexBuilder};
|
||||||
use yazi_shared::fs::Url;
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
use crate::files::Files;
|
use crate::folder::Files;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum FinderCase {
|
pub enum FinderCase {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ mod backstack;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod finder;
|
mod finder;
|
||||||
mod folder;
|
|
||||||
mod mode;
|
mod mode;
|
||||||
|
mod preview;
|
||||||
mod tab;
|
mod tab;
|
||||||
|
|
||||||
pub use backstack::*;
|
pub use backstack::*;
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use finder::*;
|
pub use finder::*;
|
||||||
pub use folder::*;
|
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
|
pub use preview::*;
|
||||||
pub use tab::*;
|
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 anyhow::Result;
|
||||||
use tokio::task::JoinHandle;
|
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 super::{Backstack, Config, Finder, Mode, Preview};
|
||||||
use crate::preview::Preview;
|
use crate::folder::Folder;
|
||||||
|
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
|
|
@ -46,21 +46,6 @@ impl From<&Url> for Tab {
|
||||||
fn from(url: &Url) -> Self { Self::from(url.clone()) }
|
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 {
|
impl Tab {
|
||||||
// --- Mode
|
// --- Mode
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ use std::io::{stdout, Write};
|
||||||
|
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
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 yazi_shared::{event::Exec, term::Term, Defer};
|
||||||
|
|
||||||
use crate::{tasks::Tasks, Ctx};
|
use crate::tasks::Tasks;
|
||||||
|
|
||||||
pub struct Opt;
|
pub struct Opt;
|
||||||
|
|
||||||
|
|
@ -32,10 +32,10 @@ impl Tasks {
|
||||||
task.logs.clone()
|
task.logs.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ctx::stop().await;
|
Scheduler::app_stop().await;
|
||||||
let _defer = Defer::new(|| {
|
let _defer = Defer::new(|| {
|
||||||
disable_raw_mode().ok();
|
disable_raw_mode().ok();
|
||||||
Ctx::resume();
|
Scheduler::app_resume();
|
||||||
});
|
});
|
||||||
|
|
||||||
Term::clear(&mut stdout()).ok();
|
Term::clear(&mut stdout()).ok();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use yazi_config::{open::Opener, BOOT};
|
use yazi_config::{open::Opener, ARGS};
|
||||||
use yazi_shared::{emit, event::Exec, Layer};
|
use yazi_shared::{emit, event::Exec, Layer};
|
||||||
|
|
||||||
use crate::tasks::Tasks;
|
use crate::tasks::Tasks;
|
||||||
|
|
@ -29,7 +29,7 @@ impl Tasks {
|
||||||
return false;
|
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, _)| {
|
let paths = opt.targets.into_iter().fold(OsString::new(), |mut s, (p, _)| {
|
||||||
s.push(p);
|
s.push(p);
|
||||||
s.push("\n");
|
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 tokio::time::sleep;
|
||||||
use tracing::debug;
|
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_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 super::{TasksProgress, TASKS_PADDING, TASKS_PERCENT};
|
||||||
use crate::{files::Files, input::Input};
|
use crate::{folder::Files, input::Input};
|
||||||
|
|
||||||
pub struct Tasks {
|
pub struct Tasks {
|
||||||
pub(super) scheduler: Arc<Scheduler>,
|
pub(super) scheduler: Arc<Scheduler>,
|
||||||
|
|
@ -149,76 +149,94 @@ impl Tasks {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn plugin_micro(&self, name: &str) -> bool {
|
||||||
pub fn precache_size(&self, targets: &Files) -> 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 {
|
if targets.sorter().by != SortBy::Size {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let targets: Vec<_> = targets
|
let targets: Vec<_> = {
|
||||||
.iter()
|
let loading = self.scheduler.preload.size_loading.read();
|
||||||
.filter(|f| f.is_dir() && !targets.sizes.contains_key(&f.url))
|
targets
|
||||||
.map(|f| &f.url)
|
.iter()
|
||||||
.collect();
|
.filter(|f| f.is_dir() && !targets.sizes.contains_key(&f.url) && !loading.contains(&f.url))
|
||||||
|
.map(|f| &f.url)
|
||||||
if !targets.is_empty() {
|
.collect()
|
||||||
self.scheduler.precache_size(targets);
|
};
|
||||||
|
if targets.is_empty() {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
let mut loading = self.scheduler.preload.size_loading.write();
|
||||||
}
|
for &target in &targets {
|
||||||
|
loading.insert(target.clone());
|
||||||
#[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);
|
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn precache_image(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
self.scheduler.preload_size(targets);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@ homepage = "https://yazi-rs.github.io"
|
||||||
repository = "https://github.com/sxyazi/yazi"
|
repository = "https://github.com/sxyazi/yazi"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
yazi-adaptor = { path = "../yazi-adaptor", version = "0.1.5" }
|
||||||
yazi-config = { path = "../yazi-config", version = "0.1.5" }
|
yazi-config = { path = "../yazi-config", version = "0.1.5" }
|
||||||
yazi-core = { path = "../yazi-core", version = "0.1.5" }
|
yazi-core = { path = "../yazi-core", version = "0.1.5" }
|
||||||
yazi-plugin = { path = "../yazi-plugin", version = "0.1.5" }
|
yazi-plugin = { path = "../yazi-plugin", version = "0.1.5" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
yazi-scheduler = { path = "../yazi-scheduler", version = "0.1.5" }
|
||||||
|
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
ansi-to-tui = "^3"
|
ansi-to-tui = "^3"
|
||||||
|
|
@ -35,6 +36,12 @@ tracing-subscriber = "^0"
|
||||||
libc = "^0"
|
libc = "^0"
|
||||||
signal-hook-tokio = { version = "^0", features = [ "futures-v0_3" ] }
|
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]]
|
[[bin]]
|
||||||
name = "yazi"
|
name = "yazi"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ use std::sync::atomic::Ordering;
|
||||||
use anyhow::{Ok, Result};
|
use anyhow::{Ok, Result};
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use ratatui::{backend::Backend, prelude::Rect};
|
use ratatui::{backend::Backend, prelude::Rect};
|
||||||
use yazi_config::{keymap::Key, BOOT};
|
use yazi_config::{keymap::Key, ARGS};
|
||||||
use yazi_core::{input::InputMode, preview::COLLISION, Ctx};
|
use yazi_core::input::InputMode;
|
||||||
use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer};
|
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) struct App {
|
||||||
pub(crate) cx: Ctx,
|
pub(crate) cx: Ctx,
|
||||||
|
|
@ -19,11 +19,14 @@ impl App {
|
||||||
pub(crate) async fn run() -> Result<()> {
|
pub(crate) async fn run() -> Result<()> {
|
||||||
Panic::install();
|
Panic::install();
|
||||||
let _log = Logs::init()?;
|
let _log = Logs::init()?;
|
||||||
let term = Term::start()?;
|
|
||||||
|
|
||||||
|
let term = Term::start()?;
|
||||||
let signals = Signals::start()?;
|
let signals = Signals::start()?;
|
||||||
|
|
||||||
|
Lives::register()?;
|
||||||
let mut app = Self { cx: Ctx::make(), term: Some(term), signals };
|
let mut app = Self { cx: Ctx::make(), term: Some(term), signals };
|
||||||
|
|
||||||
|
app.dispatch_render()?;
|
||||||
while let Some(event) = app.signals.recv().await {
|
while let Some(event) = app.signals.recv().await {
|
||||||
match event {
|
match event {
|
||||||
Event::Quit(no_cwd_file) => {
|
Event::Quit(no_cwd_file) => {
|
||||||
|
|
@ -42,7 +45,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_quit(&mut self, no_cwd_file: bool) {
|
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();
|
let cwd = self.cx.manager.cwd().as_os_str();
|
||||||
std::fs::write(p, cwd.as_encoded_bytes()).ok();
|
std::fs::write(p, cwd.as_encoded_bytes()).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +75,7 @@ impl App {
|
||||||
|
|
||||||
let collision = COLLISION.swap(false, Ordering::Relaxed);
|
let collision = COLLISION.swap(false, Ordering::Relaxed);
|
||||||
let frame = term.draw(|f| {
|
let frame = term.draw(|f| {
|
||||||
yazi_plugin::scope(&self.cx, |_| {
|
Lives::scope(&self.cx, |_| {
|
||||||
f.render_widget(Root::new(&self.cx), f.size());
|
f.render_widget(Root::new(&self.cx), f.size());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -83,8 +86,9 @@ impl App {
|
||||||
if !COLLISION.load(Ordering::Relaxed) {
|
if !COLLISION.load(Ordering::Relaxed) {
|
||||||
if collision {
|
if collision {
|
||||||
// Reload preview if collision is resolved
|
// Reload preview if collision is resolved
|
||||||
self.cx.manager.active_mut().preview.reset_image();
|
// TODO: plugin system
|
||||||
self.cx.manager.peek(0);
|
// self.cx.manager.active_mut().preview.reset_image();
|
||||||
|
self.cx.manager.peek(());
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +119,7 @@ impl App {
|
||||||
|
|
||||||
self.cx.manager.current_mut().set_page(true);
|
self.cx.manager.current_mut().set_page(true);
|
||||||
self.cx.manager.active_mut().preview.reset();
|
self.cx.manager.active_mut().preview.reset();
|
||||||
self.cx.manager.peek(0);
|
self.cx.manager.peek(());
|
||||||
emit!(Render);
|
emit!(Render);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,23 +144,12 @@ impl App {
|
||||||
emit!(Render);
|
emit!(Render);
|
||||||
}
|
}
|
||||||
if calc {
|
if calc {
|
||||||
tasks.precache_size(&manager.current().files);
|
tasks.preload_sorted(&manager.current().files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Pages(page) => {
|
Event::Pages(page) => {
|
||||||
let targets = self.cx.manager.current().paginate(page);
|
let targets = self.cx.manager.current().paginate(page);
|
||||||
tasks.precache_mime(targets, &self.cx.manager.mimetype);
|
tasks.preload_paged(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
|
mod plugin;
|
||||||
mod stop;
|
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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.cx.manager.active_mut().preview.reset_image();
|
// TODO: plugin system
|
||||||
|
// self.cx.manager.active_mut().preview.reset_image();
|
||||||
if opt.state {
|
if opt.state {
|
||||||
self.signals.stop_term(true);
|
self.signals.stop_term(true);
|
||||||
self.term = None;
|
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 ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, List, ListItem, Widget}};
|
||||||
use yazi_config::{popup::{Offset, Position}, THEME};
|
use yazi_config::{popup::{Offset, Position}, THEME};
|
||||||
use yazi_core::Ctx;
|
|
||||||
|
|
||||||
use crate::widgets;
|
use crate::{widgets, Ctx};
|
||||||
|
|
||||||
pub(crate) struct Completion<'a> {
|
pub(crate) struct Completion<'a> {
|
||||||
cx: &'a Ctx,
|
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 ratatui::prelude::Rect;
|
||||||
use yazi_config::popup::{Origin, Position};
|
use yazi_config::popup::{Origin, Position};
|
||||||
|
use yazi_core::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
|
||||||
use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
|
|
||||||
|
|
||||||
pub struct Ctx {
|
pub struct Ctx {
|
||||||
pub manager: Manager,
|
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 {
|
pub fn area(&self, position: &Position) -> Rect {
|
||||||
if position.origin != Origin::Hovered {
|
if position.origin != Origin::Hovered {
|
||||||
return position.rect();
|
return position.rect();
|
||||||
|
|
@ -84,6 +84,8 @@ impl<'a> Executor<'a> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on!(plugin);
|
||||||
|
on!(plugin_do);
|
||||||
on!(stop);
|
on!(stop);
|
||||||
|
|
||||||
false
|
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, hover);
|
||||||
|
on!(MANAGER, peek);
|
||||||
|
on!(MANAGER, seek);
|
||||||
on!(MANAGER, refresh);
|
on!(MANAGER, refresh);
|
||||||
on!(MANAGER, quit, &self.app.cx.tasks);
|
on!(MANAGER, quit, &self.app.cx.tasks);
|
||||||
on!(MANAGER, close, &self.app.cx.tasks);
|
on!(MANAGER, close, &self.app.cx.tasks);
|
||||||
on!(MANAGER, suspend);
|
on!(MANAGER, suspend);
|
||||||
on!(ACTIVE, escape);
|
on!(ACTIVE, escape);
|
||||||
|
on!(ACTIVE, preview);
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
on!(ACTIVE, arrow);
|
on!(ACTIVE, arrow);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, widgets::{List, ListItem, Widget}};
|
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, widgets::{List, ListItem, Widget}};
|
||||||
use yazi_config::THEME;
|
use yazi_config::THEME;
|
||||||
use yazi_core::Ctx;
|
|
||||||
|
use crate::Ctx;
|
||||||
|
|
||||||
pub(super) struct Bindings<'a> {
|
pub(super) struct Bindings<'a> {
|
||||||
cx: &'a Ctx,
|
cx: &'a Ctx,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, widgets::{Paragraph, Widget}};
|
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, widgets::{Paragraph, Widget}};
|
||||||
use yazi_config::THEME;
|
use yazi_config::THEME;
|
||||||
use yazi_core::Ctx;
|
|
||||||
|
|
||||||
use super::Bindings;
|
use super::Bindings;
|
||||||
use crate::widgets;
|
use crate::{Ctx, widgets};
|
||||||
|
|
||||||
pub(crate) struct Layout<'a> {
|
pub(crate) struct Layout<'a> {
|
||||||
cx: &'a Ctx,
|
cx: &'a Ctx,
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use std::ops::Range;
|
||||||
use ansi_to_tui::IntoText;
|
use ansi_to_tui::IntoText;
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Text}, widgets::{Block, BorderType, Borders, Paragraph, Widget}};
|
use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Text}, widgets::{Block, BorderType, Borders, Paragraph, Widget}};
|
||||||
use yazi_config::THEME;
|
use yazi_config::THEME;
|
||||||
use yazi_core::{input::InputMode, Ctx};
|
use yazi_core::input::InputMode;
|
||||||
use yazi_shared::term::Term;
|
use yazi_shared::term::Term;
|
||||||
|
|
||||||
use crate::widgets;
|
use crate::{Ctx, widgets};
|
||||||
|
|
||||||
pub(crate) struct Input<'a> {
|
pub(crate) struct Input<'a> {
|
||||||
cx: &'a Ctx,
|
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 mlua::{AnyUserData, Lua, MetaMethod, UserDataFields, UserDataMethods, Value};
|
||||||
|
|
||||||
use crate::LUA;
|
|
||||||
|
|
||||||
pub struct Tabs<'a, 'b> {
|
pub struct Tabs<'a, 'b> {
|
||||||
scope: &'b mlua::Scope<'a, 'a>,
|
scope: &'b mlua::Scope<'a, 'a>,
|
||||||
|
|
@ -9,8 +7,8 @@ pub struct Tabs<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Tabs<'a, 'b> {
|
impl<'a, 'b> Tabs<'a, 'b> {
|
||||||
pub(crate) fn init() -> mlua::Result<()> {
|
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||||
LUA.register_userdata_type::<yazi_core::manager::Tabs>(|reg| {
|
lua.register_userdata_type::<yazi_core::manager::Tabs>(|reg| {
|
||||||
reg.add_field_method_get("idx", |_, me| Ok(me.idx));
|
reg.add_field_method_get("idx", |_, me| Ok(me.idx));
|
||||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||||
reg.add_meta_function(MetaMethod::Index, |_, (me, index): (AnyUserData, usize)| {
|
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| {
|
lua.register_userdata_type::<yazi_core::tab::Tab>(|reg| {
|
||||||
reg.add_method("name", |_, me, ()| {
|
reg.add_method("name", |lua, me, ()| {
|
||||||
Ok(
|
Some(lua.create_string(
|
||||||
me.current
|
me.current.cwd.file_name().map_or_else(
|
||||||
.cwd
|
|| me.current.cwd.as_os_str().as_encoded_bytes(),
|
||||||
.file_name()
|
|n| n.as_encoded_bytes(),
|
||||||
.map(|n| n.to_string_lossy())
|
),
|
||||||
.or_else(|| Some(me.current.cwd.to_string_lossy()))
|
))
|
||||||
.unwrap_or_default()
|
.transpose()
|
||||||
.into_owned(),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
||||||
|
|
@ -41,7 +37,9 @@ impl<'a, 'b> Tabs<'a, 'b> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Tabs<'a, 'b> {
|
||||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a yazi_core::manager::Tabs) -> Self {
|
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a yazi_core::manager::Tabs) -> Self {
|
||||||
Self { scope, inner }
|
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("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("current", self.folder(&inner.current)?)?;
|
||||||
ud.set_named_user_value("preview", self.preview(inner)?)?;
|
|
||||||
|
|
||||||
Ok(ud)
|
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)?;
|
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||||
ud.set_named_user_value("files", self.files(&inner.files)?)?;
|
ud.set_named_user_value("files", self.scope.create_any_userdata_ref(&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()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use mlua::{AnyUserData, LuaSerdeExt, UserDataFields};
|
use mlua::{AnyUserData, Lua, LuaSerdeExt, UserDataFields};
|
||||||
|
|
||||||
use crate::LUA;
|
|
||||||
|
|
||||||
pub struct Tasks<'a, 'b> {
|
pub struct Tasks<'a, 'b> {
|
||||||
scope: &'b mlua::Scope<'a, 'a>,
|
scope: &'b mlua::Scope<'a, 'a>,
|
||||||
|
|
@ -9,8 +7,8 @@ pub struct Tasks<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Tasks<'a, 'b> {
|
impl<'a, 'b> Tasks<'a, 'b> {
|
||||||
pub(crate) fn init() -> mlua::Result<()> {
|
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||||
LUA.register_userdata_type::<yazi_core::tasks::Tasks>(|reg| {
|
lua.register_userdata_type::<yazi_core::tasks::Tasks>(|reg| {
|
||||||
reg.add_field_method_get("progress", |lua, me| lua.to_value(&me.progress))
|
reg.add_field_method_get("progress", |lua, me| lua.to_value(&me.progress))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod completion;
|
mod completion;
|
||||||
|
mod components;
|
||||||
|
mod context;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod help;
|
mod help;
|
||||||
mod input;
|
mod input;
|
||||||
|
mod lives;
|
||||||
mod logs;
|
mod logs;
|
||||||
mod panic;
|
mod panic;
|
||||||
mod root;
|
mod root;
|
||||||
|
|
@ -14,6 +17,7 @@ mod tasks;
|
||||||
mod which;
|
mod which;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
|
use context::*;
|
||||||
use executor::*;
|
use executor::*;
|
||||||
use logs::*;
|
use logs::*;
|
||||||
use panic::*;
|
use panic::*;
|
||||||
|
|
@ -29,6 +33,8 @@ async fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
yazi_core::init();
|
yazi_core::init();
|
||||||
|
|
||||||
|
yazi_scheduler::init();
|
||||||
|
|
||||||
yazi_plugin::init();
|
yazi_plugin::init();
|
||||||
|
|
||||||
yazi_adaptor::init();
|
yazi_adaptor::init();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget};
|
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 super::{completion, input, select, tasks, which};
|
||||||
use crate::help;
|
use crate::{components, help, Ctx};
|
||||||
|
|
||||||
pub(super) struct Root<'a> {
|
pub(super) struct Root<'a> {
|
||||||
cx: &'a Ctx,
|
cx: &'a Ctx,
|
||||||
|
|
@ -20,9 +18,10 @@ impl<'a> Widget for Root<'a> {
|
||||||
.constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)])
|
.constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
components::Header::new(self.cx).render(chunks[0], buf);
|
components::Header.render(chunks[0], buf);
|
||||||
components::Manager::new(self.cx).render(chunks[1], buf);
|
components::Manager.render(chunks[1], buf);
|
||||||
components::Status::new(self.cx).render(chunks[2], buf);
|
components::Status.render(chunks[2], buf);
|
||||||
|
components::Preview::new(self.cx).render(chunks[2], buf);
|
||||||
|
|
||||||
if self.cx.tasks.visible {
|
if self.cx.tasks.visible {
|
||||||
tasks::Layout::new(self.cx).render(area, buf);
|
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 ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, List, ListItem, Widget}};
|
||||||
use yazi_config::THEME;
|
use yazi_config::THEME;
|
||||||
use yazi_core::Ctx;
|
|
||||||
|
|
||||||
use crate::widgets;
|
use crate::{Ctx, widgets};
|
||||||
|
|
||||||
pub(crate) struct Select<'a> {
|
pub(crate) struct Select<'a> {
|
||||||
cx: &'a Ctx,
|
cx: &'a Ctx,
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ impl Signals {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn spawn_system_task(&self) -> Result<JoinHandle<()>> {
|
fn spawn_system_task(&self) -> Result<JoinHandle<()>> {
|
||||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM};
|
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM};
|
||||||
use yazi_core::Ctx;
|
use yazi_scheduler::Scheduler;
|
||||||
|
|
||||||
let tx = self.tx.clone();
|
let tx = self.tx.clone();
|
||||||
let mut signals = signal_hook_tokio::Signals::new([
|
let mut signals = signal_hook_tokio::Signals::new([
|
||||||
|
|
@ -68,7 +68,7 @@ impl Signals {
|
||||||
break;
|
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