1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use serde::{Deserialize, Deserializer};
use smart_default::SmartDefault;
use std::collections::HashMap;
use std::sync::Arc;

use crate::blocks::BlockConfig;
use crate::click::ClickHandler;
use crate::errors::*;
use crate::formatting::config::Config as FormatConfig;
use crate::icons::{Icon, Icons};
use crate::themes::{Theme, ThemeOverrides, ThemeUserConfig};

#[derive(Deserialize, Debug)]
pub struct Config {
    #[serde(flatten)]
    pub shared: SharedConfig,

    /// Set to `true` to invert mouse wheel direction
    #[serde(default)]
    pub invert_scrolling: bool,

    /// The maximum delay (ms) between two clicks that are considered as double click
    #[serde(default)]
    pub double_click_delay: u64,

    #[serde(default = "default_error_format")]
    pub error_format: FormatConfig,
    #[serde(default = "default_error_fullscreen")]
    pub error_fullscreen_format: FormatConfig,

    #[serde(default)]
    #[serde(rename = "block")]
    pub blocks: Vec<BlockConfigEntry>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct SharedConfig {
    #[serde(default)]
    #[serde(deserialize_with = "deserialize_theme_config")]
    pub theme: Arc<Theme>,
    #[serde(default)]
    pub icons: Arc<Icons>,
    #[serde(default = "default_icons_format")]
    pub icons_format: Arc<String>,
}

impl Default for SharedConfig {
    fn default() -> Self {
        Self {
            theme: Default::default(),
            icons: Default::default(),
            icons_format: default_icons_format(),
        }
    }
}

fn default_error_format() -> FormatConfig {
    " {$short_error_message|X} ".parse().unwrap()
}

fn default_error_fullscreen() -> FormatConfig {
    " $full_error_message ".parse().unwrap()
}

fn default_icons_format() -> Arc<String> {
    Arc::new("{icon}".into())
}

impl SharedConfig {
    pub fn get_icon(&self, icon: &str, value: Option<f64>) -> Result<String> {
        if icon.is_empty() {
            Ok(String::new())
        } else {
            Ok(self.icons_format.replace(
                "{icon}",
                self.icons
                    .get(icon, value)
                    .or_error(|| format!("Icon '{icon}' not found"))?,
            ))
        }
    }
}

#[derive(Deserialize, Debug)]
pub struct BlockConfigEntry {
    #[serde(flatten)]
    pub common: CommonBlockConfig,
    #[serde(flatten)]
    pub config: BlockConfig,
}

#[derive(Deserialize, Debug, SmartDefault)]
#[serde(default)]
pub struct CommonBlockConfig {
    pub click: ClickHandler,
    pub signal: Option<i32>,
    pub icons_format: Option<String>,
    pub theme_overrides: Option<ThemeOverrides>,
    pub icons_overrides: Option<HashMap<String, Icon>>,
    pub merge_with_next: bool,

    #[default(5)]
    pub error_interval: u64,
    pub error_format: FormatConfig,
    pub error_fullscreen_format: FormatConfig,

    pub if_command: Option<String>,
}

fn deserialize_theme_config<'de, D>(deserializer: D) -> Result<Arc<Theme>, D::Error>
where
    D: Deserializer<'de>,
{
    let theme_config = ThemeUserConfig::deserialize(deserializer)?;
    let theme = Theme::try_from(theme_config).serde_error()?;
    Ok(Arc::new(theme))
}