From 9906b316912f248e1e81a4aa82c18ab5cf9b280a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 27 Feb 2024 21:36:12 -0700 Subject: [PATCH] fix vim repeat (#8513) Release Notes: - vim: Fixed `.` when multiple windows are open ([#7446](https://github.com/zed-industries/zed/issues/7446)). - vim: Fixed switching to normal mode after `J`, `<` or `>` in visual mode ([#4439](https://github.com/zed-industries/zed/issues/4439)) - vim: Added `ctrl-t` and `ctrl-d` for indent/outdent in insert mode. - Fixed indent/outdent/join lines appearing to work in read-only buffers ([#8423](https://github.com/zed-industries/zed/issues/8423)) - Fixed indent with an empty selection when the cursor was in column 0 --- assets/keymaps/vim.json | 12 +++-- crates/editor/src/editor.rs | 11 +++- crates/vim/src/normal.rs | 31 ++++++++++- crates/vim/src/test.rs | 4 +- crates/vim/src/test/vim_test_context.rs | 1 - crates/vim/src/vim.rs | 69 ++++++++++++------------- crates/zed/src/zed.rs | 2 - 7 files changed, 82 insertions(+), 48 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 355c870053..ae936e5bb0 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -342,8 +342,8 @@ "r": ["vim::PushOperator", "Replace"], "s": "vim::Substitute", "shift-s": "vim::SubstituteLine", - "> >": "editor::Indent", - "< <": "editor::Outdent", + "> >": "vim::Indent", + "< <": "vim::Outdent", "ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pageup": "pane::ActivatePrevItem" } @@ -460,8 +460,8 @@ "ctrl-c": ["vim::SwitchMode", "Normal"], "escape": ["vim::SwitchMode", "Normal"], "ctrl-[": ["vim::SwitchMode", "Normal"], - ">": "editor::Indent", - "<": "editor::Outdent", + ">": "vim::Indent", + "<": "vim::Outdent", "i": [ "vim::PushOperator", { @@ -492,7 +492,9 @@ "ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific "ctrl-x ctrl-z": "editor::Cancel", "ctrl-w": "editor::DeleteToPreviousWordStart", - "ctrl-u": "editor::DeleteToBeginningOfLine" + "ctrl-u": "editor::DeleteToBeginningOfLine", + "ctrl-t": "vim::Indent", + "ctrl-d": "vim::Outdent" } }, { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a35f300425..ef60bdeeeb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4474,6 +4474,9 @@ impl Editor { } pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } let mut selections = self.selections.all::(cx); let mut prev_edited_row = 0; let mut row_delta = 0; @@ -4516,7 +4519,7 @@ impl Editor { // If a selection ends at the beginning of a line, don't indent // that last line. - if selection.end.column == 0 { + if selection.end.column == 0 && selection.end.row > selection.start.row { end_row -= 1; } @@ -4567,6 +4570,9 @@ impl Editor { } pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self.selections.all::(cx); let mut deletion_ranges = Vec::new(); @@ -4707,6 +4713,9 @@ impl Editor { } pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } let mut row_ranges = Vec::>::new(); for selection in self.selections.all::(cx) { let start = selection.start.row; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 43149f7aef..b7d6a0c828 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -51,6 +51,8 @@ actions!( ConvertToUpperCase, ConvertToLowerCase, JoinLines, + Indent, + Outdent, ] ); @@ -125,7 +127,34 @@ pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext"]); - cx.assert_editor_state("aa\n b«b\n ccˇ»"); + cx.assert_editor_state("aa\n bb\n cˇc"); } #[gpui::test] diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 35fd74b70d..b06ef803b6 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -67,7 +67,6 @@ impl VimTestContext { // Setup search toolbars and keypress hook cx.update_workspace(|workspace, cx| { - observe_keystrokes(cx); workspace.active_pane().update(cx, |pane, cx| { pane.toolbar().update(cx, |toolbar, cx| { let buffer_search_bar = cx.new_view(BufferSearchBar::new); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 8e30f85dba..6171ed0f98 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -22,8 +22,8 @@ use editor::{ Editor, EditorEvent, EditorMode, }; use gpui::{ - actions, impl_actions, Action, AppContext, EntityId, Global, Subscription, View, ViewContext, - WeakView, WindowContext, + actions, impl_actions, Action, AppContext, EntityId, Global, KeystrokeEvent, Subscription, + View, ViewContext, WeakView, WindowContext, }; use language::{CursorShape, Point, Selection, SelectionGoal}; pub use mode_indicator::ModeIndicator; @@ -76,6 +76,7 @@ pub fn init(cx: &mut AppContext) { VimModeSetting::register(cx); VimSettings::register(cx); + cx.observe_keystrokes(observe_keystrokes).detach(); editor_events::init(cx); cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx)) @@ -135,46 +136,42 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext) { visual::register(workspace, cx); } -/// Registers a keystroke observer to observe keystrokes for the Vim integration. -pub fn observe_keystrokes(cx: &mut WindowContext) { - cx.observe_keystrokes(|keystroke_event, cx| { - if let Some(action) = keystroke_event - .action - .as_ref() - .map(|action| action.boxed_clone()) - { - Vim::update(cx, |vim, _| { - if vim.workspace_state.recording { - vim.workspace_state - .recorded_actions - .push(ReplayableAction::Action(action.boxed_clone())); +/// Called whenever an keystroke is typed so vim can observe all actions +/// and keystrokes accordingly. +fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext) { + if let Some(action) = keystroke_event + .action + .as_ref() + .map(|action| action.boxed_clone()) + { + Vim::update(cx, |vim, _| { + if vim.workspace_state.recording { + vim.workspace_state + .recorded_actions + .push(ReplayableAction::Action(action.boxed_clone())); - if vim.workspace_state.stop_recording_after_next_action { - vim.workspace_state.recording = false; - vim.workspace_state.stop_recording_after_next_action = false; - } + if vim.workspace_state.stop_recording_after_next_action { + vim.workspace_state.recording = false; + vim.workspace_state.stop_recording_after_next_action = false; } - }); - - // Keystroke is handled by the vim system, so continue forward - if action.name().starts_with("vim::") { - return; } - } else if cx.has_pending_keystrokes() { + }); + + // Keystroke is handled by the vim system, so continue forward + if action.name().starts_with("vim::") { return; } + } else if cx.has_pending_keystrokes() { + return; + } - Vim::update(cx, |vim, cx| match vim.active_operator() { - Some( - Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace, - ) => {} - Some(_) => { - vim.clear_operator(cx); - } - _ => {} - }); - }) - .detach() + Vim::update(cx, |vim, cx| match vim.active_operator() { + Some(Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace) => {} + Some(_) => { + vim.clear_operator(cx); + } + _ => {} + }); } /// The state pertaining to Vim mode. diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c3c451c05d..2d8c8649ac 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -142,8 +142,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { auto_update::notify_of_any_new_update(cx); - vim::observe_keystrokes(cx); - let handle = cx.view().downgrade(); cx.on_window_should_close(move |cx| { handle