reedline/edit_mode/vi/
command.rs

1use super::{motion::Motion, motion::ViCharSearch, parser::ReedlineOption};
2use crate::{EditCommand, ReedlineEvent, Vi};
3use std::iter::Peekable;
4
5pub fn parse_command<'iter, I>(input: &mut Peekable<I>) -> Option<Command>
6where
7    I: Iterator<Item = &'iter char>,
8{
9    match input.peek() {
10        Some('d') => {
11            let _ = input.next();
12            Some(Command::Delete)
13        }
14        Some('p') => {
15            let _ = input.next();
16            Some(Command::PasteAfter)
17        }
18        Some('P') => {
19            let _ = input.next();
20            Some(Command::PasteBefore)
21        }
22        Some('i') => {
23            let _ = input.next();
24            Some(Command::EnterViInsert)
25        }
26        Some('a') => {
27            let _ = input.next();
28            Some(Command::EnterViAppend)
29        }
30        Some('u') => {
31            let _ = input.next();
32            Some(Command::Undo)
33        }
34        Some('c') => {
35            let _ = input.next();
36            Some(Command::Change)
37        }
38        Some('x') => {
39            let _ = input.next();
40            Some(Command::DeleteChar)
41        }
42        Some('r') => {
43            let _ = input.next();
44            match input.next() {
45                Some(c) => Some(Command::ReplaceChar(*c)),
46                None => Some(Command::Incomplete),
47            }
48        }
49        Some('s') => {
50            let _ = input.next();
51            Some(Command::SubstituteCharWithInsert)
52        }
53        Some('?') => {
54            let _ = input.next();
55            Some(Command::HistorySearch)
56        }
57        Some('C') => {
58            let _ = input.next();
59            Some(Command::ChangeToLineEnd)
60        }
61        Some('D') => {
62            let _ = input.next();
63            Some(Command::DeleteToEnd)
64        }
65        Some('I') => {
66            let _ = input.next();
67            Some(Command::PrependToStart)
68        }
69        Some('A') => {
70            let _ = input.next();
71            Some(Command::AppendToEnd)
72        }
73        Some('S') => {
74            let _ = input.next();
75            Some(Command::RewriteCurrentLine)
76        }
77        Some('~') => {
78            let _ = input.next();
79            Some(Command::Switchcase)
80        }
81        Some('.') => {
82            let _ = input.next();
83            Some(Command::RepeatLastAction)
84        }
85        _ => None,
86    }
87}
88
89#[derive(Debug, PartialEq, Eq)]
90pub enum Command {
91    Incomplete,
92    Delete,
93    DeleteChar,
94    ReplaceChar(char),
95    SubstituteCharWithInsert,
96    PasteAfter,
97    PasteBefore,
98    EnterViAppend,
99    EnterViInsert,
100    Undo,
101    ChangeToLineEnd,
102    DeleteToEnd,
103    AppendToEnd,
104    PrependToStart,
105    RewriteCurrentLine,
106    Change,
107    HistorySearch,
108    Switchcase,
109    RepeatLastAction,
110}
111
112impl Command {
113    pub fn whole_line_char(&self) -> Option<char> {
114        match self {
115            Command::Delete => Some('d'),
116            Command::Change => Some('c'),
117            _ => None,
118        }
119    }
120
121    pub fn requires_motion(&self) -> bool {
122        matches!(self, Command::Delete | Command::Change)
123    }
124
125    pub fn to_reedline(&self, vi_state: &mut Vi) -> Vec<ReedlineOption> {
126        match self {
127            Self::EnterViInsert => vec![ReedlineOption::Event(ReedlineEvent::Repaint)],
128            Self::EnterViAppend => vec![ReedlineOption::Edit(EditCommand::MoveRight)],
129            Self::PasteAfter => vec![ReedlineOption::Edit(EditCommand::PasteCutBufferAfter)],
130            Self::PasteBefore => vec![ReedlineOption::Edit(EditCommand::PasteCutBufferBefore)],
131            Self::Undo => vec![ReedlineOption::Edit(EditCommand::Undo)],
132            Self::ChangeToLineEnd => vec![ReedlineOption::Edit(EditCommand::ClearToLineEnd)],
133            Self::DeleteToEnd => vec![ReedlineOption::Edit(EditCommand::CutToLineEnd)],
134            Self::AppendToEnd => vec![ReedlineOption::Edit(EditCommand::MoveToLineEnd)],
135            Self::PrependToStart => vec![ReedlineOption::Edit(EditCommand::MoveToLineStart)],
136            Self::RewriteCurrentLine => vec![ReedlineOption::Edit(EditCommand::CutCurrentLine)],
137            Self::DeleteChar => vec![ReedlineOption::Edit(EditCommand::CutChar)],
138            Self::ReplaceChar(c) => {
139                vec![ReedlineOption::Edit(EditCommand::ReplaceChar(*c))]
140            }
141            Self::SubstituteCharWithInsert => vec![ReedlineOption::Edit(EditCommand::CutChar)],
142            Self::HistorySearch => vec![ReedlineOption::Event(ReedlineEvent::SearchHistory)],
143            Self::Switchcase => vec![ReedlineOption::Edit(EditCommand::SwitchcaseChar)],
144            // Mark a command as incomplete whenever a motion is required to finish the command
145            Self::Delete | Self::Change | Self::Incomplete => vec![ReedlineOption::Incomplete],
146            Command::RepeatLastAction => match &vi_state.previous {
147                Some(event) => vec![ReedlineOption::Event(event.clone())],
148                None => vec![],
149            },
150        }
151    }
152
153    pub fn to_reedline_with_motion(
154        &self,
155        motion: &Motion,
156        vi_state: &mut Vi,
157    ) -> Option<Vec<ReedlineOption>> {
158        match self {
159            Self::Delete => match motion {
160                Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CutToLineEnd)]),
161                Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CutCurrentLine)]),
162                Motion::NextWord => {
163                    Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightToNext)])
164                }
165                Motion::NextBigWord => Some(vec![ReedlineOption::Edit(
166                    EditCommand::CutBigWordRightToNext,
167                )]),
168                Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWordRight)]),
169                Motion::NextBigWordEnd => {
170                    Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRight)])
171                }
172                Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWordLeft)]),
173                Motion::PreviousBigWord => {
174                    Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)])
175                }
176                Motion::RightUntil(c) => {
177                    vi_state.last_char_search = Some(ViCharSearch::ToRight(*c));
178                    Some(vec![ReedlineOption::Edit(EditCommand::CutRightUntil(*c))])
179                }
180                Motion::RightBefore(c) => {
181                    vi_state.last_char_search = Some(ViCharSearch::TillRight(*c));
182                    Some(vec![ReedlineOption::Edit(EditCommand::CutRightBefore(*c))])
183                }
184                Motion::LeftUntil(c) => {
185                    vi_state.last_char_search = Some(ViCharSearch::ToLeft(*c));
186                    Some(vec![ReedlineOption::Edit(EditCommand::CutLeftUntil(*c))])
187                }
188                Motion::LeftBefore(c) => {
189                    vi_state.last_char_search = Some(ViCharSearch::TillLeft(*c));
190                    Some(vec![ReedlineOption::Edit(EditCommand::CutLeftBefore(*c))])
191                }
192                Motion::Start => Some(vec![ReedlineOption::Edit(EditCommand::CutFromLineStart)]),
193                Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::Backspace)]),
194                Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
195                Motion::Up => None,
196                Motion::Down => None,
197                Motion::ReplayCharSearch => vi_state
198                    .last_char_search
199                    .as_ref()
200                    .map(|char_search| vec![ReedlineOption::Edit(char_search.to_cut())]),
201                Motion::ReverseCharSearch => vi_state
202                    .last_char_search
203                    .as_ref()
204                    .map(|char_search| vec![ReedlineOption::Edit(char_search.reverse().to_cut())]),
205            },
206            Self::Change => {
207                let op = match motion {
208                    Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::ClearToLineEnd)]),
209                    Motion::Line => Some(vec![
210                        ReedlineOption::Edit(EditCommand::MoveToStart),
211                        ReedlineOption::Edit(EditCommand::ClearToLineEnd),
212                    ]),
213                    Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWordRight)]),
214                    Motion::NextBigWord => {
215                        Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRight)])
216                    }
217                    Motion::NextWordEnd => {
218                        Some(vec![ReedlineOption::Edit(EditCommand::CutWordRight)])
219                    }
220                    Motion::NextBigWordEnd => {
221                        Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRight)])
222                    }
223                    Motion::PreviousWord => {
224                        Some(vec![ReedlineOption::Edit(EditCommand::CutWordLeft)])
225                    }
226                    Motion::PreviousBigWord => {
227                        Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)])
228                    }
229                    Motion::RightUntil(c) => {
230                        vi_state.last_char_search = Some(ViCharSearch::ToRight(*c));
231                        Some(vec![ReedlineOption::Edit(EditCommand::CutRightUntil(*c))])
232                    }
233                    Motion::RightBefore(c) => {
234                        vi_state.last_char_search = Some(ViCharSearch::TillRight(*c));
235                        Some(vec![ReedlineOption::Edit(EditCommand::CutRightBefore(*c))])
236                    }
237                    Motion::LeftUntil(c) => {
238                        vi_state.last_char_search = Some(ViCharSearch::ToLeft(*c));
239                        Some(vec![ReedlineOption::Edit(EditCommand::CutLeftUntil(*c))])
240                    }
241                    Motion::LeftBefore(c) => {
242                        vi_state.last_char_search = Some(ViCharSearch::TillLeft(*c));
243                        Some(vec![ReedlineOption::Edit(EditCommand::CutLeftBefore(*c))])
244                    }
245                    Motion::Start => {
246                        Some(vec![ReedlineOption::Edit(EditCommand::CutFromLineStart)])
247                    }
248                    Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::Backspace)]),
249                    Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
250                    Motion::Up => None,
251                    Motion::Down => None,
252                    Motion::ReplayCharSearch => vi_state
253                        .last_char_search
254                        .as_ref()
255                        .map(|char_search| vec![ReedlineOption::Edit(char_search.to_cut())]),
256                    Motion::ReverseCharSearch => {
257                        vi_state.last_char_search.as_ref().map(|char_search| {
258                            vec![ReedlineOption::Edit(char_search.reverse().to_cut())]
259                        })
260                    }
261                };
262                // Semihack: Append `Repaint` to ensure the mode change gets displayed
263                op.map(|mut vec| {
264                    vec.push(ReedlineOption::Event(ReedlineEvent::Repaint));
265                    vec
266                })
267            }
268            _ => None,
269        }
270    }
271}