Skip to content

Commit 85d4296

Browse files
HactarCEConradIrwin
authored andcommitted
Fix Helix mode search & selection (#42928)
This PR redoes the desired behavior changes of #41583 (reverted in #42892) but less invasively Closes #41125 Closes #41164 Release Notes: - N/A Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
1 parent 9e7c982 commit 85d4296

File tree

5 files changed

+146
-8
lines changed

5 files changed

+146
-8
lines changed

assets/keymaps/vim.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,12 @@
421421
"ctrl-[": "editor::Cancel"
422422
}
423423
},
424+
{
425+
"context": "vim_mode == helix_select && !menu",
426+
"bindings": {
427+
"escape": "vim::SwitchToHelixNormalMode"
428+
}
429+
},
424430
{
425431
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
426432
"bindings": {
@@ -470,6 +476,9 @@
470476
"alt-p": "editor::SelectPreviousSyntaxNode",
471477
"alt-n": "editor::SelectNextSyntaxNode",
472478

479+
"n": "vim::HelixSelectNext",
480+
"shift-n": "vim::HelixSelectPrevious",
481+
473482
// Goto mode
474483
"g e": "vim::EndOfDocument",
475484
"g h": "vim::StartOfLine",

crates/editor/src/editor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ pub struct Editor {
10991099
searchable: bool,
11001100
cursor_shape: CursorShape,
11011101
current_line_highlight: Option<CurrentLineHighlight>,
1102-
collapse_matches: bool,
1102+
pub collapse_matches: bool,
11031103
autoindent_mode: Option<AutoindentMode>,
11041104
workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
11051105
input_enabled: bool,

crates/vim/src/helix.rs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use language::{CharClassifier, CharKind, Point};
1515
use search::{BufferSearchBar, SearchOptions};
1616
use settings::Settings;
1717
use text::{Bias, SelectionGoal};
18-
use workspace::searchable;
1918
use workspace::searchable::FilteredSearchRange;
19+
use workspace::searchable::{self, Direction};
2020

2121
use crate::motion::{self, MotionKind};
2222
use crate::state::SearchState;
@@ -52,6 +52,10 @@ actions!(
5252
HelixSubstitute,
5353
/// Delete the selection and enter edit mode, without yanking the selection.
5454
HelixSubstituteNoYank,
55+
/// Delete the selection and enter edit mode.
56+
HelixSelectNext,
57+
/// Delete the selection and enter edit mode, without yanking the selection.
58+
HelixSelectPrevious,
5559
]
5660
);
5761

@@ -74,6 +78,8 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
7478
});
7579
Vim::action(editor, cx, Vim::helix_substitute);
7680
Vim::action(editor, cx, Vim::helix_substitute_no_yank);
81+
Vim::action(editor, cx, Vim::helix_select_next);
82+
Vim::action(editor, cx, Vim::helix_select_previous);
7783
}
7884

7985
impl Vim {
@@ -97,6 +103,11 @@ impl Vim {
97103
self.update_editor(cx, |_, editor, cx| {
98104
let text_layout_details = editor.text_layout_details(window);
99105
editor.change_selections(Default::default(), window, cx, |s| {
106+
if let Motion::ZedSearchResult { new_selections, .. } = &motion {
107+
s.select_anchor_ranges(new_selections.clone());
108+
return;
109+
};
110+
100111
s.move_with(|map, selection| {
101112
let current_head = selection.head();
102113

@@ -664,6 +675,68 @@ impl Vim {
664675
) {
665676
self.do_helix_substitute(false, window, cx);
666677
}
678+
679+
fn helix_select_next(
680+
&mut self,
681+
_: &HelixSelectNext,
682+
window: &mut Window,
683+
cx: &mut Context<Self>,
684+
) {
685+
self.do_helix_select(Direction::Next, window, cx);
686+
}
687+
688+
fn helix_select_previous(
689+
&mut self,
690+
_: &HelixSelectPrevious,
691+
window: &mut Window,
692+
cx: &mut Context<Self>,
693+
) {
694+
self.do_helix_select(Direction::Prev, window, cx);
695+
}
696+
697+
fn do_helix_select(
698+
&mut self,
699+
direction: searchable::Direction,
700+
window: &mut Window,
701+
cx: &mut Context<Self>,
702+
) {
703+
let Some(pane) = self.pane(window, cx) else {
704+
return;
705+
};
706+
let count = Vim::take_count(cx).unwrap_or(1);
707+
Vim::take_forced_motion(cx);
708+
let prior_selections = self.editor_selections(window, cx);
709+
710+
let success = pane.update(cx, |pane, cx| {
711+
let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
712+
return false;
713+
};
714+
search_bar.update(cx, |search_bar, cx| {
715+
if !search_bar.has_active_match() || !search_bar.show(window, cx) {
716+
return false;
717+
}
718+
search_bar.select_match(direction, count, window, cx);
719+
true
720+
})
721+
});
722+
723+
if !success {
724+
return;
725+
}
726+
if self.mode == Mode::HelixSelect {
727+
self.update_editor(cx, |_vim, editor, cx| {
728+
let snapshot = editor.snapshot(window, cx);
729+
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
730+
s.select_anchor_ranges(
731+
prior_selections
732+
.iter()
733+
.cloned()
734+
.chain(s.all_anchors(&snapshot).iter().map(|s| s.range())),
735+
);
736+
})
737+
});
738+
}
739+
}
667740
}
668741

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

1354+
#[gpui::test]
1355+
async fn test_exit_visual_mode(cx: &mut gpui::TestAppContext) {
1356+
let mut cx = VimTestContext::new(cx, true).await;
1357+
1358+
cx.set_state("ˇone two", Mode::Normal);
1359+
cx.simulate_keystrokes("v w");
1360+
cx.assert_state("«one tˇ»wo", Mode::Visual);
1361+
cx.simulate_keystrokes("escape");
1362+
cx.assert_state("one ˇtwo", Mode::Normal);
1363+
1364+
cx.enable_helix();
1365+
cx.set_state("ˇone two", Mode::HelixNormal);
1366+
cx.simulate_keystrokes("v w");
1367+
cx.assert_state("«one ˇ»two", Mode::HelixSelect);
1368+
cx.simulate_keystrokes("escape");
1369+
cx.assert_state("«one ˇ»two", Mode::HelixNormal);
1370+
}
1371+
12811372
#[gpui::test]
12821373
async fn test_helix_select_regex(cx: &mut gpui::TestAppContext) {
12831374
let mut cx = VimTestContext::new(cx, true).await;
@@ -1297,9 +1388,47 @@ mod test {
12971388
cx.simulate_keystrokes("enter");
12981389
cx.assert_state("«oneˇ» two «oneˇ»", Mode::HelixNormal);
12991390

1300-
cx.set_state("ˇone two one", Mode::HelixNormal);
1301-
cx.simulate_keystrokes("s o n e enter");
1302-
cx.assert_state("ˇone two one", Mode::HelixNormal);
1391+
// TODO: change "search_in_selection" to not perform any search when in helix select mode with no selection
1392+
// cx.set_state("ˇstuff one two one", Mode::HelixNormal);
1393+
// cx.simulate_keystrokes("s o n e enter");
1394+
// cx.assert_state("ˇstuff one two one", Mode::HelixNormal);
1395+
}
1396+
1397+
#[gpui::test]
1398+
async fn test_helix_select_next_match(cx: &mut gpui::TestAppContext) {
1399+
let mut cx = VimTestContext::new(cx, true).await;
1400+
1401+
cx.set_state("ˇhello two one two one two one", Mode::Visual);
1402+
cx.simulate_keystrokes("/ o n e");
1403+
cx.simulate_keystrokes("enter");
1404+
cx.simulate_keystrokes("n n");
1405+
cx.assert_state("«hello two one two one two oˇ»ne", Mode::Visual);
1406+
1407+
cx.set_state("ˇhello two one two one two one", Mode::Normal);
1408+
cx.simulate_keystrokes("/ o n e");
1409+
cx.simulate_keystrokes("enter");
1410+
cx.simulate_keystrokes("n n");
1411+
cx.assert_state("hello two one two one two ˇone", Mode::Normal);
1412+
1413+
cx.set_state("ˇhello two one two one two one", Mode::Normal);
1414+
cx.simulate_keystrokes("/ o n e");
1415+
cx.simulate_keystrokes("enter");
1416+
cx.simulate_keystrokes("n g n g n");
1417+
cx.assert_state("hello two one two «one two oneˇ»", Mode::Visual);
1418+
1419+
cx.enable_helix();
1420+
1421+
cx.set_state("ˇhello two one two one two one", Mode::HelixNormal);
1422+
cx.simulate_keystrokes("/ o n e");
1423+
cx.simulate_keystrokes("enter");
1424+
cx.simulate_keystrokes("n n");
1425+
cx.assert_state("hello two one two one two «oneˇ»", Mode::HelixNormal);
1426+
1427+
cx.set_state("ˇhello two one two one two one", Mode::HelixSelect);
1428+
cx.simulate_keystrokes("/ o n e");
1429+
cx.simulate_keystrokes("enter");
1430+
cx.simulate_keystrokes("n n");
1431+
cx.assert_state("hello two «oneˇ» two «oneˇ» two «oneˇ»", Mode::HelixSelect);
13031432
}
13041433

13051434
#[gpui::test]

crates/vim/src/motion.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,6 @@ impl Vim {
691691
return;
692692
}
693693
}
694-
695694
Mode::HelixNormal | Mode::HelixSelect => {}
696695
}
697696
}

crates/vim/src/vim.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ impl Vim {
666666
editor,
667667
cx,
668668
|vim, _: &SwitchToHelixNormalMode, window, cx| {
669-
vim.switch_mode(Mode::HelixNormal, false, window, cx)
669+
vim.switch_mode(Mode::HelixNormal, true, window, cx)
670670
},
671671
);
672672
Vim::action(editor, cx, |_, _: &PushForcedMotion, _, cx| {
@@ -1928,7 +1928,8 @@ impl Vim {
19281928
self.update_editor(cx, |vim, editor, cx| {
19291929
editor.set_cursor_shape(vim.cursor_shape(cx), cx);
19301930
editor.set_clip_at_line_ends(vim.clip_at_line_ends(), cx);
1931-
editor.set_collapse_matches(true);
1931+
let collapse_matches = !HelixModeSetting::get_global(cx).0;
1932+
editor.set_collapse_matches(collapse_matches);
19321933
editor.set_input_enabled(vim.editor_input_enabled());
19331934
editor.set_autoindent(vim.should_autoindent());
19341935
editor

0 commit comments

Comments
 (0)