1use anyhow::anyhow;
2use serde::Deserialize;
3use std::fmt::Write;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
6pub struct Modifiers {
7 pub control: bool,
8 pub alt: bool,
9 pub shift: bool,
10 pub command: bool,
11 pub function: bool,
12}
13
14impl Modifiers {
15 pub fn modified(&self) -> bool {
16 self.control || self.alt || self.shift || self.command || self.function
17 }
18}
19
20#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
21pub struct Keystroke {
22 pub key: String,
23 pub modifiers: Modifiers,
24}
25
26impl Keystroke {
27 pub fn parse(source: &str) -> anyhow::Result<Self> {
28 let mut control = false;
29 let mut alt = false;
30 let mut shift = false;
31 let mut command = false;
32 let mut function = false;
33 let mut key = None;
34
35 let mut components = source.split('-').peekable();
36 while let Some(component) = components.next() {
37 match component {
38 "ctrl" => control = true,
39 "alt" => alt = true,
40 "shift" => shift = true,
41 "cmd" => command = true,
42 "fn" => function = true,
43 _ => {
44 if let Some(component) = components.peek() {
45 if component.is_empty() && source.ends_with('-') {
46 key = Some(String::from("-"));
47 break;
48 } else {
49 return Err(anyhow!("Invalid keystroke `{}`", source));
50 }
51 } else {
52 key = Some(String::from(component));
53 }
54 }
55 }
56 }
57
58 let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
59
60 Ok(Keystroke {
61 modifiers: Modifiers {
62 control,
63 alt,
64 shift,
65 command,
66 function,
67 },
68 key,
69 })
70 }
71
72 pub fn modified(&self) -> bool {
73 self.modifiers.modified()
74 }
75}
76
77impl std::fmt::Display for Keystroke {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 let Modifiers {
80 control,
81 alt,
82 shift,
83 command,
84 function,
85 } = self.modifiers;
86
87 if control {
88 f.write_char('^')?;
89 }
90 if alt {
91 f.write_char('⎇')?;
92 }
93 if command {
94 f.write_char('⌘')?;
95 }
96 if shift {
97 f.write_char('⇧')?;
98 }
99 if function {
100 f.write_char('𝙛')?;
101 }
102
103 let key = match self.key.as_str() {
104 "backspace" => '⌫',
105 "up" => '↑',
106 "down" => '↓',
107 "left" => '←',
108 "right" => '→',
109 "tab" => '⇥',
110 "escape" => '⎋',
111 key => {
112 if key.len() == 1 {
113 key.chars().next().unwrap().to_ascii_uppercase()
114 } else {
115 return f.write_str(key);
116 }
117 }
118 };
119 f.write_char(key)
120 }
121}