tui_pages/input/
key_sequence.rs1use crate::input::KeyChord;
2use crossterm::event::{KeyCode, KeyModifiers};
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum ParseKeyError {
10 Empty,
12 UnknownKey(String),
15}
16
17impl fmt::Display for ParseKeyError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 ParseKeyError::Empty => write!(f, "empty key binding"),
21 ParseKeyError::UnknownKey(token) => write!(f, "unrecognised key token: {token:?}"),
22 }
23 }
24}
25
26impl std::error::Error for ParseKeyError {}
27
28pub fn parse_binding(binding: &str) -> Vec<KeyChord> {
34 binding.split_whitespace().filter_map(parse_key).collect()
35}
36
37pub fn parse_key(token: &str) -> Option<KeyChord> {
40 try_parse_key(token).ok()
41}
42
43pub fn try_parse_binding(binding: &str) -> Result<Vec<KeyChord>, ParseKeyError> {
49 let chords = binding
50 .split_whitespace()
51 .map(try_parse_key)
52 .collect::<Result<Vec<_>, _>>()?;
53 if chords.is_empty() {
54 return Err(ParseKeyError::Empty);
55 }
56 Ok(chords)
57}
58
59pub fn try_parse_key(token: &str) -> Result<KeyChord, ParseKeyError> {
62 let mut modifiers = KeyModifiers::empty();
63 let mut key = token.trim();
64 if key.is_empty() {
65 return Err(ParseKeyError::Empty);
66 }
67
68 loop {
69 let Some((prefix, rest)) = key.split_once('+') else {
70 break;
71 };
72
73 match prefix.to_ascii_lowercase().as_str() {
74 "ctrl" | "control" | "c" => modifiers |= KeyModifiers::CONTROL,
75 "alt" | "meta" | "m" => modifiers |= KeyModifiers::ALT,
76 "shift" | "s" => modifiers |= KeyModifiers::SHIFT,
77 _ => break,
80 }
81 key = rest;
82 }
83
84 let unknown = || ParseKeyError::UnknownKey(token.to_string());
85 let code = match key.to_ascii_lowercase().as_str() {
86 "enter" | "return" => KeyCode::Enter,
87 "tab" => KeyCode::Tab,
88 "backtab" => KeyCode::BackTab,
89 "esc" | "escape" => KeyCode::Esc,
90 "backspace" | "bs" => KeyCode::Backspace,
91 "space" => KeyCode::Char(' '),
92 "up" => KeyCode::Up,
93 "down" => KeyCode::Down,
94 "left" => KeyCode::Left,
95 "right" => KeyCode::Right,
96 "home" => KeyCode::Home,
97 "end" => KeyCode::End,
98 "pageup" | "page_up" => KeyCode::PageUp,
99 "pagedown" | "page_down" => KeyCode::PageDown,
100 "delete" | "del" => KeyCode::Delete,
101 "insert" | "ins" => KeyCode::Insert,
102 text if text.starts_with('f') && text.len() > 1 => {
105 let number = text[1..].parse().map_err(|_| unknown())?;
106 KeyCode::F(number)
107 }
108 text => {
109 let mut chars = text.chars();
110 let first = chars.next().ok_or_else(unknown)?;
111 if chars.next().is_some() {
112 return Err(unknown());
113 }
114 KeyCode::Char(first)
115 }
116 };
117
118 let (code, modifiers) = if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
119 (KeyCode::BackTab, modifiers - KeyModifiers::SHIFT)
120 } else {
121 (code, modifiers)
122 };
123
124 Ok(KeyChord::new(code, modifiers))
125}