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/// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
25/// markdown to display it.
26#[derive(Debug)]
27pub struct InvalidKeystrokeError {
28 /// The invalid keystroke.
29 pub keystroke: String,
30}
31
32impl Error for InvalidKeystrokeError {}
33
34impl Display for InvalidKeystrokeError {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(
37 f,
38 "Invalid keystroke \"{}\". {}",
39 self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE
40 )
41 }
42}
43
44/// Sentence explaining what keystroke parser expects, starting with "Expected ..."
45pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \
46 (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \
47 followed by a key, separated by `-`.";
48
49impl Keystroke {
50 /// When matching a key we cannot know whether the user intended to type
51 /// the key_char or the key itself. On some non-US keyboards keys we use in our
52 /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
53 /// and on some keyboards the IME handler converts a sequence of keys into a
54 /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
55 ///
56 /// This method assumes that `self` was typed and `target' is in the keymap, and checks
57 /// both possibilities for self against the target.
58 pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
59 if let Some(key_char) = self
60 .key_char
61 .as_ref()
62 .filter(|key_char| key_char != &&self.key)
63 {
64 let ime_modifiers = Modifiers {
65 control: self.modifiers.control,
66 platform: self.modifiers.platform,
67 ..Default::default()
68 };
69
70 if &target.key == key_char && target.modifiers == ime_modifiers {
71 return true;
72 }
73 }
74
75 target.modifiers == self.modifiers && target.key == self.key
76 }
77
78 /// key syntax is:
79 /// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
80 /// key_char syntax is only used for generating test events,
81 /// when matching a key with an key_char set will be matched without it.
82 pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
83 let mut control = false;
84 let mut alt = false;
85 let mut shift = false;
86 let mut platform = false;
87 let mut function = false;
88 let mut key = None;
89 let mut key_char = None;
90
91 let mut components = source.split('-').peekable();
92 while let Some(component) = components.next() {
93 match component {
94 "ctrl" => control = true,
95 "alt" => alt = true,
96 "shift" => shift = true,
97 "fn" => function = true,
98 "cmd" | "super" | "win" => platform = true,
99 _ => {
100 if let Some(next) = components.peek() {
101 if next.is_empty() && source.ends_with('-') {
102 key = Some(String::from("-"));
103 break;
104 } else if next.len() > 1 && next.starts_with('>') {
105 key = Some(String::from(component));
106 key_char = Some(String::from(&next[1..]));
107 components.next();
108 } else {
109 return Err(InvalidKeystrokeError {
110 keystroke: source.to_owned(),
111 });
112 }
113 } else {
114 key = Some(String::from(component));
115 }
116 }
117 }
118 }
119
120 // Allow for the user to specify a keystroke modifier as the key itself
121 // This sets the `key` to the modifier, and disables the modifier
122 if key.is_none() {
123 if shift {
124 key = Some("shift".to_string());
125 shift = false;
126 } else if control {
127 key = Some("control".to_string());
128 control = false;
129 } else if alt {
130 key = Some("alt".to_string());
131 alt = false;
132 } else if platform {
133 key = Some("platform".to_string());
134 platform = false;
135 } else if function {
136 key = Some("function".to_string());
137 function = false;
138 }
139 }
140
141 let key = key.ok_or_else(|| InvalidKeystrokeError {
142 keystroke: source.to_owned(),
143 })?;
144
145 Ok(Keystroke {
146 modifiers: Modifiers {
147 control,
148 alt,
149 shift,
150 platform,
151 function,
152 },
153 key,
154 key_char: key_char,
155 })
156 }
157
158 /// Produces a representation of this key that Parse can understand.
159 pub fn unparse(&self) -> String {
160 let mut str = String::new();
161 if self.modifiers.function {
162 str.push_str("fn-");
163 }
164 if self.modifiers.control {
165 str.push_str("ctrl-");
166 }
167 if self.modifiers.alt {
168 str.push_str("alt-");
169 }
170 if self.modifiers.platform {
171 #[cfg(target_os = "macos")]
172 str.push_str("cmd-");
173
174 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
175 str.push_str("super-");
176
177 #[cfg(target_os = "windows")]
178 str.push_str("win-");
179 }
180 if self.modifiers.shift {
181 str.push_str("shift-");
182 }
183 str.push_str(&self.key);
184 str
185 }
186
187 /// Returns true if this keystroke left
188 /// the ime system in an incomplete state.
189 pub fn is_ime_in_progress(&self) -> bool {
190 self.key_char.is_none()
191 && (is_printable_key(&self.key) || self.key.is_empty())
192 && !(self.modifiers.platform
193 || self.modifiers.control
194 || self.modifiers.function
195 || self.modifiers.alt)
196 }
197
198 /// Returns a new keystroke with the key_char filled.
199 /// This is used for dispatch_keystroke where we want users to
200 /// be able to simulate typing "space", etc.
201 pub fn with_simulated_ime(mut self) -> Self {
202 if self.key_char.is_none()
203 && !self.modifiers.platform
204 && !self.modifiers.control
205 && !self.modifiers.function
206 && !self.modifiers.alt
207 {
208 self.key_char = match self.key.as_str() {
209 "space" => Some(" ".into()),
210 "tab" => Some("\t".into()),
211 "enter" => Some("\n".into()),
212 key if !is_printable_key(key) || key.is_empty() => None,
213 key => {
214 if self.modifiers.shift {
215 Some(key.to_uppercase())
216 } else {
217 Some(key.into())
218 }
219 }
220 }
221 }
222 self
223 }
224}
225
226fn is_printable_key(key: &str) -> bool {
227 !matches!(
228 key,
229 "f1" | "f2"
230 | "f3"
231 | "f4"
232 | "f5"
233 | "f6"
234 | "f7"
235 | "f8"
236 | "f9"
237 | "f10"
238 | "f11"
239 | "f12"
240 | "f13"
241 | "f14"
242 | "f15"
243 | "f16"
244 | "f17"
245 | "f18"
246 | "f19"
247 | "backspace"
248 | "delete"
249 | "left"
250 | "right"
251 | "up"
252 | "down"
253 | "pageup"
254 | "pagedown"
255 | "insert"
256 | "home"
257 | "end"
258 | "back"
259 | "forward"
260 | "escape"
261 )
262}
263
264impl std::fmt::Display for Keystroke {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 if self.modifiers.control {
267 f.write_char('^')?;
268 }
269 if self.modifiers.alt {
270 f.write_char('โฅ')?;
271 }
272 if self.modifiers.platform {
273 #[cfg(target_os = "macos")]
274 f.write_char('โ')?;
275
276 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
277 f.write_char('โ')?;
278
279 #[cfg(target_os = "windows")]
280 f.write_char('โ')?;
281 }
282 if self.modifiers.shift {
283 f.write_char('โง')?;
284 }
285 let key = match self.key.as_str() {
286 "backspace" => 'โซ',
287 "up" => 'โ',
288 "down" => 'โ',
289 "left" => 'โ',
290 "right" => 'โ',
291 "tab" => 'โฅ',
292 "escape" => 'โ',
293 "shift" => 'โง',
294 "control" => 'โ',
295 "alt" => 'โฅ',
296 "platform" => 'โ',
297 key => {
298 if key.len() == 1 {
299 key.chars().next().unwrap().to_ascii_uppercase()
300 } else {
301 return f.write_str(key);
302 }
303 }
304 };
305 f.write_char(key)
306 }
307}
308
309/// The state of the modifier keys at some point in time
310#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
311pub struct Modifiers {
312 /// The control key
313 #[serde(default)]
314 pub control: bool,
315
316 /// The alt key
317 /// Sometimes also known as the 'meta' key
318 #[serde(default)]
319 pub alt: bool,
320
321 /// The shift key
322 #[serde(default)]
323 pub shift: bool,
324
325 /// The command key, on macos
326 /// the windows key, on windows
327 /// the super key, on linux
328 #[serde(default)]
329 pub platform: bool,
330
331 /// The function key
332 #[serde(default)]
333 pub function: bool,
334}
335
336impl Modifiers {
337 /// Returns whether any modifier key is pressed.
338 pub fn modified(&self) -> bool {
339 self.control || self.alt || self.shift || self.platform || self.function
340 }
341
342 /// Whether the semantically 'secondary' modifier key is pressed.
343 ///
344 /// On macOS, this is the command key.
345 /// On Linux and Windows, this is the control key.
346 pub fn secondary(&self) -> bool {
347 #[cfg(target_os = "macos")]
348 {
349 self.platform
350 }
351
352 #[cfg(not(target_os = "macos"))]
353 {
354 self.control
355 }
356 }
357
358 /// Returns how many modifier keys are pressed.
359 pub fn number_of_modifiers(&self) -> u8 {
360 self.control as u8
361 + self.alt as u8
362 + self.shift as u8
363 + self.platform as u8
364 + self.function as u8
365 }
366
367 /// Returns [`Modifiers`] with no modifiers.
368 pub fn none() -> Modifiers {
369 Default::default()
370 }
371
372 /// Returns [`Modifiers`] with just the command key.
373 pub fn command() -> Modifiers {
374 Modifiers {
375 platform: true,
376 ..Default::default()
377 }
378 }
379
380 /// A Returns [`Modifiers`] with just the secondary key pressed.
381 pub fn secondary_key() -> Modifiers {
382 #[cfg(target_os = "macos")]
383 {
384 Modifiers {
385 platform: true,
386 ..Default::default()
387 }
388 }
389
390 #[cfg(not(target_os = "macos"))]
391 {
392 Modifiers {
393 control: true,
394 ..Default::default()
395 }
396 }
397 }
398
399 /// Returns [`Modifiers`] with just the windows key.
400 pub fn windows() -> Modifiers {
401 Modifiers {
402 platform: true,
403 ..Default::default()
404 }
405 }
406
407 /// Returns [`Modifiers`] with just the super key.
408 pub fn super_key() -> Modifiers {
409 Modifiers {
410 platform: true,
411 ..Default::default()
412 }
413 }
414
415 /// Returns [`Modifiers`] with just control.
416 pub fn control() -> Modifiers {
417 Modifiers {
418 control: true,
419 ..Default::default()
420 }
421 }
422
423 /// Returns [`Modifiers`] with just alt.
424 pub fn alt() -> Modifiers {
425 Modifiers {
426 alt: true,
427 ..Default::default()
428 }
429 }
430
431 /// Returns [`Modifiers`] with just shift.
432 pub fn shift() -> Modifiers {
433 Modifiers {
434 shift: true,
435 ..Default::default()
436 }
437 }
438
439 /// Returns [`Modifiers`] with command + shift.
440 pub fn command_shift() -> Modifiers {
441 Modifiers {
442 shift: true,
443 platform: true,
444 ..Default::default()
445 }
446 }
447
448 /// Returns [`Modifiers`] with command + shift.
449 pub fn control_shift() -> Modifiers {
450 Modifiers {
451 shift: true,
452 control: true,
453 ..Default::default()
454 }
455 }
456
457 /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
458 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
459 (other.control || !self.control)
460 && (other.alt || !self.alt)
461 && (other.shift || !self.shift)
462 && (other.platform || !self.platform)
463 && (other.function || !self.function)
464 }
465}