keystroke.rs

  1use std::fmt::Write;
  2
  3use anyhow::anyhow;
  4use serde::Deserialize;
  5use smallvec::SmallVec;
  6
  7#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
  8pub struct Keystroke {
  9    pub ctrl: bool,
 10    pub alt: bool,
 11    pub shift: bool,
 12    pub cmd: bool,
 13    pub function: bool,
 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    /// ime_key is the character inserted by the IME engine when that key was pressed.
 18    /// e.g. for option-s, ime_key is "รŸ"
 19    pub ime_key: Option<String>,
 20}
 21
 22impl Keystroke {
 23    // When matching a key we cannot know whether the user intended to type
 24    // the ime_key or the key. On some non-US keyboards keys we use in our
 25    // bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
 26    // and on some keyboards the IME handler converts a sequence of keys into a
 27    // specific character (for example `"` is typed as `" space` on a brazillian keyboard).
 28    pub fn match_possibilities(&self) -> SmallVec<[Keystroke; 2]> {
 29        let mut possibilities = SmallVec::new();
 30        match self.ime_key.as_ref() {
 31            None => possibilities.push(self.clone()),
 32            Some(ime_key) => {
 33                possibilities.push(Keystroke {
 34                    ctrl: self.ctrl,
 35                    alt: false,
 36                    shift: false,
 37                    cmd: false,
 38                    function: false,
 39                    key: ime_key.to_string(),
 40                    ime_key: None,
 41                });
 42                possibilities.push(Keystroke {
 43                    ime_key: None,
 44                    ..self.clone()
 45                });
 46            }
 47        }
 48        possibilities
 49    }
 50
 51    /// key syntax is:
 52    /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
 53    /// ime_key is only used for generating test events,
 54    /// when matching a key with an ime_key set will be matched without it.
 55    pub fn parse(source: &str) -> anyhow::Result<Self> {
 56        let mut ctrl = false;
 57        let mut alt = false;
 58        let mut shift = false;
 59        let mut cmd = false;
 60        let mut function = false;
 61        let mut key = None;
 62        let mut ime_key = None;
 63
 64        let mut components = source.split('-').peekable();
 65        while let Some(component) = components.next() {
 66            match component {
 67                "ctrl" => ctrl = true,
 68                "alt" => alt = true,
 69                "shift" => shift = true,
 70                "cmd" => cmd = true,
 71                "fn" => function = true,
 72                _ => {
 73                    if let Some(next) = components.peek() {
 74                        if next.is_empty() && source.ends_with('-') {
 75                            key = Some(String::from("-"));
 76                            break;
 77                        } else if next.len() > 1 && next.starts_with('>') {
 78                            key = Some(String::from(component));
 79                            ime_key = Some(String::from(&next[1..]));
 80                            components.next();
 81                        } else {
 82                            return Err(anyhow!("Invalid keystroke `{}`", source));
 83                        }
 84                    } else {
 85                        key = Some(String::from(component));
 86                    }
 87                }
 88            }
 89        }
 90
 91        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
 92
 93        Ok(Keystroke {
 94            ctrl,
 95            alt,
 96            shift,
 97            cmd,
 98            function,
 99            key,
100            ime_key,
101        })
102    }
103
104    pub fn modified(&self) -> bool {
105        self.ctrl || self.alt || self.shift || self.cmd
106    }
107}
108
109impl std::fmt::Display for Keystroke {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        if self.ctrl {
112            f.write_char('^')?;
113        }
114        if self.alt {
115            f.write_char('โއ')?;
116        }
117        if self.cmd {
118            f.write_char('โŒ˜')?;
119        }
120        if self.shift {
121            f.write_char('โ‡ง')?;
122        }
123        let key = match self.key.as_str() {
124            "backspace" => 'โŒซ',
125            "up" => 'โ†‘',
126            "down" => 'โ†“',
127            "left" => 'โ†',
128            "right" => 'โ†’',
129            "tab" => 'โ‡ฅ',
130            "escape" => 'โŽ‹',
131            key => {
132                if key.len() == 1 {
133                    key.chars().next().unwrap().to_ascii_uppercase()
134                } else {
135                    return f.write_str(key);
136                }
137            }
138        };
139        f.write_char(key)
140    }
141}