1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7use crate::error::ParseError;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Modifier {
13 Ctrl,
14 Shift,
15 Alt,
16 Meta,
17}
18
19impl fmt::Display for Modifier {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Modifier::Ctrl => write!(f, "Ctrl"),
23 Modifier::Shift => write!(f, "Shift"),
24 Modifier::Alt => write!(f, "Alt"),
25 Modifier::Meta => write!(f, "Meta"),
26 }
27 }
28}
29
30impl FromStr for Modifier {
31 type Err = ParseError;
32
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
34 match s.to_lowercase().as_str() {
35 "ctrl" | "control" => Ok(Modifier::Ctrl),
36 "shift" => Ok(Modifier::Shift),
37 "alt" => Ok(Modifier::Alt),
38 "meta" | "cmd" | "command" => Ok(Modifier::Meta),
39 _ => Err(ParseError::InvalidModifier(s.to_string())),
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
46#[serde(untagged)]
47pub enum Key {
48 Char(char),
49 F(u8),
50 Enter,
51 Escape,
52 Tab,
53 Backspace,
54 Delete,
55 Home,
56 End,
57 PageUp,
58 PageDown,
59 Up,
60 Down,
61 Left,
62 Right,
63}
64
65impl fmt::Display for Key {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 Key::Char(c) => write!(f, "{}", c),
69 Key::F(n) => write!(f, "F{}", n),
70 Key::Enter => write!(f, "Enter"),
71 Key::Escape => write!(f, "Escape"),
72 Key::Tab => write!(f, "Tab"),
73 Key::Backspace => write!(f, "Backspace"),
74 Key::Delete => write!(f, "Delete"),
75 Key::Home => write!(f, "Home"),
76 Key::End => write!(f, "End"),
77 Key::PageUp => write!(f, "PageUp"),
78 Key::PageDown => write!(f, "PageDown"),
79 Key::Up => write!(f, "Up"),
80 Key::Down => write!(f, "Down"),
81 Key::Left => write!(f, "Left"),
82 Key::Right => write!(f, "Right"),
83 }
84 }
85}
86
87impl FromStr for Key {
88 type Err = ParseError;
89
90 fn from_str(s: &str) -> Result<Self, Self::Err> {
91 match s.to_lowercase().as_str() {
92 "enter" | "return" => Ok(Key::Enter),
93 "escape" | "esc" => Ok(Key::Escape),
94 "tab" => Ok(Key::Tab),
95 "backspace" | "bksp" => Ok(Key::Backspace),
96 "delete" | "del" => Ok(Key::Delete),
97 "home" => Ok(Key::Home),
98 "end" => Ok(Key::End),
99 "pageup" | "page_up" => Ok(Key::PageUp),
100 "pagedown" | "page_down" => Ok(Key::PageDown),
101 "up" => Ok(Key::Up),
102 "down" => Ok(Key::Down),
103 "left" => Ok(Key::Left),
104 "right" => Ok(Key::Right),
105 s if s.starts_with('f') && s.len() > 1 => {
106 let num: u8 = s[1..].parse().map_err(|_| {
107 ParseError::InvalidKeySyntax(format!("Invalid function key: {}", s))
108 })?;
109 if (1..=12).contains(&num) {
110 Ok(Key::F(num))
111 } else {
112 Err(ParseError::InvalidKeySyntax(format!(
113 "Function key must be F1-F12, got: {}",
114 s
115 )))
116 }
117 }
118 s if s.len() == 1 => Ok(Key::Char(s.chars().next().unwrap())),
119 _ => Err(ParseError::InvalidKeySyntax(format!(
120 "Unknown key: {}",
121 s
122 ))),
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
129pub struct KeyCombo {
130 pub modifiers: Vec<Modifier>,
131 pub key: Key,
132}
133
134impl fmt::Display for KeyCombo {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 for modifier in &self.modifiers {
137 write!(f, "{}+", modifier)?;
138 }
139 write!(f, "{}", self.key)
140 }
141}
142
143impl FromStr for KeyCombo {
144 type Err = ParseError;
145
146 fn from_str(s: &str) -> Result<Self, Self::Err> {
147 let parts: Vec<&str> = s.split('+').collect();
148 if parts.is_empty() {
149 return Err(ParseError::InvalidKeySyntax(
150 "Empty key combination".to_string(),
151 ));
152 }
153
154 let mut modifiers = Vec::new();
155 for part in &parts[..parts.len() - 1] {
156 modifiers.push(Modifier::from_str(part)?);
157 }
158
159 let key = Key::from_str(parts[parts.len() - 1])?;
160
161 Ok(KeyCombo { modifiers, key })
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167pub struct Keybind {
168 pub action_id: String,
169 pub key: String,
170 pub category: String,
171 pub description: String,
172 #[serde(default)]
173 pub is_default: bool,
174}
175
176impl Keybind {
177 pub fn parse_key(&self) -> Result<KeyCombo, ParseError> {
179 KeyCombo::from_str(&self.key)
180 }
181
182 pub fn new(
184 action_id: impl Into<String>,
185 key: impl Into<String>,
186 category: impl Into<String>,
187 description: impl Into<String>,
188 ) -> Self {
189 Keybind {
190 action_id: action_id.into(),
191 key: key.into(),
192 category: category.into(),
193 description: description.into(),
194 is_default: false,
195 }
196 }
197
198 pub fn new_default(
200 action_id: impl Into<String>,
201 key: impl Into<String>,
202 category: impl Into<String>,
203 description: impl Into<String>,
204 ) -> Self {
205 Keybind {
206 action_id: action_id.into(),
207 key: key.into(),
208 category: category.into(),
209 description: description.into(),
210 is_default: true,
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_modifier_from_str() {
221 assert_eq!(Modifier::from_str("ctrl").unwrap(), Modifier::Ctrl);
222 assert_eq!(Modifier::from_str("Shift").unwrap(), Modifier::Shift);
223 assert_eq!(Modifier::from_str("alt").unwrap(), Modifier::Alt);
224 assert_eq!(Modifier::from_str("meta").unwrap(), Modifier::Meta);
225 assert_eq!(Modifier::from_str("cmd").unwrap(), Modifier::Meta);
226 }
227
228 #[test]
229 fn test_key_from_str() {
230 assert_eq!(Key::from_str("enter").unwrap(), Key::Enter);
231 assert_eq!(Key::from_str("F1").unwrap(), Key::F(1));
232 assert_eq!(Key::from_str("a").unwrap(), Key::Char('a'));
233 assert!(Key::from_str("F13").is_err());
234 }
235
236 #[test]
237 fn test_key_combo_from_str() {
238 let combo = KeyCombo::from_str("Ctrl+S").unwrap();
239 assert_eq!(combo.modifiers.len(), 1);
240 assert_eq!(combo.modifiers[0], Modifier::Ctrl);
241 assert_eq!(combo.key, Key::Char('s'));
242
243 let combo = KeyCombo::from_str("Ctrl+Shift+Z").unwrap();
244 assert_eq!(combo.modifiers.len(), 2);
245 assert_eq!(combo.key, Key::Char('z'));
246 }
247
248 #[test]
249 fn test_keybind_creation() {
250 let kb = Keybind::new("editor.save", "Ctrl+S", "editing", "Save file");
251 assert_eq!(kb.action_id, "editor.save");
252 assert_eq!(kb.key, "Ctrl+S");
253 assert!(!kb.is_default);
254
255 let kb = Keybind::new_default("editor.undo", "Ctrl+Z", "editing", "Undo");
256 assert!(kb.is_default);
257 }
258}