Keep selection in SwitchToHelixNormalMode (#41583)
Closes #41125 Release Notes: - Fixed `SwitchToHelixNormalMode` to keep selection - Added default keybinds for `SwitchToHelixNormalMode` when in Helix mode
This commit is contained in:
+60
-4
@@ -450,7 +450,7 @@ impl Vim {
|
||||
prior_selections,
|
||||
prior_operator: self.operator_stack.last().cloned(),
|
||||
prior_mode: self.mode,
|
||||
helix_select: true,
|
||||
is_helix_regex_search: true,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1278,6 +1278,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;
|
||||
@@ -1297,9 +1315,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]
|
||||
|
||||
+30
-21
@@ -672,31 +672,40 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
||||
|
||||
impl Vim {
|
||||
pub(crate) fn search_motion(&mut self, m: Motion, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Motion::ZedSearchResult {
|
||||
prior_selections, ..
|
||||
let Motion::ZedSearchResult {
|
||||
prior_selections,
|
||||
new_selections,
|
||||
} = &m
|
||||
{
|
||||
match self.mode {
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
if !prior_selections.is_empty() {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(prior_selections.iter().cloned())
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
Mode::Normal | Mode::Replace | Mode::Insert => {
|
||||
if self.active_operator().is_none() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
Mode::HelixNormal | Mode::HelixSelect => {}
|
||||
match self.mode {
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
if !prior_selections.is_empty() {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(prior_selections.iter().cloned());
|
||||
});
|
||||
});
|
||||
}
|
||||
self.motion(m, window, cx);
|
||||
}
|
||||
Mode::Normal | Mode::Replace | Mode::Insert => {
|
||||
if self.active_operator().is_some() {
|
||||
self.motion(m, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
Mode::HelixNormal => {}
|
||||
Mode::HelixSelect => {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(prior_selections.iter().chain(new_selections).cloned());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.motion(m, window, cx)
|
||||
}
|
||||
|
||||
pub(crate) fn motion(&mut self, motion: Motion, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use editor::{Editor, EditorSettings};
|
||||
use editor::{Editor, EditorSettings, VimFlavor};
|
||||
use gpui::{Action, Context, Window, actions};
|
||||
|
||||
use language::Point;
|
||||
use schemars::JsonSchema;
|
||||
use search::{BufferSearchBar, SearchOptions, buffer_search};
|
||||
@@ -195,7 +196,7 @@ impl Vim {
|
||||
prior_selections,
|
||||
prior_operator: self.operator_stack.last().cloned(),
|
||||
prior_mode,
|
||||
helix_select: false,
|
||||
is_helix_regex_search: false,
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -219,7 +220,7 @@ impl Vim {
|
||||
let new_selections = self.editor_selections(window, cx);
|
||||
let result = pane.update(cx, |pane, cx| {
|
||||
let search_bar = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()?;
|
||||
if self.search.helix_select {
|
||||
if self.search.is_helix_regex_search {
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.select_all_matches(&Default::default(), window, cx)
|
||||
});
|
||||
@@ -240,7 +241,8 @@ impl Vim {
|
||||
count = count.saturating_sub(1)
|
||||
}
|
||||
self.search.count = 1;
|
||||
search_bar.select_match(direction, count, window, cx);
|
||||
let collapse = !self.mode.is_helix();
|
||||
search_bar.select_match(direction, count, collapse, window, cx);
|
||||
search_bar.focus_editor(&Default::default(), window, cx);
|
||||
|
||||
let prior_selections: Vec<_> = self.search.prior_selections.drain(..).collect();
|
||||
@@ -307,7 +309,8 @@ impl Vim {
|
||||
if !search_bar.has_active_match() || !search_bar.show(window, cx) {
|
||||
return false;
|
||||
}
|
||||
search_bar.select_match(direction, count, window, cx);
|
||||
let collapse = !self.mode.is_helix();
|
||||
search_bar.select_match(direction, count, collapse, window, cx);
|
||||
true
|
||||
})
|
||||
});
|
||||
@@ -316,6 +319,7 @@ impl Vim {
|
||||
}
|
||||
|
||||
let new_selections = self.editor_selections(window, cx);
|
||||
|
||||
self.search_motion(
|
||||
Motion::ZedSearchResult {
|
||||
prior_selections,
|
||||
@@ -381,7 +385,8 @@ impl Vim {
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
search.await?;
|
||||
search_bar.update_in(cx, |search_bar, window, cx| {
|
||||
search_bar.select_match(direction, count, window, cx);
|
||||
let collapse = editor::vim_flavor(cx) == Some(VimFlavor::Vim);
|
||||
search_bar.select_match(direction, count, collapse, window, cx);
|
||||
|
||||
vim.update(cx, |vim, cx| {
|
||||
let new_selections = vim.editor_selections(window, cx);
|
||||
@@ -444,7 +449,7 @@ impl Vim {
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
search.await?;
|
||||
search_bar.update_in(cx, |search_bar, window, cx| {
|
||||
search_bar.select_match(direction, 1, window, cx)
|
||||
search_bar.select_match(direction, 1, true, window, cx)
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
|
||||
@@ -66,12 +66,16 @@ impl Display for Mode {
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn is_visual(&self) -> bool {
|
||||
pub fn is_visual(self) -> bool {
|
||||
match self {
|
||||
Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true,
|
||||
Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_helix(self) -> bool {
|
||||
matches!(self, Mode::HelixNormal | Mode::HelixSelect)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Mode {
|
||||
@@ -990,7 +994,7 @@ pub struct SearchState {
|
||||
pub prior_selections: Vec<Range<Anchor>>,
|
||||
pub prior_operator: Option<Operator>,
|
||||
pub prior_mode: Mode,
|
||||
pub helix_select: bool,
|
||||
pub is_helix_regex_search: bool,
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
|
||||
@@ -669,7 +669,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| {
|
||||
@@ -953,7 +953,6 @@ impl Vim {
|
||||
fn deactivate(editor: &mut Editor, cx: &mut Context<Editor>) {
|
||||
editor.set_cursor_shape(CursorShape::Bar, cx);
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.set_collapse_matches(false);
|
||||
editor.set_input_enabled(true);
|
||||
editor.set_autoindent(true);
|
||||
editor.selections.set_line_mode(false);
|
||||
@@ -1929,7 +1928,6 @@ 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);
|
||||
editor.set_input_enabled(vim.editor_input_enabled());
|
||||
editor.set_autoindent(vim.should_autoindent());
|
||||
editor
|
||||
|
||||
@@ -847,9 +847,6 @@ impl Vim {
|
||||
let mut start_selection = 0usize;
|
||||
let mut end_selection = 0usize;
|
||||
|
||||
self.update_editor(cx, |_, editor, _| {
|
||||
editor.set_collapse_matches(false);
|
||||
});
|
||||
if vim_is_normal {
|
||||
pane.update(cx, |pane, cx| {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>()
|
||||
@@ -860,7 +857,7 @@ impl Vim {
|
||||
}
|
||||
// without update_match_index there is a bug when the cursor is before the first match
|
||||
search_bar.update_match_index(window, cx);
|
||||
search_bar.select_match(direction.opposite(), 1, window, cx);
|
||||
search_bar.select_match(direction.opposite(), 1, false, window, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -878,7 +875,7 @@ impl Vim {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.update_match_index(window, cx);
|
||||
search_bar.select_match(direction, count, window, cx);
|
||||
search_bar.select_match(direction, count, false, window, cx);
|
||||
match_exists = search_bar.match_exists(window, cx);
|
||||
});
|
||||
}
|
||||
@@ -905,7 +902,6 @@ impl Vim {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges([start_selection..end_selection]);
|
||||
});
|
||||
editor.set_collapse_matches(true);
|
||||
});
|
||||
|
||||
match self.maybe_pop_operator() {
|
||||
|
||||
Reference in New Issue
Block a user