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 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 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 (_, 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}