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