1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::{
4 error::Error,
5 fmt::{Display, Write},
6};
7
8use crate::{OperatingSystem, 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: &'static str) -> std::result::Result<Self, InvalidKeystrokeError> {
121 Self::parse_internal(source, None)
122 }
123
124 pub fn parse_with_os(
126 source: &str,
127 os: OperatingSystem,
128 ) -> std::result::Result<Self, InvalidKeystrokeError> {
129 Self::parse_internal(source, Some(os))
130 }
131
132 fn parse_internal(
133 source: &str,
134 os: Option<OperatingSystem>,
135 ) -> std::result::Result<Self, InvalidKeystrokeError> {
136 let mut modifiers = Modifiers::none();
137 let mut key = None;
138 let mut key_char = None;
139
140 let mut components = source.split('-').peekable();
141 while let Some(component) = components.next() {
142 if component.eq_ignore_ascii_case("ctrl") {
143 modifiers.control = true;
144 continue;
145 }
146 if component.eq_ignore_ascii_case("alt") {
147 modifiers.alt = true;
148 continue;
149 }
150 if component.eq_ignore_ascii_case("shift") {
151 modifiers.shift = true;
152 continue;
153 }
154 if component.eq_ignore_ascii_case("fn") {
155 modifiers.function = true;
156 continue;
157 }
158 if component.eq_ignore_ascii_case("secondary") {
159 let os = os.ok_or_else(|| InvalidKeystrokeError {
160 keystroke: source.to_owned(),
161 })?;
162 if os == OperatingSystem::Mac {
163 modifiers.platform = true;
164 } else {
165 modifiers.control = true;
166 };
167 continue;
168 }
169
170 let is_platform = component.eq_ignore_ascii_case("cmd")
171 || component.eq_ignore_ascii_case("super")
172 || component.eq_ignore_ascii_case("win");
173
174 if is_platform {
175 modifiers.platform = true;
176 continue;
177 }
178
179 let mut key_str = component.to_string();
180
181 if let Some(next) = components.peek() {
182 if next.is_empty() && source.ends_with('-') {
183 key = Some(String::from("-"));
184 break;
185 } else if next.len() > 1 && next.starts_with('>') {
186 key = Some(key_str);
187 key_char = Some(String::from(&next[1..]));
188 components.next();
189 } else {
190 return Err(InvalidKeystrokeError {
191 keystroke: source.to_owned(),
192 });
193 }
194 continue;
195 }
196
197 if component.len() == 1 && component.as_bytes()[0].is_ascii_uppercase() {
198 modifiers.shift = true;
200 key_str.make_ascii_lowercase();
201 } else {
202 key_str.make_ascii_lowercase()
205 }
206 key = Some(key_str);
207 }
208
209 key = key.or_else(|| {
212 use std::mem;
213 if mem::take(&mut modifiers.shift) {
215 Some("shift".to_string())
216 } else if mem::take(&mut modifiers.control) {
217 Some("control".to_string())
218 } else if mem::take(&mut modifiers.alt) {
219 Some("alt".to_string())
220 } else if mem::take(&mut modifiers.platform) {
221 Some("platform".to_string())
222 } else if mem::take(&mut modifiers.function) {
223 Some("function".to_string())
224 } else {
225 None
226 }
227 });
228
229 let key = key.ok_or_else(|| InvalidKeystrokeError {
230 keystroke: source.to_owned(),
231 })?;
232
233 Ok(Keystroke {
234 modifiers,
235 key,
236 key_char,
237 })
238 }
239
240 pub fn unparse(&self, os: OperatingSystem) -> String {
242 unparse(&self.modifiers, &self.key, os)
243 }
244
245 pub fn display(&self, os: OperatingSystem) -> KeystrokeDisplay<'_> {
247 KeystrokeDisplay {
248 modifiers: &self.modifiers,
249 key: &self.key,
250 os,
251 }
252 }
253
254 pub fn is_ime_in_progress(&self) -> bool {
257 self.key_char.is_none()
258 && (is_printable_key(&self.key) || self.key.is_empty())
259 && !(self.modifiers.platform
260 || self.modifiers.control
261 || self.modifiers.function
262 || self.modifiers.alt)
263 }
264
265 pub fn with_simulated_ime(mut self) -> Self {
269 if self.key_char.is_none()
270 && !self.modifiers.platform
271 && !self.modifiers.control
272 && !self.modifiers.function
273 && !self.modifiers.alt
274 {
275 self.key_char = match self.key.as_str() {
276 "space" => Some(" ".into()),
277 "tab" => Some("\t".into()),
278 "enter" => Some("\n".into()),
279 key if !is_printable_key(key) || key.is_empty() => None,
280 key => {
281 if self.modifiers.shift {
282 Some(key.to_uppercase())
283 } else {
284 Some(key.into())
285 }
286 }
287 }
288 }
289 self
290 }
291}
292
293impl KeybindingKeystroke {
294 pub fn display(&self, os: OperatingSystem) -> KeystrokeDisplay<'_> {
296 KeystrokeDisplay {
297 modifiers: self.modifiers(),
298 key: self.key(),
299 os,
300 }
301 }
302
303 #[cfg(target_os = "windows")]
304 #[expect(missing_docs)]
305 pub fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
306 KeybindingKeystroke {
307 inner,
308 display_modifiers,
309 display_key,
310 }
311 }
312
313 pub fn new_with_mapper(
315 inner: Keystroke,
316 use_key_equivalents: bool,
317 keyboard_mapper: &dyn PlatformKeyboardMapper,
318 ) -> Self {
319 keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
320 }
321
322 pub fn from_keystroke(keystroke: Keystroke) -> Self {
324 #[cfg(target_os = "windows")]
325 {
326 let key = keystroke.key.clone();
327 let modifiers = keystroke.modifiers;
328 KeybindingKeystroke {
329 inner: keystroke,
330 display_modifiers: modifiers,
331 display_key: key,
332 }
333 }
334 #[cfg(not(target_os = "windows"))]
335 {
336 KeybindingKeystroke { inner: keystroke }
337 }
338 }
339
340 pub fn inner(&self) -> &Keystroke {
342 &self.inner
343 }
344
345 pub fn modifiers(&self) -> &Modifiers {
352 #[cfg(target_os = "windows")]
353 {
354 &self.display_modifiers
355 }
356 #[cfg(not(target_os = "windows"))]
357 {
358 &self.inner.modifiers
359 }
360 }
361
362 pub fn key(&self) -> &str {
368 #[cfg(target_os = "windows")]
369 {
370 &self.display_key
371 }
372 #[cfg(not(target_os = "windows"))]
373 {
374 &self.inner.key
375 }
376 }
377
378 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
380 self.inner.modifiers = modifiers;
381 #[cfg(target_os = "windows")]
382 {
383 self.display_modifiers = modifiers;
384 }
385 }
386
387 pub fn set_key(&mut self, key: String) {
389 #[cfg(target_os = "windows")]
390 {
391 self.display_key = key.clone();
392 }
393 self.inner.key = key;
394 }
395
396 pub fn unparse(&self, os: OperatingSystem) -> String {
398 #[cfg(target_os = "windows")]
399 {
400 unparse(&self.display_modifiers, &self.display_key, os)
401 }
402 #[cfg(not(target_os = "windows"))]
403 {
404 unparse(&self.inner.modifiers, &self.inner.key, os)
405 }
406 }
407
408 pub fn remove_key_char(&mut self) {
410 self.inner.key_char = None;
411 }
412}
413
414fn is_printable_key(key: &str) -> bool {
415 !matches!(
416 key,
417 "f1" | "f2"
418 | "f3"
419 | "f4"
420 | "f5"
421 | "f6"
422 | "f7"
423 | "f8"
424 | "f9"
425 | "f10"
426 | "f11"
427 | "f12"
428 | "f13"
429 | "f14"
430 | "f15"
431 | "f16"
432 | "f17"
433 | "f18"
434 | "f19"
435 | "f20"
436 | "f21"
437 | "f22"
438 | "f23"
439 | "f24"
440 | "f25"
441 | "f26"
442 | "f27"
443 | "f28"
444 | "f29"
445 | "f30"
446 | "f31"
447 | "f32"
448 | "f33"
449 | "f34"
450 | "f35"
451 | "backspace"
452 | "delete"
453 | "left"
454 | "right"
455 | "up"
456 | "down"
457 | "pageup"
458 | "pagedown"
459 | "insert"
460 | "home"
461 | "end"
462 | "back"
463 | "forward"
464 | "escape"
465 )
466}
467
468pub struct KeystrokeDisplay<'a> {
470 modifiers: &'a Modifiers,
471 key: &'a str,
472 os: OperatingSystem,
473}
474
475impl std::fmt::Display for KeystrokeDisplay<'_> {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 display_modifiers(self.modifiers, self.os, f)?;
478 display_key(self.key, self.os, f)
479 }
480}
481
482#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
484pub struct Modifiers {
485 #[serde(default)]
487 pub control: bool,
488
489 #[serde(default)]
492 pub alt: bool,
493
494 #[serde(default)]
496 pub shift: bool,
497
498 #[serde(default)]
502 pub platform: bool,
503
504 #[serde(default)]
506 pub function: bool,
507}
508
509impl Modifiers {
510 pub fn modified(&self) -> bool {
512 self.control || self.alt || self.shift || self.platform || self.function
513 }
514
515 pub fn secondary(&self, cx: &crate::App) -> bool {
520 if cx.platform_info().is_mac() {
521 self.platform
522 } else {
523 self.control
524 }
525 }
526
527 pub fn number_of_modifiers(&self) -> u8 {
529 self.control as u8
530 + self.alt as u8
531 + self.shift as u8
532 + self.platform as u8
533 + self.function as u8
534 }
535
536 pub fn none() -> Modifiers {
538 Default::default()
539 }
540
541 pub fn command() -> Modifiers {
543 Modifiers {
544 platform: true,
545 ..Default::default()
546 }
547 }
548
549 pub fn secondary_key(cx: &crate::App) -> Modifiers {
551 if cx.platform_info().is_mac() {
552 Modifiers {
553 platform: true,
554 ..Default::default()
555 }
556 } else {
557 Modifiers {
558 control: true,
559 ..Default::default()
560 }
561 }
562 }
563
564 pub fn windows() -> Modifiers {
566 Modifiers {
567 platform: true,
568 ..Default::default()
569 }
570 }
571
572 pub fn super_key() -> Modifiers {
574 Modifiers {
575 platform: true,
576 ..Default::default()
577 }
578 }
579
580 pub fn control() -> Modifiers {
582 Modifiers {
583 control: true,
584 ..Default::default()
585 }
586 }
587
588 pub fn alt() -> Modifiers {
590 Modifiers {
591 alt: true,
592 ..Default::default()
593 }
594 }
595
596 pub fn shift() -> Modifiers {
598 Modifiers {
599 shift: true,
600 ..Default::default()
601 }
602 }
603
604 pub fn function() -> Modifiers {
606 Modifiers {
607 function: true,
608 ..Default::default()
609 }
610 }
611
612 pub fn command_shift() -> Modifiers {
614 Modifiers {
615 shift: true,
616 platform: true,
617 ..Default::default()
618 }
619 }
620
621 pub fn control_shift() -> Modifiers {
623 Modifiers {
624 shift: true,
625 control: true,
626 ..Default::default()
627 }
628 }
629
630 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
632 (*other & *self) == *self
633 }
634}
635
636impl std::ops::BitOr for Modifiers {
637 type Output = Self;
638
639 fn bitor(mut self, other: Self) -> Self::Output {
640 self |= other;
641 self
642 }
643}
644
645impl std::ops::BitOrAssign for Modifiers {
646 fn bitor_assign(&mut self, other: Self) {
647 self.control |= other.control;
648 self.alt |= other.alt;
649 self.shift |= other.shift;
650 self.platform |= other.platform;
651 self.function |= other.function;
652 }
653}
654
655impl std::ops::BitXor for Modifiers {
656 type Output = Self;
657 fn bitxor(mut self, rhs: Self) -> Self::Output {
658 self ^= rhs;
659 self
660 }
661}
662
663impl std::ops::BitXorAssign for Modifiers {
664 fn bitxor_assign(&mut self, other: Self) {
665 self.control ^= other.control;
666 self.alt ^= other.alt;
667 self.shift ^= other.shift;
668 self.platform ^= other.platform;
669 self.function ^= other.function;
670 }
671}
672
673impl std::ops::BitAnd for Modifiers {
674 type Output = Self;
675 fn bitand(mut self, rhs: Self) -> Self::Output {
676 self &= rhs;
677 self
678 }
679}
680
681impl std::ops::BitAndAssign for Modifiers {
682 fn bitand_assign(&mut self, other: Self) {
683 self.control &= other.control;
684 self.alt &= other.alt;
685 self.shift &= other.shift;
686 self.platform &= other.platform;
687 self.function &= other.function;
688 }
689}
690
691#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
693pub struct Capslock {
694 #[serde(default)]
696 pub on: bool,
697}
698
699impl AsKeystroke for Keystroke {
700 fn as_keystroke(&self) -> &Keystroke {
701 self
702 }
703}
704
705impl AsKeystroke for KeybindingKeystroke {
706 fn as_keystroke(&self) -> &Keystroke {
707 &self.inner
708 }
709}
710
711impl<T: AsKeystroke + ?Sized> AsKeystroke for &T {
712 fn as_keystroke(&self) -> &Keystroke {
713 (**self).as_keystroke()
714 }
715}
716
717fn display_modifiers(
718 modifiers: &Modifiers,
719 os: OperatingSystem,
720 f: &mut std::fmt::Formatter<'_>,
721) -> std::fmt::Result {
722 if modifiers.control {
723 f.write_str(if os == OperatingSystem::Mac {
724 "^"
725 } else {
726 "ctrl-"
727 })?;
728 }
729 if modifiers.alt {
730 f.write_str(if os == OperatingSystem::Mac {
731 "⌥"
732 } else {
733 "alt-"
734 })?;
735 }
736 if modifiers.platform {
737 f.write_str(match os {
738 OperatingSystem::Mac => "⌘",
739 OperatingSystem::Windows => "⊞",
740 OperatingSystem::Linux | OperatingSystem::FreeBsd => "❖",
741 OperatingSystem::Ios | OperatingSystem::Android | OperatingSystem::Unknown => "cmd-",
742 })?;
743 }
744 if modifiers.shift {
745 f.write_str(if os == OperatingSystem::Mac {
746 "⇧"
747 } else {
748 "shift-"
749 })?;
750 }
751 Ok(())
752}
753
754fn display_key(
755 key: &str,
756 os: OperatingSystem,
757 f: &mut std::fmt::Formatter<'_>,
758) -> std::fmt::Result {
759 let key = match key {
760 "backspace" if os == OperatingSystem::Mac => '⌫',
761 "up" if os == OperatingSystem::Mac => '↑',
762 "down" if os == OperatingSystem::Mac => '↓',
763 "left" if os == OperatingSystem::Mac => '←',
764 "right" if os == OperatingSystem::Mac => '→',
765 "tab" if os == OperatingSystem::Mac => '⇥',
766 "escape" if os == OperatingSystem::Mac => '⎋',
767 "shift" if os == OperatingSystem::Mac => '⇧',
768 "control" if os == OperatingSystem::Mac => '⌃',
769 "alt" if os == OperatingSystem::Mac => '⌥',
770 "platform" if os == OperatingSystem::Mac => '⌘',
771
772 key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
773 key => return f.write_str(key),
774 };
775 f.write_char(key)
776}
777
778#[inline]
779fn unparse(modifiers: &Modifiers, key: &str, os: OperatingSystem) -> String {
780 let mut result = String::new();
781 if modifiers.function {
782 result.push_str("fn-");
783 }
784 if modifiers.control {
785 result.push_str("ctrl-");
786 }
787 if modifiers.alt {
788 result.push_str("alt-");
789 }
790 if modifiers.platform {
791 result.push_str(match os {
792 OperatingSystem::Windows => "win-",
793 OperatingSystem::Linux
794 | OperatingSystem::FreeBsd
795 | OperatingSystem::Android
796 | OperatingSystem::Unknown => "super-",
797 OperatingSystem::Mac | OperatingSystem::Ios => "cmd-",
798 });
799 }
800 if modifiers.shift {
801 result.push_str("shift-");
802 }
803 result.push_str(&key);
804 result
805}