Skip to content

Commit 39d9938

Browse files
committed
feat(vi-mode): add gg and G motion
There is a bit of divergence in behaviour of `gg` and `G` motion of reedline and vim. In vim, these motion maintain the column position of the cursor, but reedline doesn't. Instead `gg` takes the cursor to the very first character and `G` takes to the very last character. Also when using these motion with a command, e.g. `c`, `d` and `y`, there are some inconsistency in handling the new line character due reedline not supporting line wise copy unlike vim. Added test case for `dgg`, `dG`, `cgg` and `cG` command.
1 parent d027940 commit 39d9938

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

src/edit_mode/vi/command.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,18 @@ impl Command {
277277
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
278278
Motion::Up => None,
279279
Motion::Down => None,
280+
Motion::FirstLine => Some(vec![
281+
ReedlineOption::Edit(EditCommand::MoveToLineEnd { select: false }),
282+
// `MoveRight` to include the new line character
283+
ReedlineOption::Edit(EditCommand::MoveRight { select: false }),
284+
ReedlineOption::Edit(EditCommand::CutFromStart),
285+
]),
286+
Motion::LastLine => Some(vec![
287+
ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }),
288+
// `MoveLeft` to include the new line character
289+
ReedlineOption::Edit(EditCommand::MoveLeft { select: false }),
290+
ReedlineOption::Edit(EditCommand::CutToEnd),
291+
]),
280292
Motion::ReplayCharSearch => vi_state
281293
.last_char_search
282294
.as_ref()
@@ -332,6 +344,14 @@ impl Command {
332344
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
333345
Motion::Up => None,
334346
Motion::Down => None,
347+
Motion::FirstLine => Some(vec![
348+
ReedlineOption::Edit(EditCommand::MoveToLineEnd { select: false }),
349+
ReedlineOption::Edit(EditCommand::CutFromStart),
350+
]),
351+
Motion::LastLine => Some(vec![
352+
ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }),
353+
ReedlineOption::Edit(EditCommand::CutToEnd),
354+
]),
335355
Motion::ReplayCharSearch => vi_state
336356
.last_char_search
337357
.as_ref()
@@ -386,6 +406,17 @@ impl Command {
386406
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::CopyRight)]),
387407
Motion::Up => None,
388408
Motion::Down => None,
409+
Motion::FirstLine => Some(vec![
410+
ReedlineOption::Edit(EditCommand::MoveToLineEnd { select: false }),
411+
// `MoveRight` to include the new line character
412+
ReedlineOption::Edit(EditCommand::MoveRight { select: false }),
413+
ReedlineOption::Edit(EditCommand::CopyFromStart),
414+
ReedlineOption::Edit(EditCommand::MoveToStart { select: false }),
415+
]),
416+
Motion::LastLine => Some(vec![
417+
ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }),
418+
ReedlineOption::Edit(EditCommand::CopyToEnd),
419+
]),
389420
Motion::ReplayCharSearch => vi_state
390421
.last_char_search
391422
.as_ref()

src/edit_mode/vi/motion.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@ where
108108
let _ = input.next();
109109
ParseResult::Valid(Motion::ReverseCharSearch)
110110
}
111+
Some('g') => {
112+
let _ = input.next();
113+
match input.peek() {
114+
Some('g') => {
115+
input.next();
116+
ParseResult::Valid(Motion::FirstLine)
117+
}
118+
Some(_) => ParseResult::Invalid,
119+
None => ParseResult::Incomplete,
120+
}
121+
}
122+
Some('G') => {
123+
let _ = input.next();
124+
ParseResult::Valid(Motion::LastLine)
125+
}
111126
ch if ch == command_char.as_ref().as_ref() && command_char.is_some() => {
112127
let _ = input.next();
113128
ParseResult::Valid(Motion::Line)
@@ -132,6 +147,8 @@ pub enum Motion {
132147
Line,
133148
Start,
134149
End,
150+
FirstLine,
151+
LastLine,
135152
RightUntil(char),
136153
RightBefore(char),
137154
LeftUntil(char),
@@ -194,6 +211,12 @@ impl Motion {
194211
Motion::End => vec![ReedlineOption::Edit(EditCommand::MoveToLineEnd {
195212
select: select_mode,
196213
})],
214+
Motion::FirstLine => vec![ReedlineOption::Edit(EditCommand::MoveToStart {
215+
select: select_mode,
216+
})],
217+
Motion::LastLine => vec![ReedlineOption::Edit(EditCommand::MoveToEnd {
218+
select: select_mode,
219+
})],
197220
Motion::RightUntil(ch) => {
198221
vi_state.last_char_search = Some(ViCharSearch::ToRight(*ch));
199222
vec![ReedlineOption::Edit(EditCommand::MoveRightUntil {

src/edit_mode/vi/parser.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,16 @@ mod tests {
546546
#[case(&['d', 't', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutRightBefore('a')])]))]
547547
#[case(&['d', 'F', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftUntil('a')])]))]
548548
#[case(&['d', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')])]))]
549+
#[case(&['d', 'g', 'g'], ReedlineEvent::Multiple(vec![
550+
ReedlineEvent::Edit(vec![EditCommand::MoveToLineEnd { select: false }]),
551+
ReedlineEvent::Edit(vec![EditCommand::MoveRight { select: false }]),
552+
ReedlineEvent::Edit(vec![EditCommand::CutFromStart]),
553+
]))]
554+
#[case(&['d', 'G'], ReedlineEvent::Multiple(vec![
555+
ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]),
556+
ReedlineEvent::Edit(vec![EditCommand::MoveLeft { select: false }]),
557+
ReedlineEvent::Edit(vec![EditCommand::CutToEnd]),
558+
]))]
549559
#[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRight]), ReedlineEvent::Repaint]))]
550560
#[case(&['c', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))]
551561
#[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))]
@@ -554,6 +564,16 @@ mod tests {
554564
#[case(&['c', 't', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutRightBefore('a')]), ReedlineEvent::Repaint]))]
555565
#[case(&['c', 'F', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftUntil('a')]), ReedlineEvent::Repaint]))]
556566
#[case(&['c', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')]), ReedlineEvent::Repaint]))]
567+
#[case(&['c', 'g', 'g'], ReedlineEvent::Multiple(vec![
568+
ReedlineEvent::Edit(vec![EditCommand::MoveToLineEnd { select: false }]),
569+
ReedlineEvent::Edit(vec![EditCommand::CutFromStart]),
570+
ReedlineEvent::Repaint,
571+
]))]
572+
#[case(&['c', 'G'], ReedlineEvent::Multiple(vec![
573+
ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]),
574+
ReedlineEvent::Edit(vec![EditCommand::CutToEnd]),
575+
ReedlineEvent::Repaint,
576+
]))]
557577
fn test_reedline_move(#[case] input: &[char], #[case] expected: ReedlineEvent) {
558578
let mut vi = Vi::default();
559579
let res = vi_parse(input);

0 commit comments

Comments
 (0)