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