[go: up one dir, main page]

crokey/
format.rs

1//! Crokey helps incorporate configurable keybindings in [crossterm](https://github.com/crossterm-rs/crossterm)
2//! based terminal applications by providing functions
3//! - parsing key combinations from strings
4//! - describing key combinations in strings
5
6use {
7    crate::KeyCombination,
8    crossterm::event::{KeyCode::*, KeyModifiers},
9    std::fmt,
10};
11
12/// A formatter to produce key combinations descriptions.
13///
14/// ```
15/// use {
16///     crokey::*,
17///     crossterm::event::{
18///         KeyCode,
19///         KeyEvent,
20///         KeyModifiers,
21///     },
22/// };
23///
24/// let format = KeyCombinationFormat::default();
25/// assert_eq!(format.to_string(key!(shift-a)), "Shift-a");
26/// assert_eq!(format.to_string(key!(ctrl-c)), "Ctrl-c");
27///
28/// // A more compact format
29/// let format = KeyCombinationFormat::default()
30///     .with_implicit_shift()
31///     .with_control("^");
32/// assert_eq!(format.to_string(key!(shift-a)), "A");
33/// assert_eq!(format.to_string(key!(ctrl-c)), "^c");
34///
35/// // A long format with lowercased modifiers
36/// let format = KeyCombinationFormat::default()
37///     .with_lowercase_modifiers();
38/// assert_eq!(format.to_string(key!(ctrl-enter)), "ctrl-Enter");
39/// assert_eq!(format.to_string(key!(home)), "Home");
40/// assert_eq!(
41///     format.to_string(
42///         KeyCombination::new(
43///             KeyCode::F(6),
44///             KeyModifiers::ALT,
45///         )
46///     ),
47///     "alt-F6",
48/// );
49/// assert_eq!(
50///     format.to_string(
51///         KeyCombination::new(
52///             (KeyCode::Char('u'), KeyCode::Char('i')),
53///             KeyModifiers::NONE,
54///         )
55///     ),
56///     "i-u",
57/// );
58///
59/// ```
60#[derive(Debug, Clone)]
61pub struct KeyCombinationFormat {
62    pub control: String,
63    pub command: String, // also called 'super', 'apple', 'windows'
64    pub alt: String,
65    pub shift: String,
66    pub enter: String,
67    pub uppercase_shift: bool,
68    pub key_separator: String,
69}
70
71impl Default for KeyCombinationFormat {
72    fn default() -> Self {
73        Self {
74            control: "Ctrl-".to_string(),
75            command: "Cmd-".to_string(),
76            alt: "Alt-".to_string(),
77            shift: "Shift-".to_string(),
78            enter: "Enter".to_string(),
79            uppercase_shift: false,
80            key_separator: "-".to_string(),
81        }
82    }
83}
84
85impl KeyCombinationFormat {
86    pub fn with_lowercase_modifiers(mut self) -> Self {
87        self.control = self.control.to_lowercase();
88        self.alt = self.alt.to_lowercase();
89        self.shift = self.shift.to_lowercase();
90        self
91    }
92    pub fn with_control<S: Into<String>>(mut self, s: S) -> Self {
93        self.control = s.into();
94        self
95    }
96    pub fn with_command<S: Into<String>>(mut self, s: S) -> Self {
97        self.command = s.into();
98        self
99    }
100    pub fn with_alt<S: Into<String>>(mut self, s: S) -> Self {
101        self.alt = s.into();
102        self
103    }
104    pub fn with_shift<S: Into<String>>(mut self, s: S) -> Self {
105        self.shift = s.into();
106        self
107    }
108    pub fn with_implicit_shift(mut self) -> Self {
109        self.shift = "".to_string();
110        self.uppercase_shift = true;
111        self
112    }
113    /// return a wrapper of the key implementing Display
114    ///
115    /// ```
116    /// use crokey::*;
117    /// let format = KeyCombinationFormat::default();
118    /// let k = format.format(key!(f6));
119    /// let s = format!("k={}", k);
120    /// assert_eq!(s, "k=F6");
121    /// ```
122    pub fn format<K: Into<KeyCombination>>(&self, key: K) -> FormattedKeyCombination<'_> {
123        FormattedKeyCombination { format: self, key: key.into() }
124    }
125    /// return the key formatted into a string
126    ///
127    /// `format.to_string(key)` is equivalent to `format.format(key).to_string()`.
128    pub fn to_string<K: Into<KeyCombination>>(&self, key: K) -> String {
129        self.format(key).to_string()
130    }
131}
132
133pub struct FormattedKeyCombination<'s> {
134    format: &'s KeyCombinationFormat,
135    key: KeyCombination,
136}
137
138impl fmt::Display for FormattedKeyCombination<'_> {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        let format = &self.format;
141        let key = &self.key;
142        if key.modifiers.contains(KeyModifiers::CONTROL) {
143            write!(f, "{}", format.control)?;
144        }
145        if key.modifiers.contains(KeyModifiers::ALT) {
146            write!(f, "{}", format.alt)?;
147        }
148        if key.modifiers.contains(KeyModifiers::SHIFT) {
149            write!(f, "{}", format.shift)?;
150        }
151        if key.modifiers.contains(KeyModifiers::SUPER) {
152            write!(f, "{}", format.command)?;
153        }
154        for (i, code) in key.codes.iter().enumerate() {
155            if i > 0 {
156                write!(f, "{}", format.key_separator)?;
157            }
158            match code {
159                Char(' ') => {
160                    write!(f, "Space")?;
161                }
162                Char('-') => {
163                    write!(f, "Hyphen")?;
164                }
165                Char('\r') | Char('\n') | Enter => {
166                    write!(f, "{}", format.enter)?;
167                }
168                Char(c) if key.modifiers.contains(KeyModifiers::SHIFT) && format.uppercase_shift => {
169                    write!(f, "{}", c.to_ascii_uppercase())?;
170                }
171                Char(c) => {
172                    write!(f, "{}", c.to_ascii_lowercase())?;
173                }
174                F(u) => {
175                    write!(f, "F{u}")?;
176                }
177                _ => {
178                    write!(f, "{:?}", code)?;
179                }
180            }
181        }
182        Ok(())
183    }
184}