diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 7312c1e2f8..94a271f037 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -2,12 +2,6 @@ { "context": "Editor && VimControl && !VimWaiting && !menu", "bindings": { - "g": [ - "vim::PushOperator", - { - "Namespace": "G" - } - ], "i": [ "vim::PushOperator", { @@ -110,6 +104,32 @@ "*": "vim::MoveToNext", "#": "vim::MoveToPrev", "0": "vim::StartOfLine", // When no number operator present, use start of line motion + // "g" commands + "g g": "vim::StartOfDocument", + "g h": "editor::Hover", + "g t": "pane::ActivateNextItem", + "g shift-t": "pane::ActivatePrevItem", + "g d": "editor::GoToDefinition", + "g shift-d": "editor::GoToTypeDefinition", + "g .": "editor::ToggleCodeActions", // zed specific + "g shift-a": "editor::FindAllReferences", // zed specific + "g *": [ + "vim::MoveToNext", + { + "partialWord": true + } + ], + "g #": [ + "vim::MoveToPrev", + { + "partialWord": true + } + ], + // z commands + "z t": "editor::ScrollCursorTop", + "z z": "editor::ScrollCursorCenter", + "z b": "editor::ScrollCursorBottom", + // Count support "1": [ "vim::Number", 1 @@ -234,12 +254,6 @@ "vim::PushOperator", "Yank" ], - "z": [ - "vim::PushOperator", - { - "Namespace": "Z" - } - ], "i": [ "vim::SwitchMode", "Insert" @@ -278,6 +292,13 @@ "backwards": true } ], + ";": "vim::RepeatFind", + ",": [ + "vim::RepeatFind", + { + "backwards": true + } + ], "ctrl-f": "vim::PageDown", "pagedown": "vim::PageDown", "ctrl-b": "vim::PageUp", @@ -306,33 +327,11 @@ ] } }, - { - "context": "Editor && vim_operator == g", - "bindings": { - "g": "vim::StartOfDocument", - "h": "editor::Hover", - "t": "pane::ActivateNextItem", - "shift-t": "pane::ActivatePrevItem", - "d": "editor::GoToDefinition", - "shift-d": "editor::GoToTypeDefinition", - "*": [ - "vim::MoveToNext", - { - "partialWord": true - } - ], - "#": [ - "vim::MoveToPrev", - { - "partialWord": true - } - ] - } - }, { "context": "Editor && vim_operator == c", "bindings": { - "c": "vim::CurrentLine" + "c": "vim::CurrentLine", + "d": "editor::Rename" // zed specific } }, { @@ -347,14 +346,6 @@ "y": "vim::CurrentLine" } }, - { - "context": "Editor && vim_operator == z", - "bindings": { - "t": "editor::ScrollCursorTop", - "z": "editor::ScrollCursorCenter", - "b": "editor::ScrollCursorBottom" - } - }, { "context": "Editor && VimObject", "bindings": { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 07b095dd5e..b8bd256d8a 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -62,6 +62,12 @@ struct PreviousWordStart { ignore_punctuation: bool, } +#[derive(Clone, Deserialize, PartialEq)] +struct RepeatFind { + #[serde(default)] + backwards: bool, +} + actions!( vim, [ @@ -82,7 +88,10 @@ actions!( NextLineStart, ] ); -impl_actions!(vim, [NextWordStart, NextWordEnd, PreviousWordStart]); +impl_actions!( + vim, + [NextWordStart, NextWordEnd, PreviousWordStart, RepeatFind] +); pub fn init(cx: &mut AppContext) { cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx)); @@ -123,13 +132,15 @@ pub fn init(cx: &mut AppContext) { &PreviousWordStart { ignore_punctuation }: &PreviousWordStart, cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) }, ); - cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx)) + cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx)); + cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| { + repeat_motion(action.backwards, cx) + }) } pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) { - if let Some(Operator::Namespace(_)) - | Some(Operator::FindForward { .. }) - | Some(Operator::FindBackward { .. }) = Vim::read(cx).active_operator() + if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) = + Vim::read(cx).active_operator() { Vim::update(cx, |vim, cx| vim.pop_operator(cx)); } @@ -146,6 +157,35 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| vim.clear_operator(cx)); } +fn repeat_motion(backwards: bool, cx: &mut WindowContext) { + let find = match Vim::read(cx).state.last_find.clone() { + Some(Motion::FindForward { before, text }) => { + if backwards { + Motion::FindBackward { + after: before, + text, + } + } else { + Motion::FindForward { before, text } + } + } + + Some(Motion::FindBackward { after, text }) => { + if backwards { + Motion::FindForward { + before: after, + text, + } + } else { + Motion::FindBackward { after, text } + } + } + _ => return, + }; + + motion(find, cx) +} + // Motion handling is specified here: // https://github.com/vim/vim/blob/master/runtime/doc/motion.txt impl Motion { @@ -743,4 +783,23 @@ mod test { cx.simulate_shared_keystrokes(["%"]).await; cx.assert_shared_state("func boop(ˇ) {\n}").await; } + + #[gpui::test] + async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state("ˇone two three four").await; + cx.simulate_shared_keystrokes(["f", "o"]).await; + cx.assert_shared_state("one twˇo three four").await; + cx.simulate_shared_keystrokes([","]).await; + cx.assert_shared_state("ˇone two three four").await; + cx.simulate_shared_keystrokes(["2", ";"]).await; + cx.assert_shared_state("one two three fˇour").await; + cx.simulate_shared_keystrokes(["shift-t", "e"]).await; + cx.assert_shared_state("one two threeˇ four").await; + cx.simulate_shared_keystrokes(["3", ";"]).await; + cx.assert_shared_state("oneˇ two three four").await; + cx.simulate_shared_keystrokes([","]).await; + cx.assert_shared_state("one two thˇree four").await; + } } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 8dcaa5008e..79c990ffeb 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -107,7 +107,7 @@ pub fn normal_motion( Some(Operator::Delete) => delete_motion(vim, motion, times, cx), Some(Operator::Yank) => yank_motion(vim, motion, times, cx), Some(operator) => { - // Can't do anything for text objects or namespace operators. Ignoring + // Can't do anything for text objects, Ignoring error!("Unexpected normal mode motion operator: {:?}", operator) } } @@ -441,11 +441,8 @@ mod test { use indoc::indoc; use crate::{ - state::{ - Mode::{self, *}, - Namespace, Operator, - }, - test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext}, + state::Mode::{self}, + test::{ExemptionFeatures, NeovimBackedTestContext}, }; #[gpui::test] @@ -610,22 +607,6 @@ mod test { .await; } - #[gpui::test] - async fn test_g_prefix_and_abort(cx: &mut gpui::TestAppContext) { - let mut cx = VimTestContext::new(cx, true).await; - - // Can abort with escape to get back to normal mode - cx.simulate_keystroke("g"); - assert_eq!(cx.mode(), Normal); - assert_eq!( - cx.active_operator(), - Some(Operator::Namespace(Namespace::G)) - ); - cx.simulate_keystroke("escape"); - assert_eq!(cx.mode(), Normal); - assert_eq!(cx.active_operator(), None); - } - #[gpui::test] async fn test_gg(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 23471066cd..eb52945ced 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -3,6 +3,8 @@ use language::CursorShape; use serde::{Deserialize, Serialize}; use workspace::searchable::Direction; +use crate::motion::Motion; + #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum Mode { Normal, @@ -16,16 +18,9 @@ impl Default for Mode { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -pub enum Namespace { - G, - Z, -} - #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] pub enum Operator { Number(usize), - Namespace(Namespace), Change, Delete, Yank, @@ -40,6 +35,8 @@ pub struct VimState { pub mode: Mode, pub operator_stack: Vec, pub search: SearchState, + + pub last_find: Option, } pub struct SearchState { @@ -126,8 +123,6 @@ impl Operator { pub fn id(&self) -> &'static str { match self { Operator::Number(_) => "n", - Operator::Namespace(Namespace::G) => "g", - Operator::Namespace(Namespace::Z) => "z", Operator::Object { around: false } => "i", Operator::Object { around: true } => "a", Operator::Change => "c", diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 69b94428dd..e31fa4addd 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -14,8 +14,8 @@ use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Editor, EditorMode, Event}; use gpui::{ - actions, impl_actions, keymap_matcher::KeymapContext, AppContext, Subscription, ViewContext, - ViewHandle, WeakViewHandle, WindowContext, + actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext, + Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use language::CursorShape; use motion::Motion; @@ -90,7 +90,10 @@ pub fn init(cx: &mut AppContext) { } pub fn observe_keystrokes(cx: &mut WindowContext) { - cx.observe_keystrokes(|_keystroke, _result, handled_by, cx| { + cx.observe_keystrokes(|_keystroke, result, handled_by, cx| { + if result == &MatchResult::Pending { + return true; + } if let Some(handled_by) = handled_by { // Keystroke is handled by the vim system, so continue forward if handled_by.namespace() == "vim" { @@ -243,10 +246,14 @@ impl Vim { match Vim::read(cx).active_operator() { Some(Operator::FindForward { before }) => { - motion::motion(Motion::FindForward { before, text }, cx) + let find = Motion::FindForward { before, text }; + Vim::update(cx, |vim, _| vim.state.last_find = Some(find.clone())); + motion::motion(find, cx) } Some(Operator::FindBackward { after }) => { - motion::motion(Motion::FindBackward { after, text }, cx) + let find = Motion::FindBackward { after, text }; + Vim::update(cx, |vim, _| vim.state.last_find = Some(find.clone())); + motion::motion(find, cx) } Some(Operator::Replace) => match Vim::read(cx).state.mode { Mode::Normal => normal_replace(text, cx), diff --git a/crates/vim/test_data/test_comma_semicolon.json b/crates/vim/test_data/test_comma_semicolon.json new file mode 100644 index 0000000000..8cde887ed1 --- /dev/null +++ b/crates/vim/test_data/test_comma_semicolon.json @@ -0,0 +1,17 @@ +{"Put":{"state":"ˇone two three four"}} +{"Key":"f"} +{"Key":"o"} +{"Get":{"state":"one twˇo three four","mode":"Normal"}} +{"Key":","} +{"Get":{"state":"ˇone two three four","mode":"Normal"}} +{"Key":"2"} +{"Key":";"} +{"Get":{"state":"one two three fˇour","mode":"Normal"}} +{"Key":"shift-t"} +{"Key":"e"} +{"Get":{"state":"one two threeˇ four","mode":"Normal"}} +{"Key":"3"} +{"Key":";"} +{"Get":{"state":"oneˇ two three four","mode":"Normal"}} +{"Key":","} +{"Get":{"state":"one two thˇree four","mode":"Normal"}}