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