1use anyhow::anyhow;
2use serde::Deserialize;
3use smallvec::SmallVec;
4use std::fmt::Write;
5
6/// A keystroke and associated metadata generated by the platform
7#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
8pub struct Keystroke {
9 /// the state of the modifier keys at the time the keystroke was generated
10 pub modifiers: Modifiers,
11
12 /// key is the character printed on the key that was pressed
13 /// e.g. for option-s, key is "s"
14 pub key: String,
15
16 /// ime_key is the character inserted by the IME engine when that key was pressed.
17 /// e.g. for option-s, ime_key is "ร"
18 pub ime_key: Option<String>,
19}
20
21impl Keystroke {
22 /// When matching a key we cannot know whether the user intended to type
23 /// the ime_key or the key itself. On some non-US keyboards keys we use in our
24 /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
25 /// and on some keyboards the IME handler converts a sequence of keys into a
26 /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
27 ///
28 /// This method generates a list of potential keystroke candidates that could be matched
29 /// against when resolving a keybinding.
30 pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
31 let mut possibilities = SmallVec::new();
32 match self.ime_key.as_ref() {
33 None => possibilities.push(self.clone()),
34 Some(ime_key) => {
35 possibilities.push(Keystroke {
36 modifiers: Modifiers {
37 control: self.modifiers.control,
38 alt: false,
39 shift: false,
40 command: false,
41 function: false,
42 },
43 key: ime_key.to_string(),
44 ime_key: None,
45 });
46 possibilities.push(Keystroke {
47 ime_key: None,
48 ..self.clone()
49 });
50 }
51 }
52 possibilities
53 }
54
55 /// key syntax is:
56 /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
57 /// ime_key syntax is only used for generating test events,
58 /// when matching a key with an ime_key set will be matched without it.
59 pub fn parse(source: &str) -> anyhow::Result<Self> {
60 let mut control = false;
61 let mut alt = false;
62 let mut shift = false;
63 let mut command = false;
64 let mut function = false;
65 let mut key = None;
66 let mut ime_key = None;
67
68 let mut components = source.split('-').peekable();
69 while let Some(component) = components.next() {
70 match component {
71 "ctrl" => control = true,
72 "alt" => alt = true,
73 "shift" => shift = true,
74 "cmd" => command = true,
75 "fn" => function = true,
76 _ => {
77 if let Some(next) = components.peek() {
78 if next.is_empty() && source.ends_with('-') {
79 key = Some(String::from("-"));
80 break;
81 } else if next.len() > 1 && next.starts_with('>') {
82 key = Some(String::from(component));
83 ime_key = Some(String::from(&next[1..]));
84 components.next();
85 } else {
86 return Err(anyhow!("Invalid keystroke `{}`", source));
87 }
88 } else {
89 key = Some(String::from(component));
90 }
91 }
92 }
93 }
94
95 let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
96
97 Ok(Keystroke {
98 modifiers: Modifiers {
99 control,
100 alt,
101 shift,
102 command,
103 function,
104 },
105 key,
106 ime_key,
107 })
108 }
109}
110
111impl std::fmt::Display for Keystroke {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 if self.modifiers.control {
114 f.write_char('^')?;
115 }
116 if self.modifiers.alt {
117 f.write_char('โฅ')?;
118 }
119 if self.modifiers.command {
120 f.write_char('โ')?;
121 }
122 if self.modifiers.shift {
123 f.write_char('โง')?;
124 }
125 let key = match self.key.as_str() {
126 "backspace" => 'โซ',
127 "up" => 'โ',
128 "down" => 'โ',
129 "left" => 'โ',
130 "right" => 'โ',
131 "tab" => 'โฅ',
132 "escape" => 'โ',
133 key => {
134 if key.len() == 1 {
135 key.chars().next().unwrap().to_ascii_uppercase()
136 } else {
137 return f.write_str(key);
138 }
139 }
140 };
141 f.write_char(key)
142 }
143}
144
145/// The state of the modifier keys at some point in time
146#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
147pub struct Modifiers {
148 /// The control key
149 pub control: bool,
150
151 /// The alt key
152 /// Sometimes also known as the 'meta' key
153 pub alt: bool,
154
155 /// The shift key
156 pub shift: bool,
157
158 /// The command key, on macos
159 /// the windows key, on windows
160 pub command: bool,
161
162 /// The function key
163 pub function: bool,
164}
165
166impl Modifiers {
167 /// Returns true if any modifier key is pressed
168 pub fn modified(&self) -> bool {
169 self.control || self.alt || self.shift || self.command || self.function
170 }
171}