Skip to content

Commit d339c75

Browse files
romaninshFrGoIs
authored andcommitted
Add "shift-r" and "g ." support for helix mode (zed-industries#35468)
Related zed-industries#4642 Compatible with zed-industries#34136 Release Notes: - Helix: `Shift+R` works as Paste instead of taking you to ReplaceMode - Helix: `g .` goes to last modification place (similar to `. in vim)
1 parent 73b48e1 commit d339c75

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

assets/keymaps/vim.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,13 @@
428428
"g h": "vim::StartOfLine",
429429
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
430430
"g e": "vim::EndOfDocument",
431+
"g .": "vim::HelixGotoLastModification", // go to last modification
431432
"g r": "editor::FindAllReferences", // zed specific
432433
"g t": "vim::WindowTop",
433434
"g c": "vim::WindowMiddle",
434435
"g b": "vim::WindowBottom",
435436

437+
"shift-r": "editor::Paste",
436438
"x": "editor::SelectLine",
437439
"shift-x": "editor::SelectLine",
438440
"%": "editor::SelectAll",

crates/vim/src/helix.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ actions!(
2323
HelixInsert,
2424
/// Appends at the end of the selection.
2525
HelixAppend,
26+
/// Goes to the location of the last modification.
27+
HelixGotoLastModification,
2628
]
2729
);
2830

@@ -31,6 +33,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
3133
Vim::action(editor, cx, Vim::helix_insert);
3234
Vim::action(editor, cx, Vim::helix_append);
3335
Vim::action(editor, cx, Vim::helix_yank);
36+
Vim::action(editor, cx, Vim::helix_goto_last_modification);
3437
}
3538

3639
impl Vim {
@@ -430,6 +433,15 @@ impl Vim {
430433
});
431434
self.switch_mode(Mode::HelixNormal, true, window, cx);
432435
}
436+
437+
pub fn helix_goto_last_modification(
438+
&mut self,
439+
_: &HelixGotoLastModification,
440+
window: &mut Window,
441+
cx: &mut Context<Self>,
442+
) {
443+
self.jump(".".into(), false, false, window, cx);
444+
}
433445
}
434446

435447
#[cfg(test)]
@@ -441,6 +453,7 @@ mod test {
441453
#[gpui::test]
442454
async fn test_word_motions(cx: &mut gpui::TestAppContext) {
443455
let mut cx = VimTestContext::new(cx, true).await;
456+
cx.enable_helix();
444457
// «
445458
// ˇ
446459
// »
@@ -502,6 +515,7 @@ mod test {
502515
#[gpui::test]
503516
async fn test_delete(cx: &mut gpui::TestAppContext) {
504517
let mut cx = VimTestContext::new(cx, true).await;
518+
cx.enable_helix();
505519

506520
// test delete a selection
507521
cx.set_state(
@@ -582,6 +596,7 @@ mod test {
582596
#[gpui::test]
583597
async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
584598
let mut cx = VimTestContext::new(cx, true).await;
599+
cx.enable_helix();
585600

586601
cx.set_state(
587602
indoc! {"
@@ -635,6 +650,7 @@ mod test {
635650
#[gpui::test]
636651
async fn test_newline_char(cx: &mut gpui::TestAppContext) {
637652
let mut cx = VimTestContext::new(cx, true).await;
653+
cx.enable_helix();
638654

639655
cx.set_state("aa«\nˇ»bb cc", Mode::HelixNormal);
640656

@@ -652,6 +668,7 @@ mod test {
652668
#[gpui::test]
653669
async fn test_insert_selected(cx: &mut gpui::TestAppContext) {
654670
let mut cx = VimTestContext::new(cx, true).await;
671+
cx.enable_helix();
655672
cx.set_state(
656673
indoc! {"
657674
«The ˇ»quick brown
@@ -674,6 +691,7 @@ mod test {
674691
#[gpui::test]
675692
async fn test_append(cx: &mut gpui::TestAppContext) {
676693
let mut cx = VimTestContext::new(cx, true).await;
694+
cx.enable_helix();
677695
// test from the end of the selection
678696
cx.set_state(
679697
indoc! {"
@@ -716,6 +734,7 @@ mod test {
716734
#[gpui::test]
717735
async fn test_replace(cx: &mut gpui::TestAppContext) {
718736
let mut cx = VimTestContext::new(cx, true).await;
737+
cx.enable_helix();
719738

720739
// No selection (single character)
721740
cx.set_state("ˇaa", Mode::HelixNormal);
@@ -763,4 +782,72 @@ mod test {
763782
cx.shared_clipboard().assert_eq("worl");
764783
cx.assert_state("hello «worlˇ»d", Mode::HelixNormal);
765784
}
785+
#[gpui::test]
786+
async fn test_shift_r_paste(cx: &mut gpui::TestAppContext) {
787+
let mut cx = VimTestContext::new(cx, true).await;
788+
cx.enable_helix();
789+
790+
// First copy some text to clipboard
791+
cx.set_state("«hello worldˇ»", Mode::HelixNormal);
792+
cx.simulate_keystrokes("y");
793+
794+
// Test paste with shift-r on single cursor
795+
cx.set_state("foo ˇbar", Mode::HelixNormal);
796+
cx.simulate_keystrokes("shift-r");
797+
798+
cx.assert_state("foo hello worldˇbar", Mode::HelixNormal);
799+
800+
// Test paste with shift-r on selection
801+
cx.set_state("foo «barˇ» baz", Mode::HelixNormal);
802+
cx.simulate_keystrokes("shift-r");
803+
804+
cx.assert_state("foo hello worldˇ baz", Mode::HelixNormal);
805+
}
806+
807+
#[gpui::test]
808+
async fn test_insert_mode_stickiness(cx: &mut gpui::TestAppContext) {
809+
let mut cx = VimTestContext::new(cx, true).await;
810+
cx.enable_helix();
811+
812+
// Make a modification at a specific location
813+
cx.set_state("ˇhello", Mode::HelixNormal);
814+
assert_eq!(cx.mode(), Mode::HelixNormal);
815+
cx.simulate_keystrokes("i");
816+
assert_eq!(cx.mode(), Mode::Insert);
817+
cx.simulate_keystrokes("escape");
818+
assert_eq!(cx.mode(), Mode::HelixNormal);
819+
}
820+
821+
#[gpui::test]
822+
async fn test_goto_last_modification(cx: &mut gpui::TestAppContext) {
823+
let mut cx = VimTestContext::new(cx, true).await;
824+
cx.enable_helix();
825+
826+
// Make a modification at a specific location
827+
cx.set_state("line one\nline ˇtwo\nline three", Mode::HelixNormal);
828+
cx.assert_state("line one\nline ˇtwo\nline three", Mode::HelixNormal);
829+
cx.simulate_keystrokes("i");
830+
cx.simulate_keystrokes("escape");
831+
cx.simulate_keystrokes("i");
832+
cx.simulate_keystrokes("m o d i f i e d space");
833+
cx.simulate_keystrokes("escape");
834+
835+
// TODO: this fails, because state is no longer helix
836+
cx.assert_state(
837+
"line one\nline modified ˇtwo\nline three",
838+
Mode::HelixNormal,
839+
);
840+
841+
// Move cursor away from the modification
842+
cx.simulate_keystrokes("up");
843+
844+
// Use "g ." to go back to last modification
845+
cx.simulate_keystrokes("g .");
846+
847+
// Verify we're back at the modification location and still in HelixNormal mode
848+
cx.assert_state(
849+
"line one\nline modifiedˇ two\nline three",
850+
Mode::HelixNormal,
851+
);
852+
}
766853
}

0 commit comments

Comments
 (0)