Fix Visual mode flip on project search (#44284)
Previously, when project search results first appeared, vim would incorrectly switch to Visual mode. This happened because vim settings (including `collapse_matches`) weren't synced to an editor until it received focus. Since the results editor wasn't focused when the first match was selected, the selection wasn't collapsed, causing vim to interpret it as a visual selection. Now vim settings are synced immediately when vim activates on an editor, ensuring `collapse_matches` is set before any selections are made. Closes #43878 Release Notes: - Fixed vim mode incorrectly switching to Visual mode on first project search --------- Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
1 parent
b162fb4019
commit
8f5bdfc671
5 files changed
+183
-24
No files matched your search
@@ -1196,7 +1196,10 @@ pub struct Editor {
|
||||
/// selected (needed for vim visual mode)
|
||||
cursor_offset_on_selection: bool,
|
||||
current_line_highlight: Option<CurrentLineHighlight>,
|
||||
pub collapse_matches: bool,
|
||||
/// Whether to collapse search match ranges to just their start position.
|
||||
/// When true, navigating to a match positions the cursor at the match
|
||||
/// without selecting the matched text.
|
||||
collapse_matches: bool,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
|
||||
input_enabled: bool,
|
||||
|
||||
@@ -75,3 +75,4 @@ settings.workspace = true
|
||||
perf.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
search = { workspace = true, features = ["test-support"] }
|
||||
+65
-1
@@ -862,9 +862,16 @@ impl Vim {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use gpui::{UpdateGlobal, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use project::FakeFs;
|
||||
use search::{ProjectSearchView, project_search};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
use workspace::DeploySearch;
|
||||
|
||||
use crate::{state::Mode, test::VimTestContext};
|
||||
use crate::{VimAddon, state::Mode, test::VimTestContext};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_word_motions(cx: &mut gpui::TestAppContext) {
|
||||
@@ -1694,4 +1701,61 @@ mod test {
|
||||
Mode::HelixNormal,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_project_search_opens_in_normal_mode(cx: &mut gpui::TestAppContext) {
|
||||
VimTestContext::init(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"file_a.rs": "// File A.",
|
||||
"file_b.rs": "// File B.",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = project::Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| workspace::Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
cx.update(|cx| {
|
||||
VimTestContext::init_keybindings(true, cx);
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |store| store.helix_mode = Some(true));
|
||||
})
|
||||
});
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(workspace, &DeploySearch::default(), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view should be active")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
project_search::perform_project_search(&search_view, "File A", cx);
|
||||
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
let vim_mode = search_view
|
||||
.results_editor()
|
||||
.read(cx)
|
||||
.addon::<VimAddon>()
|
||||
.map(|addon| addon.entity.read(cx).mode);
|
||||
|
||||
assert_eq!(vim_mode, Some(Mode::HelixNormal));
|
||||
});
|
||||
}
|
||||
}
|
||||
+61
-2
@@ -20,13 +20,18 @@ use language::{CursorShape, Language, LanguageConfig, Point};
|
||||
pub use neovim_backed_test_context::*;
|
||||
use settings::SettingsStore;
|
||||
use ui::Pixels;
|
||||
use util::test::marked_text_ranges;
|
||||
use util::{path, test::marked_text_ranges};
|
||||
pub use vim_test_context::*;
|
||||
|
||||
use gpui::VisualTestContext;
|
||||
use indoc::indoc;
|
||||
use project::FakeFs;
|
||||
use search::BufferSearchBar;
|
||||
use search::{ProjectSearchView, project_search};
|
||||
use serde_json::json;
|
||||
use workspace::DeploySearch;
|
||||
|
||||
use crate::{PushSneak, PushSneakBackward, insert::NormalBefore, motion, state::Mode};
|
||||
use crate::{PushSneak, PushSneakBackward, VimAddon, insert::NormalBefore, motion, state::Mode};
|
||||
|
||||
use util_macros::perf;
|
||||
|
||||
@@ -2534,3 +2539,57 @@ async fn test_deactivate(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(editor.cursor_shape(), CursorShape::Underline);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_project_search_opens_in_normal_mode(cx: &mut gpui::TestAppContext) {
|
||||
VimTestContext::init(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"file_a.rs": "// File A.",
|
||||
"file_b.rs": "// File B.",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = project::Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| workspace::Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
cx.update(|cx| {
|
||||
VimTestContext::init_keybindings(true, cx);
|
||||
});
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
ProjectSearchView::deploy_search(workspace, &DeploySearch::default(), window, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let search_view = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectSearchView>())
|
||||
.expect("Project search view should be active")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
project_search::perform_project_search(&search_view, "File A", cx);
|
||||
|
||||
search_view.update(cx, |search_view, cx| {
|
||||
let vim_mode = search_view
|
||||
.results_editor()
|
||||
.read(cx)
|
||||
.addon::<VimAddon>()
|
||||
.map(|addon| addon.entity.read(cx).mode);
|
||||
|
||||
assert_eq!(vim_mode, Some(Mode::Normal));
|
||||
});
|
||||
}
|
||||
+52
-20
@@ -633,12 +633,15 @@ impl Vim {
|
||||
|
||||
fn activate(editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>) {
|
||||
let vim = Vim::new(window, cx);
|
||||
|
||||
if !editor.mode().is_full() {
|
||||
vim.update(cx, |vim, _| {
|
||||
let state = vim.update(cx, |vim, cx| {
|
||||
if !editor.mode().is_full() {
|
||||
vim.mode = Mode::Insert;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
vim.state_for_editor_settings(cx)
|
||||
});
|
||||
|
||||
Vim::sync_vim_settings_to_editor(&state, editor, window, cx);
|
||||
|
||||
editor.register_addon(VimAddon {
|
||||
entity: vim.clone(),
|
||||
@@ -1305,7 +1308,7 @@ impl Vim {
|
||||
forced_motion
|
||||
}
|
||||
|
||||
pub fn cursor_shape(&self, cx: &mut App) -> CursorShape {
|
||||
pub fn cursor_shape(&self, cx: &App) -> CursorShape {
|
||||
let cursor_shape = VimSettings::get_global(cx).cursor_shape;
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
@@ -2022,23 +2025,52 @@ impl Vim {
|
||||
}
|
||||
|
||||
fn sync_vim_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
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);
|
||||
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.set_cursor_offset_on_selection(vim.mode.is_visual());
|
||||
editor
|
||||
.selections
|
||||
.set_line_mode(matches!(vim.mode, Mode::VisualLine));
|
||||
|
||||
let hide_edit_predictions = !matches!(vim.mode, Mode::Insert | Mode::Replace);
|
||||
editor.set_edit_predictions_hidden_for_vim_mode(hide_edit_predictions, window, cx);
|
||||
let state = self.state_for_editor_settings(cx);
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
Vim::sync_vim_settings_to_editor(&state, editor, window, cx);
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn state_for_editor_settings(&self, cx: &App) -> VimEditorSettingsState {
|
||||
VimEditorSettingsState {
|
||||
cursor_shape: self.cursor_shape(cx),
|
||||
clip_at_line_ends: self.clip_at_line_ends(),
|
||||
collapse_matches: !HelixModeSetting::get_global(cx).0,
|
||||
input_enabled: self.editor_input_enabled(),
|
||||
autoindent: self.should_autoindent(),
|
||||
cursor_offset_on_selection: self.mode.is_visual(),
|
||||
line_mode: matches!(self.mode, Mode::VisualLine),
|
||||
hide_edit_predictions: !matches!(self.mode, Mode::Insert | Mode::Replace),
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_vim_settings_to_editor(
|
||||
state: &VimEditorSettingsState,
|
||||
editor: &mut Editor,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
editor.set_cursor_shape(state.cursor_shape, cx);
|
||||
editor.set_clip_at_line_ends(state.clip_at_line_ends, cx);
|
||||
editor.set_collapse_matches(state.collapse_matches);
|
||||
editor.set_input_enabled(state.input_enabled);
|
||||
editor.set_autoindent(state.autoindent);
|
||||
editor.set_cursor_offset_on_selection(state.cursor_offset_on_selection);
|
||||
editor.selections.set_line_mode(state.line_mode);
|
||||
editor.set_edit_predictions_hidden_for_vim_mode(state.hide_edit_predictions, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
struct VimEditorSettingsState {
|
||||
cursor_shape: CursorShape,
|
||||
clip_at_line_ends: bool,
|
||||
collapse_matches: bool,
|
||||
input_enabled: bool,
|
||||
autoindent: bool,
|
||||
cursor_offset_on_selection: bool,
|
||||
line_mode: bool,
|
||||
hide_edit_predictions: bool,
|
||||
}
|
||||
|
||||
#[derive(RegisterSetting)]
|
||||
|
||||
Reference in new issue
Block a user