reedline/edit_mode/vi/
motion.rs

1use std::iter::Peekable;
2
3use crate::{EditCommand, ReedlineEvent, Vi};
4
5use super::parser::{ParseResult, ReedlineOption};
6
7pub fn parse_motion<'iter, I>(
8    input: &mut Peekable<I>,
9    command_char: Option<char>,
10) -> ParseResult<Motion>
11where
12    I: Iterator<Item = &'iter char>,
13{
14    match input.peek() {
15        Some('h') => {
16            let _ = input.next();
17            ParseResult::Valid(Motion::Left)
18        }
19        Some('l') => {
20            let _ = input.next();
21            ParseResult::Valid(Motion::Right)
22        }
23        Some('j') => {
24            let _ = input.next();
25            ParseResult::Valid(Motion::Down)
26        }
27        Some('k') => {
28            let _ = input.next();
29            ParseResult::Valid(Motion::Up)
30        }
31        Some('b') => {
32            let _ = input.next();
33            ParseResult::Valid(Motion::PreviousWord)
34        }
35        Some('B') => {
36            let _ = input.next();
37            ParseResult::Valid(Motion::PreviousBigWord)
38        }
39        Some('w') => {
40            let _ = input.next();
41            ParseResult::Valid(Motion::NextWord)
42        }
43        Some('W') => {
44            let _ = input.next();
45            ParseResult::Valid(Motion::NextBigWord)
46        }
47        Some('e') => {
48            let _ = input.next();
49            ParseResult::Valid(Motion::NextWordEnd)
50        }
51        Some('E') => {
52            let _ = input.next();
53            ParseResult::Valid(Motion::NextBigWordEnd)
54        }
55        Some('0' | '^') => {
56            let _ = input.next();
57            ParseResult::Valid(Motion::Start)
58        }
59        Some('$') => {
60            let _ = input.next();
61            ParseResult::Valid(Motion::End)
62        }
63        Some('f') => {
64            let _ = input.next();
65            match input.peek() {
66                Some(&x) => {
67                    input.next();
68                    ParseResult::Valid(Motion::RightUntil(*x))
69                }
70                None => ParseResult::Incomplete,
71            }
72        }
73        Some('t') => {
74            let _ = input.next();
75            match input.peek() {
76                Some(&x) => {
77                    input.next();
78                    ParseResult::Valid(Motion::RightBefore(*x))
79                }
80                None => ParseResult::Incomplete,
81            }
82        }
83        Some('F') => {
84            let _ = input.next();
85            match input.peek() {
86                Some(&x) => {
87                    input.next();
88                    ParseResult::Valid(Motion::LeftUntil(*x))
89                }
90                None => ParseResult::Incomplete,
91            }
92        }
93        Some('T') => {
94            let _ = input.next();
95            match input.peek() {
96                Some(&x) => {
97                    input.next();
98                    ParseResult::Valid(Motion::LeftBefore(*x))
99                }
100                None => ParseResult::Incomplete,
101            }
102        }
103        Some(';') => {
104            let _ = input.next();
105            ParseResult::Valid(Motion::ReplayCharSearch)
106        }
107        Some(',') => {
108            let _ = input.next();
109            ParseResult::Valid(Motion::ReverseCharSearch)
110        }
111        ch if ch == command_char.as_ref().as_ref() && command_char.is_some() => {
112            let _ = input.next();
113            ParseResult::Valid(Motion::Line)
114        }
115        None => ParseResult::Incomplete,
116        _ => ParseResult::Invalid,
117    }
118}
119
120#[derive(Debug, PartialEq, Eq)]
121pub enum Motion {
122    Left,
123    Right,
124    Up,
125    Down,
126    NextWord,
127    NextBigWord,
128    NextWordEnd,
129    NextBigWordEnd,
130    PreviousWord,
131    PreviousBigWord,
132    Line,
133    Start,
134    End,
135    RightUntil(char),
136    RightBefore(char),
137    LeftUntil(char),
138    LeftBefore(char),
139    ReplayCharSearch,
140    ReverseCharSearch,
141}
142
143impl Motion {
144    pub fn to_reedline(&self, vi_state: &mut Vi) -> Vec<ReedlineOption> {
145        match self {
146            Motion::Left => vec![ReedlineOption::Event(ReedlineEvent::UntilFound(vec![
147                ReedlineEvent::MenuLeft,
148                ReedlineEvent::Left,
149            ]))],
150            Motion::Right => vec![ReedlineOption::Event(ReedlineEvent::UntilFound(vec![
151                ReedlineEvent::HistoryHintComplete,
152                ReedlineEvent::MenuRight,
153                ReedlineEvent::Right,
154            ]))],
155            Motion::Up => vec![ReedlineOption::Event(ReedlineEvent::UntilFound(vec![
156                ReedlineEvent::MenuUp,
157                ReedlineEvent::Up,
158            ]))],
159            Motion::Down => vec![ReedlineOption::Event(ReedlineEvent::UntilFound(vec![
160                ReedlineEvent::MenuDown,
161                ReedlineEvent::Down,
162            ]))],
163            Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveWordRightStart)],
164            Motion::NextBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightStart)],
165            Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveWordRightEnd)],
166            Motion::NextBigWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightEnd)],
167            Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveWordLeft)],
168            Motion::PreviousBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordLeft)],
169            Motion::Line => vec![], // Placeholder as unusable standalone motion
170            Motion::Start => vec![ReedlineOption::Edit(EditCommand::MoveToLineStart)],
171            Motion::End => vec![ReedlineOption::Edit(EditCommand::MoveToLineEnd)],
172            Motion::RightUntil(ch) => {
173                vi_state.last_char_search = Some(ViCharSearch::ToRight(*ch));
174                vec![ReedlineOption::Edit(EditCommand::MoveRightUntil(*ch))]
175            }
176            Motion::RightBefore(ch) => {
177                vi_state.last_char_search = Some(ViCharSearch::TillRight(*ch));
178                vec![ReedlineOption::Edit(EditCommand::MoveRightBefore(*ch))]
179            }
180            Motion::LeftUntil(ch) => {
181                vi_state.last_char_search = Some(ViCharSearch::ToLeft(*ch));
182                vec![ReedlineOption::Edit(EditCommand::MoveLeftUntil(*ch))]
183            }
184            Motion::LeftBefore(ch) => {
185                vi_state.last_char_search = Some(ViCharSearch::TillLeft(*ch));
186                vec![ReedlineOption::Edit(EditCommand::MoveLeftBefore(*ch))]
187            }
188            Motion::ReplayCharSearch => {
189                if let Some(char_search) = vi_state.last_char_search.as_ref() {
190                    vec![ReedlineOption::Edit(char_search.to_move())]
191                } else {
192                    vec![]
193                }
194            }
195            Motion::ReverseCharSearch => {
196                if let Some(char_search) = vi_state.last_char_search.as_ref() {
197                    vec![ReedlineOption::Edit(char_search.reverse().to_move())]
198                } else {
199                    vec![]
200                }
201            }
202        }
203    }
204}
205
206/// Vi left-right motions to or till a character.
207#[derive(Debug, PartialEq, Eq, Clone)]
208pub enum ViCharSearch {
209    /// f
210    ToRight(char),
211    /// F
212    ToLeft(char),
213    /// t
214    TillRight(char),
215    /// T
216    TillLeft(char),
217}
218
219impl ViCharSearch {
220    /// Swap the direction of the to or till for ','
221    pub fn reverse(&self) -> Self {
222        match self {
223            ViCharSearch::ToRight(c) => ViCharSearch::ToLeft(*c),
224            ViCharSearch::ToLeft(c) => ViCharSearch::ToRight(*c),
225            ViCharSearch::TillRight(c) => ViCharSearch::TillLeft(*c),
226            ViCharSearch::TillLeft(c) => ViCharSearch::TillRight(*c),
227        }
228    }
229
230    pub fn to_move(&self) -> EditCommand {
231        match self {
232            ViCharSearch::ToRight(c) => EditCommand::MoveRightUntil(*c),
233            ViCharSearch::ToLeft(c) => EditCommand::MoveLeftUntil(*c),
234            ViCharSearch::TillRight(c) => EditCommand::MoveRightBefore(*c),
235            ViCharSearch::TillLeft(c) => EditCommand::MoveLeftBefore(*c),
236        }
237    }
238
239    pub fn to_cut(&self) -> EditCommand {
240        match self {
241            ViCharSearch::ToRight(c) => EditCommand::CutRightUntil(*c),
242            ViCharSearch::ToLeft(c) => EditCommand::CutLeftUntil(*c),
243            ViCharSearch::TillRight(c) => EditCommand::CutRightBefore(*c),
244            ViCharSearch::TillLeft(c) => EditCommand::CutLeftBefore(*c),
245        }
246    }
247}