keystroke.rs

  1use anyhow::anyhow;
  2use serde::Deserialize;
  3use std::fmt::Write;
  4
  5#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
  6pub struct Modifiers {
  7    pub control: bool,
  8    pub alt: bool,
  9    pub shift: bool,
 10    pub command: bool,
 11    pub function: bool,
 12}
 13
 14impl Modifiers {
 15    pub fn modified(&self) -> bool {
 16        self.control || self.alt || self.shift || self.command || self.function
 17    }
 18}
 19
 20#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
 21pub struct Keystroke {
 22    pub key: String,
 23    pub modifiers: Modifiers,
 24}
 25
 26impl Keystroke {
 27    pub fn parse(source: &str) -> anyhow::Result<Self> {
 28        let mut control = false;
 29        let mut alt = false;
 30        let mut shift = false;
 31        let mut command = false;
 32        let mut function = false;
 33        let mut key = None;
 34
 35        let mut components = source.split('-').peekable();
 36        while let Some(component) = components.next() {
 37            match component {
 38                "ctrl" => control = true,
 39                "alt" => alt = true,
 40                "shift" => shift = true,
 41                "cmd" => command = true,
 42                "fn" => function = true,
 43                _ => {
 44                    if let Some(component) = components.peek() {
 45                        if component.is_empty() && source.ends_with('-') {
 46                            key = Some(String::from("-"));
 47                            break;
 48                        } else {
 49                            return Err(anyhow!("Invalid keystroke `{}`", source));
 50                        }
 51                    } else {
 52                        key = Some(String::from(component));
 53                    }
 54                }
 55            }
 56        }
 57
 58        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
 59
 60        Ok(Keystroke {
 61            modifiers: Modifiers {
 62                control,
 63                alt,
 64                shift,
 65                command,
 66                function,
 67            },
 68            key,
 69        })
 70    }
 71
 72    pub fn modified(&self) -> bool {
 73        self.modifiers.modified()
 74    }
 75}
 76
 77impl std::fmt::Display for Keystroke {
 78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 79        let Modifiers {
 80            control,
 81            alt,
 82            shift,
 83            command,
 84            function,
 85        } = self.modifiers;
 86
 87        if control {
 88            f.write_char('^')?;
 89        }
 90        if alt {
 91            f.write_char('⎇')?;
 92        }
 93        if command {
 94            f.write_char('⌘')?;
 95        }
 96        if shift {
 97            f.write_char('⇧')?;
 98        }
 99        if function {
100            f.write_char('𝙛')?;
101        }
102
103        let key = match self.key.as_str() {
104            "backspace" => '⌫',
105            "up" => '↑',
106            "down" => '↓',
107            "left" => '←',
108            "right" => '→',
109            "tab" => '⇥',
110            "escape" => '⎋',
111            key => {
112                if key.len() == 1 {
113                    key.chars().next().unwrap().to_ascii_uppercase()
114                } else {
115                    return f.write_str(key);
116                }
117            }
118        };
119        f.write_char(key)
120    }
121}