1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::{
4 error::Error,
5 fmt::{Display, Write},
6};
7
8use crate::PlatformKeyboardMapper;
9
10/// This is a helper trait so that we can simplify the implementation of some functions
11pub trait AsKeystroke {
12 /// Returns the GPUI representation of the keystroke.
13 fn as_keystroke(&self) -> &Keystroke;
14}
15
16/// A keystroke and associated metadata generated by the platform
17#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
18pub struct Keystroke {
19 /// the state of the modifier keys at the time the keystroke was generated
20 pub modifiers: Modifiers,
21
22 /// key is the character printed on the key that was pressed
23 /// e.g. for option-s, key is "s"
24 /// On layouts that do not have ascii keys (e.g. Thai)
25 /// this will be the ASCII-equivalent character (q instead of เน),
26 /// and the typed character will be present in key_char.
27 pub key: String,
28
29 /// key_char is the character that could have been typed when
30 /// this binding was pressed.
31 /// e.g. for s this is "s", for option-s "ร", and cmd-s None
32 pub key_char: Option<String>,
33}
34
35/// Represents a keystroke that can be used in keybindings and displayed to the user.
36#[derive(Debug, Clone, Eq, PartialEq, Hash)]
37pub struct KeybindingKeystroke {
38 /// The GPUI representation of the keystroke.
39 inner: Keystroke,
40 /// The modifiers to display.
41 #[cfg(target_os = "windows")]
42 display_modifiers: Modifiers,
43 /// The key to display.
44 #[cfg(target_os = "windows")]
45 display_key: String,
46}
47
48/// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
49/// markdown to display it.
50#[derive(Debug)]
51pub struct InvalidKeystrokeError {
52 /// The invalid keystroke.
53 pub keystroke: String,
54}
55
56impl Error for InvalidKeystrokeError {}
57
58impl Display for InvalidKeystrokeError {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(
61 f,
62 "Invalid keystroke \"{}\". {}",
63 self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE
64 )
65 }
66}
67
68/// Sentence explaining what keystroke parser expects, starting with "Expected ..."
69pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \
70 (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \
71 followed by a key, separated by `-`.";
72
73impl Keystroke {
74 /// When matching a key we cannot know whether the user intended to type
75 /// the key_char or the key itself. On some non-US keyboards keys we use in our
76 /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
77 /// and on some keyboards the IME handler converts a sequence of keys into a
78 /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
79 ///
80 /// This method assumes that `self` was typed and `target' is in the keymap, and checks
81 /// both possibilities for self against the target.
82 pub fn should_match(&self, target: &KeybindingKeystroke) -> bool {
83 #[cfg(not(target_os = "windows"))]
84 if let Some(key_char) = self
85 .key_char
86 .as_ref()
87 .filter(|key_char| key_char != &&self.key)
88 {
89 let ime_modifiers = Modifiers {
90 control: self.modifiers.control,
91 platform: self.modifiers.platform,
92 ..Default::default()
93 };
94
95 if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers {
96 return true;
97 }
98 }
99
100 #[cfg(target_os = "windows")]
101 if let Some(key_char) = self
102 .key_char
103 .as_ref()
104 .filter(|key_char| key_char != &&self.key)
105 {
106 // On Windows, if key_char is set, then the typed keystroke produced the key_char
107 if &target.inner.key == key_char && target.inner.modifiers == Modifiers::none() {
108 return true;
109 }
110 }
111
112 target.inner.modifiers == self.modifiers && target.inner.key == self.key
113 }
114
115 /// key syntax is:
116 /// [secondary-][ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
117 /// key_char syntax is only used for generating test events,
118 /// secondary means "cmd" on macOS and "ctrl" on other platforms
119 /// when matching a key with an key_char set will be matched without it.
120 pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
121 let mut modifiers = Modifiers::none();
122 let mut key = None;
123 let mut key_char = None;
124
125 let mut components = source.split('-').peekable();
126 while let Some(component) = components.next() {
127 if component.eq_ignore_ascii_case("ctrl") {
128 modifiers.control = true;
129 continue;
130 }
131 if component.eq_ignore_ascii_case("alt") {
132 modifiers.alt = true;
133 continue;
134 }
135 if component.eq_ignore_ascii_case("shift") {
136 modifiers.shift = true;
137 continue;
138 }
139 if component.eq_ignore_ascii_case("fn") {
140 modifiers.function = true;
141 continue;
142 }
143 if component.eq_ignore_ascii_case("secondary") {
144 if cfg!(target_os = "macos") {
145 modifiers.platform = true;
146 } else {
147 modifiers.control = true;
148 };
149 continue;
150 }
151
152 let is_platform = component.eq_ignore_ascii_case("cmd")
153 || component.eq_ignore_ascii_case("super")
154 || component.eq_ignore_ascii_case("win");
155
156 if is_platform {
157 modifiers.platform = true;
158 continue;
159 }
160
161 let mut key_str = component.to_string();
162
163 if let Some(next) = components.peek() {
164 if next.is_empty() && source.ends_with('-') {
165 key = Some(String::from("-"));
166 break;
167 } else if next.len() > 1 && next.starts_with('>') {
168 key = Some(key_str);
169 key_char = Some(String::from(&next[1..]));
170 components.next();
171 } else {
172 return Err(InvalidKeystrokeError {
173 keystroke: source.to_owned(),
174 });
175 }
176 continue;
177 }
178
179 if component.len() == 1 && component.as_bytes()[0].is_ascii_uppercase() {
180 // Convert to shift + lowercase char
181 modifiers.shift = true;
182 key_str.make_ascii_lowercase();
183 } else {
184 // convert ascii chars to lowercase so that named keys like "tab" and "enter"
185 // are accepted case insensitively and stored how we expect so they are matched properly
186 key_str.make_ascii_lowercase()
187 }
188 key = Some(key_str);
189 }
190
191 // Allow for the user to specify a keystroke modifier as the key itself
192 // This sets the `key` to the modifier, and disables the modifier
193 key = key.or_else(|| {
194 use std::mem;
195 // std::mem::take clears bool incase its true
196 if mem::take(&mut modifiers.shift) {
197 Some("shift".to_string())
198 } else if mem::take(&mut modifiers.control) {
199 Some("control".to_string())
200 } else if mem::take(&mut modifiers.alt) {
201 Some("alt".to_string())
202 } else if mem::take(&mut modifiers.platform) {
203 Some("platform".to_string())
204 } else if mem::take(&mut modifiers.function) {
205 Some("function".to_string())
206 } else {
207 None
208 }
209 });
210
211 let key = key.ok_or_else(|| InvalidKeystrokeError {
212 keystroke: source.to_owned(),
213 })?;
214
215 Ok(Keystroke {
216 modifiers,
217 key,
218 key_char,
219 })
220 }
221
222 /// Produces a representation of this key that Parse can understand.
223 pub fn unparse(&self) -> String {
224 unparse(&self.modifiers, &self.key)
225 }
226
227 /// Returns true if this keystroke left
228 /// the ime system in an incomplete state.
229 pub fn is_ime_in_progress(&self) -> bool {
230 self.key_char.is_none()
231 && (is_printable_key(&self.key) || self.key.is_empty())
232 && !(self.modifiers.platform
233 || self.modifiers.control
234 || self.modifiers.function
235 || self.modifiers.alt)
236 }
237
238 /// Returns a new keystroke with the key_char filled.
239 /// This is used for dispatch_keystroke where we want users to
240 /// be able to simulate typing "space", etc.
241 pub fn with_simulated_ime(mut self) -> Self {
242 if self.key_char.is_none()
243 && !self.modifiers.platform
244 && !self.modifiers.control
245 && !self.modifiers.function
246 && !self.modifiers.alt
247 {
248 self.key_char = match self.key.as_str() {
249 "space" => Some(" ".into()),
250 "tab" => Some("\t".into()),
251 "enter" => Some("\n".into()),
252 key if !is_printable_key(key) || key.is_empty() => None,
253 key => {
254 if self.modifiers.shift {
255 Some(key.to_uppercase())
256 } else {
257 Some(key.into())
258 }
259 }
260 }
261 }
262 self
263 }
264}
265
266impl KeybindingKeystroke {
267 #[cfg(target_os = "windows")]
268 pub(crate) fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
269 KeybindingKeystroke {
270 inner,
271 display_modifiers,
272 display_key,
273 }
274 }
275
276 /// Create a new keybinding keystroke from the given keystroke using the given keyboard mapper.
277 pub fn new_with_mapper(
278 inner: Keystroke,
279 use_key_equivalents: bool,
280 keyboard_mapper: &dyn PlatformKeyboardMapper,
281 ) -> Self {
282 keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
283 }
284
285 /// Create a new keybinding keystroke from the given keystroke, without any platform-specific mapping.
286 pub fn from_keystroke(keystroke: Keystroke) -> Self {
287 #[cfg(target_os = "windows")]
288 {
289 let key = keystroke.key.clone();
290 let modifiers = keystroke.modifiers;
291 KeybindingKeystroke {
292 inner: keystroke,
293 display_modifiers: modifiers,
294 display_key: key,
295 }
296 }
297 #[cfg(not(target_os = "windows"))]
298 {
299 KeybindingKeystroke { inner: keystroke }
300 }
301 }
302
303 /// Returns the GPUI representation of the keystroke.
304 pub fn inner(&self) -> &Keystroke {
305 &self.inner
306 }
307
308 /// Returns the modifiers.
309 ///
310 /// Platform-specific behavior:
311 /// - On macOS and Linux, this modifiers is the same as `inner.modifiers`, which is the GPUI representation of the keystroke.
312 /// - On Windows, this modifiers is the display modifiers, for example, a `ctrl-@` keystroke will have `inner.modifiers` as
313 /// `Modifiers::control()` and `display_modifiers` as `Modifiers::control_shift()`.
314 pub fn modifiers(&self) -> &Modifiers {
315 #[cfg(target_os = "windows")]
316 {
317 &self.display_modifiers
318 }
319 #[cfg(not(target_os = "windows"))]
320 {
321 &self.inner.modifiers
322 }
323 }
324
325 /// Returns the key.
326 ///
327 /// Platform-specific behavior:
328 /// - On macOS and Linux, this key is the same as `inner.key`, which is the GPUI representation of the keystroke.
329 /// - On Windows, this key is the display key, for example, a `ctrl-@` keystroke will have `inner.key` as `@` and `display_key` as `2`.
330 pub fn key(&self) -> &str {
331 #[cfg(target_os = "windows")]
332 {
333 &self.display_key
334 }
335 #[cfg(not(target_os = "windows"))]
336 {
337 &self.inner.key
338 }
339 }
340
341 /// Sets the modifiers. On Windows this modifies both `inner.modifiers` and `display_modifiers`.
342 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
343 self.inner.modifiers = modifiers;
344 #[cfg(target_os = "windows")]
345 {
346 self.display_modifiers = modifiers;
347 }
348 }
349
350 /// Sets the key. On Windows this modifies both `inner.key` and `display_key`.
351 pub fn set_key(&mut self, key: String) {
352 #[cfg(target_os = "windows")]
353 {
354 self.display_key = key.clone();
355 }
356 self.inner.key = key;
357 }
358
359 /// Produces a representation of this key that Parse can understand.
360 pub fn unparse(&self) -> String {
361 #[cfg(target_os = "windows")]
362 {
363 unparse(&self.display_modifiers, &self.display_key)
364 }
365 #[cfg(not(target_os = "windows"))]
366 {
367 unparse(&self.inner.modifiers, &self.inner.key)
368 }
369 }
370
371 /// Removes the key_char
372 pub fn remove_key_char(&mut self) {
373 self.inner.key_char = None;
374 }
375}
376
377fn is_printable_key(key: &str) -> bool {
378 !matches!(
379 key,
380 "f1" | "f2"
381 | "f3"
382 | "f4"
383 | "f5"
384 | "f6"
385 | "f7"
386 | "f8"
387 | "f9"
388 | "f10"
389 | "f11"
390 | "f12"
391 | "f13"
392 | "f14"
393 | "f15"
394 | "f16"
395 | "f17"
396 | "f18"
397 | "f19"
398 | "f20"
399 | "f21"
400 | "f22"
401 | "f23"
402 | "f24"
403 | "f25"
404 | "f26"
405 | "f27"
406 | "f28"
407 | "f29"
408 | "f30"
409 | "f31"
410 | "f32"
411 | "f33"
412 | "f34"
413 | "f35"
414 | "backspace"
415 | "delete"
416 | "left"
417 | "right"
418 | "up"
419 | "down"
420 | "pageup"
421 | "pagedown"
422 | "insert"
423 | "home"
424 | "end"
425 | "back"
426 | "forward"
427 | "escape"
428 )
429}
430
431impl std::fmt::Display for Keystroke {
432 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433 display_modifiers(&self.modifiers, f)?;
434 display_key(&self.key, f)
435 }
436}
437
438impl std::fmt::Display for KeybindingKeystroke {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 display_modifiers(self.modifiers(), f)?;
441 display_key(self.key(), f)
442 }
443}
444
445/// The state of the modifier keys at some point in time
446#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
447pub struct Modifiers {
448 /// The control key
449 #[serde(default)]
450 pub control: bool,
451
452 /// The alt key
453 /// Sometimes also known as the 'meta' key
454 #[serde(default)]
455 pub alt: bool,
456
457 /// The shift key
458 #[serde(default)]
459 pub shift: bool,
460
461 /// The command key, on macos
462 /// the windows key, on windows
463 /// the super key, on linux
464 #[serde(default)]
465 pub platform: bool,
466
467 /// The function key
468 #[serde(default)]
469 pub function: bool,
470}
471
472impl Modifiers {
473 /// Returns whether any modifier key is pressed.
474 pub fn modified(&self) -> bool {
475 self.control || self.alt || self.shift || self.platform || self.function
476 }
477
478 /// Whether the semantically 'secondary' modifier key is pressed.
479 ///
480 /// On macOS, this is the command key.
481 /// On Linux and Windows, this is the control key.
482 pub fn secondary(&self) -> bool {
483 #[cfg(target_os = "macos")]
484 {
485 self.platform
486 }
487
488 #[cfg(not(target_os = "macos"))]
489 {
490 self.control
491 }
492 }
493
494 /// Returns how many modifier keys are pressed.
495 pub fn number_of_modifiers(&self) -> u8 {
496 self.control as u8
497 + self.alt as u8
498 + self.shift as u8
499 + self.platform as u8
500 + self.function as u8
501 }
502
503 /// Returns [`Modifiers`] with no modifiers.
504 pub fn none() -> Modifiers {
505 Default::default()
506 }
507
508 /// Returns [`Modifiers`] with just the command key.
509 pub fn command() -> Modifiers {
510 Modifiers {
511 platform: true,
512 ..Default::default()
513 }
514 }
515
516 /// A Returns [`Modifiers`] with just the secondary key pressed.
517 pub fn secondary_key() -> Modifiers {
518 #[cfg(target_os = "macos")]
519 {
520 Modifiers {
521 platform: true,
522 ..Default::default()
523 }
524 }
525
526 #[cfg(not(target_os = "macos"))]
527 {
528 Modifiers {
529 control: true,
530 ..Default::default()
531 }
532 }
533 }
534
535 /// Returns [`Modifiers`] with just the windows key.
536 pub fn windows() -> Modifiers {
537 Modifiers {
538 platform: true,
539 ..Default::default()
540 }
541 }
542
543 /// Returns [`Modifiers`] with just the super key.
544 pub fn super_key() -> Modifiers {
545 Modifiers {
546 platform: true,
547 ..Default::default()
548 }
549 }
550
551 /// Returns [`Modifiers`] with just control.
552 pub fn control() -> Modifiers {
553 Modifiers {
554 control: true,
555 ..Default::default()
556 }
557 }
558
559 /// Returns [`Modifiers`] with just alt.
560 pub fn alt() -> Modifiers {
561 Modifiers {
562 alt: true,
563 ..Default::default()
564 }
565 }
566
567 /// Returns [`Modifiers`] with just shift.
568 pub fn shift() -> Modifiers {
569 Modifiers {
570 shift: true,
571 ..Default::default()
572 }
573 }
574
575 /// Returns [`Modifiers`] with command + shift.
576 pub fn command_shift() -> Modifiers {
577 Modifiers {
578 shift: true,
579 platform: true,
580 ..Default::default()
581 }
582 }
583
584 /// Returns [`Modifiers`] with command + shift.
585 pub fn control_shift() -> Modifiers {
586 Modifiers {
587 shift: true,
588 control: true,
589 ..Default::default()
590 }
591 }
592
593 /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
594 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
595 (*other & *self) == *self
596 }
597}
598
599impl std::ops::BitOr for Modifiers {
600 type Output = Self;
601
602 fn bitor(mut self, other: Self) -> Self::Output {
603 self |= other;
604 self
605 }
606}
607
608impl std::ops::BitOrAssign for Modifiers {
609 fn bitor_assign(&mut self, other: Self) {
610 self.control |= other.control;
611 self.alt |= other.alt;
612 self.shift |= other.shift;
613 self.platform |= other.platform;
614 self.function |= other.function;
615 }
616}
617
618impl std::ops::BitXor for Modifiers {
619 type Output = Self;
620 fn bitxor(mut self, rhs: Self) -> Self::Output {
621 self ^= rhs;
622 self
623 }
624}
625
626impl std::ops::BitXorAssign for Modifiers {
627 fn bitxor_assign(&mut self, other: Self) {
628 self.control ^= other.control;
629 self.alt ^= other.alt;
630 self.shift ^= other.shift;
631 self.platform ^= other.platform;
632 self.function ^= other.function;
633 }
634}
635
636impl std::ops::BitAnd for Modifiers {
637 type Output = Self;
638 fn bitand(mut self, rhs: Self) -> Self::Output {
639 self &= rhs;
640 self
641 }
642}
643
644impl std::ops::BitAndAssign for Modifiers {
645 fn bitand_assign(&mut self, other: Self) {
646 self.control &= other.control;
647 self.alt &= other.alt;
648 self.shift &= other.shift;
649 self.platform &= other.platform;
650 self.function &= other.function;
651 }
652}
653
654/// The state of the capslock key at some point in time
655#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
656pub struct Capslock {
657 /// The capslock key is on
658 #[serde(default)]
659 pub on: bool,
660}
661
662impl AsKeystroke for Keystroke {
663 fn as_keystroke(&self) -> &Keystroke {
664 self
665 }
666}
667
668impl AsKeystroke for KeybindingKeystroke {
669 fn as_keystroke(&self) -> &Keystroke {
670 &self.inner
671 }
672}
673
674fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
675 if modifiers.control {
676 #[cfg(target_os = "macos")]
677 f.write_char('^')?;
678
679 #[cfg(not(target_os = "macos"))]
680 write!(f, "ctrl-")?;
681 }
682 if modifiers.alt {
683 #[cfg(target_os = "macos")]
684 f.write_char('โฅ')?;
685
686 #[cfg(not(target_os = "macos"))]
687 write!(f, "alt-")?;
688 }
689 if modifiers.platform {
690 #[cfg(target_os = "macos")]
691 f.write_char('โ')?;
692
693 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
694 f.write_char('โ')?;
695
696 #[cfg(target_os = "windows")]
697 f.write_char('โ')?;
698 }
699 if modifiers.shift {
700 #[cfg(target_os = "macos")]
701 f.write_char('โง')?;
702
703 #[cfg(not(target_os = "macos"))]
704 write!(f, "shift-")?;
705 }
706 Ok(())
707}
708
709fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710 let key = match key {
711 #[cfg(target_os = "macos")]
712 "backspace" => 'โซ',
713 #[cfg(target_os = "macos")]
714 "up" => 'โ',
715 #[cfg(target_os = "macos")]
716 "down" => 'โ',
717 #[cfg(target_os = "macos")]
718 "left" => 'โ',
719 #[cfg(target_os = "macos")]
720 "right" => 'โ',
721 #[cfg(target_os = "macos")]
722 "tab" => 'โฅ',
723 #[cfg(target_os = "macos")]
724 "escape" => 'โ',
725 #[cfg(target_os = "macos")]
726 "shift" => 'โง',
727 #[cfg(target_os = "macos")]
728 "control" => 'โ',
729 #[cfg(target_os = "macos")]
730 "alt" => 'โฅ',
731 #[cfg(target_os = "macos")]
732 "platform" => 'โ',
733
734 key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
735 key => return f.write_str(key),
736 };
737 f.write_char(key)
738}
739
740#[inline]
741fn unparse(modifiers: &Modifiers, key: &str) -> String {
742 let mut result = String::new();
743 if modifiers.function {
744 result.push_str("fn-");
745 }
746 if modifiers.control {
747 result.push_str("ctrl-");
748 }
749 if modifiers.alt {
750 result.push_str("alt-");
751 }
752 if modifiers.platform {
753 #[cfg(target_os = "macos")]
754 result.push_str("cmd-");
755
756 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
757 result.push_str("super-");
758
759 #[cfg(target_os = "windows")]
760 result.push_str("win-");
761 }
762 if modifiers.shift {
763 result.push_str("shift-");
764 }
765 result.push_str(&key);
766 result
767}