keystroke.rs

  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            Some(ime_key) => {
 34                if ime_key != &self.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                }
 47                possibilities.push(Keystroke {
 48                    ime_key: None,
 49                    ..self.clone()
 50                });
 51            }
 52            None => possibilities.push(self.clone()),
 53        }
 54        possibilities
 55    }
 56
 57    /// key syntax is:
 58    /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
 59    /// ime_key syntax is only used for generating test events,
 60    /// when matching a key with an ime_key set will be matched without it.
 61    pub fn parse(source: &str) -> anyhow::Result<Self> {
 62        let mut control = false;
 63        let mut alt = false;
 64        let mut shift = false;
 65        let mut command = false;
 66        let mut function = false;
 67        let mut key = None;
 68        let mut ime_key = None;
 69
 70        let mut components = source.split('-').peekable();
 71        while let Some(component) = components.next() {
 72            match component {
 73                "ctrl" => control = true,
 74                "alt" => alt = true,
 75                "shift" => shift = true,
 76                "cmd" => command = true,
 77                "fn" => function = true,
 78                _ => {
 79                    if let Some(next) = components.peek() {
 80                        if next.is_empty() && source.ends_with('-') {
 81                            key = Some(String::from("-"));
 82                            break;
 83                        } else if next.len() > 1 && next.starts_with('>') {
 84                            key = Some(String::from(component));
 85                            ime_key = Some(String::from(&next[1..]));
 86                            components.next();
 87                        } else {
 88                            return Err(anyhow!("Invalid keystroke `{}`", source));
 89                        }
 90                    } else {
 91                        key = Some(String::from(component));
 92                    }
 93                }
 94            }
 95        }
 96
 97        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
 98
 99        Ok(Keystroke {
100            modifiers: Modifiers {
101                control,
102                alt,
103                shift,
104                command,
105                function,
106            },
107            key,
108            ime_key,
109        })
110    }
111
112    /// Returns a new keystroke with the ime_key filled.
113    /// This is used for dispatch_keystroke where we want users to
114    /// be able to simulate typing "space", etc.
115    pub fn with_simulated_ime(mut self) -> Self {
116        if self.ime_key.is_none()
117            && !self.modifiers.command
118            && !self.modifiers.control
119            && !self.modifiers.function
120            && !self.modifiers.alt
121        {
122            self.ime_key = match self.key.as_str() {
123                "space" => Some(" ".into()),
124                "tab" => Some("\t".into()),
125                "enter" => Some("\n".into()),
126                "up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end"
127                | "delete" | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6"
128                | "f7" | "f8" | "f9" | "f10" | "f11" | "f12" => None,
129                key => {
130                    if self.modifiers.shift {
131                        Some(key.to_uppercase())
132                    } else {
133                        Some(key.into())
134                    }
135                }
136            }
137        }
138        self
139    }
140}
141
142impl std::fmt::Display for Keystroke {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        if self.modifiers.control {
145            f.write_char('^')?;
146        }
147        if self.modifiers.alt {
148            f.write_char('โŒฅ')?;
149        }
150        if self.modifiers.command {
151            f.write_char('โŒ˜')?;
152        }
153        if self.modifiers.shift {
154            f.write_char('โ‡ง')?;
155        }
156        let key = match self.key.as_str() {
157            "backspace" => 'โŒซ',
158            "up" => 'โ†‘',
159            "down" => 'โ†“',
160            "left" => 'โ†',
161            "right" => 'โ†’',
162            "tab" => 'โ‡ฅ',
163            "escape" => 'โŽ‹',
164            key => {
165                if key.len() == 1 {
166                    key.chars().next().unwrap().to_ascii_uppercase()
167                } else {
168                    return f.write_str(key);
169                }
170            }
171        };
172        f.write_char(key)
173    }
174}
175
176/// The state of the modifier keys at some point in time
177#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
178pub struct Modifiers {
179    /// The control key
180    pub control: bool,
181
182    /// The alt key
183    /// Sometimes also known as the 'meta' key
184    pub alt: bool,
185
186    /// The shift key
187    pub shift: bool,
188
189    /// The command key, on macos
190    /// the windows key, on windows
191    pub command: bool,
192
193    /// The function key
194    pub function: bool,
195}
196
197impl Modifiers {
198    /// Returns true if any modifier key is pressed
199    pub fn modified(&self) -> bool {
200        self.control || self.alt || self.shift || self.command || self.function
201    }
202
203    /// helper method for Modifiers with no modifiers
204    pub fn none() -> Modifiers {
205        Default::default()
206    }
207
208    /// helper method for Modifiers with just command
209    pub fn command() -> Modifiers {
210        Modifiers {
211            command: true,
212            ..Default::default()
213        }
214    }
215
216    /// helper method for Modifiers with just shift
217    pub fn shift() -> Modifiers {
218        Modifiers {
219            shift: true,
220            ..Default::default()
221        }
222    }
223
224    /// helper method for Modifiers with command + shift
225    pub fn command_shift() -> Modifiers {
226        Modifiers {
227            shift: true,
228            command: true,
229            ..Default::default()
230        }
231    }
232
233    /// Checks if this Modifiers is a subset of another Modifiers
234    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
235        (other.control || !self.control)
236            && (other.alt || !self.alt)
237            && (other.shift || !self.shift)
238            && (other.command || !self.command)
239            && (other.function || !self.function)
240    }
241}