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
277impl KeybindingKeystroke {
278 /// TODO:
279 pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
280 let keystroke = Keystroke::parse(source)?;
281 let Keystroke {
282 mut modifiers, key, ..
283 } = keystroke.clone();
284 let (key, modifiers) = temp_keyboard_mapper(key, modifiers);
285 Ok(KeybindingKeystroke {
286 inner: keystroke,
287 modifiers,
288 key,
289 })
290 }
291
292 /// TODO:
293 pub fn to_string(&self) -> String {
294 let keystroke = Keystroke {
295 modifiers: self.modifiers,
296 key: self.key.clone(),
297 key_char: None,
298 };
299 keystroke.to_string()
300 }
301}
302
303fn temp_keyboard_mapper(key: String, mut modifiers: Modifiers) -> (String, Modifiers) {
304 match key.as_str() {
305 "~" => {
306 modifiers.shift = true;
307 ("`".to_string(), modifiers)
308 }
309 "!" => {
310 modifiers.shift = true;
311 ("1".to_string(), modifiers)
312 }
313 "@" => {
314 modifiers.shift = true;
315 ("2".to_string(), modifiers)
316 }
317 "#" => {
318 modifiers.shift = true;
319 ("3".to_string(), modifiers)
320 }
321 "$" => {
322 modifiers.shift = true;
323 ("4".to_string(), modifiers)
324 }
325 "%" => {
326 modifiers.shift = true;
327 ("5".to_string(), modifiers)
328 }
329 "^" => {
330 modifiers.shift = true;
331 ("6".to_string(), modifiers)
332 }
333 "&" => {
334 modifiers.shift = true;
335 ("7".to_string(), modifiers)
336 }
337 "*" => {
338 modifiers.shift = true;
339 ("8".to_string(), modifiers)
340 }
341 "(" => {
342 modifiers.shift = true;
343 ("9".to_string(), modifiers)
344 }
345 ")" => {
346 modifiers.shift = true;
347 ("0".to_string(), modifiers)
348 }
349 "_" => {
350 modifiers.shift = true;
351 ("-".to_string(), modifiers)
352 }
353 "+" => {
354 modifiers.shift = true;
355 ("=".to_string(), modifiers)
356 }
357 "{" => {
358 modifiers.shift = true;
359 ("[".to_string(), modifiers)
360 }
361 "}" => {
362 modifiers.shift = true;
363 ("]".to_string(), modifiers)
364 }
365 "|" => {
366 modifiers.shift = true;
367 ("\\".to_string(), modifiers)
368 }
369 ":" => {
370 modifiers.shift = true;
371 (";".to_string(), modifiers)
372 }
373 "\"" => {
374 modifiers.shift = true;
375 ("'".to_string(), modifiers)
376 }
377 "<" => {
378 modifiers.shift = true;
379 (",".to_string(), modifiers)
380 }
381 ">" => {
382 modifiers.shift = true;
383 (">".to_string(), modifiers)
384 }
385 "?" => {
386 modifiers.shift = true;
387 ("/".to_string(), modifiers)
388 }
389 _ => (key, modifiers),
390 }
391}
392
393fn is_printable_key(key: &str) -> bool {
394 !matches!(
395 key,
396 "f1" | "f2"
397 | "f3"
398 | "f4"
399 | "f5"
400 | "f6"
401 | "f7"
402 | "f8"
403 | "f9"
404 | "f10"
405 | "f11"
406 | "f12"
407 | "f13"
408 | "f14"
409 | "f15"
410 | "f16"
411 | "f17"
412 | "f18"
413 | "f19"
414 | "f20"
415 | "f21"
416 | "f22"
417 | "f23"
418 | "f24"
419 | "f25"
420 | "f26"
421 | "f27"
422 | "f28"
423 | "f29"
424 | "f30"
425 | "f31"
426 | "f32"
427 | "f33"
428 | "f34"
429 | "f35"
430 | "backspace"
431 | "delete"
432 | "left"
433 | "right"
434 | "up"
435 | "down"
436 | "pageup"
437 | "pagedown"
438 | "insert"
439 | "home"
440 | "end"
441 | "back"
442 | "forward"
443 | "escape"
444 )
445}
446
447impl std::fmt::Display for Keystroke {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 if self.modifiers.control {
450 #[cfg(target_os = "macos")]
451 f.write_char('^')?;
452
453 #[cfg(not(target_os = "macos"))]
454 write!(f, "ctrl-")?;
455 }
456 if self.modifiers.alt {
457 #[cfg(target_os = "macos")]
458 f.write_char('โฅ')?;
459
460 #[cfg(not(target_os = "macos"))]
461 write!(f, "alt-")?;
462 }
463 if self.modifiers.platform {
464 #[cfg(target_os = "macos")]
465 f.write_char('โ')?;
466
467 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
468 f.write_char('โ')?;
469
470 #[cfg(target_os = "windows")]
471 f.write_char('โ')?;
472 }
473 if self.modifiers.shift {
474 #[cfg(target_os = "macos")]
475 f.write_char('โง')?;
476
477 #[cfg(not(target_os = "macos"))]
478 write!(f, "shift-")?;
479 }
480 let key = match self.key.as_str() {
481 #[cfg(target_os = "macos")]
482 "backspace" => 'โซ',
483 #[cfg(target_os = "macos")]
484 "up" => 'โ',
485 #[cfg(target_os = "macos")]
486 "down" => 'โ',
487 #[cfg(target_os = "macos")]
488 "left" => 'โ',
489 #[cfg(target_os = "macos")]
490 "right" => 'โ',
491 #[cfg(target_os = "macos")]
492 "tab" => 'โฅ',
493 #[cfg(target_os = "macos")]
494 "escape" => 'โ',
495 #[cfg(target_os = "macos")]
496 "shift" => 'โง',
497 #[cfg(target_os = "macos")]
498 "control" => 'โ',
499 #[cfg(target_os = "macos")]
500 "alt" => 'โฅ',
501 #[cfg(target_os = "macos")]
502 "platform" => 'โ',
503
504 key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
505 key => return f.write_str(key),
506 };
507 f.write_char(key)
508 }
509}
510
511/// The state of the modifier keys at some point in time
512#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
513pub struct Modifiers {
514 /// The control key
515 #[serde(default)]
516 pub control: bool,
517
518 /// The alt key
519 /// Sometimes also known as the 'meta' key
520 #[serde(default)]
521 pub alt: bool,
522
523 /// The shift key
524 #[serde(default)]
525 pub shift: bool,
526
527 /// The command key, on macos
528 /// the windows key, on windows
529 /// the super key, on linux
530 #[serde(default)]
531 pub platform: bool,
532
533 /// The function key
534 #[serde(default)]
535 pub function: bool,
536}
537
538impl Modifiers {
539 /// Returns whether any modifier key is pressed.
540 pub fn modified(&self) -> bool {
541 self.control || self.alt || self.shift || self.platform || self.function
542 }
543
544 /// Whether the semantically 'secondary' modifier key is pressed.
545 ///
546 /// On macOS, this is the command key.
547 /// On Linux and Windows, this is the control key.
548 pub fn secondary(&self) -> bool {
549 #[cfg(target_os = "macos")]
550 {
551 self.platform
552 }
553
554 #[cfg(not(target_os = "macos"))]
555 {
556 self.control
557 }
558 }
559
560 /// Returns how many modifier keys are pressed.
561 pub fn number_of_modifiers(&self) -> u8 {
562 self.control as u8
563 + self.alt as u8
564 + self.shift as u8
565 + self.platform as u8
566 + self.function as u8
567 }
568
569 /// Returns [`Modifiers`] with no modifiers.
570 pub fn none() -> Modifiers {
571 Default::default()
572 }
573
574 /// Returns [`Modifiers`] with just the command key.
575 pub fn command() -> Modifiers {
576 Modifiers {
577 platform: true,
578 ..Default::default()
579 }
580 }
581
582 /// A Returns [`Modifiers`] with just the secondary key pressed.
583 pub fn secondary_key() -> Modifiers {
584 #[cfg(target_os = "macos")]
585 {
586 Modifiers {
587 platform: true,
588 ..Default::default()
589 }
590 }
591
592 #[cfg(not(target_os = "macos"))]
593 {
594 Modifiers {
595 control: true,
596 ..Default::default()
597 }
598 }
599 }
600
601 /// Returns [`Modifiers`] with just the windows key.
602 pub fn windows() -> Modifiers {
603 Modifiers {
604 platform: true,
605 ..Default::default()
606 }
607 }
608
609 /// Returns [`Modifiers`] with just the super key.
610 pub fn super_key() -> Modifiers {
611 Modifiers {
612 platform: true,
613 ..Default::default()
614 }
615 }
616
617 /// Returns [`Modifiers`] with just control.
618 pub fn control() -> Modifiers {
619 Modifiers {
620 control: true,
621 ..Default::default()
622 }
623 }
624
625 /// Returns [`Modifiers`] with just alt.
626 pub fn alt() -> Modifiers {
627 Modifiers {
628 alt: true,
629 ..Default::default()
630 }
631 }
632
633 /// Returns [`Modifiers`] with just shift.
634 pub fn shift() -> Modifiers {
635 Modifiers {
636 shift: true,
637 ..Default::default()
638 }
639 }
640
641 /// Returns [`Modifiers`] with command + shift.
642 pub fn command_shift() -> Modifiers {
643 Modifiers {
644 shift: true,
645 platform: true,
646 ..Default::default()
647 }
648 }
649
650 /// Returns [`Modifiers`] with command + shift.
651 pub fn control_shift() -> Modifiers {
652 Modifiers {
653 shift: true,
654 control: true,
655 ..Default::default()
656 }
657 }
658
659 /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
660 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
661 (other.control || !self.control)
662 && (other.alt || !self.alt)
663 && (other.shift || !self.shift)
664 && (other.platform || !self.platform)
665 && (other.function || !self.function)
666 }
667}
668
669/// The state of the capslock key at some point in time
670#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
671pub struct Capslock {
672 /// The capslock key is on
673 #[serde(default)]
674 pub on: bool,
675}