Skip to main content

clash_brush_parser/
readline_binding.rs

1//! Implements a parser for readline binding syntax.
2
3use crate::error;
4
5/// Represents a key-sequence-to-shell-command binding.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
8pub struct KeySequenceShellCommandBinding {
9    /// Key sequence to bind
10    pub seq: KeySequence,
11    /// Shell command to bind to the sequence
12    pub shell_cmd: String,
13}
14
15/// Represents a key-sequence-to-readline-command binding.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
18pub struct KeySequenceReadlineBinding {
19    /// Key sequence to bind
20    pub seq: KeySequence,
21    /// Readline target to bind to the sequence
22    pub target: ReadlineTarget,
23}
24
25/// Represents a readline target.
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
28pub enum ReadlineTarget {
29    /// A named readline function.
30    Function(String),
31    /// A readline command macro.
32    Macro(String),
33}
34
35/// Represents a key sequence.
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
38pub struct KeySequence(pub Vec<KeySequenceItem>);
39
40/// Represents an element of a key sequence.
41#[derive(Debug, Clone, PartialEq, Eq)]
42#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
43pub enum KeySequenceItem {
44    /// Control
45    Control,
46    /// Meta
47    Meta,
48    /// Regular character
49    Byte(u8),
50}
51
52/// Represents a single key stroke.
53#[derive(Debug, Default, Clone, PartialEq, Eq)]
54#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
55pub struct KeyStroke {
56    /// Meta key is held down
57    pub meta: bool,
58    /// Control key is held down
59    pub control: bool,
60    /// Primary key code
61    pub key_code: Vec<u8>,
62}
63
64/// Parses a key sequence.
65///
66/// # Arguments
67///
68/// * `input` - The input string to parse
69pub fn parse_key_sequence(input: &str) -> Result<KeySequence, error::BindingParseError> {
70    readline_binding::key_sequence(input)
71        .map_err(|_err| error::BindingParseError::Unknown(input.to_owned()))
72}
73
74/// Parses a binding specification that maps a key sequence
75/// to a shell command.
76///
77/// # Arguments
78///
79/// * `input` - The input string to parse
80pub fn parse_key_sequence_shell_cmd_binding(
81    input: &str,
82) -> Result<KeySequenceShellCommandBinding, error::BindingParseError> {
83    readline_binding::key_sequence_shell_cmd_binding(input)
84        .map_err(|_err| error::BindingParseError::Unknown(input.to_owned()))
85}
86
87/// Parses a binding specification that maps a key sequence
88/// to a readline target.
89///
90/// # Arguments
91///
92/// * `input` - The input string to parse
93pub fn parse_key_sequence_readline_binding(
94    input: &str,
95) -> Result<KeySequenceReadlineBinding, error::BindingParseError> {
96    readline_binding::key_sequence_readline_binding(input)
97        .map_err(|_err| error::BindingParseError::Unknown(input.to_owned()))
98}
99
100/// Converts a `KeySequence` to a vector of `KeyStroke`.
101///
102/// # Arguments
103///
104/// * `seq` - The key sequence to convert
105pub fn key_sequence_to_strokes(
106    seq: &KeySequence,
107) -> Result<Vec<KeyStroke>, error::BindingParseError> {
108    let mut strokes = vec![];
109    let mut current_stroke = KeyStroke::default();
110
111    for item in &seq.0 {
112        if matches!(
113            item,
114            KeySequenceItem::Control | KeySequenceItem::Meta | KeySequenceItem::Byte(b'\x1b')
115        ) && !current_stroke.key_code.is_empty()
116        {
117            strokes.push(current_stroke);
118            current_stroke = KeyStroke::default();
119        }
120
121        match item {
122            KeySequenceItem::Control => current_stroke.control = true,
123            KeySequenceItem::Meta => current_stroke.meta = true,
124            KeySequenceItem::Byte(b) => {
125                current_stroke.key_code.push(*b);
126                // If this is a control or meta stroke, the modifier only applies to this one byte,
127                // so we need to push the stroke and start fresh for subsequent bytes.
128                if current_stroke.control || current_stroke.meta {
129                    strokes.push(current_stroke);
130                    current_stroke = KeyStroke::default();
131                }
132            }
133        }
134    }
135
136    if current_stroke.key_code.is_empty() {
137        if current_stroke.control || current_stroke.meta {
138            return Err(error::BindingParseError::MissingKeyCode);
139        }
140    } else {
141        strokes.push(current_stroke);
142    }
143
144    Ok(strokes)
145}
146
147peg::parser! {
148    grammar readline_binding() for str {
149        rule _() = [' ' | '\t' | '\n']*
150
151        pub rule key_sequence_shell_cmd_binding() -> KeySequenceShellCommandBinding =
152            _ "\"" seq:key_sequence() "\"" _ ":" _ cmd:shell_cmd() _ { KeySequenceShellCommandBinding { seq, shell_cmd: cmd } }
153
154        pub rule key_sequence_readline_binding() -> KeySequenceReadlineBinding =
155            _ "\"" seq:key_sequence() "\"" _ ":" _ "\"" cmd:readline_cmd() "\"" _ {
156                KeySequenceReadlineBinding { seq, target: ReadlineTarget::Macro(cmd) }
157            } /
158            _ "\"" seq:key_sequence() "\"" _ ":" _ func:readline_function() _ {
159                KeySequenceReadlineBinding { seq, target: ReadlineTarget::Function(func) }
160            }
161
162        rule readline_cmd() -> String = s:$([^'"']*) { s.to_string() }
163        rule shell_cmd() -> String = s:$([_]*) { s.to_string() }
164        rule readline_function() -> String = s:$([_]*) { s.to_string() }
165
166        // Main rule for parsing a key sequence
167        pub rule key_sequence() -> KeySequence =
168            items:key_sequence_item()* { KeySequence(items) }
169
170        rule key_sequence_item() -> KeySequenceItem =
171            "\\C-" { KeySequenceItem::Control } /
172            "\\M-" { KeySequenceItem::Meta } /
173            "\\e" { KeySequenceItem::Byte(b'\x1b') } /
174            "\\\\" { KeySequenceItem::Byte(b'\\') } /
175            "\\\"" { KeySequenceItem::Byte(b'"') } /
176            "\\'" { KeySequenceItem::Byte(b'\'') } /
177            "\\a" { KeySequenceItem::Byte(b'\x07') } /
178            "\\b" { KeySequenceItem::Byte(b'\x08') } /
179            "\\d" { KeySequenceItem::Byte(b'\x7f') } /
180            "\\f" { KeySequenceItem::Byte(b'\x0c') } /
181            "\\n" { KeySequenceItem::Byte(b'\n') } /
182            "\\r" { KeySequenceItem::Byte(b'\r') } /
183            "\\t" { KeySequenceItem::Byte(b'\t') } /
184            "\\v" { KeySequenceItem::Byte(b'\x0b') } /
185            "\\" n:octal_number() { KeySequenceItem::Byte(n) } /
186            "\\" n:hex_number() { KeySequenceItem::Byte(n) } /
187            [c if c != '"'] { KeySequenceItem::Byte(c as u8) }
188
189        rule octal_number() -> u8 =
190            s:$(['0'..='7']*<1,3>) {? u8::from_str_radix(s, 8).or(Err("invalid octal number")) }
191
192        rule hex_number() -> u8 =
193            s:$(['0'..='9' | 'a'..='f' | 'A'..='F']*<1,2>) {? u8::from_str_radix(s, 16).or(Err("invalid hex number")) }
194    }
195}
196
197#[cfg(test)]
198#[expect(clippy::panic_in_result_fn)]
199mod tests {
200    use super::*;
201    use anyhow::Result;
202
203    #[test]
204    fn test_basic_shell_cmd_binding_parse() -> Result<()> {
205        let binding = parse_key_sequence_shell_cmd_binding(r#""\C-k": xyz"#)?;
206        assert_eq!(
207            binding.seq.0,
208            [KeySequenceItem::Control, KeySequenceItem::Byte(b'k')]
209        );
210        assert_eq!(binding.shell_cmd, "xyz");
211
212        Ok(())
213    }
214
215    #[test]
216    fn test_basic_readline_func_binding_parse() -> Result<()> {
217        let binding = parse_key_sequence_readline_binding(r#""\M-x": some-function"#)?;
218        assert_eq!(
219            binding.seq.0,
220            [KeySequenceItem::Meta, KeySequenceItem::Byte(b'x')]
221        );
222        assert_eq!(
223            binding.target,
224            ReadlineTarget::Function("some-function".to_string())
225        );
226
227        Ok(())
228    }
229
230    #[test]
231    fn test_basic_readline_cmd_binding_parse() -> Result<()> {
232        let binding = parse_key_sequence_readline_binding(r#""\C-k": "xyz""#)?;
233        assert_eq!(
234            binding.seq.0,
235            [KeySequenceItem::Control, KeySequenceItem::Byte(b'k')]
236        );
237        assert_eq!(binding.target, ReadlineTarget::Macro(String::from("xyz")));
238
239        Ok(())
240    }
241}