1use anyhow::anyhow;
2use serde::Deserialize;
3use smallvec::SmallVec;
4use std::fmt::Write;
5
6#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
7pub struct Keystroke {
8 pub modifiers: Modifiers,
9 /// key is the character printed on the key that was pressed
10 /// e.g. for option-s, key is "s"
11 pub key: String,
12 /// ime_key is the character inserted by the IME engine when that key was pressed.
13 /// e.g. for option-s, ime_key is "ร"
14 pub ime_key: Option<String>,
15}
16
17impl Keystroke {
18 // When matching a key we cannot know whether the user intended to type
19 // the ime_key or the key. On some non-US keyboards keys we use in our
20 // bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
21 // and on some keyboards the IME handler converts a sequence of keys into a
22 // specific character (for example `"` is typed as `" space` on a brazilian keyboard).
23 pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
24 let mut possibilities = SmallVec::new();
25 match self.ime_key.as_ref() {
26 None => possibilities.push(self.clone()),
27 Some(ime_key) => {
28 possibilities.push(Keystroke {
29 modifiers: Modifiers {
30 control: self.modifiers.control,
31 alt: false,
32 shift: false,
33 command: false,
34 function: false,
35 },
36 key: ime_key.to_string(),
37 ime_key: None,
38 });
39 possibilities.push(Keystroke {
40 ime_key: None,
41 ..self.clone()
42 });
43 }
44 }
45 possibilities
46 }
47
48 /// key syntax is:
49 /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
50 /// ime_key is only used for generating test events,
51 /// when matching a key with an ime_key set will be matched without it.
52 pub fn parse(source: &str) -> anyhow::Result<Self> {
53 let mut control = false;
54 let mut alt = false;
55 let mut shift = false;
56 let mut command = false;
57 let mut function = false;
58 let mut key = None;
59 let mut ime_key = None;
60
61 let mut components = source.split('-').peekable();
62 while let Some(component) = components.next() {
63 match component {
64 "ctrl" => control = true,
65 "alt" => alt = true,
66 "shift" => shift = true,
67 "cmd" => command = true,
68 "fn" => function = true,
69 _ => {
70 if let Some(next) = components.peek() {
71 if next.is_empty() && source.ends_with('-') {
72 key = Some(String::from("-"));
73 break;
74 } else if next.len() > 1 && next.starts_with('>') {
75 key = Some(String::from(component));
76 ime_key = Some(String::from(&next[1..]));
77 components.next();
78 } else {
79 return Err(anyhow!("Invalid keystroke `{}`", source));
80 }
81 } else {
82 key = Some(String::from(component));
83 }
84 }
85 }
86 }
87
88 let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
89
90 Ok(Keystroke {
91 modifiers: Modifiers {
92 control,
93 alt,
94 shift,
95 command,
96 function,
97 },
98 key,
99 ime_key,
100 })
101 }
102}
103
104impl std::fmt::Display for Keystroke {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 if self.modifiers.control {
107 f.write_char('^')?;
108 }
109 if self.modifiers.alt {
110 f.write_char('โฅ')?;
111 }
112 if self.modifiers.command {
113 f.write_char('โ')?;
114 }
115 if self.modifiers.shift {
116 f.write_char('โง')?;
117 }
118 let key = match self.key.as_str() {
119 "backspace" => 'โซ',
120 "up" => 'โ',
121 "down" => 'โ',
122 "left" => 'โ',
123 "right" => 'โ',
124 "tab" => 'โฅ',
125 "escape" => 'โ',
126 key => {
127 if key.len() == 1 {
128 key.chars().next().unwrap().to_ascii_uppercase()
129 } else {
130 return f.write_str(key);
131 }
132 }
133 };
134 f.write_char(key)
135 }
136}
137
138#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
139pub struct Modifiers {
140 pub control: bool,
141 pub alt: bool,
142 pub shift: bool,
143 pub command: bool,
144 pub function: bool,
145}
146
147impl Modifiers {
148 pub fn modified(&self) -> bool {
149 self.control || self.alt || self.shift || self.command || self.function
150 }
151}