diff --git a/src/config/keymap/exec.rs b/src/config/keymap/exec.rs index b2499e71..cdde7eb5 100644 --- a/src/config/keymap/exec.rs +++ b/src/config/keymap/exec.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, fmt}; -use serde::{de::Visitor, Deserializer}; +use serde::{de::{self, Visitor}, Deserializer}; #[derive(Debug, Default)] pub struct Exec { @@ -44,7 +44,7 @@ impl Exec { fn visit_seq(self, mut seq: A) -> Result where - A: serde::de::SeqAccess<'de>, + A: de::SeqAccess<'de>, { let mut execs = Vec::new(); while let Some(value) = &seq.next_element::()? { @@ -55,7 +55,7 @@ impl Exec { fn visit_str(self, value: &str) -> Result where - E: serde::de::Error, + E: de::Error, { Ok(value.split(';').map(Exec::from).collect()) } diff --git a/src/config/keymap/key.rs b/src/config/keymap/key.rs index d6d39e13..48b0f977 100644 --- a/src/config/keymap/key.rs +++ b/src/config/keymap/key.rs @@ -1,7 +1,9 @@ +use anyhow::bail; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use serde::{de::Visitor, Deserialize, Deserializer}; +use serde::Deserialize; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Hash)] +#[serde(try_from = "String")] pub struct Key { pub code: KeyCode, pub shift: bool, @@ -13,21 +15,36 @@ impl Default for Key { fn default() -> Self { Self { code: KeyCode::Null, shift: false, ctrl: false, alt: false } } } -impl From<&str> for Key { - fn from(value: &str) -> Self { - let mut key = Default::default(); - if value.is_empty() { - return key; +impl From for Key { + fn from(value: KeyEvent) -> Self { + let shift = if let KeyCode::Char(c) = value.code { c.is_ascii_uppercase() } else { false }; + + Self { + code: value.code, + shift: shift || value.modifiers.contains(KeyModifiers::SHIFT), + ctrl: value.modifiers.contains(KeyModifiers::CONTROL), + alt: value.modifiers.contains(KeyModifiers::ALT), + } + } +} + +impl TryFrom for Key { + type Error = anyhow::Error; + + fn try_from(s: String) -> Result { + if s.is_empty() { + bail!("empty key") } - if !value.starts_with("<") || !value.ends_with(">") { - let c = value.chars().next().unwrap(); + let mut key = Self::default(); + if !s.starts_with("<") || !s.ends_with(">") { + let c = s.chars().next().unwrap(); key.code = KeyCode::Char(c); key.shift = c.is_ascii_uppercase(); - return key; + return Ok(key); } - let mut it = value[1..value.len() - 1].split_inclusive('-').peekable(); + let mut it = s[1..s.len() - 1].split_inclusive('-').peekable(); while let Some(x) = it.next() { match x { "S-" => key.shift = true, @@ -65,48 +82,13 @@ impl From<&str> for Key { c if it.peek().is_none() => { key.code = KeyCode::Char(c.chars().next().unwrap()); } - _ => {} - } - } - key - } -} - -impl From for Key { - fn from(value: KeyEvent) -> Self { - let shift = if let KeyCode::Char(c) = value.code { c.is_ascii_uppercase() } else { false }; - - Self { - code: value.code, - shift: shift || value.modifiers.contains(KeyModifiers::SHIFT), - ctrl: value.modifiers.contains(KeyModifiers::CONTROL), - alt: value.modifiers.contains(KeyModifiers::ALT), - } - } -} - -impl<'de> Deserialize<'de> for Key { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct KeyVisitor; - - impl<'de> Visitor<'de> for KeyVisitor { - type Value = Key; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a key string, e.g. ") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(Key::from(value)) + k => bail!("unknown key: {}", k), } } - deserializer.deserialize_str(KeyVisitor) + if key.code == KeyCode::Null { + bail!("empty key") + } + Ok(key) } } diff --git a/src/config/keymap/keymap.rs b/src/config/keymap/keymap.rs index 88c6a0dc..13f7a789 100644 --- a/src/config/keymap/keymap.rs +++ b/src/config/keymap/keymap.rs @@ -1,41 +1,52 @@ use std::fs; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use xdg::BaseDirectories; use super::{Exec, Key}; -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Single { pub on: Vec, #[serde(deserialize_with = "Exec::deserialize")] pub exec: Vec, } -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct Keymap { pub manager: Vec, pub tasks: Vec, pub input: Vec, } -impl Keymap { - pub fn new() -> Self { +impl<'de> Deserialize<'de> for Keymap { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Shadow { + manager: Inner, + tasks: Inner, + input: Inner, + } #[derive(Deserialize)] struct Inner { keymap: Vec, } - #[derive(Deserialize)] - struct All { - manager: Inner, - tasks: Inner, - input: Inner, - } - - let path = BaseDirectories::new().unwrap().get_config_file("yazi/keymap.toml"); - - let all: All = toml::from_str(&fs::read_to_string(path).unwrap()).unwrap(); - Self { manager: all.manager.keymap, tasks: all.tasks.keymap, input: all.input.keymap } + let shadow = Shadow::deserialize(deserializer)?; + Ok(Self { + manager: shadow.manager.keymap, + tasks: shadow.tasks.keymap, + input: shadow.input.keymap, + }) + } +} + +impl Keymap { + pub fn new() -> Self { + let path = BaseDirectories::new().unwrap().get_config_file("yazi/keymap.toml"); + toml::from_str(&fs::read_to_string(path).unwrap()).unwrap() } } diff --git a/src/config/manager/manager.rs b/src/config/manager/manager.rs index 3a7faee3..8daea78a 100644 --- a/src/config/manager/manager.rs +++ b/src/config/manager/manager.rs @@ -5,7 +5,7 @@ use xdg::BaseDirectories; use super::SortBy; -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Manager { #[serde(skip)] pub cwd: PathBuf, diff --git a/src/config/manager/sorting.rs b/src/config/manager/sorting.rs index b86d077d..c8c3aea7 100644 --- a/src/config/manager/sorting.rs +++ b/src/config/manager/sorting.rs @@ -1,6 +1,8 @@ -use serde::{de::Visitor, Deserialize, Deserializer}; +use anyhow::bail; +use serde::Deserialize; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Deserialize, Clone, Copy)] +#[serde(try_from = "String")] pub enum SortBy { Alphabetical, Created, @@ -8,39 +10,16 @@ pub enum SortBy { Size, } -impl From<&str> for SortBy { - fn from(value: &str) -> Self { - match value { +impl TryFrom for SortBy { + type Error = anyhow::Error; + + fn try_from(s: String) -> Result { + Ok(match s.as_str() { "created" => Self::Created, "modified" => Self::Modified, "size" => Self::Size, - _ => Self::Alphabetical, - } - } -} - -impl<'de> Deserialize<'de> for SortBy { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct SortByVisitor; - - impl<'de> Visitor<'de> for SortByVisitor { - type Value = SortBy; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a sort_by string, e.g. modified") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(SortBy::from(value)) - } - } - - deserializer.deserialize_str(SortByVisitor) + "alphabetical" => Self::Alphabetical, + _ => bail!("invalid sort_by value: {}", s), + }) } } diff --git a/src/config/open/mod.rs b/src/config/open/mod.rs index dd44d4a9..5aea8dfe 100644 --- a/src/config/open/mod.rs +++ b/src/config/open/mod.rs @@ -1,3 +1,5 @@ mod open; +mod opener; pub use open::*; +pub use opener::*; diff --git a/src/config/open/open.rs b/src/config/open/open.rs index ad8d8bf1..53721f60 100644 --- a/src/config/open/open.rs +++ b/src/config/open/open.rs @@ -1,29 +1,18 @@ use std::{collections::BTreeMap, fs, path::Path}; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use xdg::BaseDirectories; +use super::Opener; use crate::config::Pattern; -#[derive(Clone, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Opener { - pub cmd: String, - pub args: Vec, - #[serde(default)] - pub block: bool, - #[serde(skip)] - pub spread: bool, -} - -#[derive(Deserialize, Debug)] +#[derive(Debug)] pub struct Open { - #[serde(skip)] openers: BTreeMap>, - - rules: Vec, + rules: Vec, } -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] struct OpenRule { name: Option, mime: Option, @@ -32,23 +21,9 @@ struct OpenRule { } impl Open { - pub fn new() -> Open { - #[derive(Deserialize)] - struct Outer { - opener: BTreeMap>, - open: Open, - } - + pub fn new() -> Self { let path = BaseDirectories::new().unwrap().get_config_file("yazi/yazi.toml"); - let mut outer = toml::from_str::(&fs::read_to_string(path).unwrap()).unwrap(); - - for opener in outer.opener.values_mut() { - for one in opener.iter_mut() { - one.spread = one.args.iter().any(|a| a == "$*"); - } - } - - Self { openers: outer.opener, rules: outer.open.rules } + toml::from_str(&fs::read_to_string(path).unwrap()).unwrap() } pub fn opener(&self, path: &Path, mime: &str) -> Option<&Opener> { @@ -63,3 +38,23 @@ impl Open { }) } } + +impl<'de> Deserialize<'de> for Open { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Outer { + opener: BTreeMap>, + open: OuterOpen, + } + #[derive(Deserialize)] + struct OuterOpen { + rules: Vec, + } + + let outer = Outer::deserialize(deserializer)?; + Ok(Self { openers: outer.opener, rules: outer.open.rules }) + } +} diff --git a/src/config/open/opener.rs b/src/config/open/opener.rs new file mode 100644 index 00000000..1c4ef77d --- /dev/null +++ b/src/config/open/opener.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Deserializer}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Opener { + pub cmd: String, + pub args: Vec, + pub block: bool, + pub spread: bool, +} + +impl<'de> Deserialize<'de> for Opener { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + pub struct Shadow { + pub cmd: String, + pub args: Vec, + #[serde(default)] + pub block: bool, + #[serde(skip)] + pub spread: bool, + } + + let shadow = Shadow::deserialize(deserializer)?; + + let spread = shadow.args.contains(&"$*".to_string()); + Ok(Self { cmd: shadow.cmd, args: shadow.args, block: shadow.block, spread }) + } +} diff --git a/src/config/pattern.rs b/src/config/pattern.rs index b2d53973..49d2871a 100644 --- a/src/config/pattern.rs +++ b/src/config/pattern.rs @@ -1,8 +1,9 @@ use std::path::Path; -use serde::{de::Visitor, Deserialize, Deserializer}; +use serde::Deserialize; -#[derive(Debug)] +#[derive(Debug, Deserialize)] +#[serde(try_from = "String")] pub struct Pattern { inner: glob::Pattern, is_folder: bool, @@ -16,35 +17,17 @@ impl Pattern { } } -impl From<&str> for Pattern { - fn from(value: &str) -> Self { - let is_folder = value.ends_with('/'); - Self { inner: glob::Pattern::new(value.trim_end_matches('/')).unwrap_or_default(), is_folder } +impl TryFrom<&str> for Pattern { + type Error = anyhow::Error; + + fn try_from(s: &str) -> Result { + let is_folder = s.ends_with('/'); + Ok(Self { inner: glob::Pattern::new(s.trim_end_matches('/'))?, is_folder }) } } -impl<'de> Deserialize<'de> for Pattern { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct PatternVisitor; +impl TryFrom for Pattern { + type Error = anyhow::Error; - impl<'de> Visitor<'de> for PatternVisitor { - type Value = Pattern; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a glob pattern, e.g. *.rs") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(Pattern::from(value)) - } - } - - deserializer.deserialize_str(PatternVisitor) - } + fn try_from(s: String) -> Result { Self::try_from(s.as_str()) } } diff --git a/src/config/preview/preview.rs b/src/config/preview/preview.rs index 0e24525a..9a5de03d 100644 --- a/src/config/preview/preview.rs +++ b/src/config/preview/preview.rs @@ -3,13 +3,7 @@ use std::fs; use serde::Deserialize; use xdg::BaseDirectories; -#[derive(Deserialize, Debug)] -pub struct Manager { - pub sort_by: String, - pub sort_reverse: bool, -} - -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Preview { pub tab_size: u32, @@ -25,6 +19,7 @@ impl Preview { } let path = BaseDirectories::new().unwrap().get_config_file("yazi/yazi.toml"); - toml::from_str::(&fs::read_to_string(path).unwrap()).unwrap().preview + let outer: Outer = toml::from_str(&fs::read_to_string(path).unwrap()).unwrap(); + outer.preview } } diff --git a/src/config/theme/color.rs b/src/config/theme/color.rs index 70f00081..f2556b7b 100644 --- a/src/config/theme/color.rs +++ b/src/config/theme/color.rs @@ -1,39 +1,42 @@ -use anyhow::Result; +use std::ops::Deref; + +use anyhow::{bail, Result}; use ratatui::style; use serde::Deserialize; #[derive(Deserialize)] -pub struct Color { - pub fg: String, - pub bg: String, +#[serde(try_from = "String")] +pub struct Color(style::Color); + +impl Default for Color { + fn default() -> Self { Self(style::Color::Reset) } } -impl Color { - pub fn fg_rgb(&self) -> style::Color { - if self.fg.len() < 7 { - return style::Color::Reset; - } - let convert = || -> Result { - Ok(style::Color::Rgb( - u8::from_str_radix(&self.fg[1..3], 16)?, - u8::from_str_radix(&self.fg[3..5], 16)?, - u8::from_str_radix(&self.fg[5..7], 16)?, - )) - }; - convert().unwrap_or(style::Color::Reset) - } +impl TryFrom for Color { + type Error = anyhow::Error; - pub fn bg_rgb(&self) -> style::Color { - if self.bg.len() < 7 { - return style::Color::Reset; + fn try_from(s: String) -> Result { + if s.len() < 7 { + bail!("Invalid color: {}", s); } - let convert = || -> Result { - Ok(style::Color::Rgb( - u8::from_str_radix(&self.bg[1..3], 16)?, - u8::from_str_radix(&self.bg[3..5], 16)?, - u8::from_str_radix(&self.bg[5..7], 16)?, - )) - }; - convert().unwrap_or(style::Color::Reset) + Ok(Self(style::Color::Rgb( + u8::from_str_radix(&s[1..3], 16)?, + u8::from_str_radix(&s[3..5], 16)?, + u8::from_str_radix(&s[5..7], 16)?, + ))) } } + +impl Deref for Color { + type Target = style::Color; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +#[derive(Deserialize)] +pub struct ColorDual { + #[serde(default)] + pub fg: Color, + #[serde(default)] + pub bg: Color, +} diff --git a/src/config/theme/filetype.rs b/src/config/theme/filetype.rs new file mode 100644 index 00000000..3a40e054 --- /dev/null +++ b/src/config/theme/filetype.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Deserializer}; + +use crate::config::Pattern; + +#[derive(Debug, Deserialize)] +pub struct Filetype { + pub name: Option, + pub mime: Option, + #[serde(default)] + pub bg: String, + #[serde(default)] + pub fg: String, + #[serde(default)] + pub bold: bool, +} + +impl Filetype { + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct FiletypeOuter { + rules: Vec, + } + + Ok(FiletypeOuter::deserialize(deserializer)?.rules) + } +} diff --git a/src/config/theme/icon.rs b/src/config/theme/icon.rs index dbb73521..5a3792ae 100644 --- a/src/config/theme/icon.rs +++ b/src/config/theme/icon.rs @@ -1,6 +1,6 @@ use std::fmt; -use serde::{de::Visitor, Deserializer}; +use serde::{de::{self, Visitor}, Deserializer}; use crate::config::Pattern; @@ -11,10 +11,6 @@ pub struct Icon { } impl Icon { - pub fn new(name: String, display: String) -> Self { - Self { name: Pattern::from(name.as_ref()), display } - } - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -30,11 +26,15 @@ impl Icon { fn visit_map(self, mut map: A) -> Result where - A: serde::de::MapAccess<'de>, + A: de::MapAccess<'de>, { let mut icons = Vec::new(); while let Some((key, value)) = &map.next_entry::()? { - icons.push(Icon::new(key.clone(), value.clone())); + icons.push(Icon { + name: Pattern::try_from(key.clone()) + .map_err(|e| de::Error::custom(e.to_string()))?, + display: value.clone(), + }); } Ok(icons) } diff --git a/src/config/theme/mod.rs b/src/config/theme/mod.rs index 7157e8d2..9989b048 100644 --- a/src/config/theme/mod.rs +++ b/src/config/theme/mod.rs @@ -1,7 +1,9 @@ mod color; +mod filetype; mod icon; mod theme; pub use color::*; +pub use filetype::*; pub use icon::*; pub use theme::*; diff --git a/src/config/theme/theme.rs b/src/config/theme/theme.rs index 228b2851..0523a4e8 100644 --- a/src/config/theme/theme.rs +++ b/src/config/theme/theme.rs @@ -3,32 +3,29 @@ use std::{fs, path::PathBuf}; use serde::Deserialize; use xdg::BaseDirectories; -use super::{Color, Icon}; +use super::{ColorDual, Filetype, Icon}; use crate::misc::absolute_path; #[derive(Deserialize)] pub struct Mode { - pub normal: Color, - pub select: Color, - pub unselect: Color, + pub normal: ColorDual, + pub select: ColorDual, + pub unselect: ColorDual, } #[derive(Deserialize)] pub struct Tab { - pub active: Color, - pub inactive: Color, + pub active: ColorDual, + pub inactive: ColorDual, } #[derive(Deserialize)] pub struct Selection { - pub normal: Color, - pub hovered: Color, - pub selected: Color, + pub normal: ColorDual, + pub hovered: ColorDual, + pub selected: ColorDual, } -#[derive(Deserialize)] -pub struct Filetype {} - #[derive(Deserialize)] pub struct Syntect { pub theme: PathBuf, @@ -39,10 +36,11 @@ pub struct Theme { pub mode: Mode, pub tab: Tab, pub selection: Selection, - pub filetype: Filetype, - pub syntect: Syntect, + #[serde(deserialize_with = "Filetype::deserialize")] + pub filetypes: Vec, #[serde(deserialize_with = "Icon::deserialize")] pub icons: Vec, + pub syntect: Syntect, } impl Theme { diff --git a/src/core/manager/mode.rs b/src/core/manager/mode.rs index 556217be..63382102 100644 --- a/src/core/manager/mode.rs +++ b/src/core/manager/mode.rs @@ -12,7 +12,7 @@ pub enum Mode { impl Mode { #[inline] - pub fn color(&self) -> &theme::Color { + pub fn color(&self) -> &theme::ColorDual { match *self { Mode::Normal => &THEME.mode.normal, Mode::Select(_) => &THEME.mode.select, diff --git a/src/ui/status/layout.rs b/src/ui/status/layout.rs index 3d1735fd..ee15d201 100644 --- a/src/ui/status/layout.rs +++ b/src/ui/status/layout.rs @@ -29,19 +29,16 @@ impl<'a> Widget for Layout<'a> { ) .split(area); - Paragraph::new("").style(Style::default().fg(mode.color().bg_rgb())).render(chunks[0], buf); + Paragraph::new("").style(Style::default().fg(*mode.color().bg)).render(chunks[0], buf); Paragraph::new(format!(" {} ", mode)) .style( - Style::default() - .fg(mode.color().fg_rgb()) - .bg(mode.color().bg_rgb()) - .add_modifier(Modifier::BOLD), + Style::default().fg(*mode.color().fg).bg(*mode.color().bg).add_modifier(Modifier::BOLD), ) .render(chunks[1], buf); Paragraph::new(" master ") - .style(Style::default().fg(mode.color().bg_rgb()).bg(Color::Rgb(72, 77, 102))) + .style(Style::default().fg(*mode.color().bg).bg(Color::Rgb(72, 77, 102))) .render(chunks[2], buf); Paragraph::new("").style(Style::default().fg(Color::Rgb(72, 77, 102))).render(chunks[3], buf);