1use anyhow::anyhow;
2use serde::Deserialize;
3use smallvec::SmallVec;
4use std::fmt::Write;
5
6/// A keystroke and associated metadata generated by the platform
7#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
8pub struct Keystroke {
9 /// the state of the modifier keys at the time the keystroke was generated
10 pub modifiers: Modifiers,
11
12 /// key is the character printed on the key that was pressed
13 /// e.g. for option-s, key is "s"
14 pub key: String,
15
16 /// ime_key is the character inserted by the IME engine when that key was pressed.
17 /// e.g. for option-s, ime_key is "ร"
18 pub ime_key: Option<String>,
19}
20
21impl Keystroke {
22 /// When matching a key we cannot know whether the user intended to type
23 /// the ime_key or the key itself. On some non-US keyboards keys we use in our
24 /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
25 /// and on some keyboards the IME handler converts a sequence of keys into a
26 /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
27 ///
28 /// This method generates a list of potential keystroke candidates that could be matched
29 /// against when resolving a keybinding.
30 pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
31 let mut possibilities = SmallVec::new();
32 match self.ime_key.as_ref() {
33 Some(ime_key) => {
34 if ime_key != &self.key {
35 possibilities.push(Keystroke {
36 modifiers: Modifiers {
37 control: self.modifiers.control,
38 alt: false,
39 shift: false,
40 platform: false,
41 function: false,
42 },
43 key: ime_key.to_string(),
44 ime_key: None,
45 });
46 }
47 possibilities.push(Keystroke {
48 ime_key: None,
49 ..self.clone()
50 });
51 }
52 None => possibilities.push(self.clone()),
53 }
54 possibilities
55 }
56
57 /// key syntax is:
58 /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
59 /// ime_key syntax is only used for generating test events,
60 /// when matching a key with an ime_key set will be matched without it.
61 pub fn parse(source: &str) -> anyhow::Result<Self> {
62 let mut control = false;
63 let mut alt = false;
64 let mut shift = false;
65 let mut platform = false;
66 let mut function = false;
67 let mut key = None;
68 let mut ime_key = None;
69
70 let mut components = source.split('-').peekable();
71 while let Some(component) = components.next() {
72 match component {
73 "ctrl" => control = true,
74 "alt" => alt = true,
75 "shift" => shift = true,
76 "fn" => function = true,
77 "cmd" | "super" | "win" => platform = true,
78 _ => {
79 if let Some(next) = components.peek() {
80 if next.is_empty() && source.ends_with('-') {
81 key = Some(String::from("-"));
82 break;
83 } else if next.len() > 1 && next.starts_with('>') {
84 key = Some(String::from(component));
85 ime_key = Some(String::from(&next[1..]));
86 components.next();
87 } else {
88 return Err(anyhow!("Invalid keystroke `{}`", source));
89 }
90 } else {
91 key = Some(String::from(component));
92 }
93 }
94 }
95 }
96
97 let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
98
99 Ok(Keystroke {
100 modifiers: Modifiers {
101 control,
102 alt,
103 shift,
104 platform,
105 function,
106 },
107 key,
108 ime_key,
109 })
110 }
111
112 /// Returns true if this keystroke left
113 /// the ime system in an incomplete state.
114 pub fn is_ime_in_progress(&self) -> bool {
115 self.ime_key.is_none()
116 && (is_printable_key(&self.key) || self.key.is_empty())
117 && !(self.modifiers.platform
118 || self.modifiers.control
119 || self.modifiers.function
120 || self.modifiers.alt)
121 }
122
123 /// Returns a new keystroke with the ime_key filled.
124 /// This is used for dispatch_keystroke where we want users to
125 /// be able to simulate typing "space", etc.
126 pub fn with_simulated_ime(mut self) -> Self {
127 if self.ime_key.is_none()
128 && !self.modifiers.platform
129 && !self.modifiers.control
130 && !self.modifiers.function
131 && !self.modifiers.alt
132 {
133 self.ime_key = match self.key.as_str() {
134 "space" => Some(" ".into()),
135 "tab" => Some("\t".into()),
136 "enter" => Some("\n".into()),
137 key if !is_printable_key(key) => None,
138 key => {
139 if self.modifiers.shift {
140 Some(key.to_uppercase())
141 } else {
142 Some(key.into())
143 }
144 }
145 }
146 }
147 self
148 }
149}
150
151fn is_printable_key(key: &str) -> bool {
152 match key {
153 "up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end" | "delete"
154 | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9"
155 | "f10" | "f11" | "f12" => false,
156 _ => true,
157 }
158}
159
160impl std::fmt::Display for Keystroke {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 if self.modifiers.control {
163 f.write_char('^')?;
164 }
165 if self.modifiers.alt {
166 f.write_char('โฅ')?;
167 }
168 if self.modifiers.platform {
169 #[cfg(target_os = "macos")]
170 f.write_char('โ')?;
171
172 #[cfg(target_os = "linux")]
173 f.write_char('โ')?;
174
175 #[cfg(target_os = "windows")]
176 f.write_char('โ')?;
177 }
178 if self.modifiers.shift {
179 f.write_char('โง')?;
180 }
181 let key = match self.key.as_str() {
182 "backspace" => 'โซ',
183 "up" => 'โ',
184 "down" => 'โ',
185 "left" => 'โ',
186 "right" => 'โ',
187 "tab" => 'โฅ',
188 "escape" => 'โ',
189 key => {
190 if key.len() == 1 {
191 key.chars().next().unwrap().to_ascii_uppercase()
192 } else {
193 return f.write_str(key);
194 }
195 }
196 };
197 f.write_char(key)
198 }
199}
200
201/// The state of the modifier keys at some point in time
202#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
203pub struct Modifiers {
204 /// The control key
205 pub control: bool,
206
207 /// The alt key
208 /// Sometimes also known as the 'meta' key
209 pub alt: bool,
210
211 /// The shift key
212 pub shift: bool,
213
214 /// The command key, on macos
215 /// the windows key, on windows
216 /// the super key, on linux
217 pub platform: bool,
218
219 /// The function key
220 pub function: bool,
221}
222
223impl Modifiers {
224 /// Returns true if any modifier key is pressed
225 pub fn modified(&self) -> bool {
226 self.control || self.alt || self.shift || self.platform || self.function
227 }
228
229 /// Whether the semantically 'secondary' modifier key is pressed
230 /// On macos, this is the command key
231 /// On windows and linux, this is the control key
232 pub fn secondary(&self) -> bool {
233 #[cfg(target_os = "macos")]
234 {
235 return self.platform;
236 }
237
238 #[cfg(not(target_os = "macos"))]
239 {
240 return self.control;
241 }
242 }
243
244 /// helper method for Modifiers with no modifiers
245 pub fn none() -> Modifiers {
246 Default::default()
247 }
248
249 /// helper method for Modifiers with just the command key
250 pub fn command() -> Modifiers {
251 Modifiers {
252 platform: true,
253 ..Default::default()
254 }
255 }
256
257 /// A helper method for Modifiers with just the secondary key pressed
258 pub fn secondary_key() -> Modifiers {
259 #[cfg(target_os = "macos")]
260 {
261 Modifiers {
262 platform: true,
263 ..Default::default()
264 }
265 }
266
267 #[cfg(not(target_os = "macos"))]
268 {
269 Modifiers {
270 control: true,
271 ..Default::default()
272 }
273 }
274 }
275
276 /// helper method for Modifiers with just the windows key
277 pub fn windows() -> Modifiers {
278 Modifiers {
279 platform: true,
280 ..Default::default()
281 }
282 }
283
284 /// helper method for Modifiers with just the super key
285 pub fn super_key() -> Modifiers {
286 Modifiers {
287 platform: true,
288 ..Default::default()
289 }
290 }
291
292 /// helper method for Modifiers with just control
293 pub fn control() -> Modifiers {
294 Modifiers {
295 control: true,
296 ..Default::default()
297 }
298 }
299
300 /// helper method for Modifiers with just shift
301 pub fn shift() -> Modifiers {
302 Modifiers {
303 shift: true,
304 ..Default::default()
305 }
306 }
307
308 /// helper method for Modifiers with command + shift
309 pub fn command_shift() -> Modifiers {
310 Modifiers {
311 shift: true,
312 platform: true,
313 ..Default::default()
314 }
315 }
316
317 /// helper method for Modifiers with command + shift
318 pub fn control_shift() -> Modifiers {
319 Modifiers {
320 shift: true,
321 control: true,
322 ..Default::default()
323 }
324 }
325
326 /// Checks if this Modifiers is a subset of another Modifiers
327 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
328 (other.control || !self.control)
329 && (other.alt || !self.alt)
330 && (other.shift || !self.shift)
331 && (other.platform || !self.platform)
332 && (other.function || !self.function)
333 }
334}