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
9 changes: 9 additions & 0 deletions assets/keymaps/vim.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,12 @@
"ctrl-[": "editor::Cancel"
}
},
{
"context": "vim_mode == helix_select && !menu",
"bindings": {
"escape": "vim::SwitchToHelixNormalMode"
}
},
{
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
"bindings": {
Expand Down Expand Up @@ -470,6 +476,9 @@
"alt-p": "editor::SelectPreviousSyntaxNode",
"alt-n": "editor::SelectNextSyntaxNode",

"n": "vim::HelixSelectNext",
"shift-n": "vim::HelixSelectPrevious",

// Goto mode
"g e": "vim::EndOfDocument",
"g h": "vim::StartOfLine",
Expand Down
2 changes: 1 addition & 1 deletion crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ pub struct Editor {
searchable: bool,
cursor_shape: CursorShape,
current_line_highlight: Option<CurrentLineHighlight>,
collapse_matches: bool,
pub collapse_matches: bool,
autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
input_enabled: bool,
Expand Down
137 changes: 133 additions & 4 deletions crates/vim/src/helix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use language::{CharClassifier, CharKind, Point};
use search::{BufferSearchBar, SearchOptions};
use settings::Settings;
use text::{Bias, SelectionGoal};
use workspace::searchable;
use workspace::searchable::FilteredSearchRange;
use workspace::searchable::{self, Direction};

use crate::motion::{self, MotionKind};
use crate::state::SearchState;
Expand Down Expand Up @@ -52,6 +52,10 @@ actions!(
HelixSubstitute,
/// Delete the selection and enter edit mode, without yanking the selection.
HelixSubstituteNoYank,
/// Delete the selection and enter edit mode.
HelixSelectNext,
/// Delete the selection and enter edit mode, without yanking the selection.
HelixSelectPrevious,
]
);

Expand All @@ -74,6 +78,8 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
});
Vim::action(editor, cx, Vim::helix_substitute);
Vim::action(editor, cx, Vim::helix_substitute_no_yank);
Vim::action(editor, cx, Vim::helix_select_next);
Vim::action(editor, cx, Vim::helix_select_previous);
}

impl Vim {
Expand All @@ -97,6 +103,11 @@ impl Vim {
self.update_editor(cx, |_, editor, cx| {
let text_layout_details = editor.text_layout_details(window);
editor.change_selections(Default::default(), window, cx, |s| {
if let Motion::ZedSearchResult { new_selections, .. } = &motion {
s.select_anchor_ranges(new_selections.clone());
return;
};

s.move_with(|map, selection| {
let current_head = selection.head();

Expand Down Expand Up @@ -664,6 +675,68 @@ impl Vim {
) {
self.do_helix_substitute(false, window, cx);
}

fn helix_select_next(
&mut self,
_: &HelixSelectNext,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.do_helix_select(Direction::Next, window, cx);
}

fn helix_select_previous(
&mut self,
_: &HelixSelectPrevious,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.do_helix_select(Direction::Prev, window, cx);
}

fn do_helix_select(
&mut self,
direction: searchable::Direction,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(pane) = self.pane(window, cx) else {
return;
};
let count = Vim::take_count(cx).unwrap_or(1);
Vim::take_forced_motion(cx);
let prior_selections = self.editor_selections(window, cx);

let success = pane.update(cx, |pane, cx| {
let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
return false;
};
search_bar.update(cx, |search_bar, cx| {
if !search_bar.has_active_match() || !search_bar.show(window, cx) {
return false;
}
search_bar.select_match(direction, count, window, cx);
true
})
});

if !success {
return;
}
if self.mode == Mode::HelixSelect {
self.update_editor(cx, |_vim, editor, cx| {
let snapshot = editor.snapshot(window, cx);
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
s.select_anchor_ranges(
prior_selections
.iter()
.cloned()
.chain(s.all_anchors(&snapshot).iter().map(|s| s.range())),
);
})
});
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -1278,6 +1351,24 @@ mod test {
cx.assert_state("«one ˇ»two", Mode::HelixSelect);
}

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

cx.set_state("ˇone two", Mode::Normal);
cx.simulate_keystrokes("v w");
cx.assert_state("«one tˇ»wo", Mode::Visual);
cx.simulate_keystrokes("escape");
cx.assert_state("one ˇtwo", Mode::Normal);

cx.enable_helix();
cx.set_state("ˇone two", Mode::HelixNormal);
cx.simulate_keystrokes("v w");
cx.assert_state("«one ˇ»two", Mode::HelixSelect);
cx.simulate_keystrokes("escape");
cx.assert_state("«one ˇ»two", Mode::HelixNormal);
}

#[gpui::test]
async fn test_helix_select_regex(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
Expand All @@ -1297,9 +1388,47 @@ mod test {
cx.simulate_keystrokes("enter");
cx.assert_state("«oneˇ» two «oneˇ»", Mode::HelixNormal);

cx.set_state("ˇone two one", Mode::HelixNormal);
cx.simulate_keystrokes("s o n e enter");
cx.assert_state("ˇone two one", Mode::HelixNormal);
// TODO: change "search_in_selection" to not perform any search when in helix select mode with no selection
// cx.set_state("ˇstuff one two one", Mode::HelixNormal);
// cx.simulate_keystrokes("s o n e enter");
// cx.assert_state("ˇstuff one two one", Mode::HelixNormal);
}

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

cx.set_state("ˇhello two one two one two one", Mode::Visual);
cx.simulate_keystrokes("/ o n e");
cx.simulate_keystrokes("enter");
cx.simulate_keystrokes("n n");
cx.assert_state("«hello two one two one two oˇ»ne", Mode::Visual);

cx.set_state("ˇhello two one two one two one", Mode::Normal);
cx.simulate_keystrokes("/ o n e");
cx.simulate_keystrokes("enter");
cx.simulate_keystrokes("n n");
cx.assert_state("hello two one two one two ˇone", Mode::Normal);

cx.set_state("ˇhello two one two one two one", Mode::Normal);
cx.simulate_keystrokes("/ o n e");
cx.simulate_keystrokes("enter");
cx.simulate_keystrokes("n g n g n");
cx.assert_state("hello two one two «one two oneˇ»", Mode::Visual);

cx.enable_helix();

cx.set_state("ˇhello two one two one two one", Mode::HelixNormal);
cx.simulate_keystrokes("/ o n e");
cx.simulate_keystrokes("enter");
cx.simulate_keystrokes("n n");
cx.assert_state("hello two one two one two «oneˇ»", Mode::HelixNormal);

cx.set_state("ˇhello two one two one two one", Mode::HelixSelect);
cx.simulate_keystrokes("/ o n e");
cx.simulate_keystrokes("enter");
cx.simulate_keystrokes("n n");
cx.assert_state("hello two «oneˇ» two «oneˇ» two «oneˇ»", Mode::HelixSelect);
}

#[gpui::test]
Expand Down
1 change: 0 additions & 1 deletion crates/vim/src/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,6 @@ impl Vim {
return;
}
}

Mode::HelixNormal | Mode::HelixSelect => {}
}
}
Expand Down
5 changes: 3 additions & 2 deletions crates/vim/src/vim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ impl Vim {
editor,
cx,
|vim, _: &SwitchToHelixNormalMode, window, cx| {
vim.switch_mode(Mode::HelixNormal, false, window, cx)
vim.switch_mode(Mode::HelixNormal, true, window, cx)
},
);
Vim::action(editor, cx, |_, _: &PushForcedMotion, _, cx| {
Expand Down Expand Up @@ -1930,7 +1930,8 @@ impl Vim {
self.update_editor(cx, |vim, editor, cx| {
editor.set_cursor_shape(vim.cursor_shape(cx), cx);
editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
editor.set_collapse_matches(true);
let collapse_matches = !HelixModeSetting::get_global(cx).0;
editor.set_collapse_matches(collapse_matches);
editor.set_input_enabled(vim.editor_input_enabled());
editor.set_autoindent(vim.should_autoindent());
editor
Expand Down
Loading