1use std::collections::HashMap;
2
3use serde::{Deserialize, Deserializer};
4
5use crate::{
6 ThemeError,
7 color::{Color, ColorRef},
8};
9
10#[derive(Clone, Default, Debug)]
16pub struct UiStyles {
17 pub background: Option<Color>,
18 pub foreground: Option<Color>,
19 pub cursor: Option<Color>,
20 pub cursorline: Option<Color>,
21 pub statusline: Option<StyleSpec>,
22 pub statusline_inactive: Option<StyleSpec>,
23 pub gutter: Option<Color>,
24 pub gutter_current: Option<Color>,
25 pub popup: Option<StyleSpec>,
26 pub selection: Option<StyleSpec>,
27 pub diagnostic_error: Option<Color>,
28 pub diagnostic_warn: Option<Color>,
29}
30
31#[derive(Clone, Debug, Default, Deserialize)]
33pub(crate) struct RawUiStyles {
34 pub background: Option<ColorRef>,
35 pub foreground: Option<ColorRef>,
36 pub cursor: Option<ColorRef>,
37 pub cursorline: Option<ColorRef>,
38 pub statusline: Option<RawStyleSpec>,
39 #[serde(rename = "statusline.inactive")]
40 pub statusline_inactive: Option<RawStyleSpec>,
41 pub gutter: Option<ColorRef>,
42 #[serde(rename = "gutter.current")]
43 pub gutter_current: Option<ColorRef>,
44 pub popup: Option<RawStyleSpec>,
45 pub selection: Option<RawStyleSpec>,
46 #[serde(rename = "diagnostic.error")]
47 pub diagnostic_error: Option<ColorRef>,
48 #[serde(rename = "diagnostic.warn")]
49 pub diagnostic_warn: Option<ColorRef>,
50}
51
52impl RawUiStyles {
53 pub(crate) fn resolve(self, palette: &HashMap<String, Color>) -> Result<UiStyles, ThemeError> {
54 let resolve_color = |c: Option<ColorRef>| c.map(|cr| cr.resolve(palette)).transpose();
55 let resolve_style = |s: Option<RawStyleSpec>| s.map(|rs| rs.resolve(palette)).transpose();
56 Ok(UiStyles {
57 background: resolve_color(self.background)?,
58 foreground: resolve_color(self.foreground)?,
59 cursor: resolve_color(self.cursor)?,
60 cursorline: resolve_color(self.cursorline)?,
61 statusline: resolve_style(self.statusline)?,
62 statusline_inactive: resolve_style(self.statusline_inactive)?,
63 gutter: resolve_color(self.gutter)?,
64 gutter_current: resolve_color(self.gutter_current)?,
65 popup: resolve_style(self.popup)?,
66 selection: resolve_style(self.selection)?,
67 diagnostic_error: resolve_color(self.diagnostic_error)?,
68 diagnostic_warn: resolve_color(self.diagnostic_warn)?,
69 })
70 }
71}
72
73#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
75pub struct Modifiers {
76 pub bold: bool,
77 pub italic: bool,
78 pub underline: bool,
79 pub reverse: bool,
80 pub strikethrough: bool,
81}
82
83#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
85pub struct StyleSpec {
86 pub fg: Option<Color>,
87 pub bg: Option<Color>,
88 pub modifiers: Modifiers,
89}
90
91#[derive(Clone, Debug, Default, Deserialize)]
97pub(crate) struct RawModifiers(pub Vec<String>);
98
99impl RawModifiers {
100 pub(crate) fn resolve(self) -> Result<Modifiers, ThemeError> {
101 let mut m = Modifiers::default();
102 for s in self.0 {
103 match s.as_str() {
104 "bold" => m.bold = true,
105 "italic" => m.italic = true,
106 "underline" => m.underline = true,
107 "reverse" => m.reverse = true,
108 "strikethrough" => m.strikethrough = true,
109 _ => return Err(ThemeError::BadModifier(s)),
110 }
111 }
112 Ok(m)
113 }
114}
115
116#[derive(Clone, Debug, Deserialize)]
118pub(crate) struct RawStyleFull {
119 pub fg: Option<ColorRef>,
120 pub bg: Option<ColorRef>,
121 #[serde(default)]
122 pub modifiers: RawModifiers,
123}
124
125#[derive(Clone, Debug)]
128pub(crate) enum RawStyleSpec {
129 Full(RawStyleFull),
130 Shorthand(ColorRef),
131}
132
133impl<'de> Deserialize<'de> for RawStyleSpec {
134 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
135 let v = toml::Value::deserialize(d)?;
137 match v {
138 toml::Value::String(_) => {
139 let cr = ColorRef::deserialize(v).map_err(serde::de::Error::custom)?;
140 Ok(RawStyleSpec::Shorthand(cr))
141 }
142 toml::Value::Table(_) => {
143 let full = RawStyleFull::deserialize(v).map_err(serde::de::Error::custom)?;
144 Ok(RawStyleSpec::Full(full))
145 }
146 other => Err(serde::de::Error::custom(format!(
147 "expected string or table for style, got {}",
148 other.type_str()
149 ))),
150 }
151 }
152}
153
154impl RawStyleSpec {
155 pub(crate) fn resolve(self, palette: &HashMap<String, Color>) -> Result<StyleSpec, ThemeError> {
156 match self {
157 RawStyleSpec::Shorthand(cr) => Ok(StyleSpec {
158 fg: Some(cr.resolve(palette)?),
159 ..Default::default()
160 }),
161 RawStyleSpec::Full(f) => {
162 let fg = f.fg.map(|cr| cr.resolve(palette)).transpose()?;
163 let bg = f.bg.map(|cr| cr.resolve(palette)).transpose()?;
164 let modifiers = f.modifiers.resolve()?;
165 Ok(StyleSpec { fg, bg, modifiers })
166 }
167 }
168 }
169}