Skip to main content

zellij_utils/input/
web_client.rs

1use kdl::{KdlDocument, KdlNode, KdlValue};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    data::PaletteColor, kdl_children_or_error, kdl_first_entry_as_string, kdl_get_child,
6    kdl_get_child_entry_bool_value, kdl_get_child_entry_string_value,
7};
8
9use super::config::ConfigError;
10
11#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
12pub struct WebClientTheme {
13    pub background: Option<String>,
14    pub foreground: Option<String>,
15    pub black: Option<String>,
16    pub blue: Option<String>,
17    pub bright_black: Option<String>,
18    pub bright_blue: Option<String>,
19    pub bright_cyan: Option<String>,
20    pub bright_green: Option<String>,
21    pub bright_magenta: Option<String>,
22    pub bright_red: Option<String>,
23    pub bright_white: Option<String>,
24    pub bright_yellow: Option<String>,
25    pub cursor: Option<String>,
26    pub cursor_accent: Option<String>,
27    pub cyan: Option<String>,
28    pub green: Option<String>,
29    pub magenta: Option<String>,
30    pub red: Option<String>,
31    pub selection_background: Option<String>,
32    pub selection_foreground: Option<String>,
33    pub selection_inactive_background: Option<String>,
34    pub white: Option<String>,
35    pub yellow: Option<String>,
36}
37
38impl WebClientTheme {
39    pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
40        let mut theme = WebClientTheme::default();
41        let colors = kdl_children_or_error!(kdl, "empty theme");
42
43        // Helper function to extract colors
44        let extract_color = |name: &str| -> Result<Option<String>, ConfigError> {
45            if colors.get(name).is_some() {
46                let color = PaletteColor::try_from((name, colors))?;
47                Ok(Some(color.as_rgb_str()))
48            } else {
49                Ok(None)
50            }
51        };
52
53        theme.background = extract_color("background")?;
54        theme.foreground = extract_color("foreground")?;
55        theme.black = extract_color("black")?;
56        theme.blue = extract_color("blue")?;
57        theme.bright_black = extract_color("bright_black")?;
58        theme.bright_blue = extract_color("bright_blue")?;
59        theme.bright_cyan = extract_color("bright_cyan")?;
60        theme.bright_green = extract_color("bright_green")?;
61        theme.bright_magenta = extract_color("bright_magenta")?;
62        theme.bright_red = extract_color("bright_red")?;
63        theme.bright_white = extract_color("bright_white")?;
64        theme.bright_yellow = extract_color("bright_yellow")?;
65        theme.cursor = extract_color("cursor")?;
66        theme.cursor_accent = extract_color("cursor_accent")?;
67        theme.cyan = extract_color("cyan")?;
68        theme.green = extract_color("green")?;
69        theme.magenta = extract_color("magenta")?;
70        theme.red = extract_color("red")?;
71        theme.selection_background = extract_color("selection_background")?;
72        theme.selection_foreground = extract_color("selection_foreground")?;
73        theme.selection_inactive_background = extract_color("selection_inactive_background")?;
74        theme.white = extract_color("white")?;
75        theme.yellow = extract_color("yellow")?;
76
77        Ok(theme)
78    }
79
80    pub fn to_kdl(&self) -> KdlNode {
81        macro_rules! add_color_nodes {
82            ($theme_children:expr, $self:expr, $($field:ident),+ $(,)?) => {
83                $(
84                    if let Some(color) = &$self.$field {
85                        let node = PaletteColor::from_rgb_str(color).to_kdl(stringify!($field));
86                        $theme_children.nodes_mut().push(node);
87                    }
88                )+
89            };
90        }
91        let mut theme_node = KdlNode::new("theme");
92        let mut theme_children = KdlDocument::new();
93
94        add_color_nodes!(
95            theme_children,
96            self,
97            background,
98            foreground,
99            black,
100            blue,
101            bright_black,
102            bright_blue,
103            bright_cyan,
104            bright_green,
105            bright_magenta,
106            bright_red,
107            bright_white,
108            bright_yellow,
109            cursor,
110            cursor_accent,
111            cyan,
112            green,
113            magenta,
114            red,
115            selection_background,
116            selection_foreground,
117            selection_inactive_background,
118            white,
119            yellow,
120        );
121
122        theme_node.set_children(theme_children);
123        theme_node
124    }
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub enum CursorInactiveStyle {
129    Outline,
130    Block,
131    Bar,
132    Underline,
133    NoStyle,
134}
135
136impl std::fmt::Display for CursorInactiveStyle {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        match self {
139            CursorInactiveStyle::Block => write!(f, "block"),
140            CursorInactiveStyle::Bar => write!(f, "bar"),
141            CursorInactiveStyle::Underline => write!(f, "underline"),
142            CursorInactiveStyle::Outline => write!(f, "outline"),
143            CursorInactiveStyle::NoStyle => write!(f, "none"),
144        }
145    }
146}
147
148impl CursorInactiveStyle {
149    pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
150        match kdl_first_entry_as_string!(kdl) {
151            Some("block") => Ok(CursorInactiveStyle::Block),
152            Some("bar") => Ok(CursorInactiveStyle::Bar),
153            Some("underline") => Ok(CursorInactiveStyle::Underline),
154            Some("outline") => Ok(CursorInactiveStyle::Outline),
155            Some("no_style") => Ok(CursorInactiveStyle::NoStyle),
156            _ => Err(ConfigError::new_kdl_error(
157                format!("Must be 'block', 'bar', 'underline', 'outline' or 'no_style'"),
158                kdl.span().offset(),
159                kdl.span().len(),
160            )),
161        }
162    }
163    pub fn to_kdl(&self) -> KdlNode {
164        let mut cursor_inactive_style_node = KdlNode::new("cursor_inactive_style");
165        match self {
166            CursorInactiveStyle::Block => {
167                cursor_inactive_style_node.push(KdlValue::String("block".to_owned()));
168            },
169            CursorInactiveStyle::Bar => {
170                cursor_inactive_style_node.push(KdlValue::String("bar".to_owned()));
171            },
172            CursorInactiveStyle::Underline => {
173                cursor_inactive_style_node.push(KdlValue::String("underline".to_owned()));
174            },
175            CursorInactiveStyle::Outline => {
176                cursor_inactive_style_node.push(KdlValue::String("outline".to_owned()));
177            },
178            CursorInactiveStyle::NoStyle => {
179                cursor_inactive_style_node.push(KdlValue::String("no_style".to_owned()));
180            },
181        }
182        cursor_inactive_style_node
183    }
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187pub enum CursorStyle {
188    Block,
189    Bar,
190    Underline,
191}
192
193impl std::fmt::Display for CursorStyle {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        match self {
196            CursorStyle::Block => write!(f, "block"),
197            CursorStyle::Bar => write!(f, "bar"),
198            CursorStyle::Underline => write!(f, "underline"),
199        }
200    }
201}
202
203impl CursorStyle {
204    pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
205        match kdl_first_entry_as_string!(kdl) {
206            Some("block") => Ok(CursorStyle::Block),
207            Some("bar") => Ok(CursorStyle::Bar),
208            Some("underline") => Ok(CursorStyle::Underline),
209            _ => Err(ConfigError::new_kdl_error(
210                format!("Must be 'block', 'bar' or 'underline'"),
211                kdl.span().offset(),
212                kdl.span().len(),
213            )),
214        }
215    }
216    pub fn to_kdl(&self) -> KdlNode {
217        let mut cursor_style_node = KdlNode::new("cursor_style");
218        match self {
219            CursorStyle::Block => {
220                cursor_style_node.push(KdlValue::String("block".to_owned()));
221            },
222            CursorStyle::Bar => {
223                cursor_style_node.push(KdlValue::String("bar".to_owned()));
224            },
225            CursorStyle::Underline => {
226                cursor_style_node.push(KdlValue::String("underline".to_owned()));
227            },
228        }
229        cursor_style_node
230    }
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct WebClientConfig {
235    pub font: String,
236    pub theme: Option<WebClientTheme>,
237    pub cursor_blink: bool,
238    pub cursor_inactive_style: Option<CursorInactiveStyle>,
239    pub cursor_style: Option<CursorStyle>,
240    pub mac_option_is_meta: bool,
241    pub base_url: Option<String>,
242}
243
244impl Default for WebClientConfig {
245    fn default() -> Self {
246        WebClientConfig {
247            font: "monospace".to_string(),
248            theme: None,
249            cursor_blink: false,
250            cursor_inactive_style: None,
251            cursor_style: None,
252            mac_option_is_meta: true, // TODO: yes? no?
253            base_url: None,
254        }
255    }
256}
257
258impl WebClientConfig {
259    pub fn from_kdl(kdl: &KdlNode) -> Result<Self, ConfigError> {
260        let mut web_client_config = WebClientConfig::default();
261
262        if let Some(font) = kdl_get_child_entry_string_value!(kdl, "font") {
263            web_client_config.font = font.to_owned();
264        }
265
266        if let Some(theme_node) = kdl_get_child!(kdl, "theme") {
267            web_client_config.theme = Some(WebClientTheme::from_kdl(theme_node)?);
268        }
269
270        if let Some(cursor_blink) = kdl_get_child_entry_bool_value!(kdl, "cursor_blink") {
271            web_client_config.cursor_blink = cursor_blink;
272        }
273
274        if let Some(cursor_inactive_style_node) = kdl_get_child!(kdl, "cursor_inactive_style") {
275            web_client_config.cursor_inactive_style =
276                Some(CursorInactiveStyle::from_kdl(cursor_inactive_style_node)?);
277        }
278
279        if let Some(cursor_style_node) = kdl_get_child!(kdl, "cursor_style") {
280            web_client_config.cursor_style = Some(CursorStyle::from_kdl(cursor_style_node)?);
281        }
282
283        if let Some(mac_option_is_meta) = kdl_get_child_entry_bool_value!(kdl, "mac_option_is_meta")
284        {
285            web_client_config.mac_option_is_meta = mac_option_is_meta;
286        }
287
288        if let Some(base_url) = kdl_get_child_entry_string_value!(kdl, "base_url") {
289            web_client_config.base_url = Some(base_url.to_owned());
290        }
291
292        Ok(web_client_config)
293    }
294
295    pub fn to_kdl(&self) -> KdlNode {
296        let mut web_client_node = KdlNode::new("web_client");
297        let mut web_client_children = KdlDocument::new();
298
299        let mut font_node = KdlNode::new("font");
300        font_node.push(KdlValue::String(self.font.clone()));
301        web_client_children.nodes_mut().push(font_node);
302
303        if let Some(theme_node) = self.theme.as_ref().map(|t| t.to_kdl()) {
304            web_client_children.nodes_mut().push(theme_node);
305        }
306
307        if self.cursor_blink {
308            // this defaults to false, so we only need to add it if it's true
309            let mut cursor_blink_node = KdlNode::new("cursor_blink");
310            cursor_blink_node.push(KdlValue::Bool(true));
311            web_client_children.nodes_mut().push(cursor_blink_node);
312        }
313
314        if let Some(cursor_inactive_style_node) =
315            self.cursor_inactive_style.as_ref().map(|c| c.to_kdl())
316        {
317            web_client_children
318                .nodes_mut()
319                .push(cursor_inactive_style_node);
320        }
321
322        if let Some(cursor_style_node) = self.cursor_style.as_ref().map(|c| c.to_kdl()) {
323            web_client_children.nodes_mut().push(cursor_style_node);
324        }
325
326        if !self.mac_option_is_meta {
327            // this defaults to true, so we only need to add it if it's false
328            let mut mac_option_is_meta_node = KdlNode::new("mac_option_is_meta");
329            mac_option_is_meta_node.push(KdlValue::Bool(false));
330            web_client_children
331                .nodes_mut()
332                .push(mac_option_is_meta_node);
333        }
334
335        if let Some(base_url) = &self.base_url {
336            let mut base_url_node = KdlNode::new("base_url");
337            base_url_node.push(KdlValue::String(base_url.clone()));
338            web_client_children.nodes_mut().push(base_url_node);
339        }
340
341        web_client_node.set_children(web_client_children);
342        web_client_node
343    }
344
345    pub fn merge(&self, other: WebClientConfig) -> Self {
346        let mut merged = self.clone();
347        merged.font = other.font;
348        merged.theme = other.theme;
349        merged.cursor_blink = other.cursor_blink;
350        merged.cursor_inactive_style = other.cursor_inactive_style;
351        merged.cursor_style = other.cursor_style;
352        merged.mac_option_is_meta = other.mac_option_is_meta;
353        merged.base_url = other.base_url;
354        merged
355    }
356}