reedline/edit_mode/vi/
parser.rs

1use super::command::{parse_command, Command};
2use super::motion::{parse_motion, Motion};
3use crate::{EditCommand, ReedlineEvent, Vi};
4use std::iter::Peekable;
5
6#[derive(Debug, Clone)]
7pub enum ReedlineOption {
8    Event(ReedlineEvent),
9    Edit(EditCommand),
10    Incomplete,
11}
12
13impl ReedlineOption {
14    pub fn into_reedline_event(self) -> Option<ReedlineEvent> {
15        match self {
16            ReedlineOption::Event(event) => Some(event),
17            ReedlineOption::Edit(edit) => Some(ReedlineEvent::Edit(vec![edit])),
18            ReedlineOption::Incomplete => None,
19        }
20    }
21}
22
23#[derive(Debug, PartialEq, Eq)]
24pub enum ParseResult<T> {
25    Valid(T),
26    Incomplete,
27    Invalid,
28}
29
30impl<T> ParseResult<T> {
31    fn is_invalid(&self) -> bool {
32        match self {
33            ParseResult::Valid(_) => false,
34            ParseResult::Incomplete => false,
35            ParseResult::Invalid => true,
36        }
37    }
38}
39
40#[derive(Debug, PartialEq, Eq)]
41pub struct ParsedViSequence {
42    multiplier: Option<usize>,
43    command: Option<Command>,
44    count: Option<usize>,
45    motion: ParseResult<Motion>,
46}
47
48impl ParsedViSequence {
49    pub fn is_valid(&self) -> bool {
50        !self.motion.is_invalid()
51    }
52
53    pub fn is_complete(&self) -> bool {
54        match (&self.command, &self.motion) {
55            (None, ParseResult::Valid(_)) => true,
56            (Some(Command::Incomplete), _) => false,
57            (Some(cmd), ParseResult::Incomplete) if !cmd.requires_motion() => true,
58            (Some(_), ParseResult::Valid(_)) => true,
59            (Some(cmd), ParseResult::Incomplete) if cmd.requires_motion() => false,
60            _ => false,
61        }
62    }
63
64    /// Combine `multiplier` and `count` as vim only considers the product
65    ///
66    /// Default return value: 1
67    ///
68    /// ### Note:
69    ///
70    /// <https://github.com/vim/vim/blob/140f6d0eda7921f2f0b057ec38ed501240903fc3/runtime/doc/motion.txt#L64-L70>
71    fn total_multiplier(&self) -> usize {
72        self.multiplier.unwrap_or(1) * self.count.unwrap_or(1)
73    }
74
75    fn apply_multiplier(&self, raw_events: Option<Vec<ReedlineOption>>) -> ReedlineEvent {
76        if let Some(raw_events) = raw_events {
77            let events = std::iter::repeat(raw_events)
78                .take(self.total_multiplier())
79                .flatten()
80                .filter_map(ReedlineOption::into_reedline_event)
81                .collect::<Vec<ReedlineEvent>>();
82
83            if events.is_empty() || events.contains(&ReedlineEvent::None) {
84                // TODO: Clarify if the `contains(ReedlineEvent::None)` path is relevant
85                ReedlineEvent::None
86            } else {
87                ReedlineEvent::Multiple(events)
88            }
89        } else {
90            ReedlineEvent::None
91        }
92    }
93
94    pub fn enters_insert_mode(&self) -> bool {
95        matches!(
96            (&self.command, &self.motion),
97            (Some(Command::EnterViInsert), ParseResult::Incomplete)
98                | (Some(Command::EnterViAppend), ParseResult::Incomplete)
99                | (Some(Command::ChangeToLineEnd), ParseResult::Incomplete)
100                | (Some(Command::AppendToEnd), ParseResult::Incomplete)
101                | (Some(Command::PrependToStart), ParseResult::Incomplete)
102                | (Some(Command::RewriteCurrentLine), ParseResult::Incomplete)
103                | (
104                    Some(Command::SubstituteCharWithInsert),
105                    ParseResult::Incomplete
106                )
107                | (Some(Command::HistorySearch), ParseResult::Incomplete)
108                | (Some(Command::Change), ParseResult::Valid(_))
109        )
110    }
111
112    pub fn to_reedline_event(&self, vi_state: &mut Vi) -> ReedlineEvent {
113        match (&self.multiplier, &self.command, &self.count, &self.motion) {
114            (_, Some(command), None, ParseResult::Incomplete) => {
115                let events = self.apply_multiplier(Some(command.to_reedline(vi_state)));
116                match &events {
117                    ReedlineEvent::None => {}
118                    event => vi_state.previous = Some(event.clone()),
119                }
120                events
121            }
122            // This case handles all combinations of commands and motions that could exist
123            (_, Some(command), _, ParseResult::Valid(motion)) => {
124                let events =
125                    self.apply_multiplier(command.to_reedline_with_motion(motion, vi_state));
126                match &events {
127                    ReedlineEvent::None => {}
128                    event => vi_state.previous = Some(event.clone()),
129                }
130                events
131            }
132            (_, None, _, ParseResult::Valid(motion)) => {
133                self.apply_multiplier(Some(motion.to_reedline(vi_state)))
134            }
135            _ => ReedlineEvent::None,
136        }
137    }
138}
139
140fn parse_number<'iter, I>(input: &mut Peekable<I>) -> Option<usize>
141where
142    I: Iterator<Item = &'iter char>,
143{
144    match input.peek() {
145        Some('0') => None,
146        Some(x) if x.is_ascii_digit() => {
147            let mut count: usize = 0;
148            while let Some(&c) = input.peek() {
149                if c.is_ascii_digit() {
150                    let c = c.to_digit(10).expect("already checked if is a digit");
151                    let _ = input.next();
152                    count *= 10;
153                    count += c as usize;
154                } else {
155                    return Some(count);
156                }
157            }
158            Some(count)
159        }
160        _ => None,
161    }
162}
163
164pub fn parse<'iter, I>(input: &mut Peekable<I>) -> ParsedViSequence
165where
166    I: Iterator<Item = &'iter char>,
167{
168    let multiplier = parse_number(input);
169    let command = parse_command(input);
170    let count = parse_number(input);
171    let motion = parse_motion(input, command.as_ref().and_then(Command::whole_line_char));
172
173    ParsedViSequence {
174        multiplier,
175        command,
176        count,
177        motion,
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use pretty_assertions::assert_eq;
185    use rstest::rstest;
186
187    fn vi_parse(input: &[char]) -> ParsedViSequence {
188        parse(&mut input.iter().peekable())
189    }
190
191    #[test]
192    fn test_delete_word() {
193        let input = ['d', 'w'];
194        let output = vi_parse(&input);
195
196        assert_eq!(
197            output,
198            ParsedViSequence {
199                multiplier: None,
200                command: Some(Command::Delete),
201                count: None,
202                motion: ParseResult::Valid(Motion::NextWord),
203            }
204        );
205        assert_eq!(output.is_valid(), true);
206        assert_eq!(output.is_complete(), true);
207    }
208
209    #[test]
210    fn test_two_delete_word() {
211        let input = ['2', 'd', 'w'];
212        let output = vi_parse(&input);
213
214        assert_eq!(
215            output,
216            ParsedViSequence {
217                multiplier: Some(2),
218                command: Some(Command::Delete),
219                count: None,
220                motion: ParseResult::Valid(Motion::NextWord),
221            }
222        );
223        assert_eq!(output.is_valid(), true);
224        assert_eq!(output.is_complete(), true);
225    }
226
227    #[test]
228    fn test_two_delete_two_word() {
229        let input = ['2', 'd', '2', 'w'];
230        let output = vi_parse(&input);
231
232        assert_eq!(
233            output,
234            ParsedViSequence {
235                multiplier: Some(2),
236                command: Some(Command::Delete),
237                count: Some(2),
238                motion: ParseResult::Valid(Motion::NextWord),
239            }
240        );
241        assert_eq!(output.is_valid(), true);
242        assert_eq!(output.is_complete(), true);
243    }
244
245    #[test]
246    fn test_two_delete_twenty_word() {
247        let input = ['2', 'd', '2', '0', 'w'];
248        let output = vi_parse(&input);
249
250        assert_eq!(
251            output,
252            ParsedViSequence {
253                multiplier: Some(2),
254                command: Some(Command::Delete),
255                count: Some(20),
256                motion: ParseResult::Valid(Motion::NextWord),
257            }
258        );
259        assert_eq!(output.is_valid(), true);
260        assert_eq!(output.is_complete(), true);
261    }
262
263    #[test]
264    fn test_two_delete_two_lines() {
265        let input = ['2', 'd', 'd'];
266        let output = vi_parse(&input);
267
268        assert_eq!(
269            output,
270            ParsedViSequence {
271                multiplier: Some(2),
272                command: Some(Command::Delete),
273                count: None,
274                motion: ParseResult::Valid(Motion::Line),
275            }
276        );
277        assert_eq!(output.is_valid(), true);
278        assert_eq!(output.is_complete(), true);
279    }
280
281    #[test]
282    fn test_find_action() {
283        let input = ['d', 't', 'd'];
284        let output = vi_parse(&input);
285
286        assert_eq!(
287            output,
288            ParsedViSequence {
289                multiplier: None,
290                command: Some(Command::Delete),
291                count: None,
292                motion: ParseResult::Valid(Motion::RightBefore('d')),
293            }
294        );
295        assert_eq!(output.is_valid(), true);
296        assert_eq!(output.is_complete(), true);
297    }
298
299    #[test]
300    fn test_has_garbage() {
301        let input = ['2', 'd', 'm'];
302        let output = vi_parse(&input);
303
304        assert_eq!(
305            output,
306            ParsedViSequence {
307                multiplier: Some(2),
308                command: Some(Command::Delete),
309                count: None,
310                motion: ParseResult::Invalid,
311            }
312        );
313        assert_eq!(output.is_valid(), false);
314    }
315
316    #[test]
317    fn test_partial_action() {
318        let input = ['r'];
319        let output = vi_parse(&input);
320
321        assert_eq!(
322            output,
323            ParsedViSequence {
324                multiplier: None,
325                command: Some(Command::Incomplete),
326                count: None,
327                motion: ParseResult::Incomplete,
328            }
329        );
330
331        assert_eq!(output.is_valid(), true);
332        assert_eq!(output.is_complete(), false);
333    }
334
335    #[test]
336    fn test_partial_motion() {
337        let input = ['f'];
338        let output = vi_parse(&input);
339
340        assert_eq!(
341            output,
342            ParsedViSequence {
343                multiplier: None,
344                command: None,
345                count: None,
346                motion: ParseResult::Incomplete,
347            }
348        );
349        assert_eq!(output.is_valid(), true);
350        assert_eq!(output.is_complete(), false);
351    }
352
353    #[test]
354    fn test_two_char_action_replace() {
355        let input = ['r', 'k'];
356        let output = vi_parse(&input);
357
358        assert_eq!(
359            output,
360            ParsedViSequence {
361                multiplier: None,
362                command: Some(Command::ReplaceChar('k')),
363                count: None,
364                motion: ParseResult::Incomplete,
365            }
366        );
367
368        assert_eq!(output.is_valid(), true);
369        assert_eq!(output.is_complete(), true);
370    }
371
372    #[test]
373    fn test_find_motion() {
374        let input = ['2', 'f', 'f'];
375        let output = vi_parse(&input);
376
377        assert_eq!(
378            output,
379            ParsedViSequence {
380                multiplier: Some(2),
381                command: None,
382                count: None,
383                motion: ParseResult::Valid(Motion::RightUntil('f')),
384            }
385        );
386        assert_eq!(output.is_valid(), true);
387        assert_eq!(output.is_complete(), true);
388    }
389
390    #[test]
391    fn test_two_up() {
392        let input = ['2', 'k'];
393        let output = vi_parse(&input);
394
395        assert_eq!(
396            output,
397            ParsedViSequence {
398                multiplier: Some(2),
399                command: None,
400                count: None,
401                motion: ParseResult::Valid(Motion::Up),
402            }
403        );
404        assert_eq!(output.is_valid(), true);
405        assert_eq!(output.is_complete(), true);
406    }
407
408    #[rstest]
409    #[case(&['2', 'k'], ReedlineEvent::Multiple(vec![ReedlineEvent::UntilFound(vec![
410                ReedlineEvent::MenuUp,
411                ReedlineEvent::Up,
412            ]), ReedlineEvent::UntilFound(vec![
413                ReedlineEvent::MenuUp,
414                ReedlineEvent::Up,
415            ])]))]
416    #[case(&['k'], ReedlineEvent::Multiple(vec![ReedlineEvent::UntilFound(vec![
417                ReedlineEvent::MenuUp,
418                ReedlineEvent::Up,
419            ])]))]
420    #[case(&['w'],
421        ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWordRightStart])]))]
422    #[case(&['W'],
423        ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart])]))]
424    #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![
425        ReedlineEvent::UntilFound(vec![
426                ReedlineEvent::HistoryHintComplete,
427                ReedlineEvent::MenuRight,
428                ReedlineEvent::Right,
429            ]),ReedlineEvent::UntilFound(vec![
430                ReedlineEvent::HistoryHintComplete,
431                ReedlineEvent::MenuRight,
432                ReedlineEvent::Right,
433            ]) ]))]
434    #[case(&['l'], ReedlineEvent::Multiple(vec![ReedlineEvent::UntilFound(vec![
435                ReedlineEvent::HistoryHintComplete,
436                ReedlineEvent::MenuRight,
437                ReedlineEvent::Right,
438            ])]))]
439    #[case(&['0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart])]))]
440    #[case(&['$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveToLineEnd])]))]
441    #[case(&['i'], ReedlineEvent::Multiple(vec![ReedlineEvent::Repaint]))]
442    #[case(&['p'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::PasteCutBufferAfter])]))]
443    #[case(&['2', 'p'], ReedlineEvent::Multiple(vec![
444        ReedlineEvent::Edit(vec![EditCommand::PasteCutBufferAfter]),
445        ReedlineEvent::Edit(vec![EditCommand::PasteCutBufferAfter])
446        ]))]
447    #[case(&['u'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Undo])]))]
448    #[case(&['2', 'u'], ReedlineEvent::Multiple(vec![
449        ReedlineEvent::Edit(vec![EditCommand::Undo]),
450        ReedlineEvent::Edit(vec![EditCommand::Undo])
451        ]))]
452    #[case(&['d', 'd'], ReedlineEvent::Multiple(vec![
453        ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))]
454    #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightToNext])]))]
455    #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightToNext])]))]
456    #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRight])]))]
457    #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordLeft])]))]
458    #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft])]))]
459    fn test_reedline_move(#[case] input: &[char], #[case] expected: ReedlineEvent) {
460        let mut vi = Vi::default();
461        let res = vi_parse(input);
462        let output = res.to_reedline_event(&mut vi);
463
464        assert_eq!(output, expected);
465    }
466}