perf: rewrite config parser to double the startup speed (#2508)

This commit is contained in:
三咲雅 · Misaki Masa 2025-03-22 01:45:21 +08:00 committed by sxyazi
parent db0300c902
commit 1a2a18e333
No known key found for this signature in database
74 changed files with 1131 additions and 1567 deletions

330
Cargo.lock generated
View file

@ -328,9 +328,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.16"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"jobserver",
"libc",
@ -689,17 +689,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.15.0"
@ -840,15 +829,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@ -1047,151 +1027,12 @@ dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "image"
version = "0.25.5"
@ -1404,12 +1245,6 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "litemap"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "lock_api"
version = "0.4.12"
@ -2404,12 +2239,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -2455,17 +2284,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syntect"
version = "5.2.0"
@ -2620,16 +2438,6 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tokio"
version = "1.44.1"
@ -2870,35 +2678,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
@ -2926,36 +2711,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "validator"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
dependencies = [
"idna",
"once_cell",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
dependencies = [
"darling",
"once_cell",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "valuable"
version = "0.1.1"
@ -3301,18 +3056,6 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yazi-adapter"
version = "25.3.7"
@ -3395,7 +3138,7 @@ dependencies = [
"serde",
"toml",
"tracing",
"validator",
"yazi-codegen",
"yazi-fs",
"yazi-macro",
"yazi-shared",
@ -3645,30 +3388,6 @@ dependencies = [
"yazi-shared",
]
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.23"
@ -3689,49 +3408,6 @@ dependencies = [
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zune-core"
version = "0.4.12"

View file

@ -5,7 +5,7 @@ use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
use crossterm::{cursor::MoveTo, queue};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
use ratatui::layout::Rect;
use yazi_config::PREVIEW;
use yazi_config::YAZI;
use crate::{CLOSE, Emulator, Image, START, adapter::Adapter};
@ -44,7 +44,7 @@ impl Iip {
if img.color().has_alpha() {
PngEncoder::new(&mut b).write_image(&img.into_rgba8(), w, h, ExtendedColorType::Rgba8)?;
} else {
JpegEncoder::new_with_quality(&mut b, PREVIEW.image_quality).encode_image(&img)?;
JpegEncoder::new_with_quality(&mut b, YAZI.preview.image_quality).encode_image(&img)?;
};
let mut buf = String::with_capacity(

View file

@ -5,7 +5,7 @@ use color_quant::NeuQuant;
use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage;
use ratatui::layout::Rect;
use yazi_config::PREVIEW;
use yazi_config::YAZI;
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
@ -44,7 +44,7 @@ impl Sixel {
tokio::task::spawn_blocking(move || {
let img = img.into_rgba8();
let nq = NeuQuant::new(PREVIEW.sixel_fraction as i32, 256 - alpha as usize, &img);
let nq = NeuQuant::new(YAZI.preview.sixel_fraction as i32, 256 - alpha as usize, &img);
let mut buf: Vec<u8> = Vec::with_capacity(1 << 16);
write!(buf, "{START}P0;1;8q\"1;1;{};{}", img.width(), img.height())?;

View file

@ -5,7 +5,7 @@ use image::ImageReader;
use ratatui::layout::Rect;
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
use tracing::{debug, warn};
use yazi_config::PREVIEW;
use yazi_config::YAZI;
use yazi_shared::{LOG_LEVEL, RoCell, env_exists};
use crate::{Adapter, Dimension};
@ -57,8 +57,8 @@ impl Ueberzug {
.map(|(r1, r2)| Rect {
x: max.x,
y: max.y,
width: max.width.min((w.min(PREVIEW.max_width as _) as f64 / r1).ceil() as _),
height: max.height.min((h.min(PREVIEW.max_height as _) as f64 / r2).ceil() as _),
width: max.width.min((w.min(YAZI.preview.max_width as _) as f64 / r1).ceil() as _),
height: max.height.min((h.min(YAZI.preview.max_height as _) as f64 / r2).ceil() as _),
})
.unwrap_or(max);
@ -101,8 +101,8 @@ impl Ueberzug {
}
fn adjust_rect(mut rect: Rect) -> Rect {
let scale = PREVIEW.ueberzug_scale;
let (x, y, w, h) = PREVIEW.ueberzug_offset;
let scale = YAZI.preview.ueberzug_scale;
let (x, y, w, h) = YAZI.preview.ueberzug_offset;
rect.x = 0f32.max(rect.x as f32 * scale + x) as u16;
rect.y = 0f32.max(rect.y as f32 * scale + y) as u16;

View file

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS};
use yazi_config::YAZI;
use crate::Dimension;
@ -12,7 +12,7 @@ pub struct Image;
impl Image {
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
let (mut img, orientation, icc) = Self::decode_from(path).await?;
let (w, h) = Self::flip_size(orientation, (PREVIEW.max_width, PREVIEW.max_height));
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
let buf = tokio::task::spawn_blocking(move || {
if img.width() > w || img.height() > h {
@ -29,7 +29,7 @@ impl Image {
icc.map(|b| encoder.set_icc_profile(b));
encoder.write_image(&rgba, rgba.width(), rgba.height(), ExtendedColorType::Rgba8)?;
} else {
let mut encoder = JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality);
let mut encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);
icc.map(|b| encoder.set_icc_profile(b));
encoder.encode_image(&img.into_rgb8())?;
}
@ -68,9 +68,9 @@ impl Image {
Dimension::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))
(w.min(YAZI.preview.max_width), h.min(YAZI.preview.max_height))
})
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
.unwrap_or((YAZI.preview.max_width, YAZI.preview.max_height))
}
pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {
@ -86,7 +86,7 @@ impl Image {
#[inline]
fn filter() -> FilterType {
match PREVIEW.image_filter.as_str() {
match YAZI.preview.image_filter.as_str() {
"nearest" => FilterType::Nearest,
"triangle" => FilterType::Triangle,
"catmull-rom" => FilterType::CatmullRom,
@ -98,14 +98,14 @@ impl Image {
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
let mut limits = Limits::no_limits();
if TASKS.image_alloc > 0 {
limits.max_alloc = Some(TASKS.image_alloc as u64);
if YAZI.tasks.image_alloc > 0 {
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
}
if TASKS.image_bound[0] > 0 {
limits.max_image_width = Some(TASKS.image_bound[0] as u32);
if YAZI.tasks.image_bound[0] > 0 {
limits.max_image_width = Some(YAZI.tasks.image_bound[0] as u32);
}
if TASKS.image_bound[1] > 0 {
limits.max_image_height = Some(TASKS.image_bound[1] as u32);
if YAZI.tasks.image_bound[1] > 0 {
limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);
}
let path = path.to_owned();

View file

@ -1,17 +1,17 @@
use yazi_config::PREVIEW;
use yazi_config::YAZI;
use yazi_fs::Xdg;
use super::Actions;
impl Actions {
pub(super) fn clear_cache() {
if PREVIEW.cache_dir == Xdg::cache_dir() {
println!("Clearing cache directory: \n{:?}", PREVIEW.cache_dir);
std::fs::remove_dir_all(&PREVIEW.cache_dir).unwrap();
if YAZI.preview.cache_dir == Xdg::cache_dir() {
println!("Clearing cache directory: \n{:?}", YAZI.preview.cache_dir);
std::fs::remove_dir_all(&YAZI.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
YAZI.preview.cache_dir
);
}
}

View file

@ -56,17 +56,17 @@ impl Actions {
writeln!(
s,
" default : {:?}",
yazi_config::OPEN.openers("f75a.txt", "text/plain").and_then(|a| a.first().cloned())
yazi_config::YAZI.opener.first(yazi_config::YAZI.open.all("f75a.txt", "text/plain"))
)?;
writeln!(
s,
" block-create: {:?}",
yazi_config::OPEN.block_opener("bulk-create.txt", "text/plain")
yazi_config::YAZI.opener.block(yazi_config::YAZI.open.all("bulk-create.txt", "text/plain"))
)?;
writeln!(
s,
" block-rename: {:?}",
yazi_config::OPEN.block_opener("bulk-rename.txt", "text/plain")
yazi_config::YAZI.opener.block(yazi_config::YAZI.open.all("bulk-rename.txt", "text/plain"))
)?;
writeln!(s, "\nMultiplexers")?;

View file

@ -69,9 +69,6 @@ pub(super) struct CommandPack {
/// Upgrade all packages.
#[arg(short = 'u', long)]
pub(super) upgrade: bool,
/// Migrate all packages.
#[arg(short = 'm', long)]
pub(super) migrate: bool, // TODO: remove this
}
#[derive(clap::Args)]

View file

@ -62,10 +62,7 @@ async fn run() -> anyhow::Result<()> {
Command::Pack(cmd) => {
package::init()?;
package::Package::load().await?.sync().await.ok();
if cmd.migrate {
outln!("Migration successful")?;
} else if cmd.install {
if cmd.install {
package::Package::load().await?.install(false).await?;
} else if cmd.list {
package::Package::load().await?.print()?;

View file

@ -48,17 +48,7 @@ Please manually delete it from `{}` and re-run the command.",
};
for file in files {
// TODO: remove this
let (from, to) = if *file == "main.lua" {
if maybe_exists(from.join(file)).await {
(from.join(file), to.join(file))
} else {
(from.join("init.lua"), to.join("main.lua"))
}
} else {
(from.join(file), to.join(file))
};
let (from, to) = (from.join(file), to.join(file));
copy_and_seal(&from, &to)
.await
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;

View file

@ -1,9 +1,9 @@
use std::{path::{Path, PathBuf}, str::FromStr};
use std::{path::PathBuf, str::FromStr};
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tokio::fs;
use yazi_fs::{Xdg, ok_or_not_found, remove_sealed, unique_name};
use yazi_fs::Xdg;
use yazi_macro::outln;
use super::Dependency;
@ -84,85 +84,6 @@ impl Package {
Ok(())
}
// TODO: remove this
pub(crate) async fn sync(&mut self) -> Result<()> {
async fn make_readonly(p: &Path) -> Result<()> {
let mut perms = fs::metadata(p).await?.permissions();
perms.set_readonly(true);
fs::set_permissions(p, perms).await?;
Ok(())
}
match fs::read_dir(Xdg::config_dir().join("plugins")).await {
Ok(mut it) => {
while let Some(entry) = it.next_entry().await? {
let dir = entry.path();
if !dir.is_dir() || dir.extension().is_none_or(|s| s != "yazi") {
continue;
}
match fs::symlink_metadata(dir.join("init.lua")).await {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => Err(e)?,
}
ok_or_not_found(
fs::rename(
dir.join("main.lua"),
unique_name(dir.join("main.lua.bak").into(), async { false }).await?,
)
.await,
)?;
ok_or_not_found(fs::rename(dir.join("init.lua"), dir.join("main.lua")).await)?;
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => Err(e)
.context(format!("failed to read `{}`", Xdg::config_dir().join("plugins").display()))?,
}
for d in &mut self.plugins {
let dir = Xdg::config_dir().join(format!("plugins/{}", d.name));
for f in ["LICENSE", "README.md", "main.lua"] {
make_readonly(&dir.join(f)).await.ok();
}
let tracker = dir.join("DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY");
if fs::read(&tracker).await.is_ok_and(|v| v.is_empty()) {
if d.hash.is_empty() {
d.hash = d.hash().await?;
}
remove_sealed(&tracker).await.ok();
}
}
for d in &mut self.flavors {
let dir = Xdg::config_dir().join(format!("flavors/{}", d.name));
for f in [
"LICENSE",
"LICENSE-tmtheme",
"README.md",
"filestyle.toml",
"flavor.toml",
"preview.png",
"tmtheme.xml",
] {
make_readonly(&dir.join(f)).await.ok();
}
let tracker = dir.join("DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY");
if fs::read(&tracker).await.is_ok_and(|v| v.is_empty()) {
if d.hash.is_empty() {
d.hash = d.hash().await?;
}
remove_sealed(&tracker).await.ok();
}
}
self.save().await
}
async fn add(&mut self, use_: &str) -> Result<()> {
let mut dep = Dependency::from_str(use_)?;
if let Some(d) = self.identical(&dep) {

View file

@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{FnArg, ItemFn};
use syn::{Attribute, Data, DeriveInput, Fields, FnArg, ItemFn, parse_macro_input};
#[proc_macro_attribute]
pub fn command(_: TokenStream, item: TokenStream) -> TokenStream {
@ -23,7 +23,7 @@ pub fn command(_: TokenStream, item: TokenStream) -> TokenStream {
// Add `__` prefix to the original function name
let name_ori = f.sig.ident;
f.sig.ident = format_ident!("__{}", name_ori);
f.sig.ident = format_ident!("__{name_ori}");
let name_new = &f.sig.ident;
// Collect the rest of the arguments
@ -39,3 +39,146 @@ pub fn command(_: TokenStream, item: TokenStream) -> TokenStream {
}
.into()
}
#[proc_macro_derive(DeserializeOver1)]
pub fn deserialize_over1(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
// Get the name of the struct
let name = &input.ident;
let shadow_name = format_ident!("__{name}Shadow");
// Process the struct fields
let (shadow_fields, field_calls) = match &input.data {
Data::Struct(struct_) => match &struct_.fields {
Fields::Named(fields) => {
let mut shadow_fields = Vec::with_capacity(fields.named.len());
let mut field_calls = Vec::with_capacity(fields.named.len());
for field in &fields.named {
let name = &field.ident;
let attrs: Vec<&Attribute> =
field.attrs.iter().filter(|&a| a.path().is_ident("serde")).collect();
shadow_fields.push(quote! {
#(#attrs)*
pub(crate) #name: Option<toml::Value>
});
field_calls.push(quote! {
if let Some(value) = shadow.#name {
self.#name = self.#name.deserialize_over(value).map_err(serde::de::Error::custom)?;
}
});
}
(shadow_fields, field_calls)
}
_ => panic!("DeserializeOver1 only supports structs with named fields"),
},
_ => panic!("DeserializeOver1 only supports structs"),
};
quote! {
#[derive(serde::Deserialize)]
pub(crate) struct #shadow_name {
#(#shadow_fields),*
}
impl #name {
#[inline]
pub(crate) fn deserialize_over<'de, D>(self, deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
self.deserialize_over_with::<D>(Self::deserialize_shadow(deserializer)?)
}
#[inline]
pub(crate) fn deserialize_shadow<'de, D>(deserializer: D) -> Result<#shadow_name, D::Error>
where
D: serde::Deserializer<'de>,
{
#shadow_name::deserialize(deserializer)
}
#[inline]
pub(crate) fn deserialize_over_with<'de, D>(mut self, shadow: #shadow_name) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#(#field_calls)*
Ok(self)
}
}
}
.into()
}
#[proc_macro_derive(DeserializeOver2)]
pub fn deserialize_over2(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
// Get the name of the struct
let name = &input.ident;
let shadow_name = format_ident!("__{name}Shadow");
// Process the struct fields
let (shadow_fields, field_assignments) = match &input.data {
Data::Struct(struct_) => match &struct_.fields {
Fields::Named(fields) => {
let mut shadow_fields = Vec::with_capacity(fields.named.len());
let mut field_assignments = Vec::with_capacity(fields.named.len());
for field in &fields.named {
let (ty, name) = (&field.ty, &field.ident);
shadow_fields.push(quote! {
pub(crate) #name: Option<#ty>
});
field_assignments.push(quote! {
if let Some(value) = shadow.#name {
self.#name = value;
}
});
}
(shadow_fields, field_assignments)
}
_ => panic!("DeserializeOver2 only supports structs with named fields"),
},
_ => panic!("DeserializeOver2 only supports structs"),
};
quote! {
#[derive(serde::Deserialize)]
pub(crate) struct #shadow_name {
#(#shadow_fields),*
}
impl #name {
#[inline]
pub(crate) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>
{
Ok(self.deserialize_over_with(Self::deserialize_shadow(deserializer)?))
}
#[inline]
pub(crate) fn deserialize_shadow<'de, D>(deserializer: D) -> Result<#shadow_name, D::Error>
where
D: serde::Deserializer<'de>
{
#shadow_name::deserialize(deserializer)
}
#[inline]
pub(crate) fn deserialize_over_with(mut self, shadow: #shadow_name) -> Self {
#(#field_assignments)*
self
}
}
}
.into()
}

View file

@ -9,9 +9,10 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-fs = { path = "../yazi-fs", version = "25.3.7" }
yazi-macro = { path = "../yazi-macro", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
yazi-codegen = { path = "../yazi-codegen", version = "25.3.7" }
yazi-fs = { path = "../yazi-fs", version = "25.3.7" }
yazi-macro = { path = "../yazi-macro", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
# External dependencies
anyhow = { workspace = true }
@ -24,7 +25,6 @@ regex = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
validator = { version = "0.20.0", features = [ "derive" ] }
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }

View file

@ -1,23 +1,21 @@
use std::{collections::HashSet, str::FromStr};
use anyhow::Context;
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer};
use anyhow::Result;
use serde::Deserialize;
use yazi_codegen::DeserializeOver1;
use yazi_shared::Layer;
use super::Chord;
use crate::{Preset, keymap::Key};
use super::{Chord, KeymapRule};
#[derive(Debug)]
#[derive(Deserialize, DeserializeOver1)]
pub struct Keymap {
pub mgr: Vec<Chord>,
pub tasks: Vec<Chord>,
pub spot: Vec<Chord>,
pub pick: Vec<Chord>,
pub input: Vec<Chord>,
pub confirm: Vec<Chord>,
pub help: Vec<Chord>,
pub cmp: Vec<Chord>,
#[serde(rename = "manager")]
pub mgr: KeymapRule,
pub tasks: KeymapRule,
pub spot: KeymapRule,
pub pick: KeymapRule,
pub input: KeymapRule,
pub confirm: KeymapRule,
pub help: KeymapRule,
pub cmp: KeymapRule,
}
impl Keymap {
@ -38,77 +36,17 @@ impl Keymap {
}
}
impl FromStr for Keymap {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse your keymap.toml")
}
}
impl<'de> Deserialize<'de> for Keymap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Shadow {
#[serde(rename = "manager")]
mgr: Inner, // TODO: remove serde(rename)
tasks: Inner,
spot: Inner,
pick: Inner,
input: Inner,
confirm: Inner,
help: Inner,
cmp: Inner,
}
#[derive(Deserialize)]
struct Inner {
keymap: IndexSet<Chord>,
#[serde(default)]
prepend_keymap: IndexSet<Chord>,
#[serde(default)]
append_keymap: IndexSet<Chord>,
}
fn mix(l: Layer, a: IndexSet<Chord>, b: IndexSet<Chord>, c: IndexSet<Chord>) -> Vec<Chord> {
#[inline]
fn on(Chord { on, .. }: &Chord) -> [Key; 2] {
[on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()]
}
let a_seen: HashSet<_> = a.iter().map(on).collect();
let b_seen: HashSet<_> = b.iter().map(on).collect();
Preset::mix(
a,
b.into_iter().filter(|v| !a_seen.contains(&on(v))),
c.into_iter().filter(|v| !b_seen.contains(&on(v))),
)
.filter(|chord| !chord.noop())
.map(|chord| chord.with_layer(l))
.collect()
}
let shadow = Shadow::deserialize(deserializer)?;
impl Keymap {
pub(crate) fn reshape(self) -> Result<Self> {
Ok(Self {
#[rustfmt::skip]
mgr: mix(Layer::Mgr, shadow.mgr.prepend_keymap, shadow.mgr.keymap, shadow.mgr.append_keymap),
#[rustfmt::skip]
tasks: mix(Layer::Tasks, shadow.tasks.prepend_keymap, shadow.tasks.keymap, shadow.tasks.append_keymap),
#[rustfmt::skip]
spot: mix(Layer::Spot, shadow.spot.prepend_keymap, shadow.spot.keymap, shadow.spot.append_keymap),
#[rustfmt::skip]
pick: mix(Layer::Pick, shadow.pick.prepend_keymap, shadow.pick.keymap, shadow.pick.append_keymap),
#[rustfmt::skip]
input: mix(Layer::Input, shadow.input.prepend_keymap, shadow.input.keymap, shadow.input.append_keymap),
#[rustfmt::skip]
confirm: mix(Layer::Confirm, shadow.confirm.prepend_keymap, shadow.confirm.keymap, shadow.confirm.append_keymap),
#[rustfmt::skip]
help: mix(Layer::Help, shadow.help.prepend_keymap, shadow.help.keymap, shadow.help.append_keymap),
#[rustfmt::skip]
cmp: mix(Layer::Cmp, shadow.cmp.prepend_keymap, shadow.cmp.keymap, shadow.cmp.append_keymap),
mgr: self.mgr.reshape(Layer::Mgr)?,
tasks: self.tasks.reshape(Layer::Tasks)?,
spot: self.spot.reshape(Layer::Spot)?,
pick: self.pick.reshape(Layer::Pick)?,
input: self.input.reshape(Layer::Input)?,
confirm: self.confirm.reshape(Layer::Confirm)?,
help: self.help.reshape(Layer::Help)?,
cmp: self.cmp.reshape(Layer::Cmp)?,
})
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(chord cow deserializers key keymap);
yazi_macro::mod_flat!(chord cow deserializers key keymap rule);

View file

@ -0,0 +1,47 @@
use std::{collections::HashSet, ops::Deref};
use anyhow::Result;
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use yazi_shared::Layer;
use super::Chord;
use crate::{Preset, keymap::Key};
#[derive(Default, Deserialize, DeserializeOver2)]
pub struct KeymapRule {
pub keymap: Vec<Chord>,
#[serde(default)]
prepend_keymap: Vec<Chord>,
#[serde(default)]
append_keymap: Vec<Chord>,
}
impl Deref for KeymapRule {
type Target = Vec<Chord>;
fn deref(&self) -> &Self::Target { &self.keymap }
}
impl KeymapRule {
pub(crate) fn reshape(self, layer: Layer) -> Result<Self> {
#[inline]
fn on(Chord { on, .. }: &Chord) -> [Key; 2] {
[on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()]
}
let a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect();
let b_seen: HashSet<_> = self.keymap.iter().map(on).collect();
let keymap = Preset::mix(
self.prepend_keymap,
self.keymap.into_iter().filter(|v| !a_seen.contains(&on(v))),
self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))),
)
.filter(|chord| !chord.noop())
.map(|chord| chord.with_layer(layer))
.collect();
Ok(Self { keymap, ..Default::default() })
}
}

View file

@ -1,25 +1,16 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_pub!(keymap mgr open plugin popup preview tasks theme which);
yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which);
yazi_macro::mod_flat!(layout pattern preset priority);
yazi_macro::mod_flat!(layout pattern preset priority yazi);
use std::{io::{Read, Write}, str::FromStr};
use std::io::{Read, Write};
use yazi_shared::{RoCell, SyncCell, tty::TTY};
pub static YAZI: RoCell<yazi::Yazi> = RoCell::new();
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
pub static MGR: RoCell<mgr::Mgr> = RoCell::new();
pub static OPEN: RoCell<open::Open> = RoCell::new();
pub static PLUGIN: RoCell<plugin::Plugin> = RoCell::new();
pub static PREVIEW: RoCell<preview::Preview> = RoCell::new();
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
pub static THEME: RoCell<theme::Theme> = RoCell::new();
pub static INPUT: RoCell<popup::Input> = RoCell::new();
pub static CONFIRM: RoCell<popup::Confirm> = RoCell::new();
pub static PICK: RoCell<popup::Pick> = RoCell::new();
pub static WHICH: RoCell<which::Which> = RoCell::new();
pub static LAYOUT: SyncCell<Layout> = SyncCell::new(Layout::default());
pub fn init() -> anyhow::Result<()> {
@ -30,53 +21,47 @@ pub fn init() -> anyhow::Result<()> {
Ok(())
}
pub fn init_flavor(light: bool) -> anyhow::Result<()> {
let mut flavor_toml = Preset::flavor(light, true);
if let Err(e) = flavor_toml {
wait_for_key(e)?;
flavor_toml = Preset::flavor(light, false);
fn try_init(merge: bool) -> anyhow::Result<()> {
let mut yazi = Preset::yazi()?;
let mut keymap = Preset::keymap()?;
if merge {
let dir = yazi_fs::Xdg::config_dir();
yazi = yazi.deserialize_over(toml::Deserializer::new(
&std::fs::read_to_string(dir.join("yazi.toml")).unwrap_or_default(),
))?;
keymap = keymap.deserialize_over(toml::Deserializer::new(
&std::fs::read_to_string(dir.join("keymap.toml")).unwrap_or_default(),
))?;
}
let mut theme: theme::Theme = <_>::from_str(&flavor_toml.unwrap())?;
theme.mgr.syntect_theme = theme
.flavor
.syntect_path(light)
.unwrap_or_else(|| yazi_fs::expand_path(&theme.mgr.syntect_theme));
THEME.init(theme);
YAZI.init(yazi.reshape()?);
KEYMAP.init(keymap.reshape()?);
Ok(())
}
fn try_init(merge: bool) -> anyhow::Result<()> {
let (yazi_toml, keymap_toml) = if merge {
let p = yazi_fs::Xdg::config_dir();
(Preset::yazi(&p)?, Preset::keymap(&p)?)
} else {
(yazi_macro::config_preset!("yazi"), yazi_macro::config_preset!("keymap"))
};
pub fn init_flavor(light: bool) -> anyhow::Result<()> {
if let Err(e) = try_init_flavor(light, true) {
wait_for_key(e)?;
try_init_flavor(light, false)?;
}
Ok(())
}
let keymap = <_>::from_str(&keymap_toml)?;
let mgr = <_>::from_str(&yazi_toml)?;
let open = <_>::from_str(&yazi_toml)?;
let plugin = <_>::from_str(&yazi_toml)?;
let preview = <_>::from_str(&yazi_toml)?;
let tasks = <_>::from_str(&yazi_toml)?;
let input = <_>::from_str(&yazi_toml)?;
let confirm = <_>::from_str(&yazi_toml)?;
let pick = <_>::from_str(&yazi_toml)?;
let which = <_>::from_str(&yazi_toml)?;
fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> {
let mut theme = Preset::theme(light)?;
KEYMAP.init(keymap);
MGR.init(mgr);
OPEN.init(open);
PLUGIN.init(plugin);
PREVIEW.init(preview);
TASKS.init(tasks);
INPUT.init(input);
CONFIRM.init(confirm);
PICK.init(pick);
WHICH.init(which);
if merge {
let shadow = theme::Theme::deserialize_shadow(toml::Deserializer::new(
&std::fs::read_to_string(yazi_fs::Xdg::config_dir().join("theme.toml")).unwrap_or_default(),
))?;
let flavor = shadow.flavor.as_ref().map(theme::Flavor::from).unwrap_or_default().read(light)?;
theme = theme.deserialize_over_with::<toml::Value>(shadow)?;
theme = theme.deserialize_over(toml::Deserializer::new(&flavor))?;
}
THEME.init(theme.reshape(light)?);
Ok(())
}

View file

@ -1,13 +1,11 @@
use std::str::FromStr;
use anyhow::Context;
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_codegen::DeserializeOver2;
use yazi_fs::SortBy;
use super::{MgrRatio, MouseEvents};
#[derive(Debug, Deserialize, Serialize, Validate)]
#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]
pub struct Mgr {
pub ratio: MgrRatio,
@ -19,7 +17,6 @@ pub struct Mgr {
pub sort_translit: bool,
// Display
#[validate(length(min = 1, max = 20, message = "must be between 1 and 20 characters"))]
pub linemode: String,
pub show_hidden: bool,
pub show_symlink: bool,
@ -28,20 +25,12 @@ pub struct Mgr {
pub title_format: String,
}
impl FromStr for Mgr {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
#[serde(rename = "manager")]
mgr: Mgr, // TODO: remove serde(rename)
impl Mgr {
pub(crate) fn reshape(self) -> Result<Self> {
if self.linemode.is_empty() || self.linemode.len() > 20 {
bail!("[mgr].linemode must be between 1 and 20 characters.");
}
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [manager] section in your yazi.toml")?;
outer.mgr.validate()?;
Ok(outer.mgr)
Ok(self)
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(open opener rule);
yazi_macro::mod_flat!(open rule);

View file

@ -1,106 +1,74 @@
use std::{collections::HashMap, path::Path, str::FromStr};
use std::{ops::Deref, path::Path};
use anyhow::Context;
use anyhow::Result;
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer};
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use yazi_shared::MIME_DIR;
use super::Opener;
use crate::{Preset, open::OpenRule};
#[derive(Debug)]
#[derive(Default, Deserialize, DeserializeOver2)]
pub struct Open {
rules: Vec<OpenRule>,
openers: HashMap<String, IndexSet<Opener>>,
rules: Vec<OpenRule>,
#[serde(default)]
prepend_rules: Vec<OpenRule>,
#[serde(default)]
append_rules: Vec<OpenRule>,
}
impl Deref for Open {
type Target = Vec<OpenRule>;
fn deref(&self) -> &Self::Target { &self.rules }
}
impl Open {
pub fn openers<P, M>(&self, path: P, mime: M) -> Option<IndexSet<&Opener>>
pub fn all<'a, 'b, P, M>(&'a self, path: P, mime: M) -> impl Iterator<Item = &'a str> + 'b
where
P: AsRef<Path>,
M: AsRef<str>,
'a: 'b,
P: AsRef<Path> + 'b,
M: AsRef<str> + 'b,
{
let is_dir = mime.as_ref() == MIME_DIR;
self.rules.iter().find_map(|rule| {
if rule.mime.as_ref().is_some_and(|p| p.match_mime(&mime))
|| rule.name.as_ref().is_some_and(|p| p.match_path(&path, is_dir))
{
let openers = rule
.use_
.iter()
.filter_map(|use_| self.openers.get(use_))
.flatten()
.collect::<IndexSet<_>>();
if openers.is_empty() { None } else { Some(openers) }
} else {
None
}
})
self
.rules
.iter()
.filter(move |&r| {
r.mime.as_ref().is_some_and(|p| p.match_mime(&mime))
|| r.name.as_ref().is_some_and(|p| p.match_path(&path, is_dir))
})
.flat_map(|r| &r.use_)
.map(String::as_str)
}
#[inline]
pub fn block_opener<P, M>(&self, path: P, mime: M) -> Option<&Opener>
where
P: AsRef<Path>,
M: AsRef<str>,
{
self.openers(path, mime).and_then(|o| o.into_iter().find(|o| o.block))
}
pub fn common_openers(&self, targets: &[(impl AsRef<Path>, impl AsRef<str>)]) -> Vec<&Opener> {
let grouped: Vec<_> = targets.iter().filter_map(|(p, m)| self.openers(p, m)).collect();
let flat: IndexSet<_> = grouped.iter().flatten().copied().collect();
flat.into_iter().filter(|&o| grouped.iter().all(|g| g.contains(o))).collect()
}
}
impl FromStr for Open {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse the [open] or [opener] section in your yazi.toml")
}
}
impl<'de> Deserialize<'de> for Open {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
opener: HashMap<String, Vec<Opener>>,
open: OuterOpen,
}
#[derive(Deserialize)]
struct OuterOpen {
rules: Vec<OpenRule>,
#[serde(default)]
prepend_rules: Vec<OpenRule>,
#[serde(default)]
append_rules: Vec<OpenRule>,
}
let mut outer = Outer::deserialize(deserializer)?;
if outer.open.append_rules.iter().any(|r| r.any_file()) {
outer.open.rules.retain(|r| !r.any_file());
}
if outer.open.append_rules.iter().any(|r| r.any_dir()) {
outer.open.rules.retain(|r| !r.any_dir());
}
let openers = outer
.opener
.into_iter()
.map(|(k, v)| (k, v.into_iter().filter_map(|o| o.take()).collect::<IndexSet<_>>()))
pub fn common<'a>(
&'a self,
targets: &[(impl AsRef<Path>, impl AsRef<str>)],
) -> IndexSet<&'a str> {
let each: Vec<IndexSet<&str>> = targets
.iter()
.map(|(p, m)| self.all(p, m).collect::<IndexSet<_>>())
.filter(|s| !s.is_empty())
.collect();
let mut flat: IndexSet<_> = each.iter().flatten().copied().collect();
flat.retain(|use_| each.iter().all(|e| e.contains(use_)));
flat
}
}
impl Open {
pub(crate) fn reshape(self) -> Result<Self> {
let any_file = self.append_rules.iter().any(|r| r.any_file());
let any_dir = self.append_rules.iter().any(|r| r.any_dir());
let it =
self.rules.into_iter().filter(|r| !(any_file && r.any_file() || any_dir && r.any_dir()));
Ok(Self {
#[rustfmt::skip]
rules: Preset::mix(outer.open.prepend_rules, outer.open.rules, outer.open.append_rules).collect(),
openers,
rules: Preset::mix(self.prepend_rules, it, self.append_rules).collect(),
..Default::default()
})
}
}

View file

@ -1,55 +0,0 @@
use serde::{Deserialize, Deserializer};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opener {
pub run: String,
pub block: bool,
pub orphan: bool,
pub desc: String,
pub for_: Option<String>,
pub spread: bool,
}
impl Opener {
pub fn take(mut self) -> Option<Self> {
if let Some(for_) = self.for_.take() {
match for_.as_str() {
"unix" if cfg!(unix) => {}
os if os == std::env::consts::OS => {}
_ => return None,
}
}
Some(self)
}
}
impl<'de> Deserialize<'de> for Opener {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct Shadow {
run: String,
#[serde(default)]
block: bool,
#[serde(default)]
orphan: bool,
desc: Option<String>,
#[serde(rename = "for")]
for_: Option<String>,
}
let shadow = Shadow::deserialize(deserializer)?;
let run = shadow.run;
if run.is_empty() {
return Err(serde::de::Error::custom("`run` cannot be empty"));
}
let desc = shadow.desc.unwrap_or_else(|| run.split_whitespace().next().unwrap().to_string());
let spread = run.contains("$@") || run.contains("%*") || run.contains("$*");
Ok(Self { run, block: shadow.block, orphan: shadow.orphan, desc, for_: shadow.for_, spread })
}
}

View file

@ -5,12 +5,12 @@ use serde::{Deserialize, Deserializer, de::{self, Visitor}};
use crate::pattern::Pattern;
#[derive(Debug, Deserialize)]
pub(super) struct OpenRule {
pub(super) name: Option<Pattern>,
pub(super) mime: Option<Pattern>,
pub struct OpenRule {
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
#[serde(rename = "use")]
#[serde(deserialize_with = "OpenRule::deserialize")]
pub(super) use_: Vec<String>,
pub use_: Vec<String>,
}
impl OpenRule {
@ -39,7 +39,7 @@ impl OpenRule {
where
A: de::SeqAccess<'de>,
{
let mut uses = vec![];
let mut uses = Vec::with_capacity(seq.size_hint().unwrap_or(0));
while let Some(use_) = seq.next_element::<String>()? {
uses.push(use_);
}

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(opener rule);

View file

@ -0,0 +1,71 @@
use std::{collections::HashMap, mem, ops::Deref};
use anyhow::Result;
use indexmap::IndexSet;
use serde::Deserialize;
use super::OpenerRule;
#[derive(Debug, Deserialize)]
pub struct Opener(HashMap<String, Vec<OpenerRule>>);
impl Deref for Opener {
type Target = HashMap<String, Vec<OpenerRule>>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl Opener {
pub fn all<'a, I>(&'a self, uses: I) -> Vec<&'a OpenerRule>
where
I: Iterator<Item = &'a str>,
{
uses.flat_map(|use_| self.get(use_)).flatten().collect()
}
pub fn first<'a, 'b, I>(&'a self, uses: I) -> Option<&'a OpenerRule>
where
I: Iterator<Item = &'b str>,
{
uses.flat_map(|use_| self.get(use_)).flatten().next()
}
pub fn block<'a, I>(&'a self, uses: I) -> Option<&'a OpenerRule>
where
I: Iterator<Item = &'a str>,
{
uses.flat_map(|use_| self.get(use_)).flatten().find(|&o| o.block)
}
}
impl Opener {
pub(crate) fn reshape(mut self) -> Result<Self> {
for rules in self.0.values_mut() {
*rules = mem::take(rules)
.into_iter()
.map(|mut r| (r.for_.take(), r))
.filter(|(for_, _)| match for_.as_ref().map(|s| s.as_str()) {
Some("unix") if cfg!(unix) => true,
Some(os) if os == std::env::consts::OS => true,
Some(_) => false,
None => true,
})
.map(|(_, r)| r.reshape())
.collect::<Result<IndexSet<_>>>()?
.into_iter()
.collect();
}
Ok(self)
}
pub(crate) fn deserialize_over<'de, D>(mut self, deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let map: HashMap<String, Vec<OpenerRule>> = HashMap::deserialize(deserializer)?;
self.0.extend(map);
Ok(self)
}
}

View file

@ -0,0 +1,49 @@
use anyhow::{Result, bail};
use serde::Deserialize;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct OpenerRule {
pub run: String,
#[serde(default)]
pub block: bool,
#[serde(default)]
pub orphan: bool,
#[serde(default)]
pub desc: String,
#[serde(rename = "for")]
pub for_: Option<String>,
#[serde(skip)]
pub spread: bool,
}
impl OpenerRule {
#[inline]
pub fn desc(&self) -> String {
if !self.desc.is_empty() {
self.desc.clone()
} else if let Some(first) = self.run.split_whitespace().next() {
first.to_owned()
} else {
String::new()
}
}
}
impl OpenerRule {
pub(super) fn reshape(mut self) -> Result<Self> {
if self.run.is_empty() {
bail!("[open].rules.*.run cannot be empty.");
}
#[cfg(unix)]
{
self.spread = self.run.contains("$@") || self.run.contains("$*");
}
#[cfg(windows)]
{
self.spread = self.run.contains("%*");
}
Ok(self)
}
}

View file

@ -1,18 +1,39 @@
use std::{collections::HashSet, path::Path, str::FromStr};
use std::{collections::HashSet, path::Path};
use anyhow::Context;
use serde::{Deserialize, Deserializer};
use anyhow::Result;
use serde::Deserialize;
use tracing::warn;
use yazi_codegen::DeserializeOver2;
use yazi_fs::File;
use super::{Fetcher, Preloader, Previewer, Spotter};
use crate::{Preset, plugin::MAX_PREWORKERS};
#[derive(Default, Deserialize, DeserializeOver2)]
pub struct Plugin {
pub fetchers: Vec<Fetcher>,
pub spotters: Vec<Spotter>,
pub preloaders: Vec<Preloader>,
pub previewers: Vec<Previewer>,
pub fetchers: Vec<Fetcher>,
#[serde(default)]
prepend_fetchers: Vec<Fetcher>,
#[serde(default)]
append_fetchers: Vec<Fetcher>,
pub spotters: Vec<Spotter>,
#[serde(default)]
prepend_spotters: Vec<Spotter>,
#[serde(default)]
append_spotters: Vec<Spotter>,
pub preloaders: Vec<Preloader>,
#[serde(default)]
prepend_preloaders: Vec<Preloader>,
#[serde(default)]
append_preloaders: Vec<Preloader>,
pub previewers: Vec<Previewer>,
#[serde(default)]
prepend_previewers: Vec<Previewer>,
#[serde(default)]
append_previewers: Vec<Previewer>,
}
impl Plugin {
@ -71,90 +92,48 @@ impl Plugin {
}
}
impl FromStr for Plugin {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s).context("Failed to parse the [plugin] section in your yazi.toml")
}
}
impl<'de> Deserialize<'de> for Plugin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
plugin: Shadow,
impl Plugin {
// TODO: remove .retain() and .collect()
pub(crate) fn reshape(mut self) -> Result<Self> {
if self.append_spotters.iter().any(|r| r.any_file()) {
self.spotters.retain(|r| !r.any_file());
}
if self.append_spotters.iter().any(|r| r.any_dir()) {
self.spotters.retain(|r| !r.any_dir());
}
if self.append_previewers.iter().any(|r| r.any_file()) {
self.previewers.retain(|r| !r.any_file());
}
if self.append_previewers.iter().any(|r| r.any_dir()) {
self.previewers.retain(|r| !r.any_dir());
}
#[derive(Deserialize)]
struct Shadow {
fetchers: Vec<Fetcher>,
#[serde(default)]
prepend_fetchers: Vec<Fetcher>,
#[serde(default)]
append_fetchers: Vec<Fetcher>,
self.fetchers =
Preset::mix(self.prepend_fetchers, self.fetchers, self.append_fetchers).collect();
self.spotters =
Preset::mix(self.prepend_spotters, self.spotters, self.append_spotters).collect();
self.preloaders =
Preset::mix(self.prepend_preloaders, self.preloaders, self.append_preloaders).collect();
self.previewers =
Preset::mix(self.prepend_previewers, self.previewers, self.append_previewers).collect();
spotters: Vec<Spotter>,
#[serde(default)]
prepend_spotters: Vec<Spotter>,
#[serde(default)]
append_spotters: Vec<Spotter>,
preloaders: Vec<Preloader>,
#[serde(default)]
prepend_preloaders: Vec<Preloader>,
#[serde(default)]
append_preloaders: Vec<Preloader>,
previewers: Vec<Previewer>,
#[serde(default)]
prepend_previewers: Vec<Previewer>,
#[serde(default)]
append_previewers: Vec<Previewer>,
}
let mut shadow = Outer::deserialize(deserializer)?.plugin;
if shadow.append_spotters.iter().any(|r| r.any_file()) {
shadow.spotters.retain(|r| !r.any_file());
}
if shadow.append_spotters.iter().any(|r| r.any_dir()) {
shadow.spotters.retain(|r| !r.any_dir());
}
if shadow.append_previewers.iter().any(|r| r.any_file()) {
shadow.previewers.retain(|r| !r.any_file());
}
if shadow.append_previewers.iter().any(|r| r.any_dir()) {
shadow.previewers.retain(|r| !r.any_dir());
}
shadow.fetchers =
Preset::mix(shadow.prepend_fetchers, shadow.fetchers, shadow.append_fetchers).collect();
shadow.spotters =
Preset::mix(shadow.prepend_spotters, shadow.spotters, shadow.append_spotters).collect();
shadow.preloaders =
Preset::mix(shadow.prepend_preloaders, shadow.preloaders, shadow.append_preloaders).collect();
shadow.previewers =
Preset::mix(shadow.prepend_previewers, shadow.previewers, shadow.append_previewers).collect();
if shadow.fetchers.len() + shadow.preloaders.len() > MAX_PREWORKERS as usize {
if self.fetchers.len() + self.preloaders.len() > MAX_PREWORKERS as usize {
panic!("Fetchers and preloaders exceed the limit of {MAX_PREWORKERS}");
}
for (i, p) in shadow.fetchers.iter_mut().enumerate() {
for (i, p) in self.fetchers.iter_mut().enumerate() {
p.idx = i as u8;
}
for (i, p) in shadow.preloaders.iter_mut().enumerate() {
p.idx = shadow.fetchers.len() as u8 + i as u8;
for (i, p) in self.preloaders.iter_mut().enumerate() {
p.idx = self.fetchers.len() as u8 + i as u8;
}
Ok(Self {
fetchers: shadow.fetchers,
spotters: shadow.spotters,
preloaders: shadow.preloaders,
previewers: shadow.previewers,
fetchers: self.fetchers,
spotters: self.spotters,
preloaders: self.preloaders,
previewers: self.previewers,
..Default::default()
})
}
}

View file

@ -1,11 +1,9 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use super::{Offset, Origin};
#[derive(Deserialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Confirm {
// trash
pub trash_title: String,
@ -30,22 +28,6 @@ pub struct Confirm {
pub quit_offset: Offset,
}
impl FromStr for Confirm {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
confirm: Confirm,
}
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [confirm] section in your yazi.toml")?;
Ok(outer.confirm)
}
}
impl Confirm {
pub const fn border(&self) -> u16 { 2 }
}

View file

@ -1,11 +1,9 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use super::{Offset, Origin};
#[derive(Deserialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Input {
pub cursor_blink: bool,
@ -48,19 +46,3 @@ pub struct Input {
impl Input {
pub const fn border(&self) -> u16 { 2 }
}
impl FromStr for Input {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
input: Input,
}
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [input] section in your yazi.toml")?;
Ok(outer.input)
}
}

View file

@ -2,7 +2,7 @@ use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};
use yazi_shared::url::Url;
use super::{Offset, Origin, Position};
use crate::{CONFIRM, INPUT, PICK};
use crate::YAZI;
#[derive(Default)]
pub struct InputCfg {
@ -33,8 +33,8 @@ pub struct ConfirmCfg {
impl InputCfg {
pub fn cd() -> Self {
Self {
title: INPUT.cd_title.to_owned(),
position: Position::new(INPUT.cd_origin, INPUT.cd_offset),
title: YAZI.input.cd_title.to_owned(),
position: Position::new(YAZI.input.cd_origin, YAZI.input.cd_offset),
completion: true,
..Default::default()
}
@ -42,24 +42,24 @@ impl InputCfg {
pub fn create(dir: bool) -> Self {
Self {
title: INPUT.create_title[dir as usize].to_owned(),
position: Position::new(INPUT.create_origin, INPUT.create_offset),
title: YAZI.input.create_title[dir as usize].to_owned(),
position: Position::new(YAZI.input.create_origin, YAZI.input.create_offset),
..Default::default()
}
}
pub fn rename() -> Self {
Self {
title: INPUT.rename_title.to_owned(),
position: Position::new(INPUT.rename_origin, INPUT.rename_offset),
title: YAZI.input.rename_title.to_owned(),
position: Position::new(YAZI.input.rename_origin, YAZI.input.rename_offset),
..Default::default()
}
}
pub fn filter() -> Self {
Self {
title: INPUT.filter_title.to_owned(),
position: Position::new(INPUT.filter_origin, INPUT.filter_offset),
title: YAZI.input.filter_title.to_owned(),
position: Position::new(YAZI.input.filter_origin, YAZI.input.filter_offset),
realtime: true,
..Default::default()
}
@ -67,8 +67,8 @@ impl InputCfg {
pub fn find(prev: bool) -> Self {
Self {
title: INPUT.find_title[prev as usize].to_owned(),
position: Position::new(INPUT.find_origin, INPUT.find_offset),
title: YAZI.input.find_title[prev as usize].to_owned(),
position: Position::new(YAZI.input.find_origin, YAZI.input.find_offset),
realtime: true,
..Default::default()
}
@ -76,16 +76,16 @@ impl InputCfg {
pub fn search(name: &str) -> Self {
Self {
title: INPUT.search_title.replace("{n}", name),
position: Position::new(INPUT.search_origin, INPUT.search_offset),
title: YAZI.input.search_title.replace("{n}", name),
position: Position::new(YAZI.input.search_origin, YAZI.input.search_offset),
..Default::default()
}
}
pub fn shell(block: bool) -> Self {
Self {
title: INPUT.shell_title[block as usize].to_owned(),
position: Position::new(INPUT.shell_origin, INPUT.shell_offset),
title: YAZI.input.shell_title[block as usize].to_owned(),
position: Position::new(YAZI.input.shell_origin, YAZI.input.shell_offset),
highlight: true,
..Default::default()
}
@ -121,8 +121,8 @@ impl ConfirmCfg {
pub fn trash(urls: &[yazi_shared::url::Url]) -> Self {
Self::new(
Self::replace_number(&CONFIRM.trash_title, urls.len()),
(CONFIRM.trash_origin, CONFIRM.trash_offset),
Self::replace_number(&YAZI.confirm.trash_title, urls.len()),
(YAZI.confirm.trash_origin, YAZI.confirm.trash_offset),
None,
Self::truncate_list(urls.iter(), urls.len(), 100),
)
@ -130,8 +130,8 @@ impl ConfirmCfg {
pub fn delete(urls: &[yazi_shared::url::Url]) -> Self {
Self::new(
Self::replace_number(&CONFIRM.delete_title, urls.len()),
(CONFIRM.delete_origin, CONFIRM.delete_offset),
Self::replace_number(&YAZI.confirm.delete_title, urls.len()),
(YAZI.confirm.delete_origin, YAZI.confirm.delete_offset),
None,
Self::truncate_list(urls.iter(), urls.len(), 100),
)
@ -139,18 +139,18 @@ impl ConfirmCfg {
pub fn overwrite(url: &Url) -> Self {
Self::new(
CONFIRM.overwrite_title.to_owned(),
(CONFIRM.overwrite_origin, CONFIRM.overwrite_offset),
Some(Text::raw(&CONFIRM.overwrite_content)),
YAZI.confirm.overwrite_title.to_owned(),
(YAZI.confirm.overwrite_origin, YAZI.confirm.overwrite_offset),
Some(Text::raw(&YAZI.confirm.overwrite_content)),
Some(url.to_string().into()),
)
}
pub fn quit(len: usize, names: Vec<String>) -> Self {
Self::new(
Self::replace_number(&CONFIRM.quit_title, len),
(CONFIRM.quit_origin, CONFIRM.quit_offset),
Some(Text::raw(&CONFIRM.quit_content)),
Self::replace_number(&YAZI.confirm.quit_title, len),
(YAZI.confirm.quit_origin, YAZI.confirm.quit_offset),
Some(Text::raw(&YAZI.confirm.quit_content)),
Self::truncate_list(names.into_iter(), len, 10),
)
}
@ -179,15 +179,18 @@ impl ConfirmCfg {
impl PickCfg {
#[inline]
fn max_height(len: usize) -> u16 {
PICK.open_offset.height.min(PICK.border().saturating_add(len as u16))
YAZI.pick.open_offset.height.min(YAZI.pick.border().saturating_add(len as u16))
}
pub fn open(items: Vec<String>) -> Self {
let max_height = Self::max_height(items.len());
Self {
title: PICK.open_title.to_owned(),
title: YAZI.pick.open_title.to_owned(),
items,
position: Position::new(PICK.open_origin, Offset { height: max_height, ..PICK.open_offset }),
position: Position::new(YAZI.pick.open_origin, Offset {
height: max_height,
..YAZI.pick.open_offset
}),
}
}
}

View file

@ -1,11 +1,9 @@
use std::str::FromStr;
use anyhow::Context;
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use super::{Offset, Origin};
#[derive(Deserialize)]
#[derive(Deserialize, DeserializeOver2)]
pub struct Pick {
// open
pub open_title: String,
@ -16,19 +14,3 @@ pub struct Pick {
impl Pick {
pub const fn border(&self) -> u16 { 2 }
}
impl FromStr for Pick {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
pick: Pick,
}
let outer =
toml::from_str::<Outer>(s).context("Failed to parse the [pick] section in your yazi.toml")?;
Ok(outer.pick)
}
}

View file

@ -1,35 +1,25 @@
use std::{borrow::Cow, path::{Path, PathBuf}, str::FromStr};
use anyhow::{Context, Result};
use toml::{Table, Value};
use yazi_fs::Xdg;
use crate::theme::Flavor;
use crate::{Yazi, keymap::Keymap, theme::Theme};
pub(crate) struct Preset;
impl Preset {
pub(crate) fn yazi(p: &Path) -> Result<Cow<'static, str>> {
Self::merge_path(p.join("yazi.toml"), yazi_macro::config_preset!("yazi"))
#[inline]
pub(super) fn yazi() -> Result<Yazi, toml::de::Error> {
toml::from_str(&yazi_macro::config_preset!("yazi"))
}
pub(crate) fn keymap(p: &Path) -> Result<Cow<'static, str>> {
Self::merge_path(p.join("keymap.toml"), yazi_macro::config_preset!("keymap"))
#[inline]
pub(super) fn keymap() -> Result<Keymap, toml::de::Error> {
toml::from_str(&yazi_macro::config_preset!("keymap"))
}
pub(crate) fn flavor(light: bool, merge: bool) -> Result<Cow<'static, str>> {
let theme = if merge {
std::fs::read_to_string(Xdg::config_dir().join("theme.toml")).unwrap_or_default()
#[inline]
pub(super) fn theme(light: bool) -> Result<Theme, toml::de::Error> {
toml::from_str(&if light {
yazi_macro::theme_preset!("light")
} else {
Default::default()
};
let flavor = Flavor::from_str(&theme)?;
let preset =
if light { yazi_macro::theme_preset!("light") } else { yazi_macro::theme_preset!("dark") };
Self::merge_str(&theme, &Self::merge_str(&flavor.read(light)?, &preset)?)
yazi_macro::theme_preset!("dark")
})
}
#[inline]
@ -41,43 +31,4 @@ impl Preset {
{
a.into_iter().chain(b).chain(c)
}
#[inline]
pub(crate) fn merge_str(user: &str, base: &str) -> Result<Cow<'static, str>> {
let mut t = user.parse()?;
Self::merge(&mut t, base.parse()?, 2);
Ok(t.to_string().into())
}
#[inline]
fn merge_path(user: PathBuf, base: Cow<str>) -> Result<Cow<str>> {
let s = std::fs::read_to_string(&user).unwrap_or_default();
if s.is_empty() {
return Ok(base);
}
Self::merge_str(&s, &base).with_context(|| format!("failed to parse config: {user:?}"))
}
fn merge(a: &mut Table, b: Table, max: u8) {
for (k, v) in b {
let Some(a) = a.get_mut(&k) else {
a.insert(k, v);
continue;
};
if max <= 1 {
continue;
}
if let Some(a) = a.as_table_mut() {
if let Value::Table(b) = v {
Self::merge(a, b, max - 1);
continue;
}
}
*a = v;
}
}
}

View file

@ -1,8 +1,8 @@
use std::{borrow::Cow, path::PathBuf, str::FromStr};
use std::{borrow::Cow, path::PathBuf};
use anyhow::Context;
use serde::{Deserialize, Deserializer, Serialize};
use validator::Validate;
use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use yazi_codegen::DeserializeOver2;
use yazi_fs::{Xdg, expand_path};
use yazi_shared::timestamp_us;
@ -11,7 +11,7 @@ use super::PreviewWrap;
#[rustfmt::skip]
const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "];
#[derive(Debug, Serialize)]
#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]
pub struct Preview {
pub wrap: PreviewWrap,
pub tab_size: u8,
@ -44,70 +44,24 @@ impl Preview {
}
}
impl FromStr for Preview {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let preview: Self =
toml::from_str(s).context("Failed to parse the [preview] section in your yazi.toml")?;
std::fs::create_dir_all(&preview.cache_dir).context("Failed to create cache directory")?;
Ok(preview)
}
}
impl<'de> Deserialize<'de> for Preview {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
preview: Shadow,
}
#[derive(Deserialize, Validate)]
struct Shadow {
wrap: PreviewWrap,
tab_size: u8,
max_width: u32,
max_height: u32,
cache_dir: Option<String>,
#[validate(range(min = 0, max = 100))]
image_delay: u8,
image_filter: String,
#[validate(range(min = 50, max = 90))]
image_quality: u8,
#[validate(range(min = 10, max = 20))]
sixel_fraction: u8,
ueberzug_scale: f32,
ueberzug_offset: (f32, f32, f32, f32),
impl Preview {
pub(crate) fn reshape(mut self) -> Result<Self> {
if self.image_delay > 100 {
bail!("[preview].image_delay must be between 0 and 100.");
} else if self.image_quality < 50 || self.image_quality > 90 {
bail!("[preview].image_quality must be between 50 and 90.");
} else if self.sixel_fraction < 10 || self.sixel_fraction > 20 {
bail!("[preview].sixel_fraction must be between 10 and 20.");
}
let preview = Outer::deserialize(deserializer)?.preview;
preview.validate().map_err(serde::de::Error::custom)?;
self.cache_dir = if self.cache_dir.as_os_str().is_empty() {
Xdg::cache_dir()
} else {
expand_path(&self.cache_dir)
};
Ok(Preview {
wrap: preview.wrap,
tab_size: preview.tab_size,
max_width: preview.max_width,
max_height: preview.max_height,
std::fs::create_dir_all(&self.cache_dir).context("Failed to create cache directory")?;
cache_dir: preview
.cache_dir
.filter(|p| !p.is_empty())
.map_or_else(Xdg::cache_dir, expand_path),
image_delay: preview.image_delay,
image_filter: preview.image_filter,
image_quality: preview.image_quality,
sixel_fraction: preview.sixel_fraction,
ueberzug_scale: preview.ueberzug_scale,
ueberzug_offset: preview.ueberzug_offset,
})
Ok(self)
}
}

View file

@ -1,16 +1,11 @@
use std::str::FromStr;
use anyhow::Context;
use anyhow::{Result, bail};
use serde::Deserialize;
use validator::Validate;
use yazi_codegen::DeserializeOver2;
#[derive(Debug, Deserialize, Validate)]
#[derive(Debug, Deserialize, DeserializeOver2)]
pub struct Tasks {
#[validate(range(min = 1, message = "Cannot be less than 1"))]
pub micro_workers: u8,
#[validate(range(min = 1, message = "Cannot be less than 1"))]
pub macro_workers: u8,
#[validate(range(min = 1, message = "Cannot be less than 1"))]
pub bizarre_retry: u8,
pub image_alloc: u32,
@ -19,19 +14,15 @@ pub struct Tasks {
pub suppress_preload: bool,
}
impl FromStr for Tasks {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
tasks: Tasks,
impl Tasks {
pub(crate) fn reshape(self) -> Result<Self> {
if self.micro_workers < 1 {
bail!("[tasks].micro_workers must be at least 1.");
} else if self.macro_workers < 1 {
bail!("[tasks].macro_workers must be at least 1.");
} else if self.bizarre_retry < 1 {
bail!("[tasks].bizarre_retry` must be at least 1.");
}
let outer = toml::from_str::<Outer>(s)
.context("Failed to parse the [tasks] section in your yazi.toml")?;
outer.tasks.validate()?;
Ok(outer.tasks)
Ok(self)
}
}

View file

@ -1,18 +1,35 @@
use serde::{Deserialize, Deserializer};
use std::ops::Deref;
use serde::Deserialize;
use yazi_codegen::DeserializeOver2;
use yazi_fs::File;
use yazi_shared::theme::{Color, Style, StyleShadow};
use yazi_shared::theme::Style;
use super::Is;
use crate::Pattern;
#[derive(Deserialize, DeserializeOver2)]
pub struct Filetype {
pub is: Is,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
rules: Vec<FiletypeRule>,
}
impl Deref for Filetype {
type Target = Vec<FiletypeRule>;
fn deref(&self) -> &Self::Target { &self.rules }
}
#[derive(Deserialize)]
pub struct FiletypeRule {
#[serde(default)]
is: Is,
name: Option<Pattern>,
mime: Option<Pattern>,
#[serde(flatten)]
pub style: Style,
}
impl Filetype {
impl FiletypeRule {
pub fn matches(&self, file: &File, mime: &str) -> bool {
if !self.is.check(&file.cha) {
return false;
@ -22,69 +39,3 @@ impl Filetype {
|| self.name.as_ref().is_some_and(|n| n.match_path(&file.url, file.is_dir()))
}
}
impl Filetype {
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Filetype>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct FiletypeOuter {
rules: Vec<FiletypeRule>,
}
#[derive(Deserialize)]
struct FiletypeRule {
#[serde(default)]
is: Is,
name: Option<Pattern>,
mime: Option<Pattern>,
fg: Option<Color>,
bg: Option<Color>,
#[serde(default)]
bold: bool,
#[serde(default)]
dim: bool,
#[serde(default)]
italic: bool,
#[serde(default)]
underline: bool,
#[serde(default)]
blink: bool,
#[serde(default)]
blink_rapid: bool,
#[serde(default)]
reversed: bool,
#[serde(default)]
hidden: bool,
#[serde(default)]
crossed: bool,
}
Ok(
FiletypeOuter::deserialize(deserializer)?
.rules
.into_iter()
.map(|r| Filetype {
is: r.is,
name: r.name,
mime: r.mime,
style: StyleShadow {
fg: r.fg,
bg: r.bg,
bold: r.bold,
dim: r.dim,
italic: r.italic,
underline: r.underline,
blink: r.blink,
blink_rapid: r.blink_rapid,
reversed: r.reversed,
hidden: r.hidden,
crossed: r.crossed,
}
.into(),
})
.collect(),
)
}
}

View file

@ -1,42 +1,39 @@
use std::{borrow::Cow, path::PathBuf, str::FromStr};
use std::path::PathBuf;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use toml::Value;
use yazi_codegen::DeserializeOver2;
use yazi_fs::Xdg;
#[derive(Default, Deserialize, Serialize)]
#[derive(Default, Deserialize, DeserializeOver2, Serialize)]
pub struct Flavor {
#[serde(default)]
pub dark: String,
#[serde(default)]
pub light: String,
}
impl FromStr for Flavor {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
#[serde(default)]
flavor: Flavor,
impl From<&Value> for Flavor {
fn from(value: &Value) -> Self {
let mut me = Self::default();
if let Value::Table(t) = value {
if let Some(s) = t.get("dark").and_then(|v| v.as_str()) {
me.dark = s.to_owned();
}
if let Some(s) = t.get("light").and_then(|v| v.as_str()) {
me.light = s.to_owned();
}
}
Ok(
toml::from_str::<Outer>(s)
.context("Failed to parse the [flavor] section in your theme.toml")?
.flavor,
)
me
}
}
impl Flavor {
pub(crate) fn read(&self, light: bool) -> Result<Cow<'static, str>> {
pub(crate) fn read(&self, light: bool) -> Result<String> {
Ok(match if light { self.light.as_str() } else { self.dark.as_str() } {
"" => Cow::Borrowed(""),
"" => String::new(),
name => {
let p = Xdg::config_dir().join(format!("flavors/{name}.yazi/flavor.toml"));
std::fs::read_to_string(&p).with_context(|| format!("Failed to load flavor {p:?}"))?.into()
std::fs::read_to_string(&p).with_context(|| format!("Failed to load flavor {p:?}"))?
}
})
}

View file

@ -0,0 +1,209 @@
use std::{collections::HashMap, ops::Deref};
use anyhow::Result;
use serde::{Deserialize, Deserializer};
use yazi_codegen::DeserializeOver2;
use yazi_fs::File;
use yazi_shared::{Condition, theme::{Color, Icon as I, Style}};
use crate::Pattern;
#[derive(Default, Deserialize, DeserializeOver2)]
pub struct Icon {
globs: PatIcons,
#[serde(default)]
prepend_globs: PatIcons,
#[serde(default)]
append_globs: PatIcons,
dirs: StrIcons,
#[serde(default)]
prepend_dirs: StrIcons,
#[serde(default)]
append_dirs: StrIcons,
files: StrIcons,
#[serde(default)]
prepend_files: StrIcons,
#[serde(default)]
append_files: StrIcons,
exts: StrIcons,
#[serde(default)]
prepend_exts: StrIcons,
#[serde(default)]
append_exts: StrIcons,
conds: CondIcons,
#[serde(default)]
prepend_conds: CondIcons,
#[serde(default)]
append_conds: CondIcons,
}
impl Icon {
pub fn matches(&self, file: &File) -> Option<&I> {
if let Some(i) = self.match_by_glob(file) {
return Some(i);
}
if let Some(i) = self.match_by_name(file) {
return Some(i);
}
let f = |s: &str| match s {
"dir" => file.is_dir(),
"hidden" => file.is_hidden(),
"link" => file.is_link(),
"orphan" => file.is_orphan(),
"dummy" => file.is_dummy(),
"block" => file.is_block(),
"char" => file.is_char(),
"fifo" => file.is_fifo(),
"sock" => file.is_sock(),
"exec" => file.is_exec(),
"sticky" => file.is_sticky(),
_ => false,
};
self.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i)
}
#[inline]
fn match_by_glob(&self, file: &File) -> Option<&I> {
self.globs.iter().find(|(p, _)| p.match_path(&file.url, file.is_dir())).map(|(_, i)| i)
}
#[inline]
fn match_by_name(&self, file: &File) -> Option<&I> {
let name = file.name().to_str()?;
if file.is_dir() {
self.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase()))
} else {
self
.files
.get(name)
.or_else(|| self.files.get(&name.to_ascii_lowercase()))
.or_else(|| self.match_by_ext(file))
}
}
#[inline]
fn match_by_ext(&self, file: &File) -> Option<&I> {
let ext = file.url.extension()?.to_str()?;
self.exts.get(ext).or_else(|| self.exts.get(&ext.to_ascii_lowercase()))
}
}
impl Icon {
pub(crate) fn reshape(self) -> Result<Self> {
Ok(Self {
globs: PatIcons(
self.prepend_globs.0.into_iter().chain(self.globs.0).chain(self.append_globs.0).collect(),
),
dirs: StrIcons(
self.prepend_dirs.0.into_iter().chain(self.dirs.0).chain(self.append_dirs.0).collect(),
),
files: StrIcons(
self.prepend_files.0.into_iter().chain(self.files.0).chain(self.append_files.0).collect(),
),
exts: StrIcons(
self.prepend_exts.0.into_iter().chain(self.exts.0).chain(self.append_exts.0).collect(),
),
conds: CondIcons(
self.prepend_conds.0.into_iter().chain(self.conds.0).chain(self.append_conds.0).collect(),
),
..Default::default()
})
}
}
#[derive(Default)]
pub struct PatIcons(Vec<(Pattern, I)>);
impl Deref for PatIcons {
type Target = Vec<(Pattern, I)>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<'de> Deserialize<'de> for PatIcons {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Shadow {
name: Pattern,
text: String,
fg: Option<Color>,
}
Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)?
.into_iter()
.map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
.collect(),
))
}
}
#[derive(Default)]
pub struct StrIcons(HashMap<String, I>);
impl Deref for StrIcons {
type Target = HashMap<String, I>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<'de> Deserialize<'de> for StrIcons {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Shadow {
name: String,
text: String,
fg: Option<Color>,
}
Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)?
.into_iter()
.map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
.collect(),
))
}
}
#[derive(Default)]
pub struct CondIcons(Vec<(Condition, I)>);
impl Deref for CondIcons {
type Target = Vec<(Condition, I)>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<'de> Deserialize<'de> for CondIcons {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Shadow {
#[serde(rename = "if")]
if_: Condition,
text: String,
fg: Option<Color>,
}
Ok(Self(
<Vec<Shadow>>::deserialize(deserializer)?
.into_iter()
.map(|s| (s.if_, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))
.collect(),
))
}
}

View file

@ -1,157 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use serde::{Deserialize, Deserializer};
use yazi_fs::File;
use yazi_shared::{Condition, theme::{Color, Icon, Style}};
use crate::{Pattern, Preset};
pub struct Icons {
globs: Vec<(Pattern, Icon)>,
dirs: HashMap<String, Icon>,
files: HashMap<String, Icon>,
exts: HashMap<String, Icon>,
conds: Vec<(Condition, Icon)>,
}
impl Icons {
pub fn matches(&self, file: &File) -> Option<&Icon> {
if let Some(i) = self.match_by_glob(file) {
return Some(i);
}
if let Some(i) = self.match_by_name(file) {
return Some(i);
}
let f = |s: &str| match s {
"dir" => file.is_dir(),
"hidden" => file.is_hidden(),
"link" => file.is_link(),
"orphan" => file.is_orphan(),
"dummy" => file.is_dummy(),
"block" => file.is_block(),
"char" => file.is_char(),
"fifo" => file.is_fifo(),
"sock" => file.is_sock(),
"exec" => file.is_exec(),
"sticky" => file.is_sticky(),
_ => false,
};
self.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i)
}
#[inline]
fn match_by_glob(&self, file: &File) -> Option<&Icon> {
self.globs.iter().find(|(p, _)| p.match_path(&file.url, file.is_dir())).map(|(_, i)| i)
}
#[inline]
fn match_by_name(&self, file: &File) -> Option<&Icon> {
let name = file.name().to_str()?;
if file.is_dir() {
self.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase()))
} else {
self
.files
.get(name)
.or_else(|| self.files.get(&name.to_ascii_lowercase()))
.or_else(|| self.match_by_ext(file))
}
}
#[inline]
fn match_by_ext(&self, file: &File) -> Option<&Icon> {
let ext = file.url.extension()?.to_str()?;
self.exts.get(ext).or_else(|| self.exts.get(&ext.to_ascii_lowercase()))
}
}
impl<'de> Deserialize<'de> for Icons {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct Shadow {
globs: Vec<ShadowPat>,
#[serde(default)]
prepend_globs: Vec<ShadowPat>,
#[serde(default)]
append_globs: Vec<ShadowPat>,
dirs: Vec<ShadowStr>,
#[serde(default)]
prepend_dirs: Vec<ShadowStr>,
#[serde(default)]
append_dirs: Vec<ShadowStr>,
files: Vec<ShadowStr>,
#[serde(default)]
prepend_files: Vec<ShadowStr>,
#[serde(default)]
append_files: Vec<ShadowStr>,
exts: Vec<ShadowStr>,
#[serde(default)]
prepend_exts: Vec<ShadowStr>,
#[serde(default)]
append_exts: Vec<ShadowStr>,
conds: Vec<ShadowCond>,
#[serde(default)]
prepend_conds: Vec<ShadowCond>,
#[serde(default)]
append_conds: Vec<ShadowCond>,
}
#[derive(Deserialize)]
pub struct ShadowPat {
name: Pattern,
text: String,
fg: Option<Color>,
}
#[derive(Deserialize)]
pub struct ShadowStr {
name: String,
text: String,
fg: Option<Color>,
}
#[derive(Deserialize)]
pub struct ShadowCond {
#[serde(rename = "if")]
if_: Condition,
text: String,
fg: Option<Color>,
}
let shadow = Shadow::deserialize(deserializer)?;
let globs = Preset::mix(shadow.prepend_globs, shadow.globs, shadow.append_globs)
.map(|v| (v.name, Icon { text: v.text, style: Style { fg: v.fg, ..Default::default() } }))
.collect();
let conds = Preset::mix(shadow.prepend_conds, shadow.conds, shadow.append_conds)
.map(|v| (v.if_, Icon { text: v.text, style: Style { fg: v.fg, ..Default::default() } }))
.collect();
fn as_map(it: impl Iterator<Item = ShadowStr>) -> HashMap<String, Icon> {
let mut map = HashMap::with_capacity(it.size_hint().0);
for v in it {
map
.entry(v.name)
.or_insert(Icon { text: v.text, style: Style { fg: v.fg, ..Default::default() } });
}
map.shrink_to_fit();
map
}
Ok(Self {
globs,
dirs: as_map(Preset::mix(shadow.prepend_dirs, shadow.dirs, shadow.append_dirs)),
files: as_map(Preset::mix(shadow.prepend_files, shadow.files, shadow.append_files)),
exts: as_map(Preset::mix(shadow.prepend_exts, shadow.exts, shadow.append_exts)),
conds,
})
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(filetype flavor icons is theme);
yazi_macro::mod_flat!(filetype flavor icon is theme);

View file

@ -1,13 +1,14 @@
use std::{path::PathBuf, str::FromStr};
use std::path::PathBuf;
use anyhow::Context;
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_codegen::{DeserializeOver1, DeserializeOver2};
use yazi_fs::expand_path;
use yazi_shared::theme::Style;
use super::{Filetype, Flavor, Icons};
use super::{Filetype, Flavor, Icon};
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver1, Serialize)]
pub struct Theme {
pub flavor: Flavor,
#[serde(rename = "manager")]
@ -25,25 +26,13 @@ pub struct Theme {
pub help: Help,
// File-specific styles
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
pub filetypes: Vec<Filetype>,
#[serde(rename = "icon", skip_serializing)]
pub icons: Icons,
#[serde(skip_serializing)]
pub filetype: Filetype,
#[serde(skip_serializing)]
pub icon: Icon,
}
impl FromStr for Theme {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let theme: Self = toml::from_str(s).context("Failed to parse your theme.toml")?;
theme.mgr.validate()?;
theme.which.validate()?;
Ok(theme)
}
}
#[derive(Deserialize, Serialize, Validate)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Mgr {
cwd: Style,
@ -64,7 +53,6 @@ pub struct Mgr {
// Tab
tab_active: Style,
tab_inactive: Style,
#[validate(range(min = 1, message = "Must be greater than 0"))]
tab_width: u8,
// Count
@ -80,7 +68,7 @@ pub struct Mgr {
pub syntect_theme: PathBuf,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Mode {
pub normal_main: Style,
pub normal_alt: Style,
@ -92,7 +80,7 @@ pub struct Mode {
pub unset_alt: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Status {
pub overall: Style,
pub sep_left: StatusSep,
@ -111,15 +99,14 @@ pub struct Status {
pub progress_error: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct StatusSep {
pub open: String,
pub close: String,
}
#[derive(Deserialize, Serialize, Validate)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Which {
#[validate(range(min = 1, max = 3, message = "Must be between 1 and 3"))]
pub cols: u8,
pub mask: Style,
pub cand: Style,
@ -130,7 +117,7 @@ pub struct Which {
pub separator_style: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Confirm {
pub border: Style,
pub title: Style,
@ -142,7 +129,7 @@ pub struct Confirm {
pub btn_labels: [String; 2],
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Spot {
pub border: Style,
pub title: Style,
@ -151,7 +138,7 @@ pub struct Spot {
pub tbl_cell: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Notify {
pub title_info: Style,
pub title_warn: Style,
@ -162,14 +149,14 @@ pub struct Notify {
pub icon_error: String,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Pick {
pub border: Style,
pub active: Style,
pub inactive: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Input {
pub border: Style,
pub title: Style,
@ -177,7 +164,7 @@ pub struct Input {
pub selected: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Cmp {
pub border: Style,
pub active: Style,
@ -188,14 +175,14 @@ pub struct Cmp {
pub icon_command: String,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Tasks {
pub border: Style,
pub title: Style,
pub hovered: Style,
}
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, DeserializeOver2, Serialize)]
pub struct Help {
pub on: Style,
pub run: Style,
@ -204,3 +191,20 @@ pub struct Help {
pub hovered: Style,
pub footer: Style,
}
impl Theme {
pub(crate) fn reshape(mut self, light: bool) -> Result<Self> {
if self.mgr.tab_width < 1 {
bail!("[mgr].tab_width must be greater than 0");
} else if self.which.cols < 1 || self.which.cols > 3 {
bail!("[which].cols must be between 1 and 3");
}
self.icon = self.icon.reshape()?;
self.mgr.syntect_theme =
self.flavor.syntect_path(light).unwrap_or_else(|| expand_path(&self.mgr.syntect_theme));
Ok(self)
}
}

View file

@ -1,12 +1,9 @@
use std::str::FromStr;
use anyhow::Context;
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_codegen::DeserializeOver2;
use super::SortBy;
#[derive(Debug, Deserialize, Serialize, Validate)]
#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]
pub struct Which {
// Sorting
pub sort_by: SortBy,
@ -14,19 +11,3 @@ pub struct Which {
pub sort_reverse: bool,
pub sort_translit: bool,
}
impl FromStr for Which {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
which: Which,
}
let outer: Outer =
toml::from_str(s).context("Failed to parse the [which] section in your yazi.toml")?;
Ok(outer.which)
}
}

37
yazi-config/src/yazi.rs Normal file
View file

@ -0,0 +1,37 @@
use anyhow::Result;
use serde::Deserialize;
use yazi_codegen::DeserializeOver1;
use crate::{mgr, open, opener, plugin, popup, preview, tasks, which};
#[derive(Deserialize, DeserializeOver1)]
pub struct Yazi {
#[serde(rename = "manager")]
pub mgr: mgr::Mgr, // TODO: remove serde(rename)
pub preview: preview::Preview,
pub opener: opener::Opener,
pub open: open::Open,
pub tasks: tasks::Tasks,
pub plugin: plugin::Plugin,
pub input: popup::Input,
pub confirm: popup::Confirm,
pub pick: popup::Pick,
pub which: which::Which,
}
impl Yazi {
pub(super) fn reshape(self) -> Result<Self> {
Ok(Self {
mgr: self.mgr.reshape()?,
preview: self.preview.reshape()?,
opener: self.opener.reshape()?,
open: self.open.reshape()?,
tasks: self.tasks.reshape()?,
plugin: self.plugin.reshape()?,
input: self.input,
confirm: self.confirm,
pick: self.pick,
which: self.which,
})
}
}

View file

@ -1,7 +1,7 @@
use crossterm::{cursor::SetCursorStyle, event::KeyCode};
use unicode_width::UnicodeWidthStr;
use yazi_adapter::Dimension;
use yazi_config::{INPUT, KEYMAP, keymap::{Chord, Key}};
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
use yazi_macro::{render, render_and};
use yazi_shared::Layer;
@ -115,6 +115,10 @@ impl Help {
#[inline]
pub fn cursor_shape(&self) -> SetCursorStyle {
if INPUT.cursor_blink { SetCursorStyle::BlinkingBlock } else { SetCursorStyle::SteadyBlock }
if YAZI.input.cursor_blink {
SetCursorStyle::BlinkingBlock
} else {
SetCursorStyle::SteadyBlock
}
}
}

View file

@ -1,5 +1,5 @@
use tokio::sync::mpsc;
use yazi_config::{INPUT, popup::InputCfg};
use yazi_config::{YAZI, popup::InputCfg};
use yazi_macro::render;
use yazi_shared::{errors::InputError, event::CmdCow};
use yazi_widgets::input::InputCallback;
@ -45,7 +45,7 @@ impl Input {
});
self.inner = yazi_widgets::input::Input::new(
opt.cfg.value,
opt.cfg.position.offset.width.saturating_sub(INPUT.border()) as usize,
opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize,
cb,
);

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap, ffi::{OsStr, OsString}, io::{Read,
use anyhow::{Result, anyhow};
use scopeguard::defer;
use tokio::{fs::{self, OpenOptions}, io::AsyncWriteExt};
use yazi_config::{OPEN, PREVIEW};
use yazi_config::YAZI;
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, paths_to_same_file};
use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER};
@ -13,7 +13,7 @@ use crate::mgr::Mgr;
impl Mgr {
pub(super) fn bulk_rename(&self) {
let Some(opener) = OPEN.block_opener("bulk-rename.txt", "text/plain") else {
let Some(opener) = YAZI.opener.block(YAZI.open.all("bulk-rename.txt", "text/plain")) else {
return AppProxy::notify_warn("Bulk rename", "No text opener found");
};
@ -24,7 +24,7 @@ impl Mgr {
let cwd = self.cwd().clone();
tokio::spawn(async move {
let tmp = PREVIEW.tmpfile("bulk");
let tmp = YAZI.preview.tmpfile("bulk");
let s = old.iter().map(|o| o.as_os_str()).collect::<Vec<_>>().join(OsStr::new("\n"));
OpenOptions::new()
.write(true)

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsString};
use tracing::error;
use yazi_boot::ARGS;
use yazi_config::{OPEN, PLUGIN, popup::PickCfg};
use yazi_config::{YAZI, popup::PickCfg};
use yazi_fs::File;
use yazi_macro::emit;
use yazi_plugin::isolate;
@ -64,7 +64,7 @@ impl Mgr {
}
done.extend(files.iter().map(|f| (f.url_owned(), "")));
for (fetcher, files) in PLUGIN.mime_fetchers(files) {
for (fetcher, files) in YAZI.plugin.mime_fetchers(files) {
if let Err(e) = isolate::fetch(CmdCow::from(&fetcher.run), files).await {
error!("Fetch mime failed on opening: {e}");
}
@ -90,16 +90,16 @@ impl Mgr {
return tasks.process_from_files(opt.cwd, opt.hovered, targets);
}
let openers: Vec<_> = OPEN.common_openers(&targets);
let openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter());
if openers.is_empty() {
return;
}
let urls = [opt.hovered].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect();
tokio::spawn(async move {
let result = yazi_proxy::PickProxy::show(PickCfg::open(
openers.iter().map(|o| o.desc.clone()).collect(),
));
let result =
yazi_proxy::PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect()));
if let Ok(choice) = result.await {
TasksProxy::open_with(Cow::Borrowed(openers[choice]), opt.cwd, urls);
}

View file

@ -1,7 +1,7 @@
use std::path::MAIN_SEPARATOR;
use crossterm::{execute, terminal::SetTitle};
use yazi_config::MGR;
use yazi_config::YAZI;
use yazi_fs::CWD;
use yazi_shared::{event::CmdCow, tty::TTY};
@ -9,7 +9,7 @@ use crate::{mgr::Mgr, tasks::Tasks};
impl Mgr {
pub fn refresh(&mut self, _: CmdCow, tasks: &Tasks) {
if CWD.set(self.cwd()) && !MGR.title_format.is_empty() {
if CWD.set(self.cwd()) && !YAZI.mgr.title_format.is_empty() {
execute!(TTY.writer(), SetTitle(self.title())).ok();
}
@ -35,6 +35,6 @@ impl Mgr {
format!("{}", self.cwd().display())
};
MGR.title_format.replace("{cwd}", &cwd)
YAZI.mgr.title_format.replace("{cwd}", &cwd)
}
}

View file

@ -1,4 +1,4 @@
use yazi_config::PLUGIN;
use yazi_config::YAZI;
use yazi_plugin::isolate;
use yazi_shared::event::{CmdCow, Data};
@ -24,7 +24,7 @@ impl Mgr {
return self.active_mut().preview.reset();
};
let Some(previewer) = PLUGIN.previewer(&hovered.url, mime) else {
let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else {
return self.active_mut().preview.reset();
};

View file

@ -1,6 +1,6 @@
use anyhow::Result;
use tokio::sync::oneshot::Sender;
use yazi_config::{PICK, popup::Position};
use yazi_config::{YAZI, popup::Position};
#[derive(Default)]
pub struct Pick {
@ -24,7 +24,7 @@ impl Pick {
#[inline]
pub(super) fn limit(&self) -> usize {
self.position.offset.height.saturating_sub(PICK.border()) as usize
self.position.offset.height.saturating_sub(YAZI.pick.border()) as usize
}
}

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use tokio_util::sync::CancellationToken;
use yazi_config::PLUGIN;
use yazi_config::YAZI;
use yazi_fs::File;
use yazi_plugin::{isolate, utils::SpotLock};
use yazi_shared::url::Url;
@ -22,7 +22,7 @@ impl Spot {
return;
}
let Some(spotter) = PLUGIN.spotter(&file.url, &mime) else {
let Some(spotter) = YAZI.plugin.spotter(&file.url, &mime) else {
return self.close(());
};

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, fmt::Display};
use anyhow::bail;
use yazi_config::{open::Opener, popup::InputCfg};
use yazi_config::{opener::OpenerRule, popup::InputCfg};
use yazi_proxy::{AppProxy, InputProxy, TasksProxy};
use yazi_shared::{event::{CmdCow, Data}, url::Url};
@ -68,7 +68,7 @@ impl Tab {
}
TasksProxy::open_with(
Cow::Owned(Opener {
Cow::Owned(OpenerRule {
run: opt.run.into_owned(),
block: opt.block,
orphan: opt.orphan,

View file

@ -1,6 +1,6 @@
use std::mem;
use yazi_config::{LAYOUT, MGR};
use yazi_config::{LAYOUT, YAZI};
use yazi_dds::Pubsub;
use yazi_fs::{File, Files, FilesOp, FolderStage, Step, cha::Cha};
use yazi_proxy::MgrProxy;
@ -24,7 +24,7 @@ impl Default for Folder {
Self {
url: Default::default(),
cha: Default::default(),
files: Files::new(MGR.show_hidden),
files: Files::new(YAZI.mgr.show_hidden),
stage: Default::default(),
offset: Default::default(),
cursor: Default::default(),
@ -140,7 +140,7 @@ impl Folder {
let len = self.files.len();
let limit = LAYOUT.get().limit();
let scrolloff = (limit / 2).min(MGR.scrolloff as usize);
let scrolloff = (limit / 2).min(YAZI.mgr.scrolloff as usize);
self.cursor = new.min(len.saturating_sub(1));
self.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) {
@ -157,7 +157,7 @@ impl Folder {
let max = self.files.len().saturating_sub(1);
let limit = LAYOUT.get().limit();
let scrolloff = (limit / 2).min(MGR.scrolloff as usize);
let scrolloff = (limit / 2).min(YAZI.mgr.scrolloff as usize);
self.cursor = new.min(max);
self.offset = if self.cursor < self.offset + scrolloff {
@ -174,7 +174,7 @@ impl Folder {
let len = self.files.len();
let limit = LAYOUT.get().limit();
let scrolloff = (limit / 2).min(MGR.scrolloff as usize);
let scrolloff = (limit / 2).min(YAZI.mgr.scrolloff as usize);
self.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) {
len.saturating_sub(limit).min(self.offset)

View file

@ -1,4 +1,4 @@
use yazi_config::MGR;
use yazi_config::YAZI;
use yazi_fs::{FilesSorter, SortBy};
#[derive(Clone, PartialEq)]
@ -19,15 +19,15 @@ impl Default for Preference {
fn default() -> Self {
Self {
// Sorting
sort_by: MGR.sort_by,
sort_sensitive: MGR.sort_sensitive,
sort_reverse: MGR.sort_reverse,
sort_dir_first: MGR.sort_dir_first,
sort_translit: MGR.sort_translit,
sort_by: YAZI.mgr.sort_by,
sort_sensitive: YAZI.mgr.sort_sensitive,
sort_reverse: YAZI.mgr.sort_reverse,
sort_dir_first: YAZI.mgr.sort_dir_first,
sort_translit: YAZI.mgr.sort_translit,
// Display
linemode: MGR.linemode.to_owned(),
show_hidden: MGR.show_hidden,
linemode: YAZI.mgr.linemode.to_owned(),
show_hidden: YAZI.mgr.show_hidden,
}
}
}

View file

@ -4,7 +4,7 @@ use tokio::{pin, task::JoinHandle};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use tokio_util::sync::CancellationToken;
use yazi_adapter::ADAPTOR;
use yazi_config::PLUGIN;
use yazi_config::YAZI;
use yazi_fs::{File, Files, FilesOp, cha::Cha};
use yazi_macro::render;
use yazi_plugin::{external::Highlighter, isolate, utils::PreviewLock};
@ -27,7 +27,7 @@ impl Preview {
return;
}
let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else {
let Some(previewer) = YAZI.plugin.previewer(&file.url, &mime) else {
return self.reset();
};

View file

@ -1,4 +1,4 @@
use yazi_config::{PLUGIN, plugin::MAX_PREWORKERS};
use yazi_config::{YAZI, plugin::MAX_PREWORKERS};
use yazi_fs::{File, Files, SortBy};
use super::Tasks;
@ -10,7 +10,7 @@ impl Tasks {
let mut tasks: [Vec<_>; MAX_PREWORKERS as usize] = Default::default();
for f in paged {
let hash = f.hash();
for g in PLUGIN.fetchers(&f.url, mimetype.by_file(f).unwrap_or_default()) {
for g in YAZI.plugin.fetchers(&f.url, mimetype.by_file(f).unwrap_or_default()) {
match loaded.get_mut(&hash) {
Some(n) if *n & (1 << g.idx) != 0 => continue,
Some(n) => *n |= 1 << g.idx,
@ -23,7 +23,7 @@ impl Tasks {
drop(loaded);
for (i, tasks) in tasks.into_iter().enumerate() {
if !tasks.is_empty() {
self.scheduler.fetch_paged(&PLUGIN.fetchers[i], tasks);
self.scheduler.fetch_paged(&YAZI.plugin.fetchers[i], tasks);
}
}
}
@ -32,7 +32,7 @@ impl Tasks {
let mut loaded = self.scheduler.prework.loaded.lock();
for f in paged {
let hash = f.hash();
for p in PLUGIN.preloaders(&f.url, mimetype.by_file(f).unwrap_or_default()) {
for p in YAZI.plugin.preloaders(&f.url, mimetype.by_file(f).unwrap_or_default()) {
match loaded.get_mut(&hash) {
Some(n) if *n & (1 << p.idx) != 0 => continue,
Some(n) => *n |= 1 << p.idx,

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::HashMap, ffi::OsString, mem};
use yazi_config::{OPEN, open::Opener};
use yazi_config::{YAZI, opener::OpenerRule};
use yazi_proxy::options::ProcessExecOpt;
use yazi_shared::url::Url;
@ -10,7 +10,7 @@ impl Tasks {
pub fn process_from_files(&self, cwd: Url, hovered: Url, targets: Vec<(Url, &str)>) {
let mut openers = HashMap::new();
for (url, mime) in targets {
if let Some(opener) = OPEN.openers(&url, mime).and_then(|o| o.first().copied()) {
if let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) {
openers.entry(opener).or_insert_with(|| vec![hovered.clone()]).push(url);
}
}
@ -26,7 +26,7 @@ impl Tasks {
pub fn process_from_opener(
&self,
cwd: Url,
opener: Cow<'static, Opener>,
opener: Cow<'static, OpenerRule>,
mut args: Vec<OsString>,
) {
if opener.spread {

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, mem};
use yazi_config::{WHICH, keymap::ChordCow, which::SortBy};
use yazi_config::{YAZI, keymap::ChordCow, which::SortBy};
use yazi_shared::{natsort, translit::Transliterator};
#[derive(Clone, Copy, PartialEq)]
@ -14,10 +14,10 @@ pub struct WhichSorter {
impl Default for WhichSorter {
fn default() -> Self {
Self {
by: WHICH.sort_by,
sensitive: WHICH.sort_sensitive,
reverse: WHICH.sort_reverse,
translit: WHICH.sort_translit,
by: YAZI.which.sort_by,
sensitive: YAZI.which.sort_sensitive,
reverse: YAZI.which.sort_reverse,
translit: YAZI.which.sort_translit,
}
}
}

View file

@ -1,7 +1,7 @@
use crossterm::event::{MouseEvent, MouseEventKind};
use mlua::{ObjectLike, Table};
use tracing::error;
use yazi_config::MGR;
use yazi_config::YAZI;
use yazi_plugin::LUA;
use crate::{app::App, lives::Lives};
@ -24,7 +24,7 @@ impl App {
let area = yazi_plugin::elements::Rect::from(size);
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
if matches!(event.kind, MouseEventKind::Down(_) if MGR.mouse_events.draggable()) {
if matches!(event.kind, MouseEventKind::Down(_) if YAZI.mgr.mouse_events.draggable()) {
root.raw_set("_drag_start", event)?;
}

View file

@ -70,7 +70,7 @@ impl UserData for File {
methods.add_method("style", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|cx: &Ctx| {
let mime = cx.mgr.mimetype.by_file(me).unwrap_or_default();
THEME.filetypes.iter().find(|&x| x.matches(me, mime)).map(|x| Style::from(x.style))
THEME.filetype.iter().find(|&x| x.matches(me, mime)).map(|x| Style::from(x.style))
})
});
methods.add_method("is_hovered", |_, me, ()| Ok(me.idx == me.folder().cursor));

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind};
use futures::StreamExt;
use tokio::{select, sync::{mpsc, oneshot}};
use yazi_config::MGR;
use yazi_config::YAZI;
use yazi_shared::event::Event;
pub(super) struct Signals {
@ -62,7 +62,7 @@ impl Signals {
Event::Key(key).emit()
}
CrosstermEvent::Mouse(mouse) => {
if MGR.mouse_events.contains(mouse.kind.into()) {
if YAZI.mgr.mouse_events.contains(mouse.kind.into()) {
Event::Mouse(mouse).emit();
}
}

View file

@ -5,7 +5,7 @@ use crossterm::{Command, event::{DisableBracketedPaste, EnableBracketedPaste, Ke
use cursor::RestoreCursor;
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
use yazi_adapter::{Emulator, Mux};
use yazi_config::MGR;
use yazi_config::YAZI;
use yazi_shared::{SyncCell, tty::{TTY, TtyWriter}};
static CSI_U: AtomicBool = AtomicBool::new(false);
@ -94,7 +94,7 @@ impl Term {
PopKeyboardEnhancementFlags.write_ansi(&mut TTY.writer()).ok();
}
if !MGR.title_format.is_empty() {
if !YAZI.mgr.title_format.is_empty() {
execute!(TTY.writer(), SetTitle("")).ok();
}
@ -160,13 +160,13 @@ impl DerefMut for Term {
// --- Mouse support
mod mouse {
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use yazi_config::MGR;
use yazi_config::YAZI;
pub struct SetMouse(pub bool);
impl crossterm::Command for SetMouse {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
if MGR.mouse_events.is_empty() {
if YAZI.mgr.mouse_events.is_empty() {
Ok(())
} else if self.0 {
EnableMouseCapture.write_ansi(f)
@ -177,7 +177,7 @@ mod mouse {
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
if MGR.mouse_events.is_empty() {
if YAZI.mgr.mouse_events.is_empty() {
Ok(())
} else if self.0 {
EnableMouseCapture.execute_winapi()

View file

@ -1,5 +1,5 @@
use mlua::{Function, IntoLua, Lua, UserData, Value};
use yazi_config::PLUGIN;
use yazi_config::YAZI;
use crate::{Composer, file::FileRef, url::UrlRef};
@ -21,25 +21,25 @@ impl Plugin {
fn fetchers(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (file, mime): (FileRef, mlua::String)| {
lua.create_sequence_from(PLUGIN.fetchers(&file.url, &mime.to_str()?).map(Fetcher))
lua.create_sequence_from(YAZI.plugin.fetchers(&file.url, &mime.to_str()?).map(Fetcher))
})
}
fn spotter(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (url, mime): (UrlRef, mlua::String)| {
Ok(PLUGIN.spotter(&url, &mime.to_str()?).map(Spotter))
Ok(YAZI.plugin.spotter(&url, &mime.to_str()?).map(Spotter))
})
}
fn preloaders(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (url, mime): (UrlRef, mlua::String)| {
lua.create_sequence_from(PLUGIN.preloaders(&url, &mime.to_str()?).map(Preloader))
lua.create_sequence_from(YAZI.plugin.preloaders(&url, &mime.to_str()?).map(Preloader))
})
}
fn previewer(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (url, mime): (UrlRef, mlua::String)| {
Ok(PLUGIN.previewer(&url, &mime.to_str()?).map(Previewer))
Ok(YAZI.plugin.previewer(&url, &mime.to_str()?).map(Previewer))
})
}
}

View file

@ -1,7 +1,7 @@
use mlua::{IntoLua, Lua, LuaSerdeExt, SerializeOptions, Value};
use yazi_adapter::EMULATOR;
use yazi_boot::ARGS;
use yazi_config::{MGR, PREVIEW, TASKS, THEME};
use yazi_config::{THEME, YAZI};
use crate::{Composer, url::Url};
@ -43,21 +43,22 @@ impl<'a> Runtime<'a> {
fn mgr(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, 15, |lua, key| {
let m = &YAZI.mgr;
match key {
b"ratio" => lua.to_value_with(&MGR.ratio, OPTS)?,
b"ratio" => lua.to_value_with(&m.ratio, OPTS)?,
b"sort_by" => lua.to_value_with(&MGR.sort_by, OPTS)?,
b"sort_sensitive" => lua.to_value_with(&MGR.sort_sensitive, OPTS)?,
b"sort_reverse" => lua.to_value_with(&MGR.sort_reverse, OPTS)?,
b"sort_dir_first" => lua.to_value_with(&MGR.sort_dir_first, OPTS)?,
b"sort_translit" => lua.to_value_with(&MGR.sort_translit, OPTS)?,
b"sort_by" => lua.to_value_with(&m.sort_by, OPTS)?,
b"sort_sensitive" => lua.to_value_with(&m.sort_sensitive, OPTS)?,
b"sort_reverse" => lua.to_value_with(&m.sort_reverse, OPTS)?,
b"sort_dir_first" => lua.to_value_with(&m.sort_dir_first, OPTS)?,
b"sort_translit" => lua.to_value_with(&m.sort_translit, OPTS)?,
b"linemode" => lua.to_value_with(&MGR.linemode, OPTS)?,
b"show_hidden" => lua.to_value_with(&MGR.show_hidden, OPTS)?,
b"show_symlink" => lua.to_value_with(&MGR.show_symlink, OPTS)?,
b"scrolloff" => lua.to_value_with(&MGR.scrolloff, OPTS)?,
b"mouse_events" => lua.to_value_with(&MGR.mouse_events, OPTS)?,
b"title_format" => lua.to_value_with(&MGR.title_format, OPTS)?,
b"linemode" => lua.to_value_with(&m.linemode, OPTS)?,
b"show_hidden" => lua.to_value_with(&m.show_hidden, OPTS)?,
b"show_symlink" => lua.to_value_with(&m.show_symlink, OPTS)?,
b"scrolloff" => lua.to_value_with(&m.scrolloff, OPTS)?,
b"mouse_events" => lua.to_value_with(&m.mouse_events, OPTS)?,
b"title_format" => lua.to_value_with(&m.title_format, OPTS)?,
_ => return Ok(Value::Nil),
}
.into_lua(lua)
@ -66,21 +67,22 @@ impl<'a> Runtime<'a> {
fn preview(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, 15, |lua, key| {
let p = &YAZI.preview;
match key {
b"wrap" => lua.to_value_with(&PREVIEW.wrap, OPTS)?,
b"tab_size" => lua.to_value_with(&PREVIEW.tab_size, OPTS)?,
b"max_width" => lua.to_value_with(&PREVIEW.max_width, OPTS)?,
b"max_height" => lua.to_value_with(&PREVIEW.max_height, OPTS)?,
b"wrap" => lua.to_value_with(&p.wrap, OPTS)?,
b"tab_size" => lua.to_value_with(&p.tab_size, OPTS)?,
b"max_width" => lua.to_value_with(&p.max_width, OPTS)?,
b"max_height" => lua.to_value_with(&p.max_height, OPTS)?,
b"cache_dir" => lua.to_value_with(&PREVIEW.cache_dir, OPTS)?,
b"cache_dir" => lua.to_value_with(&p.cache_dir, OPTS)?,
b"image_delay" => lua.to_value_with(&PREVIEW.image_delay, OPTS)?,
b"image_filter" => lua.to_value_with(&PREVIEW.image_filter, OPTS)?,
b"image_quality" => lua.to_value_with(&PREVIEW.image_quality, OPTS)?,
b"sixel_fraction" => lua.to_value_with(&PREVIEW.sixel_fraction, OPTS)?,
b"image_delay" => lua.to_value_with(&p.image_delay, OPTS)?,
b"image_filter" => lua.to_value_with(&p.image_filter, OPTS)?,
b"image_quality" => lua.to_value_with(&p.image_quality, OPTS)?,
b"sixel_fraction" => lua.to_value_with(&p.sixel_fraction, OPTS)?,
b"ueberzug_scale" => lua.to_value_with(&PREVIEW.ueberzug_scale, OPTS)?,
b"ueberzug_offset" => lua.to_value_with(&PREVIEW.ueberzug_offset, OPTS)?,
b"ueberzug_scale" => lua.to_value_with(&p.ueberzug_scale, OPTS)?,
b"ueberzug_offset" => lua.to_value_with(&p.ueberzug_offset, OPTS)?,
_ => return Ok(Value::Nil),
}
.into_lua(lua)
@ -89,15 +91,16 @@ impl<'a> Runtime<'a> {
fn tasks(lua: &Lua) -> mlua::Result<Value> {
Composer::make(lua, 10, |lua, key| {
let t = &YAZI.tasks;
match key {
b"micro_workers" => lua.to_value_with(&TASKS.micro_workers, OPTS)?,
b"macro_workers" => lua.to_value_with(&TASKS.macro_workers, OPTS)?,
b"bizarre_retry" => lua.to_value_with(&TASKS.bizarre_retry, OPTS)?,
b"micro_workers" => lua.to_value_with(&t.micro_workers, OPTS)?,
b"macro_workers" => lua.to_value_with(&t.macro_workers, OPTS)?,
b"bizarre_retry" => lua.to_value_with(&t.bizarre_retry, OPTS)?,
b"image_alloc" => lua.to_value_with(&TASKS.image_alloc, OPTS)?,
b"image_bound" => lua.to_value_with(&TASKS.image_bound, OPTS)?,
b"image_alloc" => lua.to_value_with(&t.image_alloc, OPTS)?,
b"image_bound" => lua.to_value_with(&t.image_bound, OPTS)?,
b"suppress_preload" => lua.to_value_with(&TASKS.suppress_preload, OPTS)?,
b"suppress_preload" => lua.to_value_with(&t.suppress_preload, OPTS)?,
_ => return Ok(Value::Nil),
}
.into_lua(lua)
@ -108,7 +111,7 @@ impl<'a> Runtime<'a> {
// TODO: remove this
pub fn install_manager(self) -> mlua::Result<Self> {
self.lua.globals().raw_set("MANAGER", self.lua.to_value_with(&*MGR, OPTS)?)?;
self.lua.globals().raw_set("MANAGER", self.lua.to_value_with(&YAZI.mgr, OPTS)?)?;
Ok(self)
}
@ -120,7 +123,7 @@ impl<'a> Runtime<'a> {
// TODO: remove this
pub fn install_preview(self) -> mlua::Result<Self> {
self.lua.globals().raw_set("PREVIEW", self.lua.to_value_with(&*PREVIEW, OPTS)?)?;
self.lua.globals().raw_set("PREVIEW", self.lua.to_value_with(&YAZI.preview, OPTS)?)?;
Ok(self)
}
}

View file

@ -4,7 +4,7 @@ use anyhow::{Result, anyhow};
use ratatui::{layout::Size, text::{Line, Span, Text}};
use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}};
use yazi_config::{PREVIEW, THEME, preview::PreviewWrap};
use yazi_config::{THEME, YAZI, preview::PreviewWrap};
use yazi_shared::{Ids, errors::PeekError, replace_to_printable};
static INCR: Ids = Ids::new();
@ -72,7 +72,7 @@ impl Highlighter {
} else if !plain {
before.push(String::from_utf8_lossy(&buf).into_owned());
Self::line_height(&before[before.len() - 1], size.width)
} else if PREVIEW.wrap == PreviewWrap::Yes {
} else if YAZI.preview.wrap == PreviewWrap::Yes {
Self::line_height(&String::from_utf8_lossy(&buf), size.width)
} else {
1
@ -89,7 +89,7 @@ impl Highlighter {
}
Ok(if plain {
Text::from(replace_to_printable(&after, PREVIEW.tab_size))
Text::from(replace_to_printable(&after, YAZI.preview.tab_size))
} else {
Self::highlight_with(before, after, syntax.unwrap()).await?
})
@ -113,7 +113,7 @@ impl Highlighter {
h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
}
let indent = PREVIEW.indent();
let indent = YAZI.preview.indent();
let mut lines = Vec::with_capacity(after.len());
for line in after {
if ticket != INCR.current() {
@ -158,11 +158,12 @@ impl Highlighter {
}
fn line_height(s: &str, width: u16) -> usize {
if PREVIEW.wrap != PreviewWrap::Yes {
if YAZI.preview.wrap != PreviewWrap::Yes {
return 1;
}
let pad = PREVIEW
let pad = YAZI
.preview
.tab_size
.checked_sub(1)
.map(|n| s.bytes().filter(|&b| b == b'\t').count() * n as usize)

View file

@ -110,7 +110,7 @@ macro_rules! impl_file_methods {
Ok(match me.icon.get() {
IconCache::Missing => {
let matched = yazi_config::THEME.icons.matches(me);
let matched = yazi_config::THEME.icon.matches(me);
me.icon.set(matched.map_or(IconCache::Undefined, IconCache::Icon));
matched.map(Icon::from)
}

View file

@ -1,6 +1,6 @@
use mlua::{Function, Lua, Table};
use twox_hash::XxHash3_128;
use yazi_config::PREVIEW;
use yazi_config::YAZI;
use super::Utils;
use crate::{file::FileRef, url::Url};
@ -9,7 +9,7 @@ impl Utils {
pub(super) fn file_cache(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, t: Table| {
let file: FileRef = t.raw_get("file")?;
if file.url.parent() == Some(&PREVIEW.cache_dir) {
if file.url.parent() == Some(&YAZI.preview.cache_dir) {
return Ok(None);
}
@ -20,7 +20,7 @@ impl Utils {
format!("{:x}", h.finish_128())
};
Ok(Some(Url::from(PREVIEW.cache_dir.join(hex))))
Ok(Some(Url::from(YAZI.preview.cache_dir.join(hex))))
})
}
}

View file

@ -1,5 +1,5 @@
use mlua::{AnyUserData, Function, IntoLuaMulti, Lua, Table, Value};
use yazi_config::{PREVIEW, preview::PreviewWrap};
use yazi_config::{YAZI, preview::PreviewWrap};
use yazi_macro::emit;
use yazi_shared::{errors::PeekError, event::Cmd};
@ -51,7 +51,7 @@ impl Utils {
lock.data = vec![Renderable::Text(Text {
area,
inner,
wrap: if PREVIEW.wrap == PreviewWrap::Yes { WRAP } else { WRAP_NO },
wrap: if YAZI.preview.wrap == PreviewWrap::Yes { WRAP } else { WRAP_NO },
})];
emit!(Call(Cmd::new("mgr:update_peeked").with_any("lock", lock)));

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use yazi_config::open::Opener;
use yazi_config::opener::OpenerRule;
use yazi_shared::{event::CmdCow, url::Url};
// --- Open
@ -18,7 +18,7 @@ impl From<CmdCow> for OpenDoOpt {
// --- Open with
pub struct OpenWithOpt {
pub opener: Cow<'static, Opener>,
pub opener: Cow<'static, OpenerRule>,
pub cwd: Url,
pub targets: Vec<Url>,
}

View file

@ -1,13 +1,13 @@
use std::{borrow::Cow, ffi::OsString};
use tokio::sync::oneshot;
use yazi_config::open::Opener;
use yazi_config::opener::OpenerRule;
use yazi_shared::{event::CmdCow, url::Url};
// --- Exec
pub struct ProcessExecOpt {
pub cwd: Url,
pub opener: Cow<'static, Opener>,
pub opener: Cow<'static, OpenerRule>,
pub args: Vec<OsString>,
pub done: Option<oneshot::Sender<()>>,
}

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, ffi::OsString};
use tokio::sync::oneshot;
use yazi_config::open::Opener;
use yazi_config::opener::OpenerRule;
use yazi_macro::emit;
use yazi_shared::{event::Cmd, url::Url};
@ -11,7 +11,7 @@ pub struct TasksProxy;
impl TasksProxy {
#[inline]
pub fn open_with(opener: Cow<'static, Opener>, cwd: Url, targets: Vec<Url>) {
pub fn open_with(opener: Cow<'static, OpenerRule>, cwd: Url, targets: Vec<Url>) {
emit!(Call(Cmd::new("tasks:open_with").with_any("option", OpenWithOpt {
opener,
cwd,
@ -20,7 +20,7 @@ impl TasksProxy {
}
#[inline]
pub async fn process_exec(opener: Cow<'static, Opener>, cwd: Url, args: Vec<OsString>) {
pub async fn process_exec(opener: Cow<'static, OpenerRule>, cwd: Url, args: Vec<OsString>) {
let (tx, rx) = oneshot::channel();
emit!(Call(Cmd::new("tasks:process_exec").with_any("option", ProcessExecOpt {
cwd,

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::VecDeque, path::Path};
use anyhow::{Result, anyhow};
use tokio::{fs::{self, DirEntry}, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::TASKS;
use yazi_config::YAZI;
use yazi_fs::{calculate_size, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, skip_path};
use yazi_shared::url::Url;
@ -45,7 +45,7 @@ impl File {
// Operation not permitted (os error 1)
// Attribute not found (os error 93)
Err(e)
if task.retry < TASKS.bizarre_retry
if task.retry < YAZI.tasks.bizarre_retry
&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>
{
task.retry += 1;

View file

@ -1,7 +1,7 @@
use std::collections::HashMap;
use futures::future::BoxFuture;
use yazi_config::TASKS;
use yazi_config::YAZI;
use super::{Task, TaskStage};
use crate::TaskKind;
@ -32,7 +32,7 @@ impl Ongoing {
#[inline]
pub fn len(&self) -> usize {
if TASKS.suppress_preload {
if YAZI.tasks.suppress_preload {
self.all.values().filter(|t| t.kind != TaskKind::Preload).count()
} else {
self.all.len()
@ -44,7 +44,7 @@ impl Ongoing {
#[inline]
pub fn values(&self) -> Box<dyn Iterator<Item = &Task> + '_> {
if TASKS.suppress_preload {
if YAZI.tasks.suppress_preload {
Box::new(self.all.values().filter(|t| t.kind != TaskKind::Preload))
} else {
Box::new(self.all.values())

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use futures::{FutureExt, future::BoxFuture};
use parking_lot::Mutex;
use tokio::{fs, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle};
use yazi_config::{TASKS, plugin::{Fetcher, Preloader}};
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump;
use yazi_fs::{must_be_dir, remove_dir_clean, unique_name};
use yazi_proxy::{MgrProxy, options::{PluginOpt, ProcessExecOpt}};
@ -39,14 +39,16 @@ impl Scheduler {
micro: micro_tx,
prog: prog_tx,
handles: Vec::with_capacity(TASKS.micro_workers as usize + TASKS.macro_workers as usize + 1),
handles: Vec::with_capacity(
YAZI.tasks.micro_workers as usize + YAZI.tasks.macro_workers as usize + 1,
),
ongoing: Default::default(),
};
for _ in 0..TASKS.micro_workers {
for _ in 0..YAZI.tasks.micro_workers {
scheduler.handles.push(scheduler.schedule_micro(micro_rx.clone()));
}
for _ in 0..TASKS.macro_workers {
for _ in 0..YAZI.tasks.macro_workers {
scheduler.handles.push(scheduler.schedule_macro(micro_rx.clone(), macro_rx.clone()));
}
scheduler.progress(prog_rx);

View file

@ -4,47 +4,7 @@ use serde::{Deserialize, Serialize, Serializer, ser::SerializeMap};
use super::Color;
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[serde(from = "StyleShadow")]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub modifier: Modifier,
}
impl Serialize for Style {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("fg", &self.fg)?;
map.serialize_entry("bg", &self.bg)?;
map.serialize_entry("modifier", &self.modifier.bits())?;
map.end()
}
}
impl From<Style> for ratatui::style::Style {
fn from(value: Style) -> Self {
ratatui::style::Style {
fg: value.fg.map(Into::into),
bg: value.bg.map(Into::into),
underline_color: None,
add_modifier: value.modifier,
sub_modifier: Modifier::empty(),
}
}
}
impl Style {
#[inline]
pub fn derive(self, other: ratatui::style::Style) -> ratatui::style::Style {
ratatui::style::Style::from(self).patch(other)
}
}
#[derive(Default, Deserialize)]
pub struct StyleShadow {
#[serde(default)]
pub fg: Option<Color>,
#[serde(default)]
@ -69,8 +29,33 @@ pub struct StyleShadow {
pub crossed: bool,
}
impl From<StyleShadow> for Style {
fn from(value: StyleShadow) -> Self {
impl Serialize for Style {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("fg", &self.fg)?;
map.serialize_entry("bg", &self.bg)?;
map.serialize_entry("modifier", &Modifier::from(*self).bits())?;
map.end()
}
}
impl From<Style> for ratatui::style::Style {
fn from(value: Style) -> Self {
ratatui::style::Style {
fg: value.fg.map(Into::into),
bg: value.bg.map(Into::into),
underline_color: None,
add_modifier: value.into(),
sub_modifier: Modifier::empty(),
}
}
}
impl From<Style> for ratatui::style::Modifier {
fn from(value: Style) -> Self {
let mut modifier = Modifier::empty();
if value.bold {
modifier |= Modifier::BOLD;
@ -99,7 +84,13 @@ impl From<StyleShadow> for Style {
if value.crossed {
modifier |= Modifier::CROSSED_OUT;
}
Self { fg: value.fg, bg: value.bg, modifier }
modifier
}
}
impl Style {
#[inline]
pub fn derive(self, other: ratatui::style::Style) -> ratatui::style::Style {
ratatui::style::Style::from(self).patch(other)
}
}

View file

@ -2,7 +2,7 @@ use std::ops::Range;
use ratatui::crossterm::cursor::SetCursorStyle;
use unicode_width::UnicodeWidthStr;
use yazi_config::INPUT;
use yazi_config::YAZI;
use yazi_plugin::CLIPBOARD;
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
@ -90,12 +90,12 @@ impl Input {
use InputMode as M;
match self.mode() {
M::Normal if INPUT.cursor_blink => SetCursorStyle::BlinkingBlock,
M::Normal if !INPUT.cursor_blink => SetCursorStyle::SteadyBlock,
M::Insert if INPUT.cursor_blink => SetCursorStyle::BlinkingBar,
M::Insert if !INPUT.cursor_blink => SetCursorStyle::SteadyBar,
M::Replace if INPUT.cursor_blink => SetCursorStyle::BlinkingUnderScore,
M::Replace if !INPUT.cursor_blink => SetCursorStyle::SteadyUnderScore,
M::Normal if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBlock,
M::Normal if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBlock,
M::Insert if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBar,
M::Insert if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBar,
M::Replace if YAZI.input.cursor_blink => SetCursorStyle::BlinkingUnderScore,
M::Replace if !YAZI.input.cursor_blink => SetCursorStyle::SteadyUnderScore,
M::Normal | M::Insert | M::Replace => unreachable!(),
}
}