keystroke.rs

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