1use serde::Deserialize;
2use std::{
3 error::Error,
4 fmt::{Display, Write},
5};
6
7/// A keystroke and associated metadata generated by the platform
8#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
9pub struct Keystroke {
10 /// the state of the modifier keys at the time the keystroke was generated
11 pub modifiers: Modifiers,
12
13 /// key is the character printed on the key that was pressed
14 /// e.g. for option-s, key is "s"
15 pub key: String,
16
17 /// key_char is the character that could have been typed when
18 /// this binding was pressed.
19 /// e.g. for s this is "s", for option-s "ร", and cmd-s None
20 pub key_char: Option<String>,
21}
22
23/// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
24/// markdown to display it.
25#[derive(Debug)]
26pub struct InvalidKeystrokeError {
27 /// The invalid keystroke.
28 pub keystroke: String,
29}
30
31impl Error for InvalidKeystrokeError {}
32
33impl Display for InvalidKeystrokeError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(
36 f,
37 "Invalid keystroke \"{}\". {}",
38 self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE
39 )
40 }
41}
42
43/// Sentence explaining what keystroke parser expects, starting with "Expected ..."
44pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \
45 (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \
46 followed by a key, separated by `-`.";
47
48impl Keystroke {
49 /// When matching a key we cannot know whether the user intended to type
50 /// the key_char or the key itself. On some non-US keyboards keys we use in our
51 /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
52 /// and on some keyboards the IME handler converts a sequence of keys into a
53 /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
54 ///
55 /// This method assumes that `self` was typed and `target' is in the keymap, and checks
56 /// both possibilities for self against the target.
57 pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
58 if let Some(key_char) = self
59 .key_char
60 .as_ref()
61 .filter(|key_char| key_char != &&self.key)
62 {
63 let ime_modifiers = Modifiers {
64 control: self.modifiers.control,
65 platform: self.modifiers.platform,
66 ..Default::default()
67 };
68
69 if &target.key == key_char && target.modifiers == ime_modifiers {
70 return true;
71 }
72 }
73
74 target.modifiers == self.modifiers && target.key == self.key
75 }
76
77 /// key syntax is:
78 /// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
79 /// key_char syntax is only used for generating test events,
80 /// when matching a key with an key_char set will be matched without it.
81 pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
82 let mut control = false;
83 let mut alt = false;
84 let mut shift = false;
85 let mut platform = false;
86 let mut function = false;
87 let mut key = None;
88 let mut key_char = None;
89
90 let mut components = source.split('-').peekable();
91 while let Some(component) = components.next() {
92 match component {
93 "ctrl" => control = true,
94 "alt" => alt = true,
95 "shift" => shift = true,
96 "fn" => function = true,
97 "cmd" | "super" | "win" => platform = true,
98 _ => {
99 if let Some(next) = components.peek() {
100 if next.is_empty() && source.ends_with('-') {
101 key = Some(String::from("-"));
102 break;
103 } else if next.len() > 1 && next.starts_with('>') {
104 key = Some(String::from(component));
105 key_char = Some(String::from(&next[1..]));
106 components.next();
107 } else {
108 return Err(InvalidKeystrokeError {
109 keystroke: source.to_owned(),
110 });
111 }
112 } else {
113 key = Some(String::from(component));
114 }
115 }
116 }
117 }
118
119 // Allow for the user to specify a keystroke modifier as the key itself
120 // This sets the `key` to the modifier, and disables the modifier
121 if key.is_none() {
122 if shift {
123 key = Some("shift".to_string());
124 shift = false;
125 } else if control {
126 key = Some("control".to_string());
127 control = false;
128 } else if alt {
129 key = Some("alt".to_string());
130 alt = false;
131 } else if platform {
132 key = Some("platform".to_string());
133 platform = false;
134 } else if function {
135 key = Some("function".to_string());
136 function = false;
137 }
138 }
139
140 let key = key.ok_or_else(|| InvalidKeystrokeError {
141 keystroke: source.to_owned(),
142 })?;
143
144 Ok(Keystroke {
145 modifiers: Modifiers {
146 control,
147 alt,
148 shift,
149 platform,
150 function,
151 },
152 key,
153 key_char: key_char,
154 })
155 }
156
157 /// Produces a representation of this key that Parse can understand.
158 pub fn unparse(&self) -> String {
159 let mut str = String::new();
160 if self.modifiers.function {
161 str.push_str("fn-");
162 }
163 if self.modifiers.control {
164 str.push_str("ctrl-");
165 }
166 if self.modifiers.alt {
167 str.push_str("alt-");
168 }
169 if self.modifiers.platform {
170 #[cfg(target_os = "macos")]
171 str.push_str("cmd-");
172
173 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
174 str.push_str("super-");
175
176 #[cfg(target_os = "windows")]
177 str.push_str("win-");
178 }
179 if self.modifiers.shift {
180 str.push_str("shift-");
181 }
182 str.push_str(&self.key);
183 str
184 }
185
186 /// Returns true if this keystroke left
187 /// the ime system in an incomplete state.
188 pub fn is_ime_in_progress(&self) -> bool {
189 self.key_char.is_none()
190 && (is_printable_key(&self.key) || self.key.is_empty())
191 && !(self.modifiers.platform
192 || self.modifiers.control
193 || self.modifiers.function
194 || self.modifiers.alt)
195 }
196
197 /// Returns a new keystroke with the key_char filled.
198 /// This is used for dispatch_keystroke where we want users to
199 /// be able to simulate typing "space", etc.
200 pub fn with_simulated_ime(mut self) -> Self {
201 if self.key_char.is_none()
202 && !self.modifiers.platform
203 && !self.modifiers.control
204 && !self.modifiers.function
205 && !self.modifiers.alt
206 {
207 self.key_char = match self.key.as_str() {
208 "space" => Some(" ".into()),
209 "tab" => Some("\t".into()),
210 "enter" => Some("\n".into()),
211 key if !is_printable_key(key) || key.is_empty() => None,
212 key => {
213 if self.modifiers.shift {
214 Some(key.to_uppercase())
215 } else {
216 Some(key.into())
217 }
218 }
219 }
220 }
221 self
222 }
223}
224
225fn is_printable_key(key: &str) -> bool {
226 !matches!(
227 key,
228 "f1" | "f2"
229 | "f3"
230 | "f4"
231 | "f5"
232 | "f6"
233 | "f7"
234 | "f8"
235 | "f9"
236 | "f10"
237 | "f11"
238 | "f12"
239 | "f13"
240 | "f14"
241 | "f15"
242 | "f16"
243 | "f17"
244 | "f18"
245 | "f19"
246 | "backspace"
247 | "delete"
248 | "left"
249 | "right"
250 | "up"
251 | "down"
252 | "pageup"
253 | "pagedown"
254 | "insert"
255 | "home"
256 | "end"
257 | "back"
258 | "forward"
259 | "escape"
260 )
261}
262
263impl std::fmt::Display for Keystroke {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 if self.modifiers.control {
266 f.write_char('^')?;
267 }
268 if self.modifiers.alt {
269 f.write_char('โฅ')?;
270 }
271 if self.modifiers.platform {
272 #[cfg(target_os = "macos")]
273 f.write_char('โ')?;
274
275 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
276 f.write_char('โ')?;
277
278 #[cfg(target_os = "windows")]
279 f.write_char('โ')?;
280 }
281 if self.modifiers.shift {
282 f.write_char('โง')?;
283 }
284 let key = match self.key.as_str() {
285 "backspace" => 'โซ',
286 "up" => 'โ',
287 "down" => 'โ',
288 "left" => 'โ',
289 "right" => 'โ',
290 "tab" => 'โฅ',
291 "escape" => 'โ',
292 "shift" => 'โง',
293 "control" => 'โ',
294 "alt" => 'โฅ',
295 "platform" => 'โ',
296 key => {
297 if key.len() == 1 {
298 key.chars().next().unwrap().to_ascii_uppercase()
299 } else {
300 return f.write_str(key);
301 }
302 }
303 };
304 f.write_char(key)
305 }
306}
307
308/// The state of the modifier keys at some point in time
309#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
310pub struct Modifiers {
311 /// The control key
312 pub control: bool,
313
314 /// The alt key
315 /// Sometimes also known as the 'meta' key
316 pub alt: bool,
317
318 /// The shift key
319 pub shift: bool,
320
321 /// The command key, on macos
322 /// the windows key, on windows
323 /// the super key, on linux
324 pub platform: bool,
325
326 /// The function key
327 pub function: bool,
328}
329
330impl Modifiers {
331 /// Returns whether any modifier key is pressed.
332 pub fn modified(&self) -> bool {
333 self.control || self.alt || self.shift || self.platform || self.function
334 }
335
336 /// Whether the semantically 'secondary' modifier key is pressed.
337 ///
338 /// On macOS, this is the command key.
339 /// On Linux and Windows, this is the control key.
340 pub fn secondary(&self) -> bool {
341 #[cfg(target_os = "macos")]
342 {
343 self.platform
344 }
345
346 #[cfg(not(target_os = "macos"))]
347 {
348 self.control
349 }
350 }
351
352 /// Returns how many modifier keys are pressed.
353 pub fn number_of_modifiers(&self) -> u8 {
354 self.control as u8
355 + self.alt as u8
356 + self.shift as u8
357 + self.platform as u8
358 + self.function as u8
359 }
360
361 /// Returns [`Modifiers`] with no modifiers.
362 pub fn none() -> Modifiers {
363 Default::default()
364 }
365
366 /// Returns [`Modifiers`] with just the command key.
367 pub fn command() -> Modifiers {
368 Modifiers {
369 platform: true,
370 ..Default::default()
371 }
372 }
373
374 /// A Returns [`Modifiers`] with just the secondary key pressed.
375 pub fn secondary_key() -> Modifiers {
376 #[cfg(target_os = "macos")]
377 {
378 Modifiers {
379 platform: true,
380 ..Default::default()
381 }
382 }
383
384 #[cfg(not(target_os = "macos"))]
385 {
386 Modifiers {
387 control: true,
388 ..Default::default()
389 }
390 }
391 }
392
393 /// Returns [`Modifiers`] with just the windows key.
394 pub fn windows() -> Modifiers {
395 Modifiers {
396 platform: true,
397 ..Default::default()
398 }
399 }
400
401 /// Returns [`Modifiers`] with just the super key.
402 pub fn super_key() -> Modifiers {
403 Modifiers {
404 platform: true,
405 ..Default::default()
406 }
407 }
408
409 /// Returns [`Modifiers`] with just control.
410 pub fn control() -> Modifiers {
411 Modifiers {
412 control: true,
413 ..Default::default()
414 }
415 }
416
417 /// Returns [`Modifiers`] with just alt.
418 pub fn alt() -> Modifiers {
419 Modifiers {
420 alt: true,
421 ..Default::default()
422 }
423 }
424
425 /// Returns [`Modifiers`] with just shift.
426 pub fn shift() -> Modifiers {
427 Modifiers {
428 shift: true,
429 ..Default::default()
430 }
431 }
432
433 /// Returns [`Modifiers`] with command + shift.
434 pub fn command_shift() -> Modifiers {
435 Modifiers {
436 shift: true,
437 platform: true,
438 ..Default::default()
439 }
440 }
441
442 /// Returns [`Modifiers`] with command + shift.
443 pub fn control_shift() -> Modifiers {
444 Modifiers {
445 shift: true,
446 control: true,
447 ..Default::default()
448 }
449 }
450
451 /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
452 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
453 (other.control || !self.control)
454 && (other.alt || !self.alt)
455 && (other.shift || !self.shift)
456 && (other.platform || !self.platform)
457 && (other.function || !self.function)
458 }
459}