vim2 (#3594)

Conrad Irwin created

- First round of vim tests

Add `observe_keystrokes` back to gpui2
Allow multiple actions to match a given key event

[[PR Description]]

Release Notes:

- (Added|Fixed|Improved) ...
([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).

Change summary

crates/editor2/src/test/editor_test_context.rs             |    4 
crates/gpui2/src/app.rs                                    |   26 
crates/gpui2/src/key_dispatch.rs                           |    8 
crates/gpui2/src/keymap/binding.rs                         |    2 
crates/gpui2/src/keymap/matcher.rs                         |   13 
crates/gpui2/src/platform/test/platform.rs                 |    2 
crates/gpui2/src/window.rs                                 |   41 
crates/search2/src/search.rs                               |    1 
crates/vim2/src/mode_indicator.rs                          |   50 
crates/vim2/src/normal.rs                                  | 1071 ++--
crates/vim2/src/test.rs                                    | 1511 +++----
crates/vim2/src/test/neovim_backed_binding_test_context.rs |    3 
crates/vim2/src/test/neovim_backed_test_context.rs         |   40 
crates/vim2/src/test/neovim_connection.rs                  |   26 
crates/vim2/src/test/vim_test_context.rs                   |   35 
crates/vim2/src/vim.rs                                     |   76 
16 files changed, 1,487 insertions(+), 1,422 deletions(-)

Detailed changes

crates/editor2/src/test/editor_test_context.rs 🔗

@@ -170,6 +170,10 @@ impl<'a> EditorTestContext<'a> {
         keystrokes_under_test_handle
     }
 
+    pub fn run_until_parked(&mut self) {
+        self.cx.background_executor.run_until_parked();
+    }
+
     pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
         let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
         assert_eq!(self.buffer_text(), unmarked_text);

crates/gpui2/src/app.rs 🔗

@@ -18,10 +18,10 @@ use crate::{
     current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
     AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
     DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
-    ForegroundExecutor, KeyBinding, Keymap, LayoutId, Menu, PathPromptOptions, Pixels, Platform,
-    PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
-    TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
-    WindowHandle, WindowId,
+    ForegroundExecutor, KeyBinding, Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels,
+    Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription,
+    SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window,
+    WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -170,6 +170,7 @@ impl App {
 pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
 type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
 type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
+type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
 type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
 type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
 type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
@@ -211,6 +212,7 @@ pub struct AppContext {
     pub(crate) observers: SubscriberSet<EntityId, Handler>,
     // TypeId is the type of the event that the listener callback expects
     pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
+    pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
     pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
     pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
     pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
@@ -271,6 +273,7 @@ impl AppContext {
                 observers: SubscriberSet::new(),
                 event_listeners: SubscriberSet::new(),
                 release_listeners: SubscriberSet::new(),
+                keystroke_observers: SubscriberSet::new(),
                 global_observers: SubscriberSet::new(),
                 quit_observers: SubscriberSet::new(),
                 layout_id_buffer: Default::default(),
@@ -962,6 +965,15 @@ impl AppContext {
         subscription
     }
 
+    pub fn observe_keystrokes(
+        &mut self,
+        f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
+    ) -> Subscription {
+        let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
+        activate();
+        subscription
+    }
+
     pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
         self.text_style_stack.push(text_style);
     }
@@ -1288,3 +1300,9 @@ pub(crate) struct AnyTooltip {
     pub view: AnyView,
     pub cursor_offset: Point<Pixels>,
 }
+
+#[derive(Debug)]
+pub struct KeystrokeEvent {
+    pub keystroke: Keystroke,
+    pub action: Option<Box<dyn Action>>,
+}

crates/gpui2/src/key_dispatch.rs 🔗

@@ -208,7 +208,7 @@ impl DispatchTree {
         &mut self,
         keystroke: &Keystroke,
         context: &[KeyContext],
-    ) -> Option<Box<dyn Action>> {
+    ) -> Vec<Box<dyn Action>> {
         if !self.keystroke_matchers.contains_key(context) {
             let keystroke_contexts = context.iter().cloned().collect();
             self.keystroke_matchers.insert(
@@ -218,15 +218,15 @@ impl DispatchTree {
         }
 
         let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
-        if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(keystroke, context) {
+        if let KeyMatch::Some(actions) = keystroke_matcher.match_keystroke(keystroke, context) {
             // Clear all pending keystrokes when an action has been found.
             for keystroke_matcher in self.keystroke_matchers.values_mut() {
                 keystroke_matcher.clear_pending();
             }
 
-            Some(action)
+            actions
         } else {
-            None
+            vec![]
         }
     }
 

crates/gpui2/src/keymap/binding.rs 🔗

@@ -59,7 +59,7 @@ impl KeyBinding {
         {
             // If the binding is completed, push it onto the matches list
             if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
-                KeyMatch::Some(self.action.boxed_clone())
+                KeyMatch::Some(vec![self.action.boxed_clone()])
             } else {
                 KeyMatch::Pending
             }

crates/gpui2/src/keymap/matcher.rs 🔗

@@ -54,14 +54,14 @@ impl KeystrokeMatcher {
         }
 
         let mut pending_key = None;
+        let mut found_actions = Vec::new();
 
         for binding in keymap.bindings().iter().rev() {
             for candidate in keystroke.match_candidates() {
                 self.pending_keystrokes.push(candidate.clone());
                 match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
-                    KeyMatch::Some(action) => {
-                        self.pending_keystrokes.clear();
-                        return KeyMatch::Some(action);
+                    KeyMatch::Some(mut actions) => {
+                        found_actions.append(&mut actions);
                     }
                     KeyMatch::Pending => {
                         pending_key.get_or_insert(candidate);
@@ -72,6 +72,11 @@ impl KeystrokeMatcher {
             }
         }
 
+        if !found_actions.is_empty() {
+            self.pending_keystrokes.clear();
+            return KeyMatch::Some(found_actions);
+        }
+
         if let Some(pending_key) = pending_key {
             self.pending_keystrokes.push(pending_key);
         }
@@ -101,7 +106,7 @@ impl KeystrokeMatcher {
 pub enum KeyMatch {
     None,
     Pending,
-    Some(Box<dyn Action>),
+    Some(Vec<Box<dyn Action>>),
 }
 
 impl KeyMatch {

crates/gpui2/src/platform/test/platform.rs 🔗

@@ -130,7 +130,7 @@ impl Platform for TestPlatform {
     }
 
     fn active_window(&self) -> Option<crate::AnyWindowHandle> {
-        unimplemented!()
+        self.active_window.lock().clone()
     }
 
     fn open_window(

crates/gpui2/src/window.rs 🔗

@@ -3,9 +3,9 @@ use crate::{
     AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
     DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
     EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
-    ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
-    ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path,
-    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId,
+    Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent,
+    Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
     PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
     RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style,
     SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
@@ -495,6 +495,29 @@ impl<'a> WindowContext<'a> {
         })
     }
 
+    pub(crate) fn dispatch_keystroke_observers(
+        &mut self,
+        event: &dyn Any,
+        action: Option<Box<dyn Action>>,
+    ) {
+        let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() else {
+            return;
+        };
+
+        self.keystroke_observers
+            .clone()
+            .retain(&(), move |callback| {
+                (callback)(
+                    &KeystrokeEvent {
+                        keystroke: key_down_event.keystroke.clone(),
+                        action: action.as_ref().map(|action| action.boxed_clone()),
+                    },
+                    self,
+                );
+                true
+            });
+    }
+
     /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
     /// that are currently on the stack to be returned to the app.
     pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
@@ -1423,14 +1446,12 @@ impl<'a> WindowContext<'a> {
             let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
             if node.context.is_some() {
                 if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
-                    if let Some(found) = self
+                    let mut new_actions = self
                         .window
                         .rendered_frame
                         .dispatch_tree
-                        .dispatch_key(&key_down_event.keystroke, &context_stack)
-                    {
-                        actions.push(found.boxed_clone())
-                    }
+                        .dispatch_key(&key_down_event.keystroke, &context_stack);
+                    actions.append(&mut new_actions);
                 }
 
                 context_stack.pop();
@@ -1438,11 +1459,13 @@ impl<'a> WindowContext<'a> {
         }
 
         for action in actions {
-            self.dispatch_action_on_node(node_id, action);
+            self.dispatch_action_on_node(node_id, action.boxed_clone());
             if !self.propagate_event {
+                self.dispatch_keystroke_observers(event, Some(action));
                 return;
             }
         }
+        self.dispatch_keystroke_observers(event, None);
     }
 
     fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {

crates/search2/src/search.rs 🔗

@@ -17,6 +17,7 @@ pub mod project_search;
 pub(crate) mod search_bar;
 
 pub fn init(cx: &mut AppContext) {
+    menu::init();
     buffer_search::init(cx);
     project_search::init(cx);
 }

crates/vim2/src/mode_indicator.rs 🔗

@@ -1,42 +1,40 @@
-use gpui::{div, AnyElement, Element, IntoElement, Render, ViewContext};
-use settings::{Settings, SettingsStore};
+use gpui::{div, AnyElement, Element, IntoElement, Render, Subscription, ViewContext};
+use settings::SettingsStore;
 use workspace::{item::ItemHandle, ui::Label, StatusItemView};
 
-use crate::{state::Mode, Vim, VimModeSetting};
+use crate::{state::Mode, Vim};
 
 pub struct ModeIndicator {
     pub mode: Option<Mode>,
-    // _subscription: Subscription,
+    _subscriptions: Vec<Subscription>,
 }
 
 impl ModeIndicator {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        cx.observe_global::<Vim>(|this, cx| this.set_mode(Vim::read(cx).state().mode, cx))
-            .detach();
+        let _subscriptions = vec![
+            cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
+            cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
+        ];
 
-        cx.observe_global::<SettingsStore>(move |mode_indicator, cx| {
-            if VimModeSetting::get_global(cx).0 {
-                mode_indicator.mode = cx
-                    .has_global::<Vim>()
-                    .then(|| cx.global::<Vim>().state().mode);
-            } else {
-                mode_indicator.mode.take();
-            }
-        })
-        .detach();
+        let mut this = Self {
+            mode: None,
+            _subscriptions,
+        };
+        this.update_mode(cx);
+        this
+    }
 
+    fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
         // Vim doesn't exist in some tests
-        let mode = cx
-            .has_global::<Vim>()
-            .then(|| {
-                let vim = cx.global::<Vim>();
-                vim.enabled.then(|| vim.state().mode)
-            })
-            .flatten();
+        if !cx.has_global::<Vim>() {
+            return;
+        }
 
-        Self {
-            mode,
-            //    _subscription,
+        let vim = Vim::read(cx);
+        if vim.enabled {
+            self.mode = Some(vim.state().mode);
+        } else {
+            self.mode = None;
         }
     }
 

crates/vim2/src/normal.rs 🔗

@@ -203,7 +203,6 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspa
 }
 
 fn insert_before(_: &mut Workspace, _: &InsertBefore, cx: &mut ViewContext<Workspace>) {
-    dbg!("insert before!");
     Vim::update(cx, |vim, cx| {
         vim.start_recording(cx);
         vim.switch_mode(Mode::Insert, false, cx);
@@ -372,536 +371,540 @@ pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
     });
 }
 
-// #[cfg(test)]
-// mod test {
-//     use gpui::TestAppContext;
-//     use indoc::indoc;
-
-//     use crate::{
-//         state::Mode::{self},
-//         test::NeovimBackedTestContext,
-//     };
-
-//     #[gpui::test]
-//     async fn test_h(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe qˇuick
-//             ˇbrown"
-//         })
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_backspace(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx)
-//             .await
-//             .binding(["backspace"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe qˇuick
-//             ˇbrown"
-//         })
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_j(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//         cx.set_shared_state(indoc! {"
-//                     aaˇaa
-//                     😃😃"
-//         })
-//         .await;
-//         cx.simulate_shared_keystrokes(["j"]).await;
-//         cx.assert_shared_state(indoc! {"
-//                     aaaa
-//                     😃ˇ😃"
-//         })
-//         .await;
-
-//         for marked_position in cx.each_marked_position(indoc! {"
-//                     ˇThe qˇuick broˇwn
-//                     ˇfox jumps"
-//         }) {
-//             cx.assert_neovim_compatible(&marked_position, ["j"]).await;
-//         }
-//     }
-
-//     #[gpui::test]
-//     async fn test_enter(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe qˇuick broˇwn
-//             ˇfox jumps"
-//         })
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_k(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe qˇuick
-//             ˇbrown fˇox jumˇps"
-//         })
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_l(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["l"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe qˇuicˇk
-//             ˇbrowˇn"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-//         cx.assert_binding_matches_all(
-//             ["$"],
-//             indoc! {"
-//             ˇThe qˇuicˇk
-//             ˇbrowˇn"},
-//         )
-//         .await;
-//         cx.assert_binding_matches_all(
-//             ["0"],
-//             indoc! {"
-//                 ˇThe qˇuicˇk
-//                 ˇbrowˇn"},
-//         )
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-g"]);
-
-//         cx.assert_all(indoc! {"
-//                 The ˇquick
-
-//                 brown fox jumps
-//                 overˇ the lazy doˇg"})
-//             .await;
-//         cx.assert(indoc! {"
-//             The quiˇck
-
-//             brown"})
-//             .await;
-//         cx.assert(indoc! {"
-//             The quiˇck
-
-//             "})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_w(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["w"]);
-//         cx.assert_all(indoc! {"
-//             The ˇquickˇ-ˇbrown
-//             ˇ
-//             ˇ
-//             ˇfox_jumps ˇover
-//             ˇthˇe"})
-//             .await;
-//         let mut cx = cx.binding(["shift-w"]);
-//         cx.assert_all(indoc! {"
-//             The ˇquickˇ-ˇbrown
-//             ˇ
-//             ˇ
-//             ˇfox_jumps ˇover
-//             ˇthˇe"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
-//         cx.assert_all(indoc! {"
-//             Thˇe quicˇkˇ-browˇn
-
-//             fox_jumpˇs oveˇr
-//             thˇe"})
-//             .await;
-//         let mut cx = cx.binding(["shift-e"]);
-//         cx.assert_all(indoc! {"
-//             Thˇe quicˇkˇ-browˇn
-
-//             fox_jumpˇs oveˇr
-//             thˇe"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_b(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["b"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe ˇquickˇ-ˇbrown
-//             ˇ
-//             ˇ
-//             ˇfox_jumps ˇover
-//             ˇthe"})
-//             .await;
-//         let mut cx = cx.binding(["shift-b"]);
-//         cx.assert_all(indoc! {"
-//             ˇThe ˇquickˇ-ˇbrown
-//             ˇ
-//             ˇ
-//             ˇfox_jumps ˇover
-//             ˇthe"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_gg(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-//         cx.assert_binding_matches_all(
-//             ["g", "g"],
-//             indoc! {"
-//                 The qˇuick
-
-//                 brown fox jumps
-//                 over ˇthe laˇzy dog"},
-//         )
-//         .await;
-//         cx.assert_binding_matches(
-//             ["g", "g"],
-//             indoc! {"
-
-//                 brown fox jumps
-//                 over the laˇzy dog"},
-//         )
-//         .await;
-//         cx.assert_binding_matches(
-//             ["2", "g", "g"],
-//             indoc! {"
-//                 ˇ
-
-//                 brown fox jumps
-//                 over the lazydog"},
-//         )
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-//         cx.assert_binding_matches_all(
-//             ["shift-g"],
-//             indoc! {"
-//                 The qˇuick
-
-//                 brown fox jumps
-//                 over ˇthe laˇzy dog"},
-//         )
-//         .await;
-//         cx.assert_binding_matches(
-//             ["shift-g"],
-//             indoc! {"
-
-//                 brown fox jumps
-//                 over the laˇzy dog"},
-//         )
-//         .await;
-//         cx.assert_binding_matches(
-//             ["2", "shift-g"],
-//             indoc! {"
-//                 ˇ
-
-//                 brown fox jumps
-//                 over the lazydog"},
-//         )
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_a(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["a"]);
-//         cx.assert_all("The qˇuicˇk").await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-a"]);
-//         cx.assert_all(indoc! {"
-//             ˇ
-//             The qˇuick
-//             brown ˇfox "})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["^"]);
-//         cx.assert("The qˇuick").await;
-//         cx.assert(" The qˇuick").await;
-//         cx.assert("ˇ").await;
-//         cx.assert(indoc! {"
-//                 The qˇuick
-//                 brown fox"})
-//             .await;
-//         cx.assert(indoc! {"
-//                 ˇ
-//                 The quick"})
-//             .await;
-//         // Indoc disallows trailing whitespace.
-//         cx.assert("   ˇ \nThe quick").await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-i"]);
-//         cx.assert("The qˇuick").await;
-//         cx.assert(" The qˇuick").await;
-//         cx.assert("ˇ").await;
-//         cx.assert(indoc! {"
-//                 The qˇuick
-//                 brown fox"})
-//             .await;
-//         cx.assert(indoc! {"
-//                 ˇ
-//                 The quick"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-d"]);
-//         cx.assert(indoc! {"
-//                 The qˇuick
-//                 brown fox"})
-//             .await;
-//         cx.assert(indoc! {"
-//                 The quick
-//                 ˇ
-//                 brown fox"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_x(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["x"]);
-//         cx.assert_all("ˇTeˇsˇt").await;
-//         cx.assert(indoc! {"
-//                 Tesˇt
-//                 test"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_delete_left(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-x"]);
-//         cx.assert_all("ˇTˇeˇsˇt").await;
-//         cx.assert(indoc! {"
-//                 Test
-//                 ˇtest"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_o(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["o"]);
-//         cx.assert("ˇ").await;
-//         cx.assert("The ˇquick").await;
-//         cx.assert_all(indoc! {"
-//                 The qˇuick
-//                 brown ˇfox
-//                 jumps ˇover"})
-//             .await;
-//         cx.assert(indoc! {"
-//                 The quick
-//                 ˇ
-//                 brown fox"})
-//             .await;
-
-//         cx.assert_manual(
-//             indoc! {"
-//                 fn test() {
-//                     println!(ˇ);
-//                 }"},
-//             Mode::Normal,
-//             indoc! {"
-//                 fn test() {
-//                     println!();
-//                     ˇ
-//                 }"},
-//             Mode::Insert,
-//         );
-
-//         cx.assert_manual(
-//             indoc! {"
-//                 fn test(ˇ) {
-//                     println!();
-//                 }"},
-//             Mode::Normal,
-//             indoc! {"
-//                 fn test() {
-//                     ˇ
-//                     println!();
-//                 }"},
-//             Mode::Insert,
-//         );
-//     }
-
-//     #[gpui::test]
-//     async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
-//         let cx = NeovimBackedTestContext::new(cx).await;
-//         let mut cx = cx.binding(["shift-o"]);
-//         cx.assert("ˇ").await;
-//         cx.assert("The ˇquick").await;
-//         cx.assert_all(indoc! {"
-//             The qˇuick
-//             brown ˇfox
-//             jumps ˇover"})
-//             .await;
-//         cx.assert(indoc! {"
-//             The quick
-//             ˇ
-//             brown fox"})
-//             .await;
-
-//         // Our indentation is smarter than vims. So we don't match here
-//         cx.assert_manual(
-//             indoc! {"
-//                 fn test() {
-//                     println!(ˇ);
-//                 }"},
-//             Mode::Normal,
-//             indoc! {"
-//                 fn test() {
-//                     ˇ
-//                     println!();
-//                 }"},
-//             Mode::Insert,
-//         );
-//         cx.assert_manual(
-//             indoc! {"
-//                 fn test(ˇ) {
-//                     println!();
-//                 }"},
-//             Mode::Normal,
-//             indoc! {"
-//                 ˇ
-//                 fn test() {
-//                     println!();
-//                 }"},
-//             Mode::Insert,
-//         );
-//     }
-
-//     #[gpui::test]
-//     async fn test_dd(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-//         cx.assert_neovim_compatible("ˇ", ["d", "d"]).await;
-//         cx.assert_neovim_compatible("The ˇquick", ["d", "d"]).await;
-//         for marked_text in cx.each_marked_position(indoc! {"
-//             The qˇuick
-//             brown ˇfox
-//             jumps ˇover"})
-//         {
-//             cx.assert_neovim_compatible(&marked_text, ["d", "d"]).await;
-//         }
-//         cx.assert_neovim_compatible(
-//             indoc! {"
-//                 The quick
-//                 ˇ
-//                 brown fox"},
-//             ["d", "d"],
-//         )
-//         .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_cc(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "c"]);
-//         cx.assert("ˇ").await;
-//         cx.assert("The ˇquick").await;
-//         cx.assert_all(indoc! {"
-//                 The quˇick
-//                 brown ˇfox
-//                 jumps ˇover"})
-//             .await;
-//         cx.assert(indoc! {"
-//                 The quick
-//                 ˇ
-//                 brown fox"})
-//             .await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//         for count in 1..=5 {
-//             cx.assert_binding_matches_all(
-//                 [&count.to_string(), "w"],
-//                 indoc! {"
-//                     ˇThe quˇickˇ browˇn
-//                     ˇ
-//                     ˇfox ˇjumpsˇ-ˇoˇver
-//                     ˇthe lazy dog
-//                 "},
-//             )
-//             .await;
-//         }
-//     }
-
-//     #[gpui::test]
-//     async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
-//         cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
-//     }
-
-//     #[gpui::test]
-//     async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//         for count in 1..=3 {
-//             let test_case = indoc! {"
-//                 ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
-//                 ˇ    ˇbˇaaˇa ˇbˇbˇb
-//                 ˇ
-//                 ˇb
-//             "};
-
-//             cx.assert_binding_matches_all([&count.to_string(), "f", "b"], test_case)
-//                 .await;
-
-//             cx.assert_binding_matches_all([&count.to_string(), "t", "b"], test_case)
-//                 .await;
-//         }
-//     }
-
-//     #[gpui::test]
-//     async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await;
-//         let test_case = indoc! {"
-//             ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
-//             ˇ    ˇbˇaaˇa ˇbˇbˇb
-//             ˇ•••
-//             ˇb
-//             "
-//         };
-
-//         for count in 1..=3 {
-//             cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
-//                 .await;
-
-//             cx.assert_binding_matches_all([&count.to_string(), "shift-t", "b"], test_case)
-//                 .await;
-//         }
-//     }
-
-//     #[gpui::test]
-//     async fn test_percent(cx: &mut TestAppContext) {
-//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
-//         cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
-//         cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
-//             .await;
-//         cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
-//     }
-// }
+#[cfg(test)]
+mod test {
+    use gpui::TestAppContext;
+    use indoc::indoc;
+
+    use crate::{
+        state::Mode::{self},
+        test::NeovimBackedTestContext,
+    };
+
+    #[gpui::test]
+    async fn test_h(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
+        cx.assert_all(indoc! {"
+            ˇThe qˇuick
+            ˇbrown"
+        })
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_backspace(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx)
+            .await
+            .binding(["backspace"]);
+        cx.assert_all(indoc! {"
+            ˇThe qˇuick
+            ˇbrown"
+        })
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_j(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        cx.set_shared_state(indoc! {"
+                    aaˇaa
+                    😃😃"
+        })
+        .await;
+        cx.simulate_shared_keystrokes(["j"]).await;
+        cx.assert_shared_state(indoc! {"
+                    aaaa
+                    😃ˇ😃"
+        })
+        .await;
+
+        for marked_position in cx.each_marked_position(indoc! {"
+                    ˇThe qˇuick broˇwn
+                    ˇfox jumps"
+        }) {
+            cx.assert_neovim_compatible(&marked_position, ["j"]).await;
+        }
+    }
+
+    #[gpui::test]
+    async fn test_enter(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
+        cx.assert_all(indoc! {"
+            ˇThe qˇuick broˇwn
+            ˇfox jumps"
+        })
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_k(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]);
+        cx.assert_all(indoc! {"
+            ˇThe qˇuick
+            ˇbrown fˇox jumˇps"
+        })
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_l(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["l"]);
+        cx.assert_all(indoc! {"
+            ˇThe qˇuicˇk
+            ˇbrowˇn"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        cx.assert_binding_matches_all(
+            ["$"],
+            indoc! {"
+            ˇThe qˇuicˇk
+            ˇbrowˇn"},
+        )
+        .await;
+        cx.assert_binding_matches_all(
+            ["0"],
+            indoc! {"
+                ˇThe qˇuicˇk
+                ˇbrowˇn"},
+        )
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-g"]);
+
+        cx.assert_all(indoc! {"
+                The ˇquick
+
+                brown fox jumps
+                overˇ the lazy doˇg"})
+            .await;
+        cx.assert(indoc! {"
+            The quiˇck
+
+            brown"})
+            .await;
+        cx.assert(indoc! {"
+            The quiˇck
+
+            "})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_w(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["w"]);
+        cx.assert_all(indoc! {"
+            The ˇquickˇ-ˇbrown
+            ˇ
+            ˇ
+            ˇfox_jumps ˇover
+            ˇthˇe"})
+            .await;
+        let mut cx = cx.binding(["shift-w"]);
+        cx.assert_all(indoc! {"
+            The ˇquickˇ-ˇbrown
+            ˇ
+            ˇ
+            ˇfox_jumps ˇover
+            ˇthˇe"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
+        cx.assert_all(indoc! {"
+            Thˇe quicˇkˇ-browˇn
+
+
+            fox_jumpˇs oveˇr
+            thˇe"})
+            .await;
+        let mut cx = cx.binding(["shift-e"]);
+        cx.assert_all(indoc! {"
+            Thˇe quicˇkˇ-browˇn
+
+
+            fox_jumpˇs oveˇr
+            thˇe"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_b(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["b"]);
+        cx.assert_all(indoc! {"
+            ˇThe ˇquickˇ-ˇbrown
+            ˇ
+            ˇ
+            ˇfox_jumps ˇover
+            ˇthe"})
+            .await;
+        let mut cx = cx.binding(["shift-b"]);
+        cx.assert_all(indoc! {"
+            ˇThe ˇquickˇ-ˇbrown
+            ˇ
+            ˇ
+            ˇfox_jumps ˇover
+            ˇthe"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_gg(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        cx.assert_binding_matches_all(
+            ["g", "g"],
+            indoc! {"
+                The qˇuick
+
+                brown fox jumps
+                over ˇthe laˇzy dog"},
+        )
+        .await;
+        cx.assert_binding_matches(
+            ["g", "g"],
+            indoc! {"
+
+
+                brown fox jumps
+                over the laˇzy dog"},
+        )
+        .await;
+        cx.assert_binding_matches(
+            ["2", "g", "g"],
+            indoc! {"
+                ˇ
+
+                brown fox jumps
+                over the lazydog"},
+        )
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        cx.assert_binding_matches_all(
+            ["shift-g"],
+            indoc! {"
+                The qˇuick
+
+                brown fox jumps
+                over ˇthe laˇzy dog"},
+        )
+        .await;
+        cx.assert_binding_matches(
+            ["shift-g"],
+            indoc! {"
+
+
+                brown fox jumps
+                over the laˇzy dog"},
+        )
+        .await;
+        cx.assert_binding_matches(
+            ["2", "shift-g"],
+            indoc! {"
+                ˇ
+
+                brown fox jumps
+                over the lazydog"},
+        )
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_a(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["a"]);
+        cx.assert_all("The qˇuicˇk").await;
+    }
+
+    #[gpui::test]
+    async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-a"]);
+        cx.assert_all(indoc! {"
+            ˇ
+            The qˇuick
+            brown ˇfox "})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["^"]);
+        cx.assert("The qˇuick").await;
+        cx.assert(" The qˇuick").await;
+        cx.assert("ˇ").await;
+        cx.assert(indoc! {"
+                The qˇuick
+                brown fox"})
+            .await;
+        cx.assert(indoc! {"
+                ˇ
+                The quick"})
+            .await;
+        // Indoc disallows trailing whitespace.
+        cx.assert("   ˇ \nThe quick").await;
+    }
+
+    #[gpui::test]
+    async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-i"]);
+        cx.assert("The qˇuick").await;
+        cx.assert(" The qˇuick").await;
+        cx.assert("ˇ").await;
+        cx.assert(indoc! {"
+                The qˇuick
+                brown fox"})
+            .await;
+        cx.assert(indoc! {"
+                ˇ
+                The quick"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-d"]);
+        cx.assert(indoc! {"
+                The qˇuick
+                brown fox"})
+            .await;
+        cx.assert(indoc! {"
+                The quick
+                ˇ
+                brown fox"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_x(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["x"]);
+        cx.assert_all("ˇTeˇsˇt").await;
+        cx.assert(indoc! {"
+                Tesˇt
+                test"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_delete_left(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-x"]);
+        cx.assert_all("ˇTˇeˇsˇt").await;
+        cx.assert(indoc! {"
+                Test
+                ˇtest"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_o(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["o"]);
+        cx.assert("ˇ").await;
+        cx.assert("The ˇquick").await;
+        cx.assert_all(indoc! {"
+                The qˇuick
+                brown ˇfox
+                jumps ˇover"})
+            .await;
+        cx.assert(indoc! {"
+                The quick
+                ˇ
+                brown fox"})
+            .await;
+
+        cx.assert_manual(
+            indoc! {"
+                fn test() {
+                    println!(ˇ);
+                }"},
+            Mode::Normal,
+            indoc! {"
+                fn test() {
+                    println!();
+                    ˇ
+                }"},
+            Mode::Insert,
+        );
+
+        cx.assert_manual(
+            indoc! {"
+                fn test(ˇ) {
+                    println!();
+                }"},
+            Mode::Normal,
+            indoc! {"
+                fn test() {
+                    ˇ
+                    println!();
+                }"},
+            Mode::Insert,
+        );
+    }
+
+    #[gpui::test]
+    async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
+        let cx = NeovimBackedTestContext::new(cx).await;
+        let mut cx = cx.binding(["shift-o"]);
+        cx.assert("ˇ").await;
+        cx.assert("The ˇquick").await;
+        cx.assert_all(indoc! {"
+            The qˇuick
+            brown ˇfox
+            jumps ˇover"})
+            .await;
+        cx.assert(indoc! {"
+            The quick
+            ˇ
+            brown fox"})
+            .await;
+
+        // Our indentation is smarter than vims. So we don't match here
+        cx.assert_manual(
+            indoc! {"
+                fn test() {
+                    println!(ˇ);
+                }"},
+            Mode::Normal,
+            indoc! {"
+                fn test() {
+                    ˇ
+                    println!();
+                }"},
+            Mode::Insert,
+        );
+        cx.assert_manual(
+            indoc! {"
+                fn test(ˇ) {
+                    println!();
+                }"},
+            Mode::Normal,
+            indoc! {"
+                ˇ
+                fn test() {
+                    println!();
+                }"},
+            Mode::Insert,
+        );
+    }
+
+    #[gpui::test]
+    async fn test_dd(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        cx.assert_neovim_compatible("ˇ", ["d", "d"]).await;
+        cx.assert_neovim_compatible("The ˇquick", ["d", "d"]).await;
+        for marked_text in cx.each_marked_position(indoc! {"
+            The qˇuick
+            brown ˇfox
+            jumps ˇover"})
+        {
+            cx.assert_neovim_compatible(&marked_text, ["d", "d"]).await;
+        }
+        cx.assert_neovim_compatible(
+            indoc! {"
+                The quick
+                ˇ
+                brown fox"},
+            ["d", "d"],
+        )
+        .await;
+    }
+
+    #[gpui::test]
+    async fn test_cc(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "c"]);
+        cx.assert("ˇ").await;
+        cx.assert("The ˇquick").await;
+        cx.assert_all(indoc! {"
+                The quˇick
+                brown ˇfox
+                jumps ˇover"})
+            .await;
+        cx.assert(indoc! {"
+                The quick
+                ˇ
+                brown fox"})
+            .await;
+    }
+
+    #[gpui::test]
+    async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        for count in 1..=5 {
+            cx.assert_binding_matches_all(
+                [&count.to_string(), "w"],
+                indoc! {"
+                    ˇThe quˇickˇ browˇn
+                    ˇ
+                    ˇfox ˇjumpsˇ-ˇoˇver
+                    ˇthe lazy dog
+                "},
+            )
+            .await;
+        }
+    }
+
+    #[gpui::test]
+    async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
+        cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
+    }
+
+    #[gpui::test]
+    async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+
+        for count in 1..=3 {
+            let test_case = indoc! {"
+                ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
+                ˇ    ˇbˇaaˇa ˇbˇbˇb
+                ˇ
+                ˇb
+            "};
+
+            cx.assert_binding_matches_all([&count.to_string(), "f", "b"], test_case)
+                .await;
+
+            cx.assert_binding_matches_all([&count.to_string(), "t", "b"], test_case)
+                .await;
+        }
+    }
+
+    #[gpui::test]
+    async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        let test_case = indoc! {"
+            ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
+            ˇ    ˇbˇaaˇa ˇbˇbˇb
+            ˇ•••
+            ˇb
+            "
+        };
+
+        for count in 1..=3 {
+            cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
+                .await;
+
+            cx.assert_binding_matches_all([&count.to_string(), "shift-t", "b"], test_case)
+                .await;
+        }
+    }
+
+    #[gpui::test]
+    async fn test_percent(cx: &mut TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
+        cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
+        cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
+            .await;
+        cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
+    }
+}

crates/vim2/src/test.rs 🔗

@@ -1,759 +1,752 @@
-// mod neovim_backed_binding_test_context;
-// mod neovim_backed_test_context;
-// mod neovim_connection;
-// mod vim_test_context;
-
-// use std::sync::Arc;
-
-// use command_palette::CommandPalette;
-// use editor::DisplayPoint;
-// pub use neovim_backed_binding_test_context::*;
-// pub use neovim_backed_test_context::*;
-// pub use vim_test_context::*;
-
-// use indoc::indoc;
-// use search::BufferSearchBar;
-
-// use crate::{state::Mode, ModeIndicator};
-
-// #[gpui::test]
-// async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, false).await;
-//     cx.simulate_keystrokes(["h", "j", "k", "l"]);
-//     cx.assert_editor_state("hjklˇ");
-// }
-
-// #[gpui::test]
-// async fn test_neovim(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.simulate_shared_keystroke("i").await;
-//     cx.assert_state_matches().await;
-//     cx.simulate_shared_keystrokes([
-//         "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
-//     ])
-//     .await;
-//     cx.assert_state_matches().await;
-//     cx.assert_editor_state("ˇtest");
-// }
-
-// #[gpui::test]
-// async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.simulate_keystroke("i");
-//     assert_eq!(cx.mode(), Mode::Insert);
-
-//     // Editor acts as though vim is disabled
-//     cx.disable_vim();
-//     cx.simulate_keystrokes(["h", "j", "k", "l"]);
-//     cx.assert_editor_state("hjklˇ");
-
-//     // Selections aren't changed if editor is blurred but vim-mode is still disabled.
-//     cx.set_state("«hjklˇ»", Mode::Normal);
-//     cx.assert_editor_state("«hjklˇ»");
-//     cx.update_editor(|_, cx| cx.blur());
-//     cx.assert_editor_state("«hjklˇ»");
-//     cx.update_editor(|_, cx| cx.focus_self());
-//     cx.assert_editor_state("«hjklˇ»");
-
-//     // Enabling dynamically sets vim mode again and restores normal mode
-//     cx.enable_vim();
-//     assert_eq!(cx.mode(), Mode::Normal);
-//     cx.simulate_keystrokes(["h", "h", "h", "l"]);
-//     assert_eq!(cx.buffer_text(), "hjkl".to_owned());
-//     cx.assert_editor_state("hˇjkl");
-//     cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
-//     cx.assert_editor_state("hTestˇjkl");
-
-//     // Disabling and enabling resets to normal mode
-//     assert_eq!(cx.mode(), Mode::Insert);
-//     cx.disable_vim();
-//     cx.enable_vim();
-//     assert_eq!(cx.mode(), Mode::Normal);
-// }
-
-// #[gpui::test]
-// async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.set_state(
-//         indoc! {"
-//             The quick brown
-//             fox juˇmps over
-//             the lazy dog"},
-//         Mode::Normal,
-//     );
-//     cx.simulate_keystroke("/");
-
-//     let search_bar = cx.workspace(|workspace, cx| {
-//         workspace
-//             .active_pane()
-//             .read(cx)
-//             .toolbar()
-//             .read(cx)
-//             .item_of_type::<BufferSearchBar>()
-//             .expect("Buffer search bar should be deployed")
-//     });
-
-//     cx.update_view(search_bar, |bar, cx| {
-//         assert_eq!(bar.query(cx), "");
-//     })
-// }
-
-// #[gpui::test]
-// async fn test_count_down(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
-//     cx.simulate_keystrokes(["2", "down"]);
-//     cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
-//     cx.simulate_keystrokes(["9", "down"]);
-//     cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
-// }
-
-// #[gpui::test]
-// async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     // goes to end by default
-//     cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
-//     cx.simulate_keystrokes(["shift-g"]);
-//     cx.assert_editor_state("aa\nbb\ncˇc");
-
-//     // can go to line 1 (https://github.com/zed-industries/community/issues/710)
-//     cx.simulate_keystrokes(["1", "shift-g"]);
-//     cx.assert_editor_state("aˇa\nbb\ncc");
-// }
-
-// #[gpui::test]
-// async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     // works in normal mode
-//     cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
-//     cx.simulate_keystrokes([">", ">"]);
-//     cx.assert_editor_state("aa\n    bˇb\ncc");
-//     cx.simulate_keystrokes(["<", "<"]);
-//     cx.assert_editor_state("aa\nbˇb\ncc");
-
-//     // works in visuial mode
-//     cx.simulate_keystrokes(["shift-v", "down", ">"]);
-//     cx.assert_editor_state("aa\n    b«b\n    ccˇ»");
-// }
-
-// #[gpui::test]
-// async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.set_state("aˇbc\n", Mode::Normal);
-//     cx.simulate_keystrokes(["i", "cmd-shift-p"]);
-
-//     assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
-//     cx.simulate_keystroke("escape");
-//     assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
-//     cx.assert_state("aˇbc\n", Mode::Insert);
-// }
-
-// #[gpui::test]
-// async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.set_state("aˇbˇc", Mode::Normal);
-//     cx.simulate_keystrokes(["escape"]);
-
-//     cx.assert_state("aˇbc", Mode::Normal);
-// }
-
-// #[gpui::test]
-// async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
-//     cx.simulate_keystrokes(["/", "c", "c"]);
-
-//     let search_bar = cx.workspace(|workspace, cx| {
-//         workspace
-//             .active_pane()
-//             .read(cx)
-//             .toolbar()
-//             .read(cx)
-//             .item_of_type::<BufferSearchBar>()
-//             .expect("Buffer search bar should be deployed")
-//     });
-
-//     search_bar.read_with(cx.cx, |bar, cx| {
-//         assert_eq!(bar.query(cx), "cc");
-//     });
-
-//     cx.update_editor(|editor, cx| {
-//         let highlights = editor.all_text_background_highlights(cx);
-//         assert_eq!(3, highlights.len());
-//         assert_eq!(
-//             DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
-//             highlights[0].0
-//         )
-//     });
-//     cx.simulate_keystrokes(["enter"]);
-
-//     cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
-//     cx.simulate_keystrokes(["n"]);
-//     cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
-//     cx.simulate_keystrokes(["shift-n"]);
-//     cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
-// }
-
-// #[gpui::test]
-// async fn test_status_indicator(
-//     cx: &mut gpui::TestAppContext,
-//     deterministic: Arc<gpui::executor::Deterministic>,
-// ) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-//     deterministic.run_until_parked();
-
-//     let mode_indicator = cx.workspace(|workspace, cx| {
-//         let status_bar = workspace.status_bar().read(cx);
-//         let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
-//         assert!(mode_indicator.is_some());
-//         mode_indicator.unwrap()
-//     });
-
-//     assert_eq!(
-//         cx.workspace(|_, cx| mode_indicator.read(cx).mode),
-//         Some(Mode::Normal)
-//     );
-
-//     // shows the correct mode
-//     cx.simulate_keystrokes(["i"]);
-//     deterministic.run_until_parked();
-//     assert_eq!(
-//         cx.workspace(|_, cx| mode_indicator.read(cx).mode),
-//         Some(Mode::Insert)
-//     );
-
-//     // shows even in search
-//     cx.simulate_keystrokes(["escape", "v", "/"]);
-//     deterministic.run_until_parked();
-//     assert_eq!(
-//         cx.workspace(|_, cx| mode_indicator.read(cx).mode),
-//         Some(Mode::Visual)
-//     );
-
-//     // hides if vim mode is disabled
-//     cx.disable_vim();
-//     deterministic.run_until_parked();
-//     cx.workspace(|workspace, cx| {
-//         let status_bar = workspace.status_bar().read(cx);
-//         let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
-//         assert!(mode_indicator.read(cx).mode.is_none());
-//     });
-
-//     cx.enable_vim();
-//     deterministic.run_until_parked();
-//     cx.workspace(|workspace, cx| {
-//         let status_bar = workspace.status_bar().read(cx);
-//         let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
-//         assert!(mode_indicator.read(cx).mode.is_some());
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_word_characters(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new_typescript(cx).await;
-//     cx.set_state(
-//         indoc! { "
-//         class A {
-//             #ˇgoop = 99;
-//             $ˇgoop () { return this.#gˇoop };
-//         };
-//         console.log(new A().$gooˇp())
-//     "},
-//         Mode::Normal,
-//     );
-//     cx.simulate_keystrokes(["v", "i", "w"]);
-//     cx.assert_state(
-//         indoc! {"
-//         class A {
-//             «#goopˇ» = 99;
-//             «$goopˇ» () { return this.«#goopˇ» };
-//         };
-//         console.log(new A().«$goopˇ»())
-//     "},
-//         Mode::Visual,
-//     )
-// }
-
-// #[gpui::test]
-// async fn test_join_lines(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_state(indoc! {"
-//       ˇone
-//       two
-//       three
-//       four
-//       five
-//       six
-//       "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-j"]).await;
-//     cx.assert_shared_state(indoc! {"
-//           oneˇ two
-//           three
-//           four
-//           five
-//           six
-//           "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
-//     cx.assert_shared_state(indoc! {"
-//           one two threeˇ four
-//           five
-//           six
-//           "})
-//         .await;
-
-//     cx.set_shared_state(indoc! {"
-//       ˇone
-//       two
-//       three
-//       four
-//       five
-//       six
-//       "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
-//         .await;
-//     cx.assert_shared_state(indoc! {"
-//       one
-//       two three fourˇ five
-//       six
-//       "})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_wrap(12).await;
-//     // tests line wrap as follows:
-//     //  1: twelve char
-//     //     twelve char
-//     //  2: twelve char
-//     cx.set_shared_state(indoc! { "
-//         tˇwelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["j"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char twelve char
-//         tˇwelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["k"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         tˇwelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["g", "j"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char tˇwelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["g", "j"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char twelve char
-//         tˇwelve char
-//     "})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["g", "k"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char tˇwelve char
-//         twelve char
-//     "})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["g", "^"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char ˇtwelve char
-//         twelve char
-//     "})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["^"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         ˇtwelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["g", "$"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve charˇ twelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["$"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char twelve chaˇr
-//         twelve char
-//     "})
-//         .await;
-
-//     cx.set_shared_state(indoc! { "
-//         tˇwelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["enter"]).await;
-//     cx.assert_shared_state(indoc! { "
-//             twelve char twelve char
-//             ˇtwelve char
-//         "})
-//         .await;
-
-//     cx.set_shared_state(indoc! { "
-//         twelve char
-//         tˇwelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char
-//         twelve char twelve char
-//         ˇo
-//         twelve char
-//     "})
-//         .await;
-
-//     cx.set_shared_state(indoc! { "
-//         twelve char
-//         tˇwelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
-//         .await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char
-//         twelve char twelve charˇa
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
-//         .await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char
-//         ˇitwelve char twelve chara
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-d"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char
-//         ˇ
-//         twelve char
-//     "})
-//         .await;
-
-//     cx.set_shared_state(indoc! { "
-//         twelve char
-//         twelve char tˇwelve char
-//         twelve char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
-//         .await;
-//     cx.assert_shared_state(indoc! { "
-//         twelve char
-//         ˇo
-//         twelve char twelve char
-//         twelve char
-//     "})
-//         .await;
-
-//     // line wraps as:
-//     // fourteen ch
-//     // ar
-//     // fourteen ch
-//     // ar
-//     cx.set_shared_state(indoc! { "
-//         fourteen chaˇr
-//         fourteen char
-//     "})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         fourteenˇ•
-//         fourteen char
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
-//         .await;
-//     cx.assert_shared_state(indoc! {"
-//         fourteen•
-//         fourteen chaˇr
-//     "})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_folds(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-//     cx.set_neovim_option("foldmethod=manual").await;
-
-//     cx.set_shared_state(indoc! { "
-//         fn boop() {
-//           ˇbarp()
-//           bazp()
-//         }
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
-//         .await;
-
-//     // visual display is now:
-//     // fn boop () {
-//     //  [FOLDED]
-//     // }
-
-//     // TODO: this should not be needed but currently zf does not
-//     // return to normal mode.
-//     cx.simulate_shared_keystrokes(["escape"]).await;
-
-//     // skip over fold downward
-//     cx.simulate_shared_keystrokes(["g", "g"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         ˇfn boop() {
-//           barp()
-//           bazp()
-//         }
-//     "})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["j", "j"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         fn boop() {
-//           barp()
-//           bazp()
-//         ˇ}
-//     "})
-//         .await;
-
-//     // skip over fold upward
-//     cx.simulate_shared_keystrokes(["2", "k"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         ˇfn boop() {
-//           barp()
-//           bazp()
-//         }
-//     "})
-//         .await;
-
-//     // yank the fold
-//     cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
-//     cx.assert_shared_clipboard("  barp()\n  bazp()\n").await;
-
-//     // re-open
-//     cx.simulate_shared_keystrokes(["z", "o"]).await;
-//     cx.assert_shared_state(indoc! { "
-//         fn boop() {
-//         ˇ  barp()
-//           bazp()
-//         }
-//     "})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-//     cx.set_neovim_option("foldmethod=manual").await;
-
-//     cx.set_shared_state(indoc! { "
-//         fn boop() {
-//           ˇbarp()
-//           bazp()
-//         }
-//     "})
-//         .await;
-//     cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
-//         .await;
-//     cx.simulate_shared_keystrokes(["escape"]).await;
-//     cx.simulate_shared_keystrokes(["g", "g"]).await;
-//     cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
-//     cx.assert_shared_state(indoc! { "ˇ"}).await;
-// }
-
-// #[gpui::test]
-// async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_state(indoc! {"
-//         The quick brown
-//         fox juˇmps over
-//         the lazy dog"})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
-//         .await;
-//     cx.assert_shared_state(indoc! {"
-//         The quick brown
-//         fox juˇ over
-//         the lazy dog"})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_zero(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_state(indoc! {"
-//         The quˇick brown
-//         fox jumps over
-//         the lazy dog"})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["0"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         ˇThe quick brown
-//         fox jumps over
-//         the lazy dog"})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         The quick ˇbrown
-//         fox jumps over
-//         the lazy dog"})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_state(indoc! {"
-//         ;;ˇ;
-//         Lorem Ipsum"})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
-//         .await;
-//     cx.assert_shared_state(indoc! {"
-//         ;;;;ˇ
-//         Lorem Ipsum"})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_wrap(12).await;
-
-//     cx.set_shared_state(indoc! {"
-//                 aaˇaa
-//                 😃😃"
-//     })
-//     .await;
-//     cx.simulate_shared_keystrokes(["j"]).await;
-//     cx.assert_shared_state(indoc! {"
-//                 aaaa
-//                 😃ˇ😃"
-//     })
-//     .await;
-
-//     cx.set_shared_state(indoc! {"
-//                 123456789012aaˇaa
-//                 123456789012😃😃"
-//     })
-//     .await;
-//     cx.simulate_shared_keystrokes(["j"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         123456789012aaaa
-//         123456789012😃ˇ😃"
-//     })
-//     .await;
-
-//     cx.set_shared_state(indoc! {"
-//                 123456789012aaˇaa
-//                 123456789012😃😃"
-//     })
-//     .await;
-//     cx.simulate_shared_keystrokes(["j"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         123456789012aaaa
-//         123456789012😃ˇ😃"
-//     })
-//     .await;
-
-//     cx.set_shared_state(indoc! {"
-//         123456789012aaaaˇaaaaaaaa123456789012
-//         wow
-//         123456789012😃😃😃😃😃😃123456789012"
-//     })
-//     .await;
-//     cx.simulate_shared_keystrokes(["j", "j"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         123456789012aaaaaaaaaaaa123456789012
-//         wow
-//         123456789012😃😃ˇ😃😃😃😃123456789012"
-//     })
-//     .await;
-// }
-
-// #[gpui::test]
-// async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
-//     let mut cx = NeovimBackedTestContext::new(cx).await;
-
-//     cx.set_shared_state(indoc! {"
-//         one
-//         ˇ
-//         two"})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["}", "}"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         one
-
-//         twˇo"})
-//         .await;
-
-//     cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
-//     cx.assert_shared_state(indoc! {"
-//         ˇone
-
-//         two"})
-//         .await;
-// }
-
-// #[gpui::test]
-// async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
-//     let mut cx = VimTestContext::new(cx, true).await;
-
-//     cx.set_state(
-//         indoc! {"
-//         defmodule Test do
-//             def test(a, ˇ[_, _] = b), do: IO.puts('hi')
-//         end
-//     "},
-//         Mode::Normal,
-//     );
-//     cx.simulate_keystrokes(["g", "a"]);
-//     cx.assert_state(
-//         indoc! {"
-//         defmodule Test do
-//             def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
-//         end
-//     "},
-//         Mode::Visual,
-//     );
-// }
+mod neovim_backed_binding_test_context;
+mod neovim_backed_test_context;
+mod neovim_connection;
+mod vim_test_context;
+
+use command_palette::CommandPalette;
+use editor::DisplayPoint;
+pub use neovim_backed_binding_test_context::*;
+pub use neovim_backed_test_context::*;
+pub use vim_test_context::*;
+
+use indoc::indoc;
+use search::BufferSearchBar;
+
+use crate::{state::Mode, ModeIndicator};
+
+#[gpui::test]
+async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, false).await;
+    cx.simulate_keystrokes(["h", "j", "k", "l"]);
+    cx.assert_editor_state("hjklˇ");
+}
+
+#[gpui::test]
+async fn test_neovim(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.simulate_shared_keystroke("i").await;
+    cx.assert_state_matches().await;
+    cx.simulate_shared_keystrokes([
+        "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
+    ])
+    .await;
+    cx.assert_state_matches().await;
+    cx.assert_editor_state("ˇtest");
+}
+
+#[gpui::test]
+async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.simulate_keystroke("i");
+    assert_eq!(cx.mode(), Mode::Insert);
+
+    // Editor acts as though vim is disabled
+    cx.disable_vim();
+    cx.simulate_keystrokes(["h", "j", "k", "l"]);
+    cx.assert_editor_state("hjklˇ");
+
+    // Selections aren't changed if editor is blurred but vim-mode is still disabled.
+    cx.set_state("«hjklˇ»", Mode::Normal);
+    cx.assert_editor_state("«hjklˇ»");
+    cx.update_editor(|_, cx| cx.blur());
+    cx.assert_editor_state("«hjklˇ»");
+    cx.update_editor(|_, cx| cx.focus_self());
+    cx.assert_editor_state("«hjklˇ»");
+
+    // Enabling dynamically sets vim mode again and restores normal mode
+    cx.enable_vim();
+    assert_eq!(cx.mode(), Mode::Normal);
+    cx.simulate_keystrokes(["h", "h", "h", "l"]);
+    assert_eq!(cx.buffer_text(), "hjkl".to_owned());
+    cx.assert_editor_state("hˇjkl");
+    cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
+    cx.assert_editor_state("hTestˇjkl");
+
+    // Disabling and enabling resets to normal mode
+    assert_eq!(cx.mode(), Mode::Insert);
+    cx.disable_vim();
+    cx.enable_vim();
+    assert_eq!(cx.mode(), Mode::Normal);
+}
+
+#[gpui::test]
+async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state(
+        indoc! {"
+            The quick brown
+            fox juˇmps over
+            the lazy dog"},
+        Mode::Normal,
+    );
+    cx.simulate_keystroke("/");
+
+    let search_bar = cx.workspace(|workspace, cx| {
+        workspace
+            .active_pane()
+            .read(cx)
+            .toolbar()
+            .read(cx)
+            .item_of_type::<BufferSearchBar>()
+            .expect("Buffer search bar should be deployed")
+    });
+
+    cx.update_view(search_bar, |bar, cx| {
+        assert_eq!(bar.query(cx), "");
+    })
+}
+
+#[gpui::test]
+async fn test_count_down(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
+    cx.simulate_keystrokes(["2", "down"]);
+    cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
+    cx.simulate_keystrokes(["9", "down"]);
+    cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
+}
+
+#[gpui::test]
+async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    // goes to end by default
+    cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
+    cx.simulate_keystrokes(["shift-g"]);
+    cx.assert_editor_state("aa\nbb\ncˇc");
+
+    // can go to line 1 (https://github.com/zed-industries/community/issues/710)
+    cx.simulate_keystrokes(["1", "shift-g"]);
+    cx.assert_editor_state("aˇa\nbb\ncc");
+}
+
+#[gpui::test]
+async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    // works in normal mode
+    cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
+    cx.simulate_keystrokes([">", ">"]);
+    cx.assert_editor_state("aa\n    bˇb\ncc");
+    cx.simulate_keystrokes(["<", "<"]);
+    cx.assert_editor_state("aa\nbˇb\ncc");
+
+    // works in visuial mode
+    cx.simulate_keystrokes(["shift-v", "down", ">"]);
+    cx.assert_editor_state("aa\n    b«b\n    ccˇ»");
+}
+
+#[gpui::test]
+async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state("aˇbc\n", Mode::Normal);
+    cx.simulate_keystrokes(["i", "cmd-shift-p"]);
+
+    assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
+    cx.simulate_keystroke("escape");
+    cx.run_until_parked();
+    assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
+    cx.assert_state("aˇbc\n", Mode::Insert);
+}
+
+#[gpui::test]
+async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state("aˇbˇc", Mode::Normal);
+    cx.simulate_keystrokes(["escape"]);
+
+    cx.assert_state("aˇbc", Mode::Normal);
+}
+
+#[gpui::test]
+async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
+    cx.simulate_keystrokes(["/", "c", "c"]);
+
+    let search_bar = cx.workspace(|workspace, cx| {
+        workspace
+            .active_pane()
+            .read(cx)
+            .toolbar()
+            .read(cx)
+            .item_of_type::<BufferSearchBar>()
+            .expect("Buffer search bar should be deployed")
+    });
+
+    cx.update_view(search_bar, |bar, cx| {
+        assert_eq!(bar.query(cx), "cc");
+    });
+
+    cx.update_editor(|editor, cx| {
+        let highlights = editor.all_text_background_highlights(cx);
+        assert_eq!(3, highlights.len());
+        assert_eq!(
+            DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
+            highlights[0].0
+        )
+    });
+    cx.simulate_keystrokes(["enter"]);
+
+    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
+    cx.simulate_keystrokes(["n"]);
+    cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
+    cx.simulate_keystrokes(["shift-n"]);
+    cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
+}
+
+#[gpui::test]
+async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    let mode_indicator = cx.workspace(|workspace, cx| {
+        let status_bar = workspace.status_bar().read(cx);
+        let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
+        assert!(mode_indicator.is_some());
+        mode_indicator.unwrap()
+    });
+
+    assert_eq!(
+        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
+        Some(Mode::Normal)
+    );
+
+    // shows the correct mode
+    cx.simulate_keystrokes(["i"]);
+    assert_eq!(
+        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
+        Some(Mode::Insert)
+    );
+
+    // shows even in search
+    cx.simulate_keystrokes(["escape", "v", "/"]);
+    assert_eq!(
+        cx.workspace(|_, cx| mode_indicator.read(cx).mode),
+        Some(Mode::Visual)
+    );
+
+    // hides if vim mode is disabled
+    cx.disable_vim();
+    cx.run_until_parked();
+    cx.workspace(|workspace, cx| {
+        let status_bar = workspace.status_bar().read(cx);
+        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
+        assert!(mode_indicator.read(cx).mode.is_none());
+    });
+
+    cx.enable_vim();
+    cx.run_until_parked();
+    cx.workspace(|workspace, cx| {
+        let status_bar = workspace.status_bar().read(cx);
+        let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
+        assert!(mode_indicator.read(cx).mode.is_some());
+    });
+}
+
+#[gpui::test]
+async fn test_word_characters(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new_typescript(cx).await;
+    cx.set_state(
+        indoc! { "
+        class A {
+            #ˇgoop = 99;
+            $ˇgoop () { return this.#gˇoop };
+        };
+        console.log(new A().$gooˇp())
+    "},
+        Mode::Normal,
+    );
+    cx.simulate_keystrokes(["v", "i", "w"]);
+    cx.assert_state(
+        indoc! {"
+        class A {
+            «#goopˇ» = 99;
+            «$goopˇ» () { return this.«#goopˇ» };
+        };
+        console.log(new A().«$goopˇ»())
+    "},
+        Mode::Visual,
+    )
+}
+
+#[gpui::test]
+async fn test_join_lines(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_state(indoc! {"
+      ˇone
+      two
+      three
+      four
+      five
+      six
+      "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-j"]).await;
+    cx.assert_shared_state(indoc! {"
+          oneˇ two
+          three
+          four
+          five
+          six
+          "})
+        .await;
+    cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
+    cx.assert_shared_state(indoc! {"
+          one two threeˇ four
+          five
+          six
+          "})
+        .await;
+
+    cx.set_shared_state(indoc! {"
+      ˇone
+      two
+      three
+      four
+      five
+      six
+      "})
+        .await;
+    cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
+        .await;
+    cx.assert_shared_state(indoc! {"
+      one
+      two three fourˇ five
+      six
+      "})
+        .await;
+}
+
+#[gpui::test]
+async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_wrap(12).await;
+    // tests line wrap as follows:
+    //  1: twelve char
+    //     twelve char
+    //  2: twelve char
+    cx.set_shared_state(indoc! { "
+        tˇwelve char twelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["j"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char twelve char
+        tˇwelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["k"]).await;
+    cx.assert_shared_state(indoc! { "
+        tˇwelve char twelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["g", "j"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char tˇwelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["g", "j"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char twelve char
+        tˇwelve char
+    "})
+        .await;
+
+    cx.simulate_shared_keystrokes(["g", "k"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char tˇwelve char
+        twelve char
+    "})
+        .await;
+
+    cx.simulate_shared_keystrokes(["g", "^"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char ˇtwelve char
+        twelve char
+    "})
+        .await;
+
+    cx.simulate_shared_keystrokes(["^"]).await;
+    cx.assert_shared_state(indoc! { "
+        ˇtwelve char twelve char
+        twelve char
+    "})
+        .await;
+
+    cx.simulate_shared_keystrokes(["g", "$"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve charˇ twelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["$"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char twelve chaˇr
+        twelve char
+    "})
+        .await;
+
+    cx.set_shared_state(indoc! { "
+        tˇwelve char twelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["enter"]).await;
+    cx.assert_shared_state(indoc! { "
+            twelve char twelve char
+            ˇtwelve char
+        "})
+        .await;
+
+    cx.set_shared_state(indoc! { "
+        twelve char
+        tˇwelve char twelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char
+        twelve char twelve char
+        ˇo
+        twelve char
+    "})
+        .await;
+
+    cx.set_shared_state(indoc! { "
+        twelve char
+        tˇwelve char twelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
+        .await;
+    cx.assert_shared_state(indoc! { "
+        twelve char
+        twelve char twelve charˇa
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
+        .await;
+    cx.assert_shared_state(indoc! { "
+        twelve char
+        ˇitwelve char twelve chara
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-d"]).await;
+    cx.assert_shared_state(indoc! { "
+        twelve char
+        ˇ
+        twelve char
+    "})
+        .await;
+
+    cx.set_shared_state(indoc! { "
+        twelve char
+        twelve char tˇwelve char
+        twelve char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
+        .await;
+    cx.assert_shared_state(indoc! { "
+        twelve char
+        ˇo
+        twelve char twelve char
+        twelve char
+    "})
+        .await;
+
+    // line wraps as:
+    // fourteen ch
+    // ar
+    // fourteen ch
+    // ar
+    cx.set_shared_state(indoc! { "
+        fourteen chaˇr
+        fourteen char
+    "})
+        .await;
+
+    cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
+    cx.assert_shared_state(indoc! {"
+        fourteenˇ•
+        fourteen char
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
+        .await;
+    cx.assert_shared_state(indoc! {"
+        fourteen•
+        fourteen chaˇr
+    "})
+        .await;
+}
+
+#[gpui::test]
+async fn test_folds(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+    cx.set_neovim_option("foldmethod=manual").await;
+
+    cx.set_shared_state(indoc! { "
+        fn boop() {
+          ˇbarp()
+          bazp()
+        }
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
+        .await;
+
+    // visual display is now:
+    // fn boop () {
+    //  [FOLDED]
+    // }
+
+    // TODO: this should not be needed but currently zf does not
+    // return to normal mode.
+    cx.simulate_shared_keystrokes(["escape"]).await;
+
+    // skip over fold downward
+    cx.simulate_shared_keystrokes(["g", "g"]).await;
+    cx.assert_shared_state(indoc! { "
+        ˇfn boop() {
+          barp()
+          bazp()
+        }
+    "})
+        .await;
+
+    cx.simulate_shared_keystrokes(["j", "j"]).await;
+    cx.assert_shared_state(indoc! { "
+        fn boop() {
+          barp()
+          bazp()
+        ˇ}
+    "})
+        .await;
+
+    // skip over fold upward
+    cx.simulate_shared_keystrokes(["2", "k"]).await;
+    cx.assert_shared_state(indoc! { "
+        ˇfn boop() {
+          barp()
+          bazp()
+        }
+    "})
+        .await;
+
+    // yank the fold
+    cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
+    cx.assert_shared_clipboard("  barp()\n  bazp()\n").await;
+
+    // re-open
+    cx.simulate_shared_keystrokes(["z", "o"]).await;
+    cx.assert_shared_state(indoc! { "
+        fn boop() {
+        ˇ  barp()
+          bazp()
+        }
+    "})
+        .await;
+}
+
+#[gpui::test]
+async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+    cx.set_neovim_option("foldmethod=manual").await;
+
+    cx.set_shared_state(indoc! { "
+        fn boop() {
+          ˇbarp()
+          bazp()
+        }
+    "})
+        .await;
+    cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
+        .await;
+    cx.simulate_shared_keystrokes(["escape"]).await;
+    cx.simulate_shared_keystrokes(["g", "g"]).await;
+    cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
+    cx.assert_shared_state(indoc! { "ˇ"}).await;
+}
+
+#[gpui::test]
+async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_state(indoc! {"
+        The quick brown
+        fox juˇmps over
+        the lazy dog"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
+        .await;
+    cx.assert_shared_state(indoc! {"
+        The quick brown
+        fox juˇ over
+        the lazy dog"})
+        .await;
+}
+
+#[gpui::test]
+async fn test_zero(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_state(indoc! {"
+        The quˇick brown
+        fox jumps over
+        the lazy dog"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["0"]).await;
+    cx.assert_shared_state(indoc! {"
+        ˇThe quick brown
+        fox jumps over
+        the lazy dog"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
+    cx.assert_shared_state(indoc! {"
+        The quick ˇbrown
+        fox jumps over
+        the lazy dog"})
+        .await;
+}
+
+#[gpui::test]
+async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_state(indoc! {"
+        ;;ˇ;
+        Lorem Ipsum"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
+        .await;
+    cx.assert_shared_state(indoc! {"
+        ;;;;ˇ
+        Lorem Ipsum"})
+        .await;
+}
+
+#[gpui::test]
+async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_wrap(12).await;
+
+    cx.set_shared_state(indoc! {"
+                aaˇaa
+                😃😃"
+    })
+    .await;
+    cx.simulate_shared_keystrokes(["j"]).await;
+    cx.assert_shared_state(indoc! {"
+                aaaa
+                😃ˇ😃"
+    })
+    .await;
+
+    cx.set_shared_state(indoc! {"
+                123456789012aaˇaa
+                123456789012😃😃"
+    })
+    .await;
+    cx.simulate_shared_keystrokes(["j"]).await;
+    cx.assert_shared_state(indoc! {"
+        123456789012aaaa
+        123456789012😃ˇ😃"
+    })
+    .await;
+
+    cx.set_shared_state(indoc! {"
+                123456789012aaˇaa
+                123456789012😃😃"
+    })
+    .await;
+    cx.simulate_shared_keystrokes(["j"]).await;
+    cx.assert_shared_state(indoc! {"
+        123456789012aaaa
+        123456789012😃ˇ😃"
+    })
+    .await;
+
+    cx.set_shared_state(indoc! {"
+        123456789012aaaaˇaaaaaaaa123456789012
+        wow
+        123456789012😃😃😃😃😃😃123456789012"
+    })
+    .await;
+    cx.simulate_shared_keystrokes(["j", "j"]).await;
+    cx.assert_shared_state(indoc! {"
+        123456789012aaaaaaaaaaaa123456789012
+        wow
+        123456789012😃😃ˇ😃😃😃😃123456789012"
+    })
+    .await;
+}
+
+#[gpui::test]
+async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
+    let mut cx = NeovimBackedTestContext::new(cx).await;
+
+    cx.set_shared_state(indoc! {"
+        one
+        ˇ
+        two"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["}", "}"]).await;
+    cx.assert_shared_state(indoc! {"
+        one
+
+        twˇo"})
+        .await;
+
+    cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
+    cx.assert_shared_state(indoc! {"
+        ˇone
+
+        two"})
+        .await;
+}
+
+#[gpui::test]
+async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
+    let mut cx = VimTestContext::new(cx, true).await;
+
+    cx.set_state(
+        indoc! {"
+        defmodule Test do
+            def test(a, ˇ[_, _] = b), do: IO.puts('hi')
+        end
+    "},
+        Mode::Normal,
+    );
+    cx.simulate_keystrokes(["g", "a"]);
+    cx.assert_state(
+        indoc! {"
+        defmodule Test do
+            def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
+        end
+    "},
+        Mode::Visual,
+    );
+}

crates/vim2/src/test/neovim_backed_test_context.rs 🔗

@@ -1,4 +1,7 @@
-use editor::scroll::VERTICAL_SCROLL_MARGIN;
+#![allow(unused)]
+// todo!()
+
+use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle};
 use indoc::indoc;
 use settings::SettingsStore;
 use std::{
@@ -7,7 +10,6 @@ use std::{
 };
 
 use collections::{HashMap, HashSet};
-use gpui::{geometry::vector::vec2f, ContextHandle};
 use language::language_settings::{AllLanguageSettings, SoftWrap};
 use util::test::marked_text_offsets;
 
@@ -151,19 +153,20 @@ impl<'a> NeovimBackedTestContext<'a> {
         })
     }
 
-    pub async fn set_scroll_height(&mut self, rows: u32) {
-        // match Zed's scrolling behavior
-        self.neovim
-            .set_option(&format!("scrolloff={}", VERTICAL_SCROLL_MARGIN))
-            .await;
-        // +2 to account for the vim command UI at the bottom.
-        self.neovim.set_option(&format!("lines={}", rows + 2)).await;
-        let window = self.window;
-        let line_height =
-            self.editor(|editor, cx| editor.style().text.line_height(cx.font_cache()));
-
-        window.simulate_resize(vec2f(1000., (rows as f32) * line_height), &mut self.cx);
-    }
+    // todo!()
+    // pub async fn set_scroll_height(&mut self, rows: u32) {
+    //     // match Zed's scrolling behavior
+    //     self.neovim
+    //         .set_option(&format!("scrolloff={}", VERTICAL_SCROLL_MARGIN))
+    //         .await;
+    //     // +2 to account for the vim command UI at the bottom.
+    //     self.neovim.set_option(&format!("lines={}", rows + 2)).await;
+    //     let window = self.window;
+    //     let line_height =
+    //         self.editor(|editor, cx| editor.style().text.line_height(cx.font_cache()));
+
+    //     window.simulate_resize(vec2f(1000., (rows as f32) * line_height), &mut self.cx);
+    // }
 
     pub async fn set_neovim_option(&mut self, option: &str) {
         self.neovim.set_option(option).await;
@@ -211,12 +214,7 @@ impl<'a> NeovimBackedTestContext<'a> {
 
     pub async fn assert_shared_clipboard(&mut self, text: &str) {
         let neovim = self.neovim.read_register('"').await;
-        let editor = self
-            .platform()
-            .read_from_clipboard()
-            .unwrap()
-            .text()
-            .clone();
+        let editor = self.read_from_clipboard().unwrap().text().clone();
 
         if text == neovim && text == editor {
             return;

crates/vim2/src/test/neovim_connection.rs 🔗

@@ -10,7 +10,7 @@ use async_compat::Compat;
 #[cfg(feature = "neovim")]
 use async_trait::async_trait;
 #[cfg(feature = "neovim")]
-use gpui::keymap_matcher::Keystroke;
+use gpui::Keystroke;
 
 #[cfg(feature = "neovim")]
 use language::Point;
@@ -116,16 +116,24 @@ impl NeovimConnection {
             keystroke.key = "lt".to_string()
         }
 
-        let special = keystroke.shift
-            || keystroke.ctrl
-            || keystroke.alt
-            || keystroke.cmd
+        let special = keystroke.modifiers.shift
+            || keystroke.modifiers.control
+            || keystroke.modifiers.alt
+            || keystroke.modifiers.command
             || keystroke.key.len() > 1;
         let start = if special { "<" } else { "" };
-        let shift = if keystroke.shift { "S-" } else { "" };
-        let ctrl = if keystroke.ctrl { "C-" } else { "" };
-        let alt = if keystroke.alt { "M-" } else { "" };
-        let cmd = if keystroke.cmd { "D-" } else { "" };
+        let shift = if keystroke.modifiers.shift { "S-" } else { "" };
+        let ctrl = if keystroke.modifiers.control {
+            "C-"
+        } else {
+            ""
+        };
+        let alt = if keystroke.modifiers.alt { "M-" } else { "" };
+        let cmd = if keystroke.modifiers.command {
+            "D-"
+        } else {
+            ""
+        };
         let end = if special { ">" } else { "" };
 
         let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);

crates/vim2/src/test/vim_test_context.rs 🔗

@@ -1,3 +1,6 @@
+#![allow(unused)]
+// todo!()
+
 use std::ops::{Deref, DerefMut};
 
 use editor::test::{
@@ -16,11 +19,25 @@ pub struct VimTestContext<'a> {
 
 impl<'a> VimTestContext<'a> {
     pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
+        cx.update(|cx| {
+            search::init(cx);
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
+            command_palette::init(cx);
+            crate::init(cx);
+        });
         let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
         Self::new_with_lsp(lsp, enabled)
     }
 
     pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
+        cx.update(|cx| {
+            search::init(cx);
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
+            command_palette::init(cx);
+            crate::init(cx);
+        });
         Self::new_with_lsp(
             EditorLspTestContext::new_typescript(Default::default(), cx).await,
             true,
@@ -28,12 +45,6 @@ impl<'a> VimTestContext<'a> {
     }
 
     pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
-        cx.update(|cx| {
-            search::init(cx);
-            crate::init(cx);
-            command_palette::init(cx);
-        });
-
         cx.update(|cx| {
             cx.update_global(|store: &mut SettingsStore, cx| {
                 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
@@ -65,9 +76,11 @@ impl<'a> VimTestContext<'a> {
 
     pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
     where
-        F: FnOnce(&mut T, &mut ViewContext<T>) -> R,
+        T: 'static,
+        F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
     {
-        self.update_window(self.window, |_, cx| view.update(cx, update))
+        let window = self.window.clone();
+        self.update_window(window, move |_, cx| view.update(cx, update))
             .unwrap()
     }
 
@@ -75,8 +88,7 @@ impl<'a> VimTestContext<'a> {
     where
         F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
     {
-        self.update_window(self.window, |_, cx| self.cx.workspace.update(cx, update))
-            .unwrap()
+        self.cx.update_workspace(update)
     }
 
     pub fn enable_vim(&mut self) {
@@ -111,7 +123,8 @@ impl<'a> VimTestContext<'a> {
             Vim::update(cx, |vim, cx| {
                 vim.switch_mode(mode, true, cx);
             })
-        });
+        })
+        .unwrap();
         self.cx.cx.cx.run_until_parked();
     }
 

crates/vim2/src/vim.rs 🔗

@@ -116,45 +116,43 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
     visual::register(workspace, cx);
 }
 
-pub fn observe_keystrokes(_: &mut WindowContext) {
-    // todo!()
-
-    // cx.observe_keystrokes(|_keystroke, result, handled_by, cx| {
-    //     if result == &MatchResult::Pending {
-    //         return true;
-    //     }
-    //     if let Some(handled_by) = handled_by {
-    //         Vim::update(cx, |vim, _| {
-    //             if vim.workspace_state.recording {
-    //                 vim.workspace_state
-    //                     .recorded_actions
-    //                     .push(ReplayableAction::Action(handled_by.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;
-    //                 }
-    //             }
-    //         });
-
-    //         // Keystroke is handled by the vim system, so continue forward
-    //         if handled_by.namespace() == "vim" {
-    //             return true;
-    //         }
-    //     }
-
-    //     Vim::update(cx, |vim, cx| match vim.active_operator() {
-    //         Some(
-    //             Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace,
-    //         ) => {}
-    //         Some(_) => {
-    //             vim.clear_operator(cx);
-    //         }
-    //         _ => {}
-    //     });
-    //     true
-    // })
-    // .detach()
+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()));
+
+                    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;
+            }
+        }
+
+        Vim::update(cx, |vim, cx| match vim.active_operator() {
+            Some(
+                Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace,
+            ) => {}
+            Some(_) => {
+                vim.clear_operator(cx);
+            }
+            _ => {}
+        });
+    })
+    .detach()
 }
 
 #[derive(Default)]