diff --git a/Cargo.lock b/Cargo.lock index 6af3be004..c33b9f4d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "ansi-to-tui" -version = "6.0.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd01ee70df708c9ecb68705af08cee6fe85493992a7e205c52ddbe569314ba47" +checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" dependencies = [ "nom", "ratatui", @@ -1137,6 +1137,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "inotify" version = "0.10.2" @@ -1909,23 +1915,23 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ "bitflags 2.6.0", "cassowary", "compact_str", "crossterm", + "indoc", "instability", "itertools 0.13.0", "lru", "paste", "strum", - "strum_macros", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f6924b2c8..ca3962ce7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ panic = "abort" strip = true [workspace.dependencies] -ansi-to-tui = "6.0.0" +ansi-to-tui = "7.0.0" anyhow = "1.0.91" arc-swap = "1.7.1" base64 = "0.22.1" @@ -24,7 +24,7 @@ libc = "0.2.161" md-5 = "0.10.6" mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } parking_lot = "0.12.3" -ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info" ] } +ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] } regex = "1.11.1" scopeguard = "1.2.0" serde = { version = "1.0.213", features = [ "derive" ] } diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index 2cf291ad8..7948e9685 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, str::FromStr}; +use anyhow::Context; use indexmap::IndexSet; use serde::{Deserialize, Deserializer}; use yazi_shared::Layer; @@ -20,7 +21,7 @@ pub struct Keymap { impl Keymap { #[inline] - pub fn get(&self, layer: Layer) -> &Vec { + pub fn get(&self, layer: Layer) -> &[Chord] { match layer { Layer::App => unreachable!(), Layer::Manager => &self.manager, @@ -36,9 +37,11 @@ impl Keymap { } impl FromStr for Keymap { - type Err = toml::de::Error; + type Err = anyhow::Error; - fn from_str(s: &str) -> Result { toml::from_str(s) } + fn from_str(s: &str) -> Result { + toml::from_str(s).context("Failed to parse your keymap.toml") + } } impl<'de> Deserialize<'de> for Keymap { diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 7c09c86d8..e039b965a 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -24,25 +24,25 @@ pub static PICK: RoCell = RoCell::new(); pub static WHICH: RoCell = RoCell::new(); pub fn init() -> anyhow::Result<()> { - let config_dir = Xdg::config_dir(); - let yazi_toml = &Preset::yazi(&config_dir)?; - let keymap_toml = &Preset::keymap(&config_dir)?; - let theme_toml = &Preset::theme(&config_dir)?; + if let Err(e) = try_init(true) { + eprintln!("{e}"); + if let Some(src) = e.source() { + eprintln!("\nCaused by:\n{src}"); + } - LAYOUT.with(<_>::default); + use crossterm::style::{Attribute, Print, SetAttributes}; + crossterm::execute!( + std::io::stderr(), + SetAttributes(Attribute::Reverse.into()), + SetAttributes(Attribute::Bold.into()), + Print("Press to continue with preset settings..."), + SetAttributes(Attribute::Reset.into()), + Print("\n"), + )?; - KEYMAP.init(<_>::from_str(keymap_toml)?); - LOG.init(<_>::from_str(yazi_toml)?); - MANAGER.init(<_>::from_str(yazi_toml)?); - OPEN.init(<_>::from_str(yazi_toml)?); - PLUGIN.init(<_>::from_str(yazi_toml)?); - PREVIEW.init(<_>::from_str(yazi_toml)?); - TASKS.init(<_>::from_str(yazi_toml)?); - THEME.init(<_>::from_str(theme_toml)?); - INPUT.init(<_>::from_str(yazi_toml)?); - CONFIRM.init(<_>::from_str(yazi_toml)?); - PICK.init(<_>::from_str(yazi_toml)?); - WHICH.init(<_>::from_str(yazi_toml)?); + std::io::stdin().read_line(&mut String::new())?; + try_init(false)?; + } // TODO: Remove in v0.3.2 for c in &KEYMAP.manager { @@ -78,3 +78,43 @@ Please change `create_title = "Create:"` to `create_title = ["Create:", "Create Ok(()) } + +fn try_init(merge: bool) -> anyhow::Result<()> { + let (yazi_toml, keymap_toml, theme_toml) = if merge { + let p = Xdg::config_dir(); + (Preset::yazi(&p)?, Preset::keymap(&p)?, Preset::theme(&p)?) + } else { + use yazi_macro::config_preset as preset; + (preset!("yazi"), preset!("keymap"), preset!("theme")) + }; + + let keymap = <_>::from_str(&keymap_toml)?; + let log = <_>::from_str(&yazi_toml)?; + let manager = <_>::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 theme = <_>::from_str(&theme_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)?; + + LAYOUT.with(<_>::default); + + KEYMAP.init(keymap); + LOG.init(log); + MANAGER.init(manager); + OPEN.init(open); + PLUGIN.init(plugin); + PREVIEW.init(preview); + TASKS.init(tasks); + THEME.init(theme); + INPUT.init(input); + CONFIRM.init(confirm); + PICK.init(pick); + WHICH.init(which); + + Ok(()) +} diff --git a/yazi-config/src/log/log.rs b/yazi-config/src/log/log.rs index c92cf6040..d5defe0ca 100644 --- a/yazi-config/src/log/log.rs +++ b/yazi-config/src/log/log.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::{Deserialize, Deserializer}; #[derive(Debug)] @@ -8,9 +9,11 @@ pub struct Log { } impl FromStr for Log { - type Err = toml::de::Error; + type Err = anyhow::Error; - fn from_str(s: &str) -> Result { toml::from_str(s) } + fn from_str(s: &str) -> Result { + toml::from_str(s).context("Failed to parse the [log] section in your yazi.toml") + } } impl<'de> Deserialize<'de> for Log { diff --git a/yazi-config/src/manager/manager.rs b/yazi-config/src/manager/manager.rs index 8e48bbde1..78efa5c3d 100644 --- a/yazi-config/src/manager/manager.rs +++ b/yazi-config/src/manager/manager.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::{Deserialize, Serialize}; use validator::Validate; @@ -35,9 +36,10 @@ impl FromStr for Manager { manager: Manager, } - let manager = toml::from_str::(s)?.manager; - manager.validate()?; + let outer = toml::from_str::(s) + .context("Failed to parse the [manager] section in your yazi.toml")?; + outer.manager.validate()?; - Ok(manager) + Ok(outer.manager) } } diff --git a/yazi-config/src/open/open.rs b/yazi-config/src/open/open.rs index 603618185..a9e2d0e6a 100644 --- a/yazi-config/src/open/open.rs +++ b/yazi-config/src/open/open.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, path::Path, str::FromStr}; +use anyhow::Context; use indexmap::IndexSet; use serde::{Deserialize, Deserializer}; use yazi_shared::MIME_DIR; @@ -55,9 +56,11 @@ impl Open { } impl FromStr for Open { - type Err = toml::de::Error; + type Err = anyhow::Error; - fn from_str(s: &str) -> Result { toml::from_str(s) } + fn from_str(s: &str) -> Result { + toml::from_str(s).context("Failed to parse the [open] or [opener] section in your yazi.toml") + } } impl<'de> Deserialize<'de> for Open { diff --git a/yazi-config/src/plugin/plugin.rs b/yazi-config/src/plugin/plugin.rs index ba274011b..4cd84eeeb 100644 --- a/yazi-config/src/plugin/plugin.rs +++ b/yazi-config/src/plugin/plugin.rs @@ -1,11 +1,11 @@ use std::{collections::HashSet, path::Path, str::FromStr}; -use serde::Deserialize; +use anyhow::Context; +use serde::{Deserialize, Deserializer}; use super::{Fetcher, Preloader, Previewer}; use crate::{Preset, plugin::MAX_PREWORKERS}; -#[derive(Deserialize)] pub struct Plugin { pub fetchers: Vec, pub preloaders: Vec, @@ -55,9 +55,18 @@ impl Plugin { } impl FromStr for Plugin { - type Err = toml::de::Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { + toml::from_str(s).context("Failed to parse the [plugin] section in your yazi.toml") + } +} + +impl<'de> Deserialize<'de> for Plugin { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { #[derive(Deserialize)] struct Outer { plugin: Shadow, @@ -84,7 +93,7 @@ impl FromStr for Plugin { append_previewers: Vec, } - let mut shadow = toml::from_str::(s)?.plugin; + let mut shadow = Outer::deserialize(deserializer)?.plugin; if shadow.append_previewers.iter().any(|r| r.any_file()) { shadow.previewers.retain(|r| !r.any_file()); } diff --git a/yazi-config/src/popup/confirm.rs b/yazi-config/src/popup/confirm.rs index b55686f78..e4cca76d2 100644 --- a/yazi-config/src/popup/confirm.rs +++ b/yazi-config/src/popup/confirm.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::Deserialize; use super::{Offset, Origin}; @@ -30,7 +31,7 @@ pub struct Confirm { } impl FromStr for Confirm { - type Err = toml::de::Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { #[derive(Deserialize)] @@ -38,7 +39,10 @@ impl FromStr for Confirm { confirm: Confirm, } - Ok(toml::from_str::(s)?.confirm) + let outer = toml::from_str::(s) + .context("Failed to parse the [confirm] section in your yazi.toml")?; + + Ok(outer.confirm) } } diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index f0f351664..1cba01f09 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::Deserialize; use super::{Offset, Origin}; @@ -49,7 +50,7 @@ impl Input { } impl FromStr for Input { - type Err = toml::de::Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { #[derive(Deserialize)] @@ -57,7 +58,10 @@ impl FromStr for Input { input: Input, } - Ok(toml::from_str::(s)?.input) + let outer = toml::from_str::(s) + .context("Failed to parse the [input] section in your yazi.toml")?; + + Ok(outer.input) } } diff --git a/yazi-config/src/popup/pick.rs b/yazi-config/src/popup/pick.rs index ea2218c3d..786fb8a84 100644 --- a/yazi-config/src/popup/pick.rs +++ b/yazi-config/src/popup/pick.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::Deserialize; use super::{Offset, Origin}; @@ -17,7 +18,7 @@ impl Pick { } impl FromStr for Pick { - type Err = toml::de::Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { #[derive(Deserialize)] @@ -25,6 +26,9 @@ impl FromStr for Pick { pick: Pick, } - Ok(toml::from_str::(s)?.pick) + let outer = + toml::from_str::(s).context("Failed to parse the [pick] section in your yazi.toml")?; + + Ok(outer.pick) } } diff --git a/yazi-config/src/preset.rs b/yazi-config/src/preset.rs index 02cf80468..d52a3bacb 100644 --- a/yazi-config/src/preset.rs +++ b/yazi-config/src/preset.rs @@ -9,15 +9,15 @@ use crate::theme::Flavor; pub(crate) struct Preset; impl Preset { - pub(crate) fn yazi(p: &Path) -> Result> { + pub(crate) fn yazi(p: &Path) -> Result> { Self::merge_path(p.join("yazi.toml"), preset!("yazi")) } - pub(crate) fn keymap(p: &Path) -> Result> { + pub(crate) fn keymap(p: &Path) -> Result> { Self::merge_path(p.join("keymap.toml"), preset!("keymap")) } - pub(crate) fn theme(p: &Path) -> Result> { + pub(crate) fn theme(p: &Path) -> Result> { let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else { return Ok(preset!("theme")); }; diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index 8e9f3f8a2..f6a48264c 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, path::PathBuf, str::FromStr, time::{SystemTime, UNIX_EPOCH}}; use anyhow::Context; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use validator::Validate; use yazi_shared::fs::expand_path; @@ -49,6 +49,20 @@ impl FromStr for Preview { type Err = anyhow::Error; fn from_str(s: &str) -> Result { + 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(deserializer: D) -> Result + where + D: Deserializer<'de>, + { #[derive(Deserialize)] struct Outer { preview: Shadow, @@ -74,27 +88,26 @@ impl FromStr for Preview { ueberzug_offset: (f32, f32, f32, f32), } - let preview = toml::from_str::(s)?.preview; - preview.validate()?; - - let cache_dir = - preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path); - std::fs::create_dir_all(&cache_dir).context("Failed to create cache directory")?; + let preview = Outer::deserialize(deserializer)?.preview; + preview.validate().map_err(serde::de::Error::custom)?; Ok(Preview { - wrap: preview.wrap, - tab_size: preview.tab_size, - max_width: preview.max_width, + wrap: preview.wrap, + tab_size: preview.tab_size, + max_width: preview.max_width, max_height: preview.max_height, - cache_dir, + 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, + 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_scale: preview.ueberzug_scale, ueberzug_offset: preview.ueberzug_offset, }) } diff --git a/yazi-config/src/tasks/tasks.rs b/yazi-config/src/tasks/tasks.rs index e922d3598..b401e0758 100644 --- a/yazi-config/src/tasks/tasks.rs +++ b/yazi-config/src/tasks/tasks.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::Deserialize; use validator::Validate; @@ -27,9 +28,10 @@ impl FromStr for Tasks { tasks: Tasks, } - let tasks = toml::from_str::(s)?.tasks; - tasks.validate()?; + let outer = toml::from_str::(s) + .context("Failed to parse the [tasks] section in your yazi.toml")?; + outer.tasks.validate()?; - Ok(tasks) + Ok(outer.tasks) } } diff --git a/yazi-config/src/theme/theme.rs b/yazi-config/src/theme/theme.rs index 9227e8c26..7712b102a 100644 --- a/yazi-config/src/theme/theme.rs +++ b/yazi-config/src/theme/theme.rs @@ -1,5 +1,6 @@ use std::{path::PathBuf, str::FromStr}; +use anyhow::Context; use serde::{Deserialize, Serialize}; use validator::Validate; use yazi_shared::{Xdg, fs::expand_path, theme::Style}; @@ -31,7 +32,7 @@ impl FromStr for Theme { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - let mut theme: Self = toml::from_str(s)?; + let mut theme: Self = toml::from_str(s).context("Failed to parse your yazi.toml")?; theme.manager.validate()?; theme.which.validate()?; diff --git a/yazi-config/src/which/which.rs b/yazi-config/src/which/which.rs index 205a2bc49..8a488d3f3 100644 --- a/yazi-config/src/which/which.rs +++ b/yazi-config/src/which/which.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use anyhow::Context; use serde::{Deserialize, Serialize}; use validator::Validate; @@ -23,6 +24,9 @@ impl FromStr for Which { which: Which, } - Ok(toml::from_str::(s)?.which) + let outer = toml::from_str::(s) + .context("Failed to parse the [which] section in your yazi.toml")?; + + Ok(outer.which) } } diff --git a/yazi-plugin/preset/plugins/extract.lua b/yazi-plugin/preset/plugins/extract.lua index 6528221ad..96770870c 100644 --- a/yazi-plugin/preset/plugins/extract.lua +++ b/yazi-plugin/preset/plugins/extract.lua @@ -105,7 +105,7 @@ function M:tidy(from, to, tmp) fs.remove("dir", tmp) end -function M.tmp_name(url) return ".tmp_" .. ya.md5(string.format("extract//%s//%.10f", url, ya.time())) end +function M.tmp_name(url) return ".tmp_" .. ya.hash(string.format("extract//%s//%.10f", url, ya.time())) end function M.trim_ext(name) -- stylua: ignore diff --git a/yazi-plugin/src/elements/table.rs b/yazi-plugin/src/elements/table.rs index ab3016ed6..5c62bc8e1 100644 --- a/yazi-plugin/src/elements/table.rs +++ b/yazi-plugin/src/elements/table.rs @@ -9,17 +9,17 @@ use crate::elements::Constraint; pub struct Table { area: Rect, - rows: Vec>, - header: Option>, - footer: Option>, - widths: Vec, - column_spacing: u16, - block: Option>, - style: ratatui::style::Style, - highlight_style: ratatui::style::Style, - highlight_symbol: ratatui::text::Text<'static>, - highlight_spacing: ratatui::widgets::HighlightSpacing, - flex: ratatui::layout::Flex, + rows: Vec>, + header: Option>, + footer: Option>, + widths: Vec, + column_spacing: u16, + block: Option>, + style: ratatui::style::Style, + row_highlight_style: ratatui::style::Style, + highlight_symbol: ratatui::text::Text<'static>, + highlight_spacing: ratatui::widgets::HighlightSpacing, + flex: ratatui::layout::Flex, state: ratatui::widgets::TableState, } @@ -61,7 +61,7 @@ impl Renderable for Table { let mut table = ratatui::widgets::Table::new(self.rows, self.widths) .column_spacing(self.column_spacing) .style(self.style) - .highlight_style(self.highlight_style) + .row_highlight_style(self.row_highlight_style) .highlight_symbol(self.highlight_symbol) .highlight_spacing(self.highlight_spacing) .flex(self.flex); diff --git a/yazi-plugin/src/utils/text.rs b/yazi-plugin/src/utils/text.rs index 9ac04f74a..83ce73f2e 100644 --- a/yazi-plugin/src/utils/text.rs +++ b/yazi-plugin/src/utils/text.rs @@ -9,6 +9,7 @@ use crate::CLIPBOARD; impl Utils { pub(super) fn text(lua: &Lua, ya: &Table) -> mlua::Result<()> { + // TODO: deprecate this in the future ya.raw_set( "md5", lua.create_async_function(|_, s: mlua::String| async move { @@ -16,6 +17,13 @@ impl Utils { })?, )?; + ya.raw_set( + "hash", + lua.create_async_function(|_, s: mlua::String| async move { + Ok(format!("{:x}", Md5::new_with_prefix(s.as_bytes()).finalize())) + })?, + )?; + ya.raw_set( "quote", lua.create_function(|_, (s, unix): (mlua::String, Option)| {