brush_parser/
readline_binding.rs

1//! Implements a parser for readline binding syntax.
2
3use crate::error;
4
5/// Represents a readline key-sequence binding.
6#[derive(Debug, Clone, PartialEq)]
7pub struct KeySequenceBinding {
8    /// Key sequence to bind
9    pub seq: KeySequence,
10    /// Command to bind to the sequence
11    pub command: String,
12}
13
14/// Represents a key sequence.
15#[derive(Debug, Clone, PartialEq)]
16pub struct KeySequence(pub Vec<KeySequenceItem>);
17
18/// Represents an element of a key sequence.
19#[derive(Debug, Clone, PartialEq)]
20pub enum KeySequenceItem {
21    /// Control
22    Control,
23    /// Meta
24    Meta,
25    /// Regular character
26    Byte(u8),
27}
28
29/// Represents a single key stroke.
30#[derive(Debug, Default, Clone, PartialEq)]
31pub struct KeyStroke {
32    /// Meta key is held down
33    pub meta: bool,
34    /// Control key is held down
35    pub control: bool,
36    /// Primary key code
37    pub key_code: Vec<u8>,
38}
39
40/// Parses a key-sequence binding specification.
41///
42/// # Arguments
43///
44/// * `input` - The input string to parse
45pub fn parse_key_sequence_binding(
46    input: &str,
47) -> Result<KeySequenceBinding, error::BindingParseError> {
48    readline_binding::key_sequence_binding(input)
49        .map_err(|_err| error::BindingParseError::Unknown(input.to_owned()))
50}
51
52/// Converts a `KeySequence` to a vector of `KeyStroke`.
53///
54/// # Arguments
55///
56/// * `seq` - The key sequence to convert
57pub fn key_sequence_to_strokes(
58    seq: &KeySequence,
59) -> Result<Vec<KeyStroke>, error::BindingParseError> {
60    let mut strokes = vec![];
61    let mut current_stroke = KeyStroke::default();
62
63    for item in &seq.0 {
64        if matches!(item, KeySequenceItem::Control | KeySequenceItem::Meta)
65            && !current_stroke.key_code.is_empty()
66        {
67            strokes.push(current_stroke);
68            current_stroke = KeyStroke::default();
69        }
70
71        match item {
72            KeySequenceItem::Control => current_stroke.control = true,
73            KeySequenceItem::Meta => current_stroke.meta = true,
74            KeySequenceItem::Byte(b) => current_stroke.key_code.push(*b),
75        }
76    }
77
78    if current_stroke.key_code.is_empty() {
79        if current_stroke.control || current_stroke.meta {
80            return Err(error::BindingParseError::MissingKeyCode);
81        }
82    } else {
83        strokes.push(current_stroke);
84    }
85
86    Ok(strokes)
87}
88
89peg::parser! {
90    grammar readline_binding() for str {
91        rule _() = [' ' | '\t' | '\n']*
92
93        pub rule key_sequence_binding() -> KeySequenceBinding =
94            _ "\"" seq:key_sequence() "\"" _ ":" _ command:cmd() _ { KeySequenceBinding { seq, command } }
95
96        rule cmd() -> String = s:$([_]*) { s.to_string() }
97
98        // Main rule for parsing a key sequence
99        rule key_sequence() -> KeySequence =
100            items:key_sequence_item()* { KeySequence(items) }
101
102        rule key_sequence_item() -> KeySequenceItem =
103            "\\C-" { KeySequenceItem::Control } /
104            "\\M-" { KeySequenceItem::Meta } /
105            "\\e" { KeySequenceItem::Byte(b'\x1b') } /
106            "\\\\" { KeySequenceItem::Byte(b'\\') } /
107            "\\\"" { KeySequenceItem::Byte(b'"') } /
108            "\\'" { KeySequenceItem::Byte(b'\'') } /
109            "\\a" { KeySequenceItem::Byte(b'\x07') } /
110            "\\b" { KeySequenceItem::Byte(b'\x08') } /
111            "\\d" { KeySequenceItem::Byte(b'\x7f') } /
112            "\\f" { KeySequenceItem::Byte(b'\x0c') } /
113            "\\n" { KeySequenceItem::Byte(b'\n') } /
114            "\\r" { KeySequenceItem::Byte(b'\r') } /
115            "\\t" { KeySequenceItem::Byte(b'\t') } /
116            "\\v" { KeySequenceItem::Byte(b'\x0b') } /
117            "\\" n:octal_number() { KeySequenceItem::Byte(n) } /
118            "\\" n:hex_number() { KeySequenceItem::Byte(n) } /
119            [c if c != '"'] { KeySequenceItem::Byte(c as u8) }
120
121        rule octal_number() -> u8 =
122            s:$(['0'..='7']*<1,3>) {? u8::from_str_radix(s, 8).or(Err("invalid octal number")) }
123
124        rule hex_number() -> u8 =
125            s:$(['0'..='9' | 'a'..='f' | 'A'..='F']*<1,2>) {? u8::from_str_radix(s, 16).or(Err("invalid hex number")) }
126    }
127}
128
129#[cfg(test)]
130#[allow(clippy::unnecessary_wraps)]
131#[allow(clippy::panic_in_result_fn)]
132mod tests {
133    use super::*;
134    use anyhow::Result;
135
136    #[test]
137    fn test_basic_parse() -> Result<()> {
138        let binding = parse_key_sequence_binding(r#""\C-k": xyz"#)?;
139        assert_eq!(
140            binding.seq.0,
141            [KeySequenceItem::Control, KeySequenceItem::Byte(b'k')]
142        );
143        assert_eq!(binding.command, "xyz");
144
145        Ok(())
146    }
147}