1//! Input event handling, currently backed by crossterm.
2use anyhow::{anyhow, Error};
3use helix_core::unicode::width::UnicodeWidthStr;
4use serde::de::{self, Deserialize, Deserializer};
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, Ord, Clone, Copy, Hash)]
14 pub modifiers: KeyModifiers,
18 /// If a character was pressed, return it.
19 pub fn char(&self) -> Option<char> {
21 KeyCode::Char(ch) => Some(ch),
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 DELETE: &str = "del";
40 pub(crate) const INSERT: &str = "ins";
41 pub(crate) const NULL: &str = "null";
42 pub(crate) const ESC: &str = "esc";
43 pub(crate) const SPACE: &str = "space";
44 pub(crate) const LESS_THAN: &str = "lt";
45 pub(crate) const GREATER_THAN: &str = "gt";
46 pub(crate) const PLUS: &str = "plus";
47 pub(crate) const MINUS: &str = "minus";
48 pub(crate) const SEMICOLON: &str = "semicolon";
49 pub(crate) const PERCENT: &str = "percent";
52impl fmt::Display for KeyEvent {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
54 f.write_fmt(format_args!(
56 if self.modifiers.contains(KeyModifiers::SHIFT) {
61 if self.modifiers.contains(KeyModifiers::ALT) {
66 if self.modifiers.contains(KeyModifiers::CONTROL) {
73 KeyCode::Backspace => f.write_str(keys::BACKSPACE)?,
74 KeyCode::Enter => f.write_str(keys::ENTER)?,
75 KeyCode::Left => f.write_str(keys::LEFT)?,
76 KeyCode::Right => f.write_str(keys::RIGHT)?,
77 KeyCode::Up => f.write_str(keys::UP)?,
78 KeyCode::Down => f.write_str(keys::DOWN)?,
79 KeyCode::Home => f.write_str(keys::HOME)?,
80 KeyCode::End => f.write_str(keys::END)?,
81 KeyCode::PageUp => f.write_str(keys::PAGEUP)?,
82 KeyCode::PageDown => f.write_str(keys::PAGEDOWN)?,
83 KeyCode::Tab => f.write_str(keys::TAB)?,
84 KeyCode::Delete => f.write_str(keys::DELETE)?,
85 KeyCode::Insert => f.write_str(keys::INSERT)?,
86 KeyCode::Null => f.write_str(keys::NULL)?,
87 KeyCode::Esc => f.write_str(keys::ESC)?,
88 KeyCode::Char(' ') => f.write_str(keys::SPACE)?,
89 KeyCode::Char('<') => f.write_str(keys::LESS_THAN)?,
90 KeyCode::Char('>') => f.write_str(keys::GREATER_THAN)?,
91 KeyCode::Char('+') => f.write_str(keys::PLUS)?,
92 KeyCode::Char('-') => f.write_str(keys::MINUS)?,
93 KeyCode::Char(';') => f.write_str(keys::SEMICOLON)?,
94 KeyCode::Char('%') => f.write_str(keys::PERCENT)?,
95 KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
96 KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
102impl UnicodeWidthStr for KeyEvent {
103 fn width(&self) -> usize {
104 use helix_core::unicode::width::UnicodeWidthChar;
105 let mut width = match self.code {
106 KeyCode::Backspace => keys::BACKSPACE.len(),
107 KeyCode::Enter => keys::ENTER.len(),
108 KeyCode::Left => keys::LEFT.len(),
109 KeyCode::Right => keys::RIGHT.len(),
110 KeyCode::Up => keys::UP.len(),
111 KeyCode::Down => keys::DOWN.len(),
112 KeyCode::Home => keys::HOME.len(),
113 KeyCode::End => keys::END.len(),
114 KeyCode::PageUp => keys::PAGEUP.len(),
115 KeyCode::PageDown => keys::PAGEDOWN.len(),
116 KeyCode::Tab => keys::TAB.len(),
117 KeyCode::Delete => keys::DELETE.len(),
118 KeyCode::Insert => keys::INSERT.len(),
119 KeyCode::Null => keys::NULL.len(),
120 KeyCode::Esc => keys::ESC.len(),
121 KeyCode::Char(' ') => keys::SPACE.len(),
122 KeyCode::Char('<') => keys::LESS_THAN.len(),
123 KeyCode::Char('>') => keys::GREATER_THAN.len(),
124 KeyCode::Char('+') => keys::PLUS.len(),
125 KeyCode::Char('-') => keys::MINUS.len(),
126 KeyCode::Char(';') => keys::SEMICOLON.len(),
127 KeyCode::Char('%') => keys::PERCENT.len(),
128 KeyCode::F(1..=9) => 2,
130 KeyCode::Char(c) => c.width().unwrap_or(0),
132 if self.modifiers.contains(KeyModifiers::SHIFT) {
135 if self.modifiers.contains(KeyModifiers::ALT) {
138 if self.modifiers.contains(KeyModifiers::CONTROL) {
144 fn width_cjk(&self) -> usize {
149impl std::str::FromStr for KeyEvent {
152 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 let mut tokens: Vec<_> = s.split('-').collect();
154 let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
155 keys::BACKSPACE => KeyCode::Backspace,
156 keys::ENTER => KeyCode::Enter,
157 keys::LEFT => KeyCode::Left,
158 keys::RIGHT => KeyCode::Right,
159 keys::UP => KeyCode::Up,
160 keys::DOWN => KeyCode::Down,
161 keys::HOME => KeyCode::Home,
162 keys::END => KeyCode::End,
163 keys::PAGEUP => KeyCode::PageUp,
164 keys::PAGEDOWN => KeyCode::PageDown,
165 keys::TAB => KeyCode::Tab,
166 keys::DELETE => KeyCode::Delete,
167 keys::INSERT => KeyCode::Insert,
168 keys::NULL => KeyCode::Null,
169 keys::ESC => KeyCode::Esc,
170 keys::SPACE => KeyCode::Char(' '),
171 keys::LESS_THAN => KeyCode::Char('<'),
172 keys::GREATER_THAN => KeyCode::Char('>'),
173 keys::PLUS => KeyCode::Char('+'),
174 keys::MINUS => KeyCode::Char('-'),
175 keys::SEMICOLON => KeyCode::Char(';'),
176 keys::PERCENT => KeyCode::Char('%'),
177 single if single.chars().count() == 1 => KeyCode::Char(single.chars().next().unwrap()),
178 function if function.len() > 1 && function.starts_with('F') => {
179 let function: String = function.chars().skip(1).collect();
180 let function = str::parse::<u8>(&function)?;
181 (function > 0 && function < 13)
182 .then(|| KeyCode::F(function))
183 .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
185 invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
188 let mut modifiers = KeyModifiers::empty();
189 for token in tokens {
190 let flag = match token {
191 "S" => KeyModifiers::SHIFT,
192 "A" => KeyModifiers::ALT,
193 "C" => KeyModifiers::CONTROL,
194 _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
197 if modifiers.contains(flag) {
198 return Err(anyhow!("Repeated key modifier '{}-'", token));
200 modifiers.insert(flag);
203 Ok(KeyEvent { code, modifiers })
207impl<'de> Deserialize<'de> for KeyEvent {
208 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210 D: Deserializer<'de>,
212 let s = String::deserialize(deserializer)?;
213 s.parse().map_err(de::Error::custom)
217#[cfg(feature = "term")]
218impl From<crossterm::event::KeyEvent> for KeyEvent {
219 fn from(crossterm::event::KeyEvent { code, modifiers }: crossterm::event::KeyEvent) -> Self {
220 if code == crossterm::event::KeyCode::BackTab {
221 // special case for BackTab -> Shift-Tab
222 let mut modifiers: KeyModifiers = modifiers.into();
223 modifiers.insert(KeyModifiers::SHIFT);
231 modifiers: modifiers.into(),
237#[cfg(feature = "term")]
238impl From<KeyEvent> for crossterm::event::KeyEvent {
239 fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
240 if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
241 // special case for Shift-Tab -> BackTab
242 let mut modifiers = modifiers;
243 modifiers.remove(KeyModifiers::SHIFT);
244 crossterm::event::KeyEvent {
245 code: crossterm::event::KeyCode::BackTab,
246 modifiers: modifiers.into(),
249 crossterm::event::KeyEvent {
251 modifiers: modifiers.into(),
257pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
259 let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
261 while let Ok(keys) = &mut keys_res {
262 if i >= keys_str.len() {
265 if !keys_str.is_char_boundary(i) {
270 let s = &keys_str[i..];
272 while !s.is_char_boundary(end_i) {
277 keys_res = Err(anyhow!("Unmatched '>'"));
282 match s.find('>').context("'>' expected") {
284 keys.push(&s[1..end_i]);
287 Err(err) => keys_res = Err(err),
291 keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
299 fn parsing_unmodified_keys() {
301 str::parse::<KeyEvent>("backspace").unwrap(),
303 code: KeyCode::Backspace,
304 modifiers: KeyModifiers::NONE
309 str::parse::<KeyEvent>("left").unwrap(),
312 modifiers: KeyModifiers::NONE
317 str::parse::<KeyEvent>(",").unwrap(),
319 code: KeyCode::Char(','),
320 modifiers: KeyModifiers::NONE
325 str::parse::<KeyEvent>("w").unwrap(),
327 code: KeyCode::Char('w'),
328 modifiers: KeyModifiers::NONE
333 str::parse::<KeyEvent>("F12").unwrap(),
335 code: KeyCode::F(12),
336 modifiers: KeyModifiers::NONE
342 fn parsing_modified_keys() {
344 str::parse::<KeyEvent>("S-minus").unwrap(),
346 code: KeyCode::Char('-'),
347 modifiers: KeyModifiers::SHIFT
352 str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
354 code: KeyCode::F(12),
355 modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
360 str::parse::<KeyEvent>("S-C-2").unwrap(),
362 code: KeyCode::Char('2'),
363 modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
369 fn parsing_nonsensical_keys_fails() {
370 assert!(str::parse::<KeyEvent>("F13").is_err());
371 assert!(str::parse::<KeyEvent>("F0").is_err());
372 assert!(str::parse::<KeyEvent>("aaa").is_err());
373 assert!(str::parse::<KeyEvent>("S-S-a").is_err());
374 assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
375 assert!(str::parse::<KeyEvent>("FU").is_err());
376 assert!(str::parse::<KeyEvent>("123").is_err());
377 assert!(str::parse::<KeyEvent>("S--").is_err());
381 fn parsing_valid_macros() {
383 parse_macro("xdo").ok(),
386 code: KeyCode::Char('x'),
387 modifiers: KeyModifiers::NONE,
390 code: KeyCode::Char('d'),
391 modifiers: KeyModifiers::NONE,
394 code: KeyCode::Char('o'),
395 modifiers: KeyModifiers::NONE,
401 parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
404 code: KeyCode::Char('w'),
405 modifiers: KeyModifiers::CONTROL,
408 code: KeyCode::Char('v'),
409 modifiers: KeyModifiers::NONE,
412 code: KeyCode::Char('w'),
413 modifiers: KeyModifiers::CONTROL,
416 code: KeyCode::Char('h'),
417 modifiers: KeyModifiers::NONE,
420 code: KeyCode::Char('o'),
421 modifiers: KeyModifiers::CONTROL,
424 code: KeyCode::Char('x'),
425 modifiers: KeyModifiers::NONE,
428 code: KeyCode::Char('x'),
429 modifiers: KeyModifiers::NONE,
432 code: KeyCode::Char('s'),
433 modifiers: KeyModifiers::ALT,
439 parse_macro(":o foo.bar<ret>").ok(),
442 code: KeyCode::Char(':'),
443 modifiers: KeyModifiers::NONE,
446 code: KeyCode::Char('o'),
447 modifiers: KeyModifiers::NONE,
450 code: KeyCode::Char(' '),
451 modifiers: KeyModifiers::NONE,
454 code: KeyCode::Char('f'),
455 modifiers: KeyModifiers::NONE,
458 code: KeyCode::Char('o'),
459 modifiers: KeyModifiers::NONE,
462 code: KeyCode::Char('o'),
463 modifiers: KeyModifiers::NONE,
466 code: KeyCode::Char('.'),
467 modifiers: KeyModifiers::NONE,
470 code: KeyCode::Char('b'),
471 modifiers: KeyModifiers::NONE,
474 code: KeyCode::Char('a'),
475 modifiers: KeyModifiers::NONE,
478 code: KeyCode::Char('r'),
479 modifiers: KeyModifiers::NONE,
482 code: KeyCode::Enter,
483 modifiers: KeyModifiers::NONE,
490 fn parsing_invalid_macros_fails() {
491 assert!(parse_macro("abc<C-").is_err());
492 assert!(parse_macro("abc>123").is_err());
493 assert!(parse_macro("wd<foo>").is_err());
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list