brush_parser/
readline_binding.rs1use crate::error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7#[cfg_attr(test, derive(serde::Serialize))]
8pub struct KeySequenceBinding {
9 pub seq: KeySequence,
11 pub command: String,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(test, derive(serde::Serialize))]
18pub struct KeySequence(pub Vec<KeySequenceItem>);
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(test, derive(serde::Serialize))]
23pub enum KeySequenceItem {
24 Control,
26 Meta,
28 Byte(u8),
30}
31
32#[derive(Debug, Default, Clone, PartialEq, Eq)]
34#[cfg_attr(test, derive(serde::Serialize))]
35pub struct KeyStroke {
36 pub meta: bool,
38 pub control: bool,
40 pub key_code: Vec<u8>,
42}
43
44pub fn parse_key_sequence_binding(
50 input: &str,
51) -> Result<KeySequenceBinding, error::BindingParseError> {
52 readline_binding::key_sequence_binding(input)
53 .map_err(|_err| error::BindingParseError::Unknown(input.to_owned()))
54}
55
56pub fn key_sequence_to_strokes(
62 seq: &KeySequence,
63) -> Result<Vec<KeyStroke>, error::BindingParseError> {
64 let mut strokes = vec![];
65 let mut current_stroke = KeyStroke::default();
66
67 for item in &seq.0 {
68 if matches!(item, KeySequenceItem::Control | KeySequenceItem::Meta)
69 && !current_stroke.key_code.is_empty()
70 {
71 strokes.push(current_stroke);
72 current_stroke = KeyStroke::default();
73 }
74
75 match item {
76 KeySequenceItem::Control => current_stroke.control = true,
77 KeySequenceItem::Meta => current_stroke.meta = true,
78 KeySequenceItem::Byte(b) => current_stroke.key_code.push(*b),
79 }
80 }
81
82 if current_stroke.key_code.is_empty() {
83 if current_stroke.control || current_stroke.meta {
84 return Err(error::BindingParseError::MissingKeyCode);
85 }
86 } else {
87 strokes.push(current_stroke);
88 }
89
90 Ok(strokes)
91}
92
93peg::parser! {
94 grammar readline_binding() for str {
95 rule _() = [' ' | '\t' | '\n']*
96
97 pub rule key_sequence_binding() -> KeySequenceBinding =
98 _ "\"" seq:key_sequence() "\"" _ ":" _ command:cmd() _ { KeySequenceBinding { seq, command } }
99
100 rule cmd() -> String = s:$([_]*) { s.to_string() }
101
102 rule key_sequence() -> KeySequence =
104 items:key_sequence_item()* { KeySequence(items) }
105
106 rule key_sequence_item() -> KeySequenceItem =
107 "\\C-" { KeySequenceItem::Control } /
108 "\\M-" { KeySequenceItem::Meta } /
109 "\\e" { KeySequenceItem::Byte(b'\x1b') } /
110 "\\\\" { KeySequenceItem::Byte(b'\\') } /
111 "\\\"" { KeySequenceItem::Byte(b'"') } /
112 "\\'" { KeySequenceItem::Byte(b'\'') } /
113 "\\a" { KeySequenceItem::Byte(b'\x07') } /
114 "\\b" { KeySequenceItem::Byte(b'\x08') } /
115 "\\d" { KeySequenceItem::Byte(b'\x7f') } /
116 "\\f" { KeySequenceItem::Byte(b'\x0c') } /
117 "\\n" { KeySequenceItem::Byte(b'\n') } /
118 "\\r" { KeySequenceItem::Byte(b'\r') } /
119 "\\t" { KeySequenceItem::Byte(b'\t') } /
120 "\\v" { KeySequenceItem::Byte(b'\x0b') } /
121 "\\" n:octal_number() { KeySequenceItem::Byte(n) } /
122 "\\" n:hex_number() { KeySequenceItem::Byte(n) } /
123 [c if c != '"'] { KeySequenceItem::Byte(c as u8) }
124
125 rule octal_number() -> u8 =
126 s:$(['0'..='7']*<1,3>) {? u8::from_str_radix(s, 8).or(Err("invalid octal number")) }
127
128 rule hex_number() -> u8 =
129 s:$(['0'..='9' | 'a'..='f' | 'A'..='F']*<1,2>) {? u8::from_str_radix(s, 16).or(Err("invalid hex number")) }
130 }
131}
132
133#[cfg(test)]
134#[expect(clippy::panic_in_result_fn)]
135mod tests {
136 use super::*;
137 use anyhow::Result;
138
139 #[test]
140 fn test_basic_parse() -> Result<()> {
141 let binding = parse_key_sequence_binding(r#""\C-k": xyz"#)?;
142 assert_eq!(
143 binding.seq.0,
144 [KeySequenceItem::Control, KeySequenceItem::Byte(b'k')]
145 );
146 assert_eq!(binding.command, "xyz");
147
148 Ok(())
149 }
150}