Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/keymaps/vim.json
Original file line number Diff line number Diff line change
Expand Up @@ -428,11 +428,13 @@
"g h": "vim::StartOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g e": "vim::EndOfDocument",
"g .": "vim::HelixGotoLastModification", // go to last modification
"g r": "editor::FindAllReferences", // zed specific
"g t": "vim::WindowTop",
"g c": "vim::WindowMiddle",
"g b": "vim::WindowBottom",

"shift-r": "editor::Paste",
"x": "editor::SelectLine",
"shift-x": "editor::SelectLine",
"%": "editor::SelectAll",
Expand Down
87 changes: 87 additions & 0 deletions crates/vim/src/helix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ actions!(
HelixInsert,
/// Appends at the end of the selection.
HelixAppend,
/// Goes to the location of the last modification.
HelixGotoLastModification,
]
);

Expand All @@ -31,6 +33,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, Vim::helix_insert);
Vim::action(editor, cx, Vim::helix_append);
Vim::action(editor, cx, Vim::helix_yank);
Vim::action(editor, cx, Vim::helix_goto_last_modification);
}

impl Vim {
Expand Down Expand Up @@ -443,6 +446,15 @@ impl Vim {
});
self.switch_mode(Mode::HelixNormal, true, window, cx);
}

pub fn helix_goto_last_modification(
&mut self,
_: &HelixGotoLastModification,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.jump(".".into(), false, false, window, cx);
}
}

#[cfg(test)]
Expand All @@ -454,6 +466,7 @@ mod test {
#[gpui::test]
async fn test_word_motions(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();
// «
// ˇ
// »
Expand Down Expand Up @@ -515,6 +528,7 @@ mod test {
#[gpui::test]
async fn test_delete(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

// test delete a selection
cx.set_state(
Expand Down Expand Up @@ -595,6 +609,7 @@ mod test {
#[gpui::test]
async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

cx.set_state(
indoc! {"
Expand Down Expand Up @@ -648,6 +663,7 @@ mod test {
#[gpui::test]
async fn test_newline_char(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

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

Expand All @@ -665,6 +681,7 @@ mod test {
#[gpui::test]
async fn test_insert_selected(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();
cx.set_state(
indoc! {"
«The ˇ»quick brown
Expand All @@ -687,6 +704,7 @@ mod test {
#[gpui::test]
async fn test_append(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();
// test from the end of the selection
cx.set_state(
indoc! {"
Expand Down Expand Up @@ -729,6 +747,7 @@ mod test {
#[gpui::test]
async fn test_replace(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

// No selection (single character)
cx.set_state("ˇaa", Mode::HelixNormal);
Expand Down Expand Up @@ -776,4 +795,72 @@ mod test {
cx.shared_clipboard().assert_eq("worl");
cx.assert_state("hello «worlˇ»d", Mode::HelixNormal);
}
#[gpui::test]
async fn test_shift_r_paste(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

// First copy some text to clipboard
cx.set_state("«hello worldˇ»", Mode::HelixNormal);
cx.simulate_keystrokes("y");

// Test paste with shift-r on single cursor
cx.set_state("foo ˇbar", Mode::HelixNormal);
cx.simulate_keystrokes("shift-r");

cx.assert_state("foo hello worldˇbar", Mode::HelixNormal);

// Test paste with shift-r on selection
cx.set_state("foo «barˇ» baz", Mode::HelixNormal);
cx.simulate_keystrokes("shift-r");

cx.assert_state("foo hello worldˇ baz", Mode::HelixNormal);
}

#[gpui::test]
async fn test_insert_mode_stickiness(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

// Make a modification at a specific location
cx.set_state("ˇhello", Mode::HelixNormal);
assert_eq!(cx.mode(), Mode::HelixNormal);
cx.simulate_keystrokes("i");
assert_eq!(cx.mode(), Mode::Insert);
cx.simulate_keystrokes("escape");
assert_eq!(cx.mode(), Mode::HelixNormal);
}

#[gpui::test]
async fn test_goto_last_modification(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();

// Make a modification at a specific location
cx.set_state("line one\nline ˇtwo\nline three", Mode::HelixNormal);
cx.assert_state("line one\nline ˇtwo\nline three", Mode::HelixNormal);
cx.simulate_keystrokes("i");
cx.simulate_keystrokes("escape");
cx.simulate_keystrokes("i");
cx.simulate_keystrokes("m o d i f i e d space");
cx.simulate_keystrokes("escape");

// TODO: this fails, because state is no longer helix
cx.assert_state(
"line one\nline modified ˇtwo\nline three",
Mode::HelixNormal,
);

// Move cursor away from the modification
cx.simulate_keystrokes("up");

// Use "g ." to go back to last modification
cx.simulate_keystrokes("g .");

// Verify we're back at the modification location and still in HelixNormal mode
cx.assert_state(
"line one\nline modifiedˇ two\nline three",
Mode::HelixNormal,
);
}
}
Loading