1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::{
4 error::Error,
5 fmt::{Display, Write},
6};
7
8use crate::PlatformKeyboardMapper;
9
10pub trait AsKeystroke {
12 fn as_keystroke(&self) -> &Keystroke;
14}
15
16#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
18pub struct Keystroke {
19 pub modifiers: Modifiers,
21
22 pub key: String,
28
29 pub key_char: Option<String>,
33}
34
35#[derive(Debug, Clone, Eq, PartialEq, Hash)]
37pub struct KeybindingKeystroke {
38 inner: Keystroke,
40 #[cfg(target_os = "windows")]
42 display_modifiers: Modifiers,
43 #[cfg(target_os = "windows")]
45 display_key: String,
46}
47
48#[derive(Debug)]
51pub struct InvalidKeystrokeError {
52 pub keystroke: String,
54}
55
56impl Error for InvalidKeystrokeError {}
57
58impl Display for InvalidKeystrokeError {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(
61 f,
62 "Invalid keystroke \"{}\". {}",
63 self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE
64 )
65 }
66}
67
68pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \
70 (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \
71 followed by a key, separated by `-`.";
72
73impl Keystroke {
74 pub fn should_match(&self, target: &KeybindingKeystroke) -> bool {
83 #[cfg(not(target_os = "windows"))]
84 if let Some(key_char) = self
85 .key_char
86 .as_ref()
87 .filter(|key_char| key_char != &&self.key)
88 {
89 let ime_modifiers = Modifiers {
90 control: self.modifiers.control,
91 platform: self.modifiers.platform,
92 ..Default::default()
93 };
94
95 if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers {
96 return true;
97 }
98 }
99
100 #[cfg(target_os = "windows")]
101 if let Some(key_char) = self
102 .key_char
103 .as_ref()
104 .filter(|key_char| key_char != &&self.key)
105 {
106 if &target.inner.key == key_char && target.inner.modifiers == Modifiers::none() {
108 return true;
109 }
110 }
111
112 target.inner.modifiers == self.modifiers && target.inner.key == self.key
113 }
114
115 pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
121 let mut modifiers = Modifiers::none();
122 let mut key = None;
123 let mut key_char = None;
124
125 let mut components = source.split('-').peekable();
126 while let Some(component) = components.next() {
127 if component.eq_ignore_ascii_case("ctrl") {
128 modifiers.control = true;
129 continue;
130 }
131 if component.eq_ignore_ascii_case("alt") {
132 modifiers.alt = true;
133 continue;
134 }
135 if component.eq_ignore_ascii_case("shift") {
136 modifiers.shift = true;
137 continue;
138 }
139 if component.eq_ignore_ascii_case("fn") {
140 modifiers.function = true;
141 continue;
142 }
143 if component.eq_ignore_ascii_case("secondary") {
144 if cfg!(target_os = "macos") {
145 modifiers.platform = true;
146 } else {
147 modifiers.control = true;
148 };
149 continue;
150 }
151
152 let is_platform = component.eq_ignore_ascii_case("cmd")
153 || component.eq_ignore_ascii_case("super")
154 || component.eq_ignore_ascii_case("win");
155
156 if is_platform {
157 modifiers.platform = true;
158 continue;
159 }
160
161 let mut key_str = component.to_string();
162
163 if let Some(next) = components.peek() {
164 if next.is_empty() && source.ends_with('-') {
165 key = Some(String::from("-"));
166 break;
167 } else if next.len() > 1 && next.starts_with('>') {
168 key = Some(key_str);
169 key_char = Some(String::from(&next[1..]));
170 components.next();
171 } else {
172 return Err(InvalidKeystrokeError {
173 keystroke: source.to_owned(),
174 });
175 }
176 continue;
177 }
178
179 if component.len() == 1 && component.as_bytes()[0].is_ascii_uppercase() {
180 modifiers.shift = true;
182 key_str.make_ascii_lowercase();
183 } else {
184 key_str.make_ascii_lowercase()
187 }
188 key = Some(key_str);
189 }
190
191 key = key.or_else(|| {
194 use std::mem;
195 if mem::take(&mut modifiers.shift) {
197 Some("shift".to_string())
198 } else if mem::take(&mut modifiers.control) {
199 Some("control".to_string())
200 } else if mem::take(&mut modifiers.alt) {
201 Some("alt".to_string())
202 } else if mem::take(&mut modifiers.platform) {
203 Some("platform".to_string())
204 } else if mem::take(&mut modifiers.function) {
205 Some("function".to_string())
206 } else {
207 None
208 }
209 });
210
211 let key = key.ok_or_else(|| InvalidKeystrokeError {
212 keystroke: source.to_owned(),
213 })?;
214
215 Ok(Keystroke {
216 modifiers,
217 key,
218 key_char,
219 })
220 }
221
222 pub fn unparse(&self) -> String {
224 unparse(&self.modifiers, &self.key)
225 }
226
227 pub fn is_ime_in_progress(&self) -> bool {
230 self.key_char.is_none()
231 && (is_printable_key(&self.key) || self.key.is_empty())
232 && !(self.modifiers.platform
233 || self.modifiers.control
234 || self.modifiers.function
235 || self.modifiers.alt)
236 }
237
238 pub fn with_simulated_ime(mut self) -> Self {
242 if self.key_char.is_none()
243 && !self.modifiers.platform
244 && !self.modifiers.control
245 && !self.modifiers.function
246 && !self.modifiers.alt
247 {
248 self.key_char = match self.key.as_str() {
249 "space" => Some(" ".into()),
250 "tab" => Some("\t".into()),
251 "enter" => Some("\n".into()),
252 key if !is_printable_key(key) || key.is_empty() => None,
253 key => {
254 if self.modifiers.shift {
255 Some(key.to_uppercase())
256 } else {
257 Some(key.into())
258 }
259 }
260 }
261 }
262 self
263 }
264}
265
266impl KeybindingKeystroke {
267 #[cfg(target_os = "windows")]
268 #[doc(hidden)]
269 pub fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
270 KeybindingKeystroke {
271 inner,
272 display_modifiers,
273 display_key,
274 }
275 }
276
277 pub fn new_with_mapper(
279 inner: Keystroke,
280 use_key_equivalents: bool,
281 keyboard_mapper: &dyn PlatformKeyboardMapper,
282 ) -> Self {
283 keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
284 }
285
286 pub fn from_keystroke(keystroke: Keystroke) -> Self {
288 #[cfg(target_os = "windows")]
289 {
290 let key = keystroke.key.clone();
291 let modifiers = keystroke.modifiers;
292 KeybindingKeystroke {
293 inner: keystroke,
294 display_modifiers: modifiers,
295 display_key: key,
296 }
297 }
298 #[cfg(not(target_os = "windows"))]
299 {
300 KeybindingKeystroke { inner: keystroke }
301 }
302 }
303
304 pub fn inner(&self) -> &Keystroke {
306 &self.inner
307 }
308
309 pub fn modifiers(&self) -> &Modifiers {
316 #[cfg(target_os = "windows")]
317 {
318 &self.display_modifiers
319 }
320 #[cfg(not(target_os = "windows"))]
321 {
322 &self.inner.modifiers
323 }
324 }
325
326 pub fn key(&self) -> &str {
332 #[cfg(target_os = "windows")]
333 {
334 &self.display_key
335 }
336 #[cfg(not(target_os = "windows"))]
337 {
338 &self.inner.key
339 }
340 }
341
342 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
344 self.inner.modifiers = modifiers;
345 #[cfg(target_os = "windows")]
346 {
347 self.display_modifiers = modifiers;
348 }
349 }
350
351 pub fn set_key(&mut self, key: String) {
353 #[cfg(target_os = "windows")]
354 {
355 self.display_key = key.clone();
356 }
357 self.inner.key = key;
358 }
359
360 pub fn unparse(&self) -> String {
362 #[cfg(target_os = "windows")]
363 {
364 unparse(&self.display_modifiers, &self.display_key)
365 }
366 #[cfg(not(target_os = "windows"))]
367 {
368 unparse(&self.inner.modifiers, &self.inner.key)
369 }
370 }
371
372 pub fn remove_key_char(&mut self) {
374 self.inner.key_char = None;
375 }
376}
377
378fn is_printable_key(key: &str) -> bool {
379 !matches!(
380 key,
381 "f1" | "f2"
382 | "f3"
383 | "f4"
384 | "f5"
385 | "f6"
386 | "f7"
387 | "f8"
388 | "f9"
389 | "f10"
390 | "f11"
391 | "f12"
392 | "f13"
393 | "f14"
394 | "f15"
395 | "f16"
396 | "f17"
397 | "f18"
398 | "f19"
399 | "f20"
400 | "f21"
401 | "f22"
402 | "f23"
403 | "f24"
404 | "f25"
405 | "f26"
406 | "f27"
407 | "f28"
408 | "f29"
409 | "f30"
410 | "f31"
411 | "f32"
412 | "f33"
413 | "f34"
414 | "f35"
415 | "backspace"
416 | "delete"
417 | "left"
418 | "right"
419 | "up"
420 | "down"
421 | "pageup"
422 | "pagedown"
423 | "insert"
424 | "home"
425 | "end"
426 | "back"
427 | "forward"
428 | "escape"
429 )
430}
431
432impl std::fmt::Display for Keystroke {
433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434 display_modifiers(&self.modifiers, f)?;
435 display_key(&self.key, f)
436 }
437}
438
439impl std::fmt::Display for KeybindingKeystroke {
440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441 display_modifiers(self.modifiers(), f)?;
442 display_key(self.key(), f)
443 }
444}
445
446#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
448pub struct Modifiers {
449 #[serde(default)]
451 pub control: bool,
452
453 #[serde(default)]
456 pub alt: bool,
457
458 #[serde(default)]
460 pub shift: bool,
461
462 #[serde(default)]
466 pub platform: bool,
467
468 #[serde(default)]
470 pub function: bool,
471}
472
473impl Modifiers {
474 pub fn modified(&self) -> bool {
476 self.control || self.alt || self.shift || self.platform || self.function
477 }
478
479 pub fn secondary(&self, cx: &crate::App) -> bool {
484 if cx.platform_info().is_mac() {
485 self.platform
486 } else {
487 self.control
488 }
489 }
490
491 pub fn number_of_modifiers(&self) -> u8 {
493 self.control as u8
494 + self.alt as u8
495 + self.shift as u8
496 + self.platform as u8
497 + self.function as u8
498 }
499
500 pub fn none() -> Modifiers {
502 Default::default()
503 }
504
505 pub fn command() -> Modifiers {
507 Modifiers {
508 platform: true,
509 ..Default::default()
510 }
511 }
512
513 pub fn secondary_key(cx: &crate::App) -> Modifiers {
515 if cx.platform_info().is_mac() {
516 Modifiers {
517 platform: true,
518 ..Default::default()
519 }
520 } else {
521 Modifiers {
522 control: true,
523 ..Default::default()
524 }
525 }
526 }
527
528 pub fn windows() -> Modifiers {
530 Modifiers {
531 platform: true,
532 ..Default::default()
533 }
534 }
535
536 pub fn super_key() -> Modifiers {
538 Modifiers {
539 platform: true,
540 ..Default::default()
541 }
542 }
543
544 pub fn control() -> Modifiers {
546 Modifiers {
547 control: true,
548 ..Default::default()
549 }
550 }
551
552 pub fn alt() -> Modifiers {
554 Modifiers {
555 alt: true,
556 ..Default::default()
557 }
558 }
559
560 pub fn shift() -> Modifiers {
562 Modifiers {
563 shift: true,
564 ..Default::default()
565 }
566 }
567
568 pub fn command_shift() -> Modifiers {
570 Modifiers {
571 shift: true,
572 platform: true,
573 ..Default::default()
574 }
575 }
576
577 pub fn control_shift() -> Modifiers {
579 Modifiers {
580 shift: true,
581 control: true,
582 ..Default::default()
583 }
584 }
585
586 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
588 (*other & *self) == *self
589 }
590}
591
592impl std::ops::BitOr for Modifiers {
593 type Output = Self;
594
595 fn bitor(mut self, other: Self) -> Self::Output {
596 self |= other;
597 self
598 }
599}
600
601impl std::ops::BitOrAssign for Modifiers {
602 fn bitor_assign(&mut self, other: Self) {
603 self.control |= other.control;
604 self.alt |= other.alt;
605 self.shift |= other.shift;
606 self.platform |= other.platform;
607 self.function |= other.function;
608 }
609}
610
611impl std::ops::BitXor for Modifiers {
612 type Output = Self;
613 fn bitxor(mut self, rhs: Self) -> Self::Output {
614 self ^= rhs;
615 self
616 }
617}
618
619impl std::ops::BitXorAssign for Modifiers {
620 fn bitxor_assign(&mut self, other: Self) {
621 self.control ^= other.control;
622 self.alt ^= other.alt;
623 self.shift ^= other.shift;
624 self.platform ^= other.platform;
625 self.function ^= other.function;
626 }
627}
628
629impl std::ops::BitAnd for Modifiers {
630 type Output = Self;
631 fn bitand(mut self, rhs: Self) -> Self::Output {
632 self &= rhs;
633 self
634 }
635}
636
637impl std::ops::BitAndAssign for Modifiers {
638 fn bitand_assign(&mut self, other: Self) {
639 self.control &= other.control;
640 self.alt &= other.alt;
641 self.shift &= other.shift;
642 self.platform &= other.platform;
643 self.function &= other.function;
644 }
645}
646
647#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
649pub struct Capslock {
650 #[serde(default)]
652 pub on: bool,
653}
654
655impl AsKeystroke for Keystroke {
656 fn as_keystroke(&self) -> &Keystroke {
657 self
658 }
659}
660
661impl AsKeystroke for KeybindingKeystroke {
662 fn as_keystroke(&self) -> &Keystroke {
663 &self.inner
664 }
665}
666
667impl<T: AsKeystroke + ?Sized> AsKeystroke for &T {
668 fn as_keystroke(&self) -> &Keystroke {
669 (**self).as_keystroke()
670 }
671}
672
673fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
674 if modifiers.control {
675 #[cfg(target_os = "macos")]
676 f.write_char('^')?;
677
678 #[cfg(not(target_os = "macos"))]
679 write!(f, "ctrl-")?;
680 }
681 if modifiers.alt {
682 #[cfg(target_os = "macos")]
683 f.write_char('⌥')?;
684
685 #[cfg(not(target_os = "macos"))]
686 write!(f, "alt-")?;
687 }
688 if modifiers.platform {
689 #[cfg(target_os = "macos")]
690 f.write_char('⌘')?;
691
692 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
693 f.write_char('❖')?;
694
695 #[cfg(target_os = "windows")]
696 f.write_char('⊞')?;
697 }
698 if modifiers.shift {
699 #[cfg(target_os = "macos")]
700 f.write_char('⇧')?;
701
702 #[cfg(not(target_os = "macos"))]
703 write!(f, "shift-")?;
704 }
705 Ok(())
706}
707
708fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709 let key = match key {
710 #[cfg(target_os = "macos")]
711 "backspace" => '⌫',
712 #[cfg(target_os = "macos")]
713 "up" => '↑',
714 #[cfg(target_os = "macos")]
715 "down" => '↓',
716 #[cfg(target_os = "macos")]
717 "left" => '←',
718 #[cfg(target_os = "macos")]
719 "right" => '→',
720 #[cfg(target_os = "macos")]
721 "tab" => '⇥',
722 #[cfg(target_os = "macos")]
723 "escape" => '⎋',
724 #[cfg(target_os = "macos")]
725 "shift" => '⇧',
726 #[cfg(target_os = "macos")]
727 "control" => '⌃',
728 #[cfg(target_os = "macos")]
729 "alt" => '⌥',
730 #[cfg(target_os = "macos")]
731 "platform" => '⌘',
732
733 key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
734 key => return f.write_str(key),
735 };
736 f.write_char(key)
737}
738
739#[inline]
740fn unparse(modifiers: &Modifiers, key: &str) -> String {
741 let mut result = String::new();
742 if modifiers.function {
743 result.push_str("fn-");
744 }
745 if modifiers.control {
746 result.push_str("ctrl-");
747 }
748 if modifiers.alt {
749 result.push_str("alt-");
750 }
751 if modifiers.platform {
752 #[cfg(target_os = "macos")]
753 result.push_str("cmd-");
754
755 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
756 result.push_str("super-");
757
758 #[cfg(target_os = "windows")]
759 result.push_str("win-");
760 }
761 if modifiers.shift {
762 result.push_str("shift-");
763 }
764 result.push_str(&key);
765 result
766}