diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 78d4b3e7072c1ee8db57fdd31ef8afa1f3375b15..ed79b00c16a7586f4a121c3fbd6e71f883dbb2d4 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -325,6 +325,27 @@ "\"": "vim::PushRegister" } }, + { + "context": "vim_mode == helix_select", + "bindings": { + "escape": "vim::NormalBefore", + ";": "vim::HelixCollapseSelection", + "~": "vim::ChangeCase", + "ctrl-a": "vim::Increment", + "ctrl-x": "vim::Decrement", + "shift-j": "vim::JoinLines", + "i": "vim::InsertBefore", + "a": "vim::InsertAfter", + "p": "vim::Paste", + "u": "vim::Undo", + "r": "vim::PushReplace", + "s": "vim::Substitute", + "ctrl-pageup": "pane::ActivatePreviousItem", + "ctrl-pagedown": "pane::ActivateNextItem", + ".": "vim::Repeat", + "alt-.": "vim::RepeatFind" + } + }, { "context": "vim_mode == insert", "bindings": { @@ -396,7 +417,12 @@ "bindings": { "i": "vim::HelixInsert", "a": "vim::HelixAppend", - "ctrl-[": "editor::Cancel", + "ctrl-[": "editor::Cancel" + } + }, + { + "context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu", + "bindings": { ";": "vim::HelixCollapseSelection", ":": "command_palette::Toggle", "m": "vim::PushHelixMatch", diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 34ef2b40ec1bad03957a22ef66c40f6f53697699..9f5580d75cc4f3588e4211d1ef2bbfb7df49c92f 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -6,7 +6,7 @@ use editor::display_map::DisplaySnapshot; use editor::{ DisplayPoint, Editor, HideMouseCursorOrigin, SelectionEffects, ToOffset, ToPoint, movement, }; -use gpui::{Action, actions}; +use gpui::actions; use gpui::{Context, Window}; use language::{CharClassifier, CharKind, Point}; use text::{Bias, SelectionGoal}; @@ -21,8 +21,6 @@ use crate::{ actions!( vim, [ - /// Switches to normal mode after the cursor (Helix-style). - HelixNormalAfter, /// Yanks the current selection or character if no selection. HelixYank, /// Inserts at the beginning of the selection. @@ -37,7 +35,6 @@ actions!( ); pub fn register(editor: &mut Editor, cx: &mut Context) { - Vim::action(editor, cx, Vim::helix_normal_after); Vim::action(editor, cx, Vim::helix_select_lines); Vim::action(editor, cx, Vim::helix_insert); Vim::action(editor, cx, Vim::helix_append); @@ -46,21 +43,6 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { } impl Vim { - pub fn helix_normal_after( - &mut self, - action: &HelixNormalAfter, - window: &mut Window, - cx: &mut Context, - ) { - if self.active_operator().is_some() { - self.operator_stack.clear(); - self.sync_vim_settings(window, cx); - return; - } - self.stop_recording_immediately(action.boxed_clone(), cx); - self.switch_mode(Mode::HelixNormal, false, window, cx); - } - pub fn helix_normal_motion( &mut self, motion: Motion, @@ -854,6 +836,19 @@ mod test { cx.assert_state("foo hello worldˇ baz", Mode::HelixNormal); } + #[gpui::test] + async fn test_helix_select_mode(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + assert_eq!(cx.mode(), Mode::Normal); + cx.enable_helix(); + + cx.simulate_keystrokes("v"); + assert_eq!(cx.mode(), Mode::HelixSelect); + cx.simulate_keystrokes("escape"); + assert_eq!(cx.mode(), Mode::HelixNormal); + } + #[gpui::test] async fn test_insert_mode_stickiness(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index 8ef1cd78117adec2f1ae30783be2aeacb13790ec..5b9fef402a7b4fee9ae1d8722cb2cf22f3c2fdb9 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -63,11 +63,7 @@ impl Vim { } }); - if HelixModeSetting::get_global(cx).0 { - self.switch_mode(Mode::HelixNormal, false, window, cx); - } else { - self.switch_mode(Mode::Normal, false, window, cx); - } + self.switch_mode(Mode::Normal, false, window, cx); return; } diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index a54d3caa60e58d7bb61b3dfe0daa83affeba29b7..e2dfb24008c2f23044ca1f3633dcc02930b41e54 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -692,7 +692,7 @@ impl Vim { } } - Mode::HelixNormal => {} + Mode::HelixNormal | Mode::HelixSelect => {} } } @@ -726,7 +726,9 @@ impl Vim { self.visual_motion(motion, count, window, cx) } - Mode::HelixNormal => self.helix_normal_motion(motion, count, window, cx), + Mode::HelixNormal | Mode::HelixSelect => { + self.helix_normal_motion(motion, count, window, cx) + } } self.clear_operator(window, cx); if let Some(operator) = waiting_operator { diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 4b9c3fc8f777693e223de91753e2ca185e3a2c68..d5875fe963778756daf5cd78f452b3436b53642f 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -212,7 +212,7 @@ impl Vim { } } - Mode::HelixNormal => { + Mode::HelixNormal | Mode::HelixSelect => { if selection.is_empty() { // Handle empty selection by operating on the whole word let (word_range, _) = snapshot.surrounding_word(selection.start, false); diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 430149cada78b5deb08f3df551aff480f68ce992..e6eb46c0faeba8a80608259189577d73fcbb2599 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -398,7 +398,7 @@ impl Vim { match self.mode { Mode::Normal | Mode::HelixNormal => self.normal_object(object, count, window, cx), - Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { + Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixSelect => { self.visual_object(object, count, window, cx) } Mode::Insert | Mode::Replace => { diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index cd9382da461b941e0162353583131d17ae052a25..f81299169058b430b6ea6557f3d66762a6705a82 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -46,6 +46,7 @@ pub enum Mode { VisualLine, VisualBlock, HelixNormal, + HelixSelect, } impl Display for Mode { @@ -58,6 +59,7 @@ impl Display for Mode { Mode::VisualLine => write!(f, "VISUAL LINE"), Mode::VisualBlock => write!(f, "VISUAL BLOCK"), Mode::HelixNormal => write!(f, "HELIX NORMAL"), + Mode::HelixSelect => write!(f, "HELIX SELECT"), } } } @@ -65,7 +67,7 @@ impl Display for Mode { impl Mode { pub fn is_visual(&self) -> bool { match self { - Self::Visual | Self::VisualLine | Self::VisualBlock => true, + Self::Visual | Self::VisualLine | Self::VisualBlock | Self::HelixSelect => true, Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false, } } diff --git a/crates/vim/src/test/neovim_connection.rs b/crates/vim/src/test/neovim_connection.rs index 13b3e8b58d7198f4b92ccdcd6f8673745f1f482b..4b0cc5b0c5377854b3c6c9950283f68f0c79b66c 100644 --- a/crates/vim/src/test/neovim_connection.rs +++ b/crates/vim/src/test/neovim_connection.rs @@ -443,7 +443,7 @@ impl NeovimConnection { } Mode::Insert | Mode::Normal | Mode::Replace => selections .push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)), - Mode::HelixNormal => unreachable!(), + Mode::HelixNormal | Mode::HelixSelect => unreachable!(), } let ranges = encode_ranges(&text, &selections); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index fdf18dfef98c151a6801f2c73336e04df2ac89bb..7f388acd0c4bb342a4a7f1e39142cf70c9047f95 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -516,11 +516,7 @@ impl Vim { vim.update(cx, |_, cx| { Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| { - if HelixModeSetting::get_global(cx).0 { - vim.switch_mode(Mode::HelixNormal, false, window, cx) - } else { - vim.switch_mode(Mode::Normal, false, window, cx) - } + vim.switch_mode(Mode::Normal, false, window, cx) }); Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| { @@ -1030,6 +1026,13 @@ impl Vim { editor.set_relative_line_number(Some(is_relative), cx) }); } + if HelixModeSetting::get_global(cx).0 { + if self.mode == Mode::Normal { + self.mode = Mode::HelixNormal + } else if self.mode == Mode::Visual { + self.mode = Mode::HelixSelect + } + } if leave_selections { return; @@ -1151,7 +1154,7 @@ impl Vim { } Mode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block), Mode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline), - Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { + Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::HelixSelect => { cursor_shape.visual.unwrap_or(CursorShape::Block) } Mode::Insert => cursor_shape.insert.unwrap_or({ @@ -1175,7 +1178,8 @@ impl Vim { | Mode::Replace | Mode::Visual | Mode::VisualLine - | Mode::VisualBlock => false, + | Mode::VisualBlock + | Mode::HelixSelect => false, } } @@ -1190,7 +1194,8 @@ impl Vim { | Mode::VisualLine | Mode::VisualBlock | Mode::Replace - | Mode::HelixNormal => false, + | Mode::HelixNormal + | Mode::HelixSelect => false, Mode::Normal => true, } } @@ -1202,6 +1207,7 @@ impl Vim { Mode::Insert => "insert", Mode::Replace => "replace", Mode::HelixNormal => "helix_normal", + Mode::HelixSelect => "helix_select", } .to_string(); @@ -1227,7 +1233,12 @@ impl Vim { } } - if mode == "normal" || mode == "visual" || mode == "operator" || mode == "helix_normal" { + if mode == "normal" + || mode == "visual" + || mode == "operator" + || mode == "helix_normal" + || mode == "helix_select" + { context.add("VimControl"); } context.set("vim_mode", mode); @@ -1522,7 +1533,7 @@ impl Vim { cx: &mut Context, ) { match self.mode { - Mode::VisualLine | Mode::VisualBlock | Mode::Visual => { + Mode::VisualLine | Mode::VisualBlock | Mode::Visual | Mode::HelixSelect => { self.update_editor(cx, |vim, editor, cx| { let original_mode = vim.undo_modes.get(transaction_id); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {