patica/
config.rs

1use crate::command::Command;
2use orfail::OrFail;
3use pagurus::event::KeyEvent;
4use pati::{Color, Point};
5use serde::{Deserialize, Serialize};
6use std::{collections::BTreeMap, path::Path};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Config {
10    #[serde(default)]
11    pub key: KeyConfig,
12
13    #[serde(default)]
14    pub initial: InitialConfig,
15}
16
17impl Config {
18    pub fn load_config_file() -> orfail::Result<Option<Self>> {
19        let Ok(home_dir) = std::env::var("HOME") else {
20            return Ok(None);
21        };
22
23        let path = Path::new(&home_dir).join(".config").join("patica.json");
24        if !path.exists() {
25            return Ok(None);
26        }
27
28        let json = std::fs::read_to_string(&path)
29            .or_fail_with(|e| format!("Failed to read config file {}: {e}", path.display()))?;
30        serde_json::from_str(&json)
31            .or_fail_with(|e| {
32                format!(
33                    "Failed to parse config file: path={}, reason={e}",
34                    path.display()
35                )
36            })
37            .map(Some)
38    }
39}
40
41impl Default for Config {
42    fn default() -> Self {
43        serde_json::from_str(include_str!("../default-config.json")).expect("unreachable")
44    }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct InitialConfig {
49    pub background_color: Color,
50    pub anchors: BTreeMap<String, Point>,
51}
52
53impl Default for InitialConfig {
54    fn default() -> Self {
55        Config::default().initial
56    }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct KeyConfig(BTreeMap<Key, Vec<Command>>);
61
62impl KeyConfig {
63    pub fn get_commands(&self, key: KeyEvent) -> impl Iterator<Item = &Command> {
64        let key = Key(key);
65        self.0.get(&key).into_iter().flatten()
66    }
67}
68
69impl Default for KeyConfig {
70    fn default() -> Self {
71        Config::default().key
72    }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(try_from = "String", into = "String")]
77pub struct Key(KeyEvent);
78
79impl PartialOrd for Key {
80    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
81        Some(self.cmp(other))
82    }
83}
84
85impl Ord for Key {
86    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
87        self.to_string().cmp(&other.to_string())
88    }
89}
90
91impl std::fmt::Display for Key {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        if self.0.ctrl {
94            write!(f, "Ctrl+")?;
95        }
96        if self.0.alt {
97            write!(f, "Alt+")?;
98        }
99        match self.0.key {
100            pagurus::event::Key::Return => write!(f, "Enter"),
101            pagurus::event::Key::Left => write!(f, "Left"),
102            pagurus::event::Key::Right => write!(f, "Right"),
103            pagurus::event::Key::Up => write!(f, "Up"),
104            pagurus::event::Key::Down => write!(f, "Down"),
105            pagurus::event::Key::Backspace => write!(f, "Backspace"),
106            pagurus::event::Key::Delete => write!(f, "Delete"),
107            pagurus::event::Key::Tab => write!(f, "Tab"),
108            pagurus::event::Key::BackTab => write!(f, "BackTab"),
109            pagurus::event::Key::Esc => write!(f, "Esc"),
110            pagurus::event::Key::Char(c) => write!(f, "{}", c),
111            _ => unreachable!(),
112        }
113    }
114}
115
116impl From<Key> for String {
117    fn from(key: Key) -> Self {
118        key.to_string()
119    }
120}
121
122impl TryFrom<String> for Key {
123    type Error = orfail::Failure;
124
125    fn try_from(s: String) -> Result<Self, Self::Error> {
126        let mut ctrl = false;
127        let mut alt = false;
128        let mut tokens = if s == "+" {
129            // TODO
130            vec!["+"]
131        } else {
132            s.split('+').collect::<Vec<_>>()
133        };
134
135        let last = tokens
136            .pop()
137            .or_fail_with(|()| "Empty key string".to_owned())?;
138        let key = match last {
139            "Enter" => pagurus::event::Key::Return,
140            "Left" => pagurus::event::Key::Left,
141            "Right" => pagurus::event::Key::Right,
142            "Up" => pagurus::event::Key::Up,
143            "Down" => pagurus::event::Key::Down,
144            "Backspace" => pagurus::event::Key::Backspace,
145            "Delete" => pagurus::event::Key::Delete,
146            "Tab" => pagurus::event::Key::Tab,
147            "BackTab" => pagurus::event::Key::BackTab,
148            "Esc" => pagurus::event::Key::Esc,
149            _ if last.chars().count() == 1 => match last.chars().next().or_fail()? {
150                c @ ('a'..='z'
151                | 'A'..='Z'
152                | '0'..='9'
153                | ' '
154                | '+'
155                | '-'
156                | '_'
157                | '('
158                | ')'
159                | '{'
160                | '}'
161                | '['
162                | ']'
163                | '<'
164                | '>'
165                | ';'
166                | ':'
167                | '='
168                | '.'
169                | ','
170                | '!'
171                | '?'
172                | '/'
173                | '@'
174                | '#'
175                | '$'
176                | '%'
177                | '^'
178                | '&'
179                | '*'
180                | '"'
181                | '\''
182                | '`'
183                | '~') => pagurus::event::Key::Char(c),
184                _ => return Err(orfail::Failure::new(format!("Unknown key: {last:?}"))),
185            },
186            _ => return Err(orfail::Failure::new(format!("Unknown key: {last:?}"))),
187        };
188        for token in tokens {
189            match token {
190                "Ctrl" => ctrl = true,
191                "Alt" => alt = true,
192                _ => {
193                    return Err(orfail::Failure::new(format!(
194                        "Unknown key modifier: {token:?}"
195                    )))
196                }
197            }
198        }
199
200        Ok(Self(KeyEvent { key, ctrl, alt }))
201    }
202}