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}