on 2158366 help sign in
helix/helix-view/src/input.rs
318 lines 11 kB history raw
1//! Input event handling, currently backed by crossterm.
2use anyhow::{anyhow, Error};
3use helix_core::unicode::width::UnicodeWidthStr;
4use serde::de::{self, Deserialize, Deserializer};
5use std::fmt;
7use crate::keyboard::{KeyCode, KeyModifiers};
9/// Represents a key event.
10// We use a newtype here because we want to customize Deserialize and Display.
11#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
12pub struct KeyEvent {
13 pub code: KeyCode,
14 pub modifiers: KeyModifiers,
17impl KeyEvent {
18 /// If a character was pressed (without modifiers), return it.
19 pub fn char(&self) -> Option<char> {
20 match self.code {
21 KeyCode::Char(ch) if self.modifiers.is_empty() => Some(ch),
22 _ => None,
23 }
24 }
27pub(crate) mod keys {
28 pub(crate) const BACKSPACE: &str = "backspace";
29 pub(crate) const ENTER: &str = "ret";
30 pub(crate) const LEFT: &str = "left";
31 pub(crate) const RIGHT: &str = "right";
32 pub(crate) const UP: &str = "up";
33 pub(crate) const DOWN: &str = "down";
34 pub(crate) const HOME: &str = "home";
35 pub(crate) const END: &str = "end";
36 pub(crate) const PAGEUP: &str = "pageup";
37 pub(crate) const PAGEDOWN: &str = "pagedown";
38 pub(crate) const TAB: &str = "tab";
39 pub(crate) const BACKTAB: &str = "backtab";
40 pub(crate) const DELETE: &str = "del";
41 pub(crate) const INSERT: &str = "ins";
42 pub(crate) const NULL: &str = "null";
43 pub(crate) const ESC: &str = "esc";
44 pub(crate) const SPACE: &str = "space";
45 pub(crate) const LESS_THAN: &str = "lt";
46 pub(crate) const GREATER_THAN: &str = "gt";
47 pub(crate) const PLUS: &str = "plus";
48 pub(crate) const MINUS: &str = "minus";
49 pub(crate) const SEMICOLON: &str = "semicolon";
50 pub(crate) const PERCENT: &str = "percent";
53impl fmt::Display for KeyEvent {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
55 f.write_fmt(format_args!(
56 "{}{}{}",
57 if self.modifiers.contains(KeyModifiers::SHIFT) {
58 "S-"
59 } else {
60 ""
61 },
62 if self.modifiers.contains(KeyModifiers::ALT) {
63 "A-"
64 } else {
65 ""
66 },
67 if self.modifiers.contains(KeyModifiers::CONTROL) {
68 "C-"
69 } else {
70 ""
71 },
72 ))?;
73 match self.code {
74 KeyCode::Backspace => f.write_str(keys::BACKSPACE)?,
75 KeyCode::Enter => f.write_str(keys::ENTER)?,
76 KeyCode::Left => f.write_str(keys::LEFT)?,
77 KeyCode::Right => f.write_str(keys::RIGHT)?,
78 KeyCode::Up => f.write_str(keys::UP)?,
79 KeyCode::Down => f.write_str(keys::DOWN)?,
80 KeyCode::Home => f.write_str(keys::HOME)?,
81 KeyCode::End => f.write_str(keys::END)?,
82 KeyCode::PageUp => f.write_str(keys::PAGEUP)?,
83 KeyCode::PageDown => f.write_str(keys::PAGEDOWN)?,
84 KeyCode::Tab => f.write_str(keys::TAB)?,
85 KeyCode::BackTab => f.write_str(keys::BACKTAB)?,
86 KeyCode::Delete => f.write_str(keys::DELETE)?,
87 KeyCode::Insert => f.write_str(keys::INSERT)?,
88 KeyCode::Null => f.write_str(keys::NULL)?,
89 KeyCode::Esc => f.write_str(keys::ESC)?,
90 KeyCode::Char(' ') => f.write_str(keys::SPACE)?,
91 KeyCode::Char('<') => f.write_str(keys::LESS_THAN)?,
92 KeyCode::Char('>') => f.write_str(keys::GREATER_THAN)?,
93 KeyCode::Char('+') => f.write_str(keys::PLUS)?,
94 KeyCode::Char('-') => f.write_str(keys::MINUS)?,
95 KeyCode::Char(';') => f.write_str(keys::SEMICOLON)?,
96 KeyCode::Char('%') => f.write_str(keys::PERCENT)?,
97 KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
98 KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
99 };
100 Ok(())
101 }
104impl UnicodeWidthStr for KeyEvent {
105 fn width(&self) -> usize {
106 use helix_core::unicode::width::UnicodeWidthChar;
107 let mut width = match self.code {
108 KeyCode::Backspace => keys::BACKSPACE.len(),
109 KeyCode::Enter => keys::ENTER.len(),
110 KeyCode::Left => keys::LEFT.len(),
111 KeyCode::Right => keys::RIGHT.len(),
112 KeyCode::Up => keys::UP.len(),
113 KeyCode::Down => keys::DOWN.len(),
114 KeyCode::Home => keys::HOME.len(),
115 KeyCode::End => keys::END.len(),
116 KeyCode::PageUp => keys::PAGEUP.len(),
117 KeyCode::PageDown => keys::PAGEDOWN.len(),
118 KeyCode::Tab => keys::TAB.len(),
119 KeyCode::BackTab => keys::BACKTAB.len(),
120 KeyCode::Delete => keys::DELETE.len(),
121 KeyCode::Insert => keys::INSERT.len(),
122 KeyCode::Null => keys::NULL.len(),
123 KeyCode::Esc => keys::ESC.len(),
124 KeyCode::Char(' ') => keys::SPACE.len(),
125 KeyCode::Char('<') => keys::LESS_THAN.len(),
126 KeyCode::Char('>') => keys::GREATER_THAN.len(),
127 KeyCode::Char('+') => keys::PLUS.len(),
128 KeyCode::Char('-') => keys::MINUS.len(),
129 KeyCode::Char(';') => keys::SEMICOLON.len(),
130 KeyCode::Char('%') => keys::PERCENT.len(),
131 KeyCode::F(1..=9) => 2,
132 KeyCode::F(_) => 3,
133 KeyCode::Char(c) => c.width().unwrap_or(0),
134 };
135 if self.modifiers.contains(KeyModifiers::SHIFT) {
136 width += 2;
137 }
138 if self.modifiers.contains(KeyModifiers::ALT) {
139 width += 2;
140 }
141 if self.modifiers.contains(KeyModifiers::CONTROL) {
142 width += 2;
143 }
144 width
145 }
147 fn width_cjk(&self) -> usize {
148 self.width()
149 }
152impl std::str::FromStr for KeyEvent {
153 type Err = Error;
155 fn from_str(s: &str) -> Result<Self, Self::Err> {
156 let mut tokens: Vec<_> = s.split('-').collect();
157 let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
158 keys::BACKSPACE => KeyCode::Backspace,
159 keys::ENTER => KeyCode::Enter,
160 keys::LEFT => KeyCode::Left,
161 keys::RIGHT => KeyCode::Right,
162 keys::UP => KeyCode::Up,
163 keys::DOWN => KeyCode::Down,
164 keys::HOME => KeyCode::Home,
165 keys::END => KeyCode::End,
166 keys::PAGEUP => KeyCode::PageUp,
167 keys::PAGEDOWN => KeyCode::PageDown,
168 keys::TAB => KeyCode::Tab,
169 keys::BACKTAB => KeyCode::BackTab,
170 keys::DELETE => KeyCode::Delete,
171 keys::INSERT => KeyCode::Insert,
172 keys::NULL => KeyCode::Null,
173 keys::ESC => KeyCode::Esc,
174 keys::SPACE => KeyCode::Char(' '),
175 keys::LESS_THAN => KeyCode::Char('<'),
176 keys::GREATER_THAN => KeyCode::Char('>'),
177 keys::PLUS => KeyCode::Char('+'),
178 keys::MINUS => KeyCode::Char('-'),
179 keys::SEMICOLON => KeyCode::Char(';'),
180 keys::PERCENT => KeyCode::Char('%'),
181 single if single.chars().count() == 1 => KeyCode::Char(single.chars().next().unwrap()),
182 function if function.len() > 1 && function.starts_with('F') => {
183 let function: String = function.chars().skip(1).collect();
184 let function = str::parse::<u8>(&function)?;
185 (function > 0 && function < 13)
186 .then(|| KeyCode::F(function))
187 .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
188 }
189 invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
190 };
192 let mut modifiers = KeyModifiers::empty();
193 for token in tokens {
194 let flag = match token {
195 "S" => KeyModifiers::SHIFT,
196 "A" => KeyModifiers::ALT,
197 "C" => KeyModifiers::CONTROL,
198 _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
199 };
201 if modifiers.contains(flag) {
202 return Err(anyhow!("Repeated key modifier '{}-'", token));
203 }
204 modifiers.insert(flag);
205 }
207 Ok(KeyEvent { code, modifiers })
208 }
211impl<'de> Deserialize<'de> for KeyEvent {
212 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
213 where
214 D: Deserializer<'de>,
215 {
216 let s = String::deserialize(deserializer)?;
217 s.parse().map_err(de::Error::custom)
218 }
221#[cfg(feature = "term")]
222impl From<crossterm::event::KeyEvent> for KeyEvent {
223 fn from(
224 crossterm::event::KeyEvent { code, modifiers }: crossterm::event::KeyEvent,
225 ) -> KeyEvent {
226 KeyEvent {
227 code: code.into(),
228 modifiers: modifiers.into(),
229 }
230 }
233#[cfg(test)]
234mod test {
235 use super::*;
237 #[test]
238 fn parsing_unmodified_keys() {
239 assert_eq!(
240 str::parse::<KeyEvent>("backspace").unwrap(),
241 KeyEvent {
242 code: KeyCode::Backspace,
243 modifiers: KeyModifiers::NONE
244 }
245 );
247 assert_eq!(
248 str::parse::<KeyEvent>("left").unwrap(),
249 KeyEvent {
250 code: KeyCode::Left,
251 modifiers: KeyModifiers::NONE
252 }
253 );
255 assert_eq!(
256 str::parse::<KeyEvent>(",").unwrap(),
257 KeyEvent {
258 code: KeyCode::Char(','),
259 modifiers: KeyModifiers::NONE
260 }
261 );
263 assert_eq!(
264 str::parse::<KeyEvent>("w").unwrap(),
265 KeyEvent {
266 code: KeyCode::Char('w'),
267 modifiers: KeyModifiers::NONE
268 }
269 );
271 assert_eq!(
272 str::parse::<KeyEvent>("F12").unwrap(),
273 KeyEvent {
274 code: KeyCode::F(12),
275 modifiers: KeyModifiers::NONE
276 }
277 );
278 }
280 #[test]
281 fn parsing_modified_keys() {
282 assert_eq!(
283 str::parse::<KeyEvent>("S-minus").unwrap(),
284 KeyEvent {
285 code: KeyCode::Char('-'),
286 modifiers: KeyModifiers::SHIFT
287 }
288 );
290 assert_eq!(
291 str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
292 KeyEvent {
293 code: KeyCode::F(12),
294 modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
295 }
296 );
298 assert_eq!(
299 str::parse::<KeyEvent>("S-C-2").unwrap(),
300 KeyEvent {
301 code: KeyCode::Char('2'),
302 modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
303 }
304 );
305 }
307 #[test]
308 fn parsing_nonsensical_keys_fails() {
309 assert!(str::parse::<KeyEvent>("F13").is_err());
310 assert!(str::parse::<KeyEvent>("F0").is_err());
311 assert!(str::parse::<KeyEvent>("aaa").is_err());
312 assert!(str::parse::<KeyEvent>("S-S-a").is_err());
313 assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
314 assert!(str::parse::<KeyEvent>("FU").is_err());
315 assert!(str::parse::<KeyEvent>("123").is_err());
316 assert!(str::parse::<KeyEvent>("S--").is_err());
317 }