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