refactor: config

This commit is contained in:
sxyazi 2023-07-14 19:34:24 +08:00
parent 43ec2ada0b
commit 19fcc78a12
No known key found for this signature in database
17 changed files with 237 additions and 230 deletions

View file

@ -1,6 +1,6 @@
use std::{collections::BTreeMap, fmt}; use std::{collections::BTreeMap, fmt};
use serde::{de::Visitor, Deserializer}; use serde::{de::{self, Visitor}, Deserializer};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Exec { pub struct Exec {
@ -44,7 +44,7 @@ impl Exec {
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where where
A: serde::de::SeqAccess<'de>, A: de::SeqAccess<'de>,
{ {
let mut execs = Vec::new(); let mut execs = Vec::new();
while let Some(value) = &seq.next_element::<String>()? { while let Some(value) = &seq.next_element::<String>()? {
@ -55,7 +55,7 @@ impl Exec {
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where where
E: serde::de::Error, E: de::Error,
{ {
Ok(value.split(';').map(Exec::from).collect()) Ok(value.split(';').map(Exec::from).collect())
} }

View file

@ -1,7 +1,9 @@
use anyhow::bail;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; 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 struct Key {
pub code: KeyCode, pub code: KeyCode,
pub shift: bool, pub shift: bool,
@ -13,21 +15,36 @@ impl Default for Key {
fn default() -> Self { Self { code: KeyCode::Null, shift: false, ctrl: false, alt: false } } fn default() -> Self { Self { code: KeyCode::Null, shift: false, ctrl: false, alt: false } }
} }
impl From<&str> for Key { impl From<KeyEvent> for Key {
fn from(value: &str) -> Self { fn from(value: KeyEvent) -> Self {
let mut key = Default::default(); let shift = if let KeyCode::Char(c) = value.code { c.is_ascii_uppercase() } else { false };
if value.is_empty() {
return key; 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<String> for Key {
type Error = anyhow::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.is_empty() {
bail!("empty key")
} }
if !value.starts_with("<") || !value.ends_with(">") { let mut key = Self::default();
let c = value.chars().next().unwrap(); if !s.starts_with("<") || !s.ends_with(">") {
let c = s.chars().next().unwrap();
key.code = KeyCode::Char(c); key.code = KeyCode::Char(c);
key.shift = c.is_ascii_uppercase(); 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() { while let Some(x) = it.next() {
match x { match x {
"S-" => key.shift = true, "S-" => key.shift = true,
@ -65,48 +82,13 @@ impl From<&str> for Key {
c if it.peek().is_none() => { c if it.peek().is_none() => {
key.code = KeyCode::Char(c.chars().next().unwrap()); key.code = KeyCode::Char(c.chars().next().unwrap());
} }
_ => {} k => bail!("unknown key: {}", k),
}
}
key
}
}
impl From<KeyEvent> 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<D>(deserializer: D) -> Result<Self, D::Error>
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. <C-a>")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Key::from(value))
} }
} }
deserializer.deserialize_str(KeyVisitor) if key.code == KeyCode::Null {
bail!("empty key")
}
Ok(key)
} }
} }

View file

@ -1,41 +1,52 @@
use std::fs; use std::fs;
use serde::Deserialize; use serde::{Deserialize, Deserializer};
use xdg::BaseDirectories; use xdg::BaseDirectories;
use super::{Exec, Key}; use super::{Exec, Key};
#[derive(Deserialize, Debug)] #[derive(Debug, Deserialize)]
pub struct Single { pub struct Single {
pub on: Vec<Key>, pub on: Vec<Key>,
#[serde(deserialize_with = "Exec::deserialize")] #[serde(deserialize_with = "Exec::deserialize")]
pub exec: Vec<Exec>, pub exec: Vec<Exec>,
} }
#[derive(Deserialize, Debug)] #[derive(Debug)]
pub struct Keymap { pub struct Keymap {
pub manager: Vec<Single>, pub manager: Vec<Single>,
pub tasks: Vec<Single>, pub tasks: Vec<Single>,
pub input: Vec<Single>, pub input: Vec<Single>,
} }
impl Keymap { impl<'de> Deserialize<'de> for Keymap {
pub fn new() -> Self { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Shadow {
manager: Inner,
tasks: Inner,
input: Inner,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct Inner { struct Inner {
keymap: Vec<Single>, keymap: Vec<Single>,
} }
#[derive(Deserialize)] let shadow = Shadow::deserialize(deserializer)?;
struct All { Ok(Self {
manager: Inner, manager: shadow.manager.keymap,
tasks: Inner, tasks: shadow.tasks.keymap,
input: Inner, input: shadow.input.keymap,
} })
}
let path = BaseDirectories::new().unwrap().get_config_file("yazi/keymap.toml"); }
let all: All = toml::from_str(&fs::read_to_string(path).unwrap()).unwrap(); impl Keymap {
Self { manager: all.manager.keymap, tasks: all.tasks.keymap, input: all.input.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()
} }
} }

View file

@ -5,7 +5,7 @@ use xdg::BaseDirectories;
use super::SortBy; use super::SortBy;
#[derive(Deserialize, Debug)] #[derive(Debug, Deserialize)]
pub struct Manager { pub struct Manager {
#[serde(skip)] #[serde(skip)]
pub cwd: PathBuf, pub cwd: PathBuf,

View file

@ -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 { pub enum SortBy {
Alphabetical, Alphabetical,
Created, Created,
@ -8,39 +10,16 @@ pub enum SortBy {
Size, Size,
} }
impl From<&str> for SortBy { impl TryFrom<String> for SortBy {
fn from(value: &str) -> Self { type Error = anyhow::Error;
match value {
fn try_from(s: String) -> Result<Self, Self::Error> {
Ok(match s.as_str() {
"created" => Self::Created, "created" => Self::Created,
"modified" => Self::Modified, "modified" => Self::Modified,
"size" => Self::Size, "size" => Self::Size,
_ => Self::Alphabetical, "alphabetical" => Self::Alphabetical,
} _ => bail!("invalid sort_by value: {}", s),
} })
}
impl<'de> Deserialize<'de> for SortBy {
fn deserialize<D>(deserializer: D) -> Result<SortBy, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(SortBy::from(value))
}
}
deserializer.deserialize_str(SortByVisitor)
} }
} }

View file

@ -1,3 +1,5 @@
mod open; mod open;
mod opener;
pub use open::*; pub use open::*;
pub use opener::*;

View file

@ -1,29 +1,18 @@
use std::{collections::BTreeMap, fs, path::Path}; use std::{collections::BTreeMap, fs, path::Path};
use serde::Deserialize; use serde::{Deserialize, Deserializer};
use xdg::BaseDirectories; use xdg::BaseDirectories;
use super::Opener;
use crate::config::Pattern; use crate::config::Pattern;
#[derive(Clone, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug)]
pub struct Opener {
pub cmd: String,
pub args: Vec<String>,
#[serde(default)]
pub block: bool,
#[serde(skip)]
pub spread: bool,
}
#[derive(Deserialize, Debug)]
pub struct Open { pub struct Open {
#[serde(skip)]
openers: BTreeMap<String, Vec<Opener>>, openers: BTreeMap<String, Vec<Opener>>,
rules: Vec<OpenRule>,
rules: Vec<OpenRule>,
} }
#[derive(Deserialize, Debug)] #[derive(Debug, Deserialize)]
struct OpenRule { struct OpenRule {
name: Option<Pattern>, name: Option<Pattern>,
mime: Option<Pattern>, mime: Option<Pattern>,
@ -32,23 +21,9 @@ struct OpenRule {
} }
impl Open { impl Open {
pub fn new() -> Open { pub fn new() -> Self {
#[derive(Deserialize)]
struct Outer {
opener: BTreeMap<String, Vec<Opener>>,
open: Open,
}
let path = BaseDirectories::new().unwrap().get_config_file("yazi/yazi.toml"); let path = BaseDirectories::new().unwrap().get_config_file("yazi/yazi.toml");
let mut outer = toml::from_str::<Outer>(&fs::read_to_string(path).unwrap()).unwrap(); 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 }
} }
pub fn opener(&self, path: &Path, mime: &str) -> Option<&Opener> { pub fn opener(&self, path: &Path, mime: &str) -> Option<&Opener> {
@ -63,3 +38,23 @@ impl Open {
}) })
} }
} }
impl<'de> Deserialize<'de> for Open {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Outer {
opener: BTreeMap<String, Vec<Opener>>,
open: OuterOpen,
}
#[derive(Deserialize)]
struct OuterOpen {
rules: Vec<OpenRule>,
}
let outer = Outer::deserialize(deserializer)?;
Ok(Self { openers: outer.opener, rules: outer.open.rules })
}
}

31
src/config/open/opener.rs Normal file
View file

@ -0,0 +1,31 @@
use serde::{Deserialize, Deserializer};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Opener {
pub cmd: String,
pub args: Vec<String>,
pub block: bool,
pub spread: bool,
}
impl<'de> Deserialize<'de> for Opener {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
pub struct Shadow {
pub cmd: String,
pub args: Vec<String>,
#[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 })
}
}

View file

@ -1,8 +1,9 @@
use std::path::Path; 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 { pub struct Pattern {
inner: glob::Pattern, inner: glob::Pattern,
is_folder: bool, is_folder: bool,
@ -16,35 +17,17 @@ impl Pattern {
} }
} }
impl From<&str> for Pattern { impl TryFrom<&str> for Pattern {
fn from(value: &str) -> Self { type Error = anyhow::Error;
let is_folder = value.ends_with('/');
Self { inner: glob::Pattern::new(value.trim_end_matches('/')).unwrap_or_default(), is_folder } fn try_from(s: &str) -> Result<Self, Self::Error> {
let is_folder = s.ends_with('/');
Ok(Self { inner: glob::Pattern::new(s.trim_end_matches('/'))?, is_folder })
} }
} }
impl<'de> Deserialize<'de> for Pattern { impl TryFrom<String> for Pattern {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> type Error = anyhow::Error;
where
D: Deserializer<'de>,
{
struct PatternVisitor;
impl<'de> Visitor<'de> for PatternVisitor { fn try_from(s: String) -> Result<Self, Self::Error> { Self::try_from(s.as_str()) }
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Pattern::from(value))
}
}
deserializer.deserialize_str(PatternVisitor)
}
} }

View file

@ -3,13 +3,7 @@ use std::fs;
use serde::Deserialize; use serde::Deserialize;
use xdg::BaseDirectories; use xdg::BaseDirectories;
#[derive(Deserialize, Debug)] #[derive(Debug, Deserialize)]
pub struct Manager {
pub sort_by: String,
pub sort_reverse: bool,
}
#[derive(Deserialize, Debug)]
pub struct Preview { pub struct Preview {
pub tab_size: u32, pub tab_size: u32,
@ -25,6 +19,7 @@ impl Preview {
} }
let path = BaseDirectories::new().unwrap().get_config_file("yazi/yazi.toml"); let path = BaseDirectories::new().unwrap().get_config_file("yazi/yazi.toml");
toml::from_str::<Outer>(&fs::read_to_string(path).unwrap()).unwrap().preview let outer: Outer = toml::from_str(&fs::read_to_string(path).unwrap()).unwrap();
outer.preview
} }
} }

View file

@ -1,39 +1,42 @@
use anyhow::Result; use std::ops::Deref;
use anyhow::{bail, Result};
use ratatui::style; use ratatui::style;
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Color { #[serde(try_from = "String")]
pub fg: String, pub struct Color(style::Color);
pub bg: String,
impl Default for Color {
fn default() -> Self { Self(style::Color::Reset) }
} }
impl Color { impl TryFrom<String> for Color {
pub fn fg_rgb(&self) -> style::Color { type Error = anyhow::Error;
if self.fg.len() < 7 {
return style::Color::Reset;
}
let convert = || -> Result<style::Color> {
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)
}
pub fn bg_rgb(&self) -> style::Color { fn try_from(s: String) -> Result<Self, Self::Error> {
if self.bg.len() < 7 { if s.len() < 7 {
return style::Color::Reset; bail!("Invalid color: {}", s);
} }
let convert = || -> Result<style::Color> { Ok(Self(style::Color::Rgb(
Ok(style::Color::Rgb( u8::from_str_radix(&s[1..3], 16)?,
u8::from_str_radix(&self.bg[1..3], 16)?, u8::from_str_radix(&s[3..5], 16)?,
u8::from_str_radix(&self.bg[3..5], 16)?, u8::from_str_radix(&s[5..7], 16)?,
u8::from_str_radix(&self.bg[5..7], 16)?, )))
))
};
convert().unwrap_or(style::Color::Reset)
} }
} }
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,
}

View file

@ -0,0 +1,29 @@
use serde::{Deserialize, Deserializer};
use crate::config::Pattern;
#[derive(Debug, Deserialize)]
pub struct Filetype {
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
#[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<Vec<Filetype>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct FiletypeOuter {
rules: Vec<Filetype>,
}
Ok(FiletypeOuter::deserialize(deserializer)?.rules)
}
}

View file

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use serde::{de::Visitor, Deserializer}; use serde::{de::{self, Visitor}, Deserializer};
use crate::config::Pattern; use crate::config::Pattern;
@ -11,10 +11,6 @@ pub struct Icon {
} }
impl 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<Vec<Icon>, D::Error> pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Icon>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -30,11 +26,15 @@ impl Icon {
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where where
A: serde::de::MapAccess<'de>, A: de::MapAccess<'de>,
{ {
let mut icons = Vec::new(); let mut icons = Vec::new();
while let Some((key, value)) = &map.next_entry::<String, String>()? { while let Some((key, value)) = &map.next_entry::<String, String>()? {
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) Ok(icons)
} }

View file

@ -1,7 +1,9 @@
mod color; mod color;
mod filetype;
mod icon; mod icon;
mod theme; mod theme;
pub use color::*; pub use color::*;
pub use filetype::*;
pub use icon::*; pub use icon::*;
pub use theme::*; pub use theme::*;

View file

@ -3,32 +3,29 @@ use std::{fs, path::PathBuf};
use serde::Deserialize; use serde::Deserialize;
use xdg::BaseDirectories; use xdg::BaseDirectories;
use super::{Color, Icon}; use super::{ColorDual, Filetype, Icon};
use crate::misc::absolute_path; use crate::misc::absolute_path;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Mode { pub struct Mode {
pub normal: Color, pub normal: ColorDual,
pub select: Color, pub select: ColorDual,
pub unselect: Color, pub unselect: ColorDual,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Tab { pub struct Tab {
pub active: Color, pub active: ColorDual,
pub inactive: Color, pub inactive: ColorDual,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Selection { pub struct Selection {
pub normal: Color, pub normal: ColorDual,
pub hovered: Color, pub hovered: ColorDual,
pub selected: Color, pub selected: ColorDual,
} }
#[derive(Deserialize)]
pub struct Filetype {}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Syntect { pub struct Syntect {
pub theme: PathBuf, pub theme: PathBuf,
@ -39,10 +36,11 @@ pub struct Theme {
pub mode: Mode, pub mode: Mode,
pub tab: Tab, pub tab: Tab,
pub selection: Selection, pub selection: Selection,
pub filetype: Filetype, #[serde(deserialize_with = "Filetype::deserialize")]
pub syntect: Syntect, pub filetypes: Vec<Filetype>,
#[serde(deserialize_with = "Icon::deserialize")] #[serde(deserialize_with = "Icon::deserialize")]
pub icons: Vec<Icon>, pub icons: Vec<Icon>,
pub syntect: Syntect,
} }
impl Theme { impl Theme {

View file

@ -12,7 +12,7 @@ pub enum Mode {
impl Mode { impl Mode {
#[inline] #[inline]
pub fn color(&self) -> &theme::Color { pub fn color(&self) -> &theme::ColorDual {
match *self { match *self {
Mode::Normal => &THEME.mode.normal, Mode::Normal => &THEME.mode.normal,
Mode::Select(_) => &THEME.mode.select, Mode::Select(_) => &THEME.mode.select,

View file

@ -29,19 +29,16 @@ impl<'a> Widget for Layout<'a> {
) )
.split(area); .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)) Paragraph::new(format!(" {} ", mode))
.style( .style(
Style::default() Style::default().fg(*mode.color().fg).bg(*mode.color().bg).add_modifier(Modifier::BOLD),
.fg(mode.color().fg_rgb())
.bg(mode.color().bg_rgb())
.add_modifier(Modifier::BOLD),
) )
.render(chunks[1], buf); .render(chunks[1], buf);
Paragraph::new(" master ") 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); .render(chunks[2], buf);
Paragraph::new("").style(Style::default().fg(Color::Rgb(72, 77, 102))).render(chunks[3], buf); Paragraph::new("").style(Style::default().fg(Color::Rgb(72, 77, 102))).render(chunks[3], buf);