vim-flight entertainment (#3574)

Conrad Irwin created

- vim2 compiling (but mostly commented out)
- More code written, similar lack of workingness so far

Still todo:
[ ] Figure out the focus/blur stuff
[ ] Uncoment more code
[ ] Fix VimTestContext
[ ] Uncomment the tests

Release Notes:

- N/A

Change summary

Cargo.lock                                                               |   35 
crates/command_palette2/src/command_palette.rs                           |    1 
crates/editor2/src/editor.rs                                             |   15 
crates/editor2/src/element.rs                                            |    7 
crates/settings2/src/settings_file.rs                                    |   11 
crates/vim/src/editor_events.rs                                          |   47 
crates/vim/src/vim.rs                                                    |    2 
crates/vim2/Cargo.toml                                                   |   53 
crates/vim2/src/command.rs                                               |  438 
crates/vim2/src/editor_events.rs                                         |   99 
crates/vim2/src/insert.rs                                                |  135 
crates/vim2/src/mode_indicator.rs                                        |   78 
crates/vim2/src/motion.rs                                                | 1098 
crates/vim2/src/normal.rs                                                |  907 
crates/vim2/src/normal/case.rs                                           |  116 
crates/vim2/src/normal/change.rs                                         |  502 
crates/vim2/src/normal/delete.rs                                         |  475 
crates/vim2/src/normal/increment.rs                                      |  278 
crates/vim2/src/normal/paste.rs                                          |  476 
crates/vim2/src/normal/repeat.rs                                         |  523 
crates/vim2/src/normal/scroll.rs                                         |  229 
crates/vim2/src/normal/search.rs                                         |  495 
crates/vim2/src/normal/substitute.rs                                     |  276 
crates/vim2/src/normal/yank.rs                                           |   50 
crates/vim2/src/object.rs                                                | 1025 
crates/vim2/src/state.rs                                                 |  234 
crates/vim2/src/test.rs                                                  |  759 
crates/vim2/src/test/neovim_backed_binding_test_context.rs               |   95 
crates/vim2/src/test/neovim_backed_test_context.rs                       |  427 
crates/vim2/src/test/neovim_connection.rs                                |  591 
crates/vim2/src/test/vim_test_context.rs                                 |  165 
crates/vim2/src/utils.rs                                                 |   50 
crates/vim2/src/vim.rs                                                   |  607 
crates/vim2/src/visual.rs                                                | 1029 
crates/vim2/test_data/neovim_backed_test_context_works.json              |    3 
crates/vim2/test_data/test_a.json                                        |    6 
crates/vim2/test_data/test_b.json                                        |   54 
crates/vim2/test_data/test_backspace.json                                |    9 
crates/vim2/test_data/test_capital_f_and_capital_t.json                  |  570 
crates/vim2/test_data/test_cc.json                                       |   24 
crates/vim2/test_data/test_change_0.json                                 |    8 
crates/vim2/test_data/test_change_b.json                                 |   24 
crates/vim2/test_data/test_change_backspace.json                         |   16 
crates/vim2/test_data/test_change_case.json                              |   23 
crates/vim2/test_data/test_change_e.json                                 |   24 
crates/vim2/test_data/test_change_end_of_document.json                   |   16 
crates/vim2/test_data/test_change_end_of_line.json                       |    8 
crates/vim2/test_data/test_change_gg.json                                |   20 
crates/vim2/test_data/test_change_h.json                                 |   16 
crates/vim2/test_data/test_change_j.json                                 |   16 
crates/vim2/test_data/test_change_k.json                                 |   16 
crates/vim2/test_data/test_change_l.json                                 |    8 
crates/vim2/test_data/test_change_sentence_object.json                   |  270 
crates/vim2/test_data/test_change_surrounding_character_objects.json     | 2380 
crates/vim2/test_data/test_change_w.json                                 |   28 
crates/vim2/test_data/test_change_word_object.json                       |  460 
crates/vim2/test_data/test_clear_counts.json                             |    7 
crates/vim2/test_data/test_comma_semicolon.json                          |   17 
crates/vim2/test_data/test_command_basics.json                           |    6 
crates/vim2/test_data/test_command_goto.json                             |    5 
crates/vim2/test_data/test_command_replace.json                          |   22 
crates/vim2/test_data/test_command_search.json                           |   11 
crates/vim2/test_data/test_ctrl_d_u.json                                 |   22 
crates/vim2/test_data/test_dd.json                                       |   24 
crates/vim2/test_data/test_delete_0.json                                 |    8 
crates/vim2/test_data/test_delete_b.json                                 |   24 
crates/vim2/test_data/test_delete_end_of_document.json                   |   16 
crates/vim2/test_data/test_delete_end_of_line.json                       |    8 
crates/vim2/test_data/test_delete_gg.json                                |   20 
crates/vim2/test_data/test_delete_h.json                                 |   16 
crates/vim2/test_data/test_delete_j.json                                 |   16 
crates/vim2/test_data/test_delete_k.json                                 |   16 
crates/vim2/test_data/test_delete_l.json                                 |   16 
crates/vim2/test_data/test_delete_left.json                              |   15 
crates/vim2/test_data/test_delete_next_word_end.json                     |   12 
crates/vim2/test_data/test_delete_sentence_object.json                   |  270 
crates/vim2/test_data/test_delete_surrounding_character_objects.json     | 2372 
crates/vim2/test_data/test_delete_to_end_of_line.json                    |    6 
crates/vim2/test_data/test_delete_w.json                                 |   28 
crates/vim2/test_data/test_delete_with_counts.json                       |   16 
crates/vim2/test_data/test_delete_word_object.json                       |  460 
crates/vim2/test_data/test_dot_repeat.json                               |   38 
crates/vim2/test_data/test_end_of_document.json                          |   15 
crates/vim2/test_data/test_end_of_word.json                              |   32 
crates/vim2/test_data/test_enter.json                                    |   11 
crates/vim2/test_data/test_enter_visual_line_mode.json                   |   15 
crates/vim2/test_data/test_enter_visual_mode.json                        |   20 
crates/vim2/test_data/test_f_and_t.json                                  |  557 
crates/vim2/test_data/test_folds.json                                    |   23 
crates/vim2/test_data/test_folds_panic.json                              |   13 
crates/vim2/test_data/test_gg.json                                       |   21 
crates/vim2/test_data/test_h.json                                        |    9 
crates/vim2/test_data/test_h_through_unicode.json                        |   12 
crates/vim2/test_data/test_increment.json                                |   16 
crates/vim2/test_data/test_increment_radix.json                          |   18 
crates/vim2/test_data/test_increment_steps.json                          |   15 
crates/vim2/test_data/test_insert_end_of_line.json                       |    9 
crates/vim2/test_data/test_insert_first_non_whitespace.json              |   15 
crates/vim2/test_data/test_insert_line_above.json                        |   18 
crates/vim2/test_data/test_insert_with_counts.json                       |   36 
crates/vim2/test_data/test_insert_with_repeat.json                       |   23 
crates/vim2/test_data/test_j.json                                        |   15 
crates/vim2/test_data/test_join_lines.json                               |   13 
crates/vim2/test_data/test_jump_to_end.json                              |   14 
crates/vim2/test_data/test_jump_to_first_non_whitespace.json             |   18 
crates/vim2/test_data/test_jump_to_line_boundaries.json                  |   28 
crates/vim2/test_data/test_k.json                                        |   15 
crates/vim2/test_data/test_l.json                                        |   15 
crates/vim2/test_data/test_matching.json                                 |   17 
crates/vim2/test_data/test_multiline_surrounding_character_objects.json  |   15 
crates/vim2/test_data/test_neovim.json                                   |   16 
crates/vim2/test_data/test_next_line_start.json                          |    3 
crates/vim2/test_data/test_o.json                                        |   18 
crates/vim2/test_data/test_paragraphs_dont_wrap.json                     |    8 
crates/vim2/test_data/test_paste.json                                    |   31 
crates/vim2/test_data/test_paste_visual.json                             |   42 
crates/vim2/test_data/test_paste_visual_block.json                       |   31 
crates/vim2/test_data/test_percent.json                                  |   58 
crates/vim2/test_data/test_repeat_motion_counts.json                     |   13 
crates/vim2/test_data/test_repeat_visual.json                            |   51 
crates/vim2/test_data/test_repeated_cb.json                              |  275 
crates/vim2/test_data/test_repeated_ce.json                              |  275 
crates/vim2/test_data/test_repeated_cj.json                              |  275 
crates/vim2/test_data/test_repeated_cl.json                              |  275 
crates/vim2/test_data/test_repeated_word.json                            |  214 
crates/vim2/test_data/test_selection_goal.json                           |    8 
crates/vim2/test_data/test_singleline_surrounding_character_objects.json |   27 
crates/vim2/test_data/test_start_end_of_paragraph.json                   |   13 
crates/vim2/test_data/test_substitute_line.json                          |   29 
crates/vim2/test_data/test_visual_block_insert.json                      |   18 
crates/vim2/test_data/test_visual_block_issue_2123.json                  |    5 
crates/vim2/test_data/test_visual_block_mode.json                        |   38 
crates/vim2/test_data/test_visual_change.json                            |   47 
crates/vim2/test_data/test_visual_delete.json                            |   48 
crates/vim2/test_data/test_visual_line_change.json                       |   35 
crates/vim2/test_data/test_visual_line_delete.json                       |   23 
crates/vim2/test_data/test_visual_object.json                            |   19 
crates/vim2/test_data/test_visual_sentence_object.json                   |    0 
crates/vim2/test_data/test_visual_word_object.json                       |  236 
crates/vim2/test_data/test_visual_yank.json                              |   35 
crates/vim2/test_data/test_w.json                                        |   40 
crates/vim2/test_data/test_wrapped_lines.json                            |   61 
crates/vim2/test_data/test_wrapped_motions.json                          |   15 
crates/vim2/test_data/test_x.json                                        |   12 
crates/vim2/test_data/test_zero.json                                     |    7 
crates/zed2/Cargo.toml                                                   |    2 
crates/zed2/src/app_menus.rs                                             |    4 
crates/zed2/src/main.rs                                                  |    2 
crates/zed2/src/zed2.rs                                                  |    9 
crates/zed_actions2/src/lib.rs                                           |    4 
150 files changed, 22,093 insertions(+), 55 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -10886,6 +10886,40 @@ dependencies = [
  "zed-actions",
 ]
 
+[[package]]
+name = "vim2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-compat",
+ "async-trait",
+ "collections",
+ "command_palette2",
+ "diagnostics2",
+ "editor2",
+ "futures 0.3.28",
+ "gpui2",
+ "indoc",
+ "itertools 0.10.5",
+ "language2",
+ "log",
+ "lsp2",
+ "nvim-rs",
+ "parking_lot 0.11.2",
+ "project2",
+ "search2",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "settings2",
+ "theme2",
+ "tokio",
+ "ui2",
+ "util",
+ "workspace2",
+ "zed_actions2",
+]
+
 [[package]]
 name = "vte"
 version = "0.11.1"
@@ -12105,6 +12139,7 @@ dependencies = [
  "urlencoding",
  "util",
  "uuid 1.4.1",
+ "vim2",
  "welcome2",
  "workspace2",
  "zed_actions2",

crates/command_palette2/src/command_palette.rs 🔗

@@ -23,6 +23,7 @@ actions!(command_palette, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
     cx.set_global(HitCounts::default());
+    cx.set_global(CommandPaletteFilter::default());
     cx.observe_new_views(CommandPalette::register).detach();
 }
 

crates/editor2/src/editor.rs 🔗

@@ -1923,9 +1923,9 @@ impl Editor {
         self.buffer.read(cx).replica_id()
     }
 
-    //     pub fn leader_peer_id(&self) -> Option<PeerId> {
-    //         self.leader_peer_id
-    //     }
+    pub fn leader_peer_id(&self) -> Option<PeerId> {
+        self.leader_peer_id
+    }
 
     pub fn buffer(&self) -> &Model<MultiBuffer> {
         &self.buffer
@@ -9096,10 +9096,7 @@ impl Editor {
         }
     }
 
-    fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
-        // todo!()
-        // let blurred_event = EditorBlurred(cx.handle());
-        // cx.emit_global(blurred_event);
+    pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
         self.blink_manager.update(cx, BlinkManager::disable);
         self.buffer
             .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
@@ -9283,10 +9280,6 @@ pub enum EditorEvent {
     Closed,
 }
 
-pub struct EditorFocused(pub View<Editor>);
-pub struct EditorBlurred(pub View<Editor>);
-pub struct EditorReleased(pub WeakView<Editor>);
-
 impl EventEmitter<EditorEvent> for Editor {}
 
 impl FocusableView for Editor {

crates/editor2/src/element.rs 🔗

@@ -1049,7 +1049,12 @@ impl EditorElement {
                                         .chars_at(cursor_position)
                                         .next()
                                         .and_then(|(character, _)| {
-                                            let text = SharedString::from(character.to_string());
+                                            // todo!() currently shape_line panics if text conatins newlines
+                                            let text = if character == '\n' {
+                                                SharedString::from(" ")
+                                            } else {
+                                                SharedString::from(character.to_string())
+                                            };
                                             let len = text.len();
                                             cx.text_system()
                                                 .shape_line(

crates/settings2/src/settings_file.rs 🔗

@@ -124,17 +124,6 @@ pub fn update_settings_file<T: Settings>(
 
 pub fn load_default_keymap(cx: &mut AppContext) {
     for path in ["keymaps/default.json", "keymaps/vim.json"] {
-        // TODO: Remove this conditional when we're ready to add Vim support.
-        // Right now we're avoiding loading the Vim keymap to silence the warnings
-        // about invalid action bindings.
-        if path.contains("vim") {
-            let _: Option<()> = Err(format!(
-                "TODO: Skipping {path} until we're ready to add Vim support"
-            ))
-            .log_err();
-            continue;
-        }
-
         KeymapFile::load_asset(path, cx).unwrap();
     }
 

crates/vim/src/editor_events.rs 🔗

@@ -63,30 +63,31 @@ fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
     });
 }
 
-#[cfg(test)]
-mod test {
-    use crate::{test::VimTestContext, Vim};
-    use editor::Editor;
-    use gpui::View;
-    use language::Buffer;
+// #[cfg(test)]
+// mod test {
+//     use crate::{test::VimTestContext, Vim};
+//     use editor::Editor;
+//     use gpui::View;
+//     use language::Buffer;
 
-    // regression test for blur called with a different active editor
-    #[gpui::test]
-    async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
-        let mut cx = VimTestContext::new(cx, true).await;
+//     // regression test for blur called with a different active editor
+//     #[gpui::test]
+//     async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
 
-        let buffer = cx.add_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
-        let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
-        let editor2 = cx.read(|cx| window2.root(cx)).unwrap();
+//         let buffer = cx.add_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
+//         let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
+//         let editor2 = cx.read(|cx| window2.root(cx)).unwrap();
 
-        cx.update(|cx| {
-            let vim = Vim::read(cx);
-            assert_eq!(vim.active_editor.unwrap().id(), editor2.id())
-        });
+//         cx.update(|cx| {
+//             let vim = Vim::read(cx);
+//             assert_eq!(vim.active_editor.unwrap().id(), editor2.id())
+//         });
 
-        // no panic when blurring an editor in a different window.
-        cx.update_editor(|editor1, cx| {
-            editor1.focus_out(cx.handle().into_any(), cx);
-        });
-    }
-}
+//         // no panic when blurring an editor in a different window.
+//         cx.update_editor(|editor1, cx| {
+//             todo!()
+//             // editor1.focus_out(cx.handle().into_any(), cx);
+//         });
+//     }
+// }

crates/vim2/Cargo.toml 🔗

@@ -0,0 +1,53 @@
+[package]
+name = "vim2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/vim.rs"
+doctest = false
+
+[features]
+neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
+
+[dependencies]
+anyhow.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+itertools = "0.10"
+log.workspace = true
+
+async-compat = { version = "0.2.1", "optional" = true }
+async-trait = { workspace = true, "optional" = true }
+nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
+tokio = { version = "1.15", "optional" = true }
+serde_json.workspace = true
+
+collections = { path = "../collections" }
+command_palette = { package = "command_palette2", path = "../command_palette2" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+language = { package = "language2", path = "../language2" }
+search = { package = "search2", path = "../search2" }
+settings = { package = "settings2", path = "../settings2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2"}
+diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
+zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
+
+[dev-dependencies]
+indoc.workspace = true
+parking_lot.workspace = true
+futures.workspace = true
+
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+language = { package = "language2", path = "../language2", features = ["test-support"] }
+project = { package = "project2", path = "../project2", features = ["test-support"] }
+util = { path = "../util", features = ["test-support"] }
+settings = { package = "settings2", path = "../settings2" }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
+lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }

crates/vim2/src/command.rs 🔗

@@ -0,0 +1,438 @@
+use command_palette::CommandInterceptResult;
+use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
+use gpui::{impl_actions, Action, AppContext, ViewContext};
+use serde_derive::Deserialize;
+use workspace::{SaveIntent, Workspace};
+
+use crate::{
+    motion::{EndOfDocument, Motion},
+    normal::{
+        move_cursor,
+        search::{FindCommand, ReplaceCommand},
+        JoinLines,
+    },
+    state::Mode,
+    Vim,
+};
+
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+pub struct GoToLine {
+    pub line: u32,
+}
+
+impl_actions!(vim, [GoToLine]);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, action: &GoToLine, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.switch_mode(Mode::Normal, false, cx);
+            move_cursor(vim, Motion::StartOfDocument, Some(action.line as usize), cx);
+        });
+    });
+}
+
+pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option<CommandInterceptResult> {
+    // Note: this is a very poor simulation of vim's command palette.
+    // In the future we should adjust it to handle parsing range syntax,
+    // and then calling the appropriate commands with/without ranges.
+    //
+    // We also need to support passing arguments to commands like :w
+    // (ideally with filename autocompletion).
+    //
+    // For now, you can only do a replace on the % range, and you can
+    // only use a specific line number range to "go to line"
+    while query.starts_with(":") {
+        query = &query[1..];
+    }
+
+    let (name, action) = match query {
+        // save and quit
+        "w" | "wr" | "wri" | "writ" | "write" => (
+            "write",
+            workspace::Save {
+                save_intent: Some(SaveIntent::Save),
+            }
+            .boxed_clone(),
+        ),
+        "w!" | "wr!" | "wri!" | "writ!" | "write!" => (
+            "write!",
+            workspace::Save {
+                save_intent: Some(SaveIntent::Overwrite),
+            }
+            .boxed_clone(),
+        ),
+        "q" | "qu" | "qui" | "quit" => (
+            "quit",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::Close),
+            }
+            .boxed_clone(),
+        ),
+        "q!" | "qu!" | "qui!" | "quit!" => (
+            "quit!",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::Skip),
+            }
+            .boxed_clone(),
+        ),
+        "wq" => (
+            "wq",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::Save),
+            }
+            .boxed_clone(),
+        ),
+        "wq!" => (
+            "wq!",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::Overwrite),
+            }
+            .boxed_clone(),
+        ),
+        "x" | "xi" | "xit" | "exi" | "exit" => (
+            "exit",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::SaveAll),
+            }
+            .boxed_clone(),
+        ),
+        "x!" | "xi!" | "xit!" | "exi!" | "exit!" => (
+            "exit!",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::Overwrite),
+            }
+            .boxed_clone(),
+        ),
+        "up" | "upd" | "upda" | "updat" | "update" => (
+            "update",
+            workspace::Save {
+                save_intent: Some(SaveIntent::SaveAll),
+            }
+            .boxed_clone(),
+        ),
+        "wa" | "wal" | "wall" => (
+            "wall",
+            workspace::SaveAll {
+                save_intent: Some(SaveIntent::SaveAll),
+            }
+            .boxed_clone(),
+        ),
+        "wa!" | "wal!" | "wall!" => (
+            "wall!",
+            workspace::SaveAll {
+                save_intent: Some(SaveIntent::Overwrite),
+            }
+            .boxed_clone(),
+        ),
+        "qa" | "qal" | "qall" | "quita" | "quital" | "quitall" => (
+            "quitall",
+            workspace::CloseAllItemsAndPanes {
+                save_intent: Some(SaveIntent::Close),
+            }
+            .boxed_clone(),
+        ),
+        "qa!" | "qal!" | "qall!" | "quita!" | "quital!" | "quitall!" => (
+            "quitall!",
+            workspace::CloseAllItemsAndPanes {
+                save_intent: Some(SaveIntent::Skip),
+            }
+            .boxed_clone(),
+        ),
+        "xa" | "xal" | "xall" => (
+            "xall",
+            workspace::CloseAllItemsAndPanes {
+                save_intent: Some(SaveIntent::SaveAll),
+            }
+            .boxed_clone(),
+        ),
+        "xa!" | "xal!" | "xall!" => (
+            "xall!",
+            workspace::CloseAllItemsAndPanes {
+                save_intent: Some(SaveIntent::Overwrite),
+            }
+            .boxed_clone(),
+        ),
+        "wqa" | "wqal" | "wqall" => (
+            "wqall",
+            workspace::CloseAllItemsAndPanes {
+                save_intent: Some(SaveIntent::SaveAll),
+            }
+            .boxed_clone(),
+        ),
+        "wqa!" | "wqal!" | "wqall!" => (
+            "wqall!",
+            workspace::CloseAllItemsAndPanes {
+                save_intent: Some(SaveIntent::Overwrite),
+            }
+            .boxed_clone(),
+        ),
+        "cq" | "cqu" | "cqui" | "cquit" | "cq!" | "cqu!" | "cqui!" | "cquit!" => {
+            ("cquit!", zed_actions::Quit.boxed_clone())
+        }
+
+        // pane management
+        "sp" | "spl" | "spli" | "split" => ("split", workspace::SplitUp.boxed_clone()),
+        "vs" | "vsp" | "vspl" | "vspli" | "vsplit" => {
+            ("vsplit", workspace::SplitLeft.boxed_clone())
+        }
+        "new" => (
+            "new",
+            workspace::NewFileInDirection(workspace::SplitDirection::Up).boxed_clone(),
+        ),
+        "vne" | "vnew" => (
+            "vnew",
+            workspace::NewFileInDirection(workspace::SplitDirection::Left).boxed_clone(),
+        ),
+        "tabe" | "tabed" | "tabedi" | "tabedit" => ("tabedit", workspace::NewFile.boxed_clone()),
+        "tabnew" => ("tabnew", workspace::NewFile.boxed_clone()),
+
+        "tabn" | "tabne" | "tabnex" | "tabnext" => {
+            ("tabnext", workspace::ActivateNextItem.boxed_clone())
+        }
+        "tabp" | "tabpr" | "tabpre" | "tabprev" | "tabprevi" | "tabprevio" | "tabpreviou"
+        | "tabprevious" => ("tabprevious", workspace::ActivatePrevItem.boxed_clone()),
+        "tabN" | "tabNe" | "tabNex" | "tabNext" => {
+            ("tabNext", workspace::ActivatePrevItem.boxed_clone())
+        }
+        "tabc" | "tabcl" | "tabclo" | "tabclos" | "tabclose" => (
+            "tabclose",
+            workspace::CloseActiveItem {
+                save_intent: Some(SaveIntent::Close),
+            }
+            .boxed_clone(),
+        ),
+
+        // quickfix / loclist (merged together for now)
+        "cl" | "cli" | "clis" | "clist" => ("clist", diagnostics::Deploy.boxed_clone()),
+        "cc" => ("cc", editor::Hover.boxed_clone()),
+        "ll" => ("ll", editor::Hover.boxed_clone()),
+        "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
+        "lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
+
+        "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => {
+            ("cprevious", editor::GoToPrevDiagnostic.boxed_clone())
+        }
+        "cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()),
+        "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => {
+            ("lprevious", editor::GoToPrevDiagnostic.boxed_clone())
+        }
+        "lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()),
+
+        // modify the buffer (should accept [range])
+        "j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()),
+        "d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl"
+        | "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => {
+            ("delete", editor::DeleteLine.boxed_clone())
+        }
+        "sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()),
+        "sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()),
+
+        // goto (other ranges handled under _ => )
+        "$" => ("$", EndOfDocument.boxed_clone()),
+
+        _ => {
+            if query.starts_with("/") || query.starts_with("?") {
+                (
+                    query,
+                    FindCommand {
+                        query: query[1..].to_string(),
+                        backwards: query.starts_with("?"),
+                    }
+                    .boxed_clone(),
+                )
+            } else if query.starts_with("%") {
+                (
+                    query,
+                    ReplaceCommand {
+                        query: query.to_string(),
+                    }
+                    .boxed_clone(),
+                )
+            } else if let Ok(line) = query.parse::<u32>() {
+                (query, GoToLine { line }.boxed_clone())
+            } else {
+                return None;
+            }
+        }
+    };
+
+    let string = ":".to_owned() + name;
+    let positions = generate_positions(&string, query);
+
+    Some(CommandInterceptResult {
+        action,
+        string,
+        positions,
+    })
+}
+
+fn generate_positions(string: &str, query: &str) -> Vec<usize> {
+    let mut positions = Vec::new();
+    let mut chars = query.chars().into_iter();
+
+    let Some(mut current) = chars.next() else {
+        return positions;
+    };
+
+    for (i, c) in string.chars().enumerate() {
+        if c == current {
+            positions.push(i);
+            if let Some(c) = chars.next() {
+                current = c;
+            } else {
+                break;
+            }
+        }
+    }
+
+    positions
+}
+
+// #[cfg(test)]
+// mod test {
+//     use std::path::Path;
+
+//     use crate::test::{NeovimBackedTestContext, VimTestContext};
+//     use gpui::TestAppContext;
+//     use indoc::indoc;
+
+//     #[gpui::test]
+//     async fn test_command_basics(cx: &mut TestAppContext) {
+//         if let Foreground::Deterministic { cx_id: _, executor } = cx.foreground().as_ref() {
+//             executor.run_until_parked();
+//         }
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {"
+//             ˇa
+//             b
+//             c"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes([":", "j", "enter"]).await;
+
+//         // hack: our cursor positionining after a join command is wrong
+//         cx.simulate_shared_keystrokes(["^"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇa b
+//             c"
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_command_goto(cx: &mut TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {"
+//             ˇa
+//             b
+//             c"})
+//             .await;
+//         cx.simulate_shared_keystrokes([":", "3", "enter"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             b
+//             ˇc"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_command_replace(cx: &mut TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {"
+//             ˇa
+//             b
+//             c"})
+//             .await;
+//         cx.simulate_shared_keystrokes([":", "%", "s", "/", "b", "/", "d", "enter"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             ˇd
+//             c"})
+//             .await;
+//         cx.simulate_shared_keystrokes([
+//             ":", "%", "s", ":", ".", ":", "\\", "0", "\\", "0", "enter",
+//         ])
+//         .await;
+//         cx.assert_shared_state(indoc! {"
+//             aa
+//             dd
+//             ˇcc"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_command_search(cx: &mut TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {"
+//                 ˇa
+//                 b
+//                 a
+//                 c"})
+//             .await;
+//         cx.simulate_shared_keystrokes([":", "/", "b", "enter"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//                 a
+//                 ˇb
+//                 a
+//                 c"})
+//             .await;
+//         cx.simulate_shared_keystrokes([":", "?", "a", "enter"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//                 ˇa
+//                 b
+//                 a
+//                 c"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_command_write(cx: &mut TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+//         let path = Path::new("/root/dir/file.rs");
+//         let fs = cx.workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
+
+//         cx.simulate_keystrokes(["i", "@", "escape"]);
+//         cx.simulate_keystrokes([":", "w", "enter"]);
+
+//         assert_eq!(fs.load(&path).await.unwrap(), "@\n");
+
+//         fs.as_fake()
+//             .write_file_internal(path, "oops\n".to_string())
+//             .unwrap();
+
+//         // conflict!
+//         cx.simulate_keystrokes(["i", "@", "escape"]);
+//         cx.simulate_keystrokes([":", "w", "enter"]);
+//         let window = cx.window;
+//         assert!(window.has_pending_prompt(cx.cx));
+//         // "Cancel"
+//         window.simulate_prompt_answer(0, cx.cx);
+//         assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
+//         assert!(!window.has_pending_prompt(cx.cx));
+//         // force overwrite
+//         cx.simulate_keystrokes([":", "w", "!", "enter"]);
+//         assert!(!window.has_pending_prompt(cx.cx));
+//         assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
+//     }
+
+//     #[gpui::test]
+//     async fn test_command_quit(cx: &mut TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
+//         cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
+//         cx.simulate_keystrokes([":", "q", "enter"]);
+//         cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
+//         cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
+//         cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
+//         cx.simulate_keystrokes([":", "q", "a", "enter"]);
+//         cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 0));
+//     }
+// }

crates/vim2/src/editor_events.rs 🔗

@@ -0,0 +1,99 @@
+use crate::Vim;
+use editor::{Editor, EditorEvent};
+use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
+
+pub fn init(cx: &mut AppContext) {
+    cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
+        let editor = cx.view().clone();
+        cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
+            EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
+            EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
+            _ => {}
+        })
+        .detach();
+
+        let id = cx.view().entity_id();
+        cx.on_release(move |_, cx| released(id, cx)).detach();
+    })
+    .detach();
+}
+
+fn focused(editor: View<Editor>, cx: &mut WindowContext) {
+    if Vim::read(cx).active_editor.clone().is_some() {
+        Vim::update(cx, |vim, cx| {
+            vim.update_active_editor(cx, |previously_active_editor, cx| {
+                vim.unhook_vim_settings(previously_active_editor, cx)
+            });
+        });
+    }
+
+    Vim::update(cx, |vim, cx| {
+        vim.set_active_editor(editor.clone(), cx);
+    });
+}
+
+fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        vim.workspace_state.recording = false;
+        vim.workspace_state.recorded_actions.clear();
+        if let Some(previous_editor) = vim.active_editor.clone() {
+            if previous_editor
+                .upgrade()
+                .is_some_and(|previous| previous == editor.clone())
+            {
+                vim.clear_operator(cx);
+                vim.active_editor = None;
+                vim.editor_subscription = None;
+            }
+        }
+
+        editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
+    });
+}
+
+fn released(entity_id: EntityId, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, _| {
+        if vim
+            .active_editor
+            .as_ref()
+            .is_some_and(|previous| previous.entity_id() == entity_id)
+        {
+            vim.active_editor = None;
+            vim.editor_subscription = None;
+        }
+        vim.editor_states.remove(&entity_id)
+    });
+}
+
+// #[cfg(test)]
+// mod test {
+//     use crate::{test::VimTestContext, Vim};
+//     use editor::Editor;
+//     use gpui::{Context, Entity};
+//     use language::Buffer;
+
+//     // regression test for blur called with a different active editor
+//     #[gpui::test]
+//     async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         let buffer = cx.build_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
+//         let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
+//         let editor2 = cx
+//             .update(|cx| window2.update(cx, |editor, cx| cx.view()))
+//             .unwrap();
+
+//         cx.update(|cx| {
+//             let vim = Vim::read(cx);
+//             assert_eq!(
+//                 vim.active_editor.unwrap().entity_id().unwrap(),
+//                 editor2.entity_id()
+//             )
+//         });
+
+//         // no panic when blurring an editor in a different window.
+//         cx.update_editor(|editor1, cx| {
+//             editor1.focus_out(cx.handle().into_any(), cx);
+//         });
+//     }
+// }

crates/vim2/src/insert.rs 🔗

@@ -0,0 +1,135 @@
+use crate::{normal::repeat, state::Mode, Vim};
+use editor::{scroll::autoscroll::Autoscroll, Bias};
+use gpui::{actions, Action, ViewContext};
+use language::SelectionGoal;
+use workspace::Workspace;
+
+actions!(vim, [NormalBefore]);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(normal_before);
+}
+
+fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
+    let should_repeat = Vim::update(cx, |vim, cx| {
+        let count = vim.take_count(cx).unwrap_or(1);
+        vim.stop_recording_immediately(action.boxed_clone());
+        if count <= 1 || vim.workspace_state.replaying {
+            vim.update_active_editor(cx, |editor, cx| {
+                editor.cancel(&Default::default(), cx);
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.move_cursors_with(|map, mut cursor, _| {
+                        *cursor.column_mut() = cursor.column().saturating_sub(1);
+                        (map.clip_point(cursor, Bias::Left), SelectionGoal::None)
+                    });
+                });
+            });
+            vim.switch_mode(Mode::Normal, false, cx);
+            false
+        } else {
+            true
+        }
+    });
+
+    if should_repeat {
+        repeat::repeat(cx, true)
+    }
+}
+
+// #[cfg(test)]
+// mod test {
+//     use std::sync::Arc;
+
+//     use gpui::executor::Deterministic;
+
+//     use crate::{
+//         state::Mode,
+//         test::{NeovimBackedTestContext, VimTestContext},
+//     };
+
+//     #[gpui::test]
+//     async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+//         cx.simulate_keystroke("i");
+//         assert_eq!(cx.mode(), Mode::Insert);
+//         cx.simulate_keystrokes(["T", "e", "s", "t"]);
+//         cx.assert_editor_state("Testˇ");
+//         cx.simulate_keystroke("escape");
+//         assert_eq!(cx.mode(), Mode::Normal);
+//         cx.assert_editor_state("Tesˇt");
+//     }
+
+//     #[gpui::test]
+//     async fn test_insert_with_counts(
+//         deterministic: Arc<Deterministic>,
+//         cx: &mut gpui::TestAppContext,
+//     ) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state("ˇhello\n").await;
+//         cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("----ˇ-hello\n").await;
+
+//         cx.set_shared_state("ˇhello\n").await;
+//         cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("h----ˇ-ello\n").await;
+
+//         cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("---ˇ-h-----ello\n").await;
+
+//         cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("----h-----ello--ˇ-\n").await;
+
+//         cx.set_shared_state("ˇhello\n").await;
+//         cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
+
+//         cx.set_shared_state("ˇhello\n").await;
+//         cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_insert_with_repeat(
+//         deterministic: Arc<Deterministic>,
+//         cx: &mut gpui::TestAppContext,
+//     ) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state("ˇhello\n").await;
+//         cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("--ˇ-hello\n").await;
+//         cx.simulate_shared_keystrokes(["."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("----ˇ--hello\n").await;
+//         cx.simulate_shared_keystrokes(["2", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("-----ˇ---hello\n").await;
+
+//         cx.set_shared_state("ˇhello\n").await;
+//         cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("hello\nkk\nkˇk\n").await;
+//         cx.simulate_shared_keystrokes(["."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
+//         cx.simulate_shared_keystrokes(["1", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
+//     }
+// }

crates/vim2/src/mode_indicator.rs 🔗

@@ -0,0 +1,78 @@
+use gpui::{div, AnyElement, Element, IntoElement, Render, ViewContext};
+use settings::{Settings, SettingsStore};
+use workspace::{item::ItemHandle, ui::Label, StatusItemView};
+
+use crate::{state::Mode, Vim, VimModeSetting};
+
+pub struct ModeIndicator {
+    pub mode: Option<Mode>,
+    // _subscription: 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();
+
+        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();
+
+        // 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();
+
+        Self {
+            mode,
+            //    _subscription,
+        }
+    }
+
+    pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
+        if self.mode != Some(mode) {
+            self.mode = Some(mode);
+            cx.notify();
+        }
+    }
+}
+
+impl Render for ModeIndicator {
+    type Element = AnyElement;
+
+    fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement {
+        let Some(mode) = self.mode.as_ref() else {
+            return div().into_any();
+        };
+
+        let text = match mode {
+            Mode::Normal => "-- NORMAL --",
+            Mode::Insert => "-- INSERT --",
+            Mode::Visual => "-- VISUAL --",
+            Mode::VisualLine => "-- VISUAL LINE --",
+            Mode::VisualBlock => "-- VISUAL BLOCK --",
+        };
+        Label::new(text).into_any_element()
+    }
+}
+
+impl StatusItemView for ModeIndicator {
+    fn set_active_pane_item(
+        &mut self,
+        _active_pane_item: Option<&dyn ItemHandle>,
+        _cx: &mut ViewContext<Self>,
+    ) {
+        // nothing to do.
+    }
+}

crates/vim2/src/motion.rs 🔗

@@ -0,0 +1,1098 @@
+use editor::{
+    char_kind,
+    display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
+    movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
+    Bias, CharKind, DisplayPoint, ToOffset,
+};
+use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
+use language::{Point, Selection, SelectionGoal};
+use serde::Deserialize;
+use workspace::Workspace;
+
+use crate::{
+    normal::normal_motion,
+    state::{Mode, Operator},
+    visual::visual_motion,
+    Vim,
+};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Motion {
+    Left,
+    Backspace,
+    Down { display_lines: bool },
+    Up { display_lines: bool },
+    Right,
+    NextWordStart { ignore_punctuation: bool },
+    NextWordEnd { ignore_punctuation: bool },
+    PreviousWordStart { ignore_punctuation: bool },
+    FirstNonWhitespace { display_lines: bool },
+    CurrentLine,
+    StartOfLine { display_lines: bool },
+    EndOfLine { display_lines: bool },
+    StartOfParagraph,
+    EndOfParagraph,
+    StartOfDocument,
+    EndOfDocument,
+    Matching,
+    FindForward { before: bool, char: char },
+    FindBackward { after: bool, char: char },
+    NextLineStart,
+    StartOfLineDownward,
+    EndOfLineDownward,
+    GoToColumn,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct NextWordStart {
+    #[serde(default)]
+    ignore_punctuation: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct NextWordEnd {
+    #[serde(default)]
+    ignore_punctuation: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct PreviousWordStart {
+    #[serde(default)]
+    ignore_punctuation: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct Up {
+    #[serde(default)]
+    pub(crate) display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Down {
+    #[serde(default)]
+    display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct FirstNonWhitespace {
+    #[serde(default)]
+    display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct EndOfLine {
+    #[serde(default)]
+    display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct StartOfLine {
+    #[serde(default)]
+    pub(crate) display_lines: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+struct RepeatFind {
+    #[serde(default)]
+    backwards: bool,
+}
+
+impl_actions!(
+    vim,
+    [
+        RepeatFind,
+        StartOfLine,
+        EndOfLine,
+        FirstNonWhitespace,
+        Down,
+        Up,
+        PreviousWordStart,
+        NextWordEnd,
+        NextWordStart
+    ]
+);
+
+actions!(
+    vim,
+    [
+        Left,
+        Backspace,
+        Right,
+        CurrentLine,
+        StartOfParagraph,
+        EndOfParagraph,
+        StartOfDocument,
+        EndOfDocument,
+        Matching,
+        NextLineStart,
+        StartOfLineDownward,
+        EndOfLineDownward,
+        GoToColumn,
+    ]
+);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
+    workspace
+        .register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
+    workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
+        motion(
+            Motion::Down {
+                display_lines: action.display_lines,
+            },
+            cx,
+        )
+    });
+    workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
+        motion(
+            Motion::Up {
+                display_lines: action.display_lines,
+            },
+            cx,
+        )
+    });
+    workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
+    workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
+        motion(
+            Motion::FirstNonWhitespace {
+                display_lines: action.display_lines,
+            },
+            cx,
+        )
+    });
+    workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
+        motion(
+            Motion::StartOfLine {
+                display_lines: action.display_lines,
+            },
+            cx,
+        )
+    });
+    workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
+        motion(
+            Motion::EndOfLine {
+                display_lines: action.display_lines,
+            },
+            cx,
+        )
+    });
+    workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
+        motion(Motion::CurrentLine, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
+        motion(Motion::StartOfParagraph, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
+        motion(Motion::EndOfParagraph, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
+        motion(Motion::StartOfDocument, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
+        motion(Motion::EndOfDocument, cx)
+    });
+    workspace
+        .register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
+
+    workspace.register_action(
+        |_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
+            motion(Motion::NextWordStart { ignore_punctuation }, cx)
+        },
+    );
+    workspace.register_action(
+        |_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
+            motion(Motion::NextWordEnd { ignore_punctuation }, cx)
+        },
+    );
+    workspace.register_action(
+        |_: &mut Workspace,
+         &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
+         cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
+    );
+    workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
+        motion(Motion::NextLineStart, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
+        motion(Motion::StartOfLineDownward, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
+        motion(Motion::EndOfLineDownward, cx)
+    });
+    workspace
+        .register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
+    workspace.register_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
+        repeat_motion(action.backwards, cx)
+    });
+}
+
+pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
+    if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
+        Vim::read(cx).active_operator()
+    {
+        Vim::update(cx, |vim, cx| vim.pop_operator(cx));
+    }
+
+    let count = Vim::update(cx, |vim, cx| vim.take_count(cx));
+    let operator = Vim::read(cx).active_operator();
+    match Vim::read(cx).state().mode {
+        Mode::Normal => normal_motion(motion, operator, count, cx),
+        Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_motion(motion, count, cx),
+        Mode::Insert => {
+            // Shouldn't execute a motion in insert mode. Ignoring
+        }
+    }
+    Vim::update(cx, |vim, cx| vim.clear_operator(cx));
+}
+
+fn repeat_motion(backwards: bool, cx: &mut WindowContext) {
+    let find = match Vim::read(cx).workspace_state.last_find.clone() {
+        Some(Motion::FindForward { before, char }) => {
+            if backwards {
+                Motion::FindBackward {
+                    after: before,
+                    char,
+                }
+            } else {
+                Motion::FindForward { before, char }
+            }
+        }
+
+        Some(Motion::FindBackward { after, char }) => {
+            if backwards {
+                Motion::FindForward {
+                    before: after,
+                    char,
+                }
+            } else {
+                Motion::FindBackward { after, char }
+            }
+        }
+        _ => return,
+    };
+
+    motion(find, cx)
+}
+
+// Motion handling is specified here:
+// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
+impl Motion {
+    pub fn linewise(&self) -> bool {
+        use Motion::*;
+        match self {
+            Down { .. }
+            | Up { .. }
+            | StartOfDocument
+            | EndOfDocument
+            | CurrentLine
+            | NextLineStart
+            | StartOfLineDownward
+            | StartOfParagraph
+            | EndOfParagraph => true,
+            EndOfLine { .. }
+            | NextWordEnd { .. }
+            | Matching
+            | FindForward { .. }
+            | Left
+            | Backspace
+            | Right
+            | StartOfLine { .. }
+            | EndOfLineDownward
+            | GoToColumn
+            | NextWordStart { .. }
+            | PreviousWordStart { .. }
+            | FirstNonWhitespace { .. }
+            | FindBackward { .. } => false,
+        }
+    }
+
+    pub fn infallible(&self) -> bool {
+        use Motion::*;
+        match self {
+            StartOfDocument | EndOfDocument | CurrentLine => true,
+            Down { .. }
+            | Up { .. }
+            | EndOfLine { .. }
+            | NextWordEnd { .. }
+            | Matching
+            | FindForward { .. }
+            | Left
+            | Backspace
+            | Right
+            | StartOfLine { .. }
+            | StartOfParagraph
+            | EndOfParagraph
+            | StartOfLineDownward
+            | EndOfLineDownward
+            | GoToColumn
+            | NextWordStart { .. }
+            | PreviousWordStart { .. }
+            | FirstNonWhitespace { .. }
+            | FindBackward { .. }
+            | NextLineStart => false,
+        }
+    }
+
+    pub fn inclusive(&self) -> bool {
+        use Motion::*;
+        match self {
+            Down { .. }
+            | Up { .. }
+            | StartOfDocument
+            | EndOfDocument
+            | CurrentLine
+            | EndOfLine { .. }
+            | EndOfLineDownward
+            | NextWordEnd { .. }
+            | Matching
+            | FindForward { .. }
+            | NextLineStart => true,
+            Left
+            | Backspace
+            | Right
+            | StartOfLine { .. }
+            | StartOfLineDownward
+            | StartOfParagraph
+            | EndOfParagraph
+            | GoToColumn
+            | NextWordStart { .. }
+            | PreviousWordStart { .. }
+            | FirstNonWhitespace { .. }
+            | FindBackward { .. } => false,
+        }
+    }
+
+    pub fn move_point(
+        &self,
+        map: &DisplaySnapshot,
+        point: DisplayPoint,
+        goal: SelectionGoal,
+        maybe_times: Option<usize>,
+        text_layout_details: &TextLayoutDetails,
+    ) -> Option<(DisplayPoint, SelectionGoal)> {
+        let times = maybe_times.unwrap_or(1);
+        use Motion::*;
+        let infallible = self.infallible();
+        let (new_point, goal) = match self {
+            Left => (left(map, point, times), SelectionGoal::None),
+            Backspace => (backspace(map, point, times), SelectionGoal::None),
+            Down {
+                display_lines: false,
+            } => up_down_buffer_rows(map, point, goal, times as isize, &text_layout_details),
+            Down {
+                display_lines: true,
+            } => down_display(map, point, goal, times, &text_layout_details),
+            Up {
+                display_lines: false,
+            } => up_down_buffer_rows(map, point, goal, 0 - times as isize, &text_layout_details),
+            Up {
+                display_lines: true,
+            } => up_display(map, point, goal, times, &text_layout_details),
+            Right => (right(map, point, times), SelectionGoal::None),
+            NextWordStart { ignore_punctuation } => (
+                next_word_start(map, point, *ignore_punctuation, times),
+                SelectionGoal::None,
+            ),
+            NextWordEnd { ignore_punctuation } => (
+                next_word_end(map, point, *ignore_punctuation, times),
+                SelectionGoal::None,
+            ),
+            PreviousWordStart { ignore_punctuation } => (
+                previous_word_start(map, point, *ignore_punctuation, times),
+                SelectionGoal::None,
+            ),
+            FirstNonWhitespace { display_lines } => (
+                first_non_whitespace(map, *display_lines, point),
+                SelectionGoal::None,
+            ),
+            StartOfLine { display_lines } => (
+                start_of_line(map, *display_lines, point),
+                SelectionGoal::None,
+            ),
+            EndOfLine { display_lines } => {
+                (end_of_line(map, *display_lines, point), SelectionGoal::None)
+            }
+            StartOfParagraph => (
+                movement::start_of_paragraph(map, point, times),
+                SelectionGoal::None,
+            ),
+            EndOfParagraph => (
+                map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
+                SelectionGoal::None,
+            ),
+            CurrentLine => (next_line_end(map, point, times), SelectionGoal::None),
+            StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
+            EndOfDocument => (
+                end_of_document(map, point, maybe_times),
+                SelectionGoal::None,
+            ),
+            Matching => (matching(map, point), SelectionGoal::None),
+            FindForward { before, char } => (
+                find_forward(map, point, *before, *char, times),
+                SelectionGoal::None,
+            ),
+            FindBackward { after, char } => (
+                find_backward(map, point, *after, *char, times),
+                SelectionGoal::None,
+            ),
+            NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
+            StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None),
+            EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None),
+            GoToColumn => (go_to_column(map, point, times), SelectionGoal::None),
+        };
+
+        (new_point != point || infallible).then_some((new_point, goal))
+    }
+
+    // Expands a selection using self motion for an operator
+    pub fn expand_selection(
+        &self,
+        map: &DisplaySnapshot,
+        selection: &mut Selection<DisplayPoint>,
+        times: Option<usize>,
+        expand_to_surrounding_newline: bool,
+        text_layout_details: &TextLayoutDetails,
+    ) -> bool {
+        if let Some((new_head, goal)) = self.move_point(
+            map,
+            selection.head(),
+            selection.goal,
+            times,
+            &text_layout_details,
+        ) {
+            selection.set_head(new_head, goal);
+
+            if self.linewise() {
+                selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
+
+                if expand_to_surrounding_newline {
+                    if selection.end.row() < map.max_point().row() {
+                        *selection.end.row_mut() += 1;
+                        *selection.end.column_mut() = 0;
+                        selection.end = map.clip_point(selection.end, Bias::Right);
+                        // Don't reset the end here
+                        return true;
+                    } else if selection.start.row() > 0 {
+                        *selection.start.row_mut() -= 1;
+                        *selection.start.column_mut() = map.line_len(selection.start.row());
+                        selection.start = map.clip_point(selection.start, Bias::Left);
+                    }
+                }
+
+                (_, selection.end) = map.next_line_boundary(selection.end.to_point(map));
+            } else {
+                // Another special case: When using the "w" motion in combination with an
+                // operator and the last word moved over is at the end of a line, the end of
+                // that word becomes the end of the operated text, not the first word in the
+                // next line.
+                if let Motion::NextWordStart {
+                    ignore_punctuation: _,
+                } = self
+                {
+                    let start_row = selection.start.to_point(&map).row;
+                    if selection.end.to_point(&map).row > start_row {
+                        selection.end =
+                            Point::new(start_row, map.buffer_snapshot.line_len(start_row))
+                                .to_display_point(&map)
+                    }
+                }
+
+                // If the motion is exclusive and the end of the motion is in column 1, the
+                // end of the motion is moved to the end of the previous line and the motion
+                // becomes inclusive. Example: "}" moves to the first line after a paragraph,
+                // but "d}" will not include that line.
+                let mut inclusive = self.inclusive();
+                if !inclusive
+                    && self != &Motion::Backspace
+                    && selection.end.row() > selection.start.row()
+                    && selection.end.column() == 0
+                {
+                    inclusive = true;
+                    *selection.end.row_mut() -= 1;
+                    *selection.end.column_mut() = 0;
+                    selection.end = map.clip_point(
+                        map.next_line_boundary(selection.end.to_point(map)).1,
+                        Bias::Left,
+                    );
+                }
+
+                if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
+                    *selection.end.column_mut() += 1;
+                }
+            }
+            true
+        } else {
+            false
+        }
+    }
+}
+
+fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
+    for _ in 0..times {
+        point = movement::saturating_left(map, point);
+        if point.column() == 0 {
+            break;
+        }
+    }
+    point
+}
+
+fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
+    for _ in 0..times {
+        point = movement::left(map, point);
+    }
+    point
+}
+
+pub(crate) fn start_of_relative_buffer_row(
+    map: &DisplaySnapshot,
+    point: DisplayPoint,
+    times: isize,
+) -> DisplayPoint {
+    let start = map.display_point_to_fold_point(point, Bias::Left);
+    let target = start.row() as isize + times;
+    let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row());
+
+    map.clip_point(
+        map.fold_point_to_display_point(
+            map.fold_snapshot
+                .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
+        ),
+        Bias::Right,
+    )
+}
+
+fn up_down_buffer_rows(
+    map: &DisplaySnapshot,
+    point: DisplayPoint,
+    mut goal: SelectionGoal,
+    times: isize,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    let start = map.display_point_to_fold_point(point, Bias::Left);
+    let begin_folded_line = map.fold_point_to_display_point(
+        map.fold_snapshot
+            .clip_point(FoldPoint::new(start.row(), 0), Bias::Left),
+    );
+    let select_nth_wrapped_row = point.row() - begin_folded_line.row();
+
+    let (goal_wrap, goal_x) = match goal {
+        SelectionGoal::WrappedHorizontalPosition((row, x)) => (row, x),
+        SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end),
+        SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x),
+        _ => {
+            let x = map.x_for_display_point(point, text_layout_details);
+            goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x.0));
+            (select_nth_wrapped_row, x.0)
+        }
+    };
+
+    let target = start.row() as isize + times;
+    let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row());
+
+    let mut begin_folded_line = map.fold_point_to_display_point(
+        map.fold_snapshot
+            .clip_point(FoldPoint::new(new_row, 0), Bias::Left),
+    );
+
+    let mut i = 0;
+    while i < goal_wrap && begin_folded_line.row() < map.max_point().row() {
+        let next_folded_line = DisplayPoint::new(begin_folded_line.row() + 1, 0);
+        if map
+            .display_point_to_fold_point(next_folded_line, Bias::Right)
+            .row()
+            == new_row
+        {
+            i += 1;
+            begin_folded_line = next_folded_line;
+        } else {
+            break;
+        }
+    }
+
+    let new_col = if i == goal_wrap {
+        map.display_column_for_x(begin_folded_line.row(), px(goal_x), text_layout_details)
+    } else {
+        map.line_len(begin_folded_line.row())
+    };
+
+    (
+        map.clip_point(
+            DisplayPoint::new(begin_folded_line.row(), new_col),
+            Bias::Left,
+        ),
+        goal,
+    )
+}
+
+fn down_display(
+    map: &DisplaySnapshot,
+    mut point: DisplayPoint,
+    mut goal: SelectionGoal,
+    times: usize,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    for _ in 0..times {
+        (point, goal) = movement::down(map, point, goal, true, text_layout_details);
+    }
+
+    (point, goal)
+}
+
+fn up_display(
+    map: &DisplaySnapshot,
+    mut point: DisplayPoint,
+    mut goal: SelectionGoal,
+    times: usize,
+    text_layout_details: &TextLayoutDetails,
+) -> (DisplayPoint, SelectionGoal) {
+    for _ in 0..times {
+        (point, goal) = movement::up(map, point, goal, true, &text_layout_details);
+    }
+
+    (point, goal)
+}
+
+pub(crate) fn right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
+    for _ in 0..times {
+        let new_point = movement::saturating_right(map, point);
+        if point == new_point {
+            break;
+        }
+        point = new_point;
+    }
+    point
+}
+
+pub(crate) fn next_word_start(
+    map: &DisplaySnapshot,
+    mut point: DisplayPoint,
+    ignore_punctuation: bool,
+    times: usize,
+) -> DisplayPoint {
+    let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
+    for _ in 0..times {
+        let mut crossed_newline = false;
+        point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
+            let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
+            let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+            let at_newline = right == '\n';
+
+            let found = (left_kind != right_kind && right_kind != CharKind::Whitespace)
+                || at_newline && crossed_newline
+                || at_newline && left == '\n'; // Prevents skipping repeated empty lines
+
+            crossed_newline |= at_newline;
+            found
+        })
+    }
+    point
+}
+
+fn next_word_end(
+    map: &DisplaySnapshot,
+    mut point: DisplayPoint,
+    ignore_punctuation: bool,
+    times: usize,
+) -> DisplayPoint {
+    let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
+    for _ in 0..times {
+        if point.column() < map.line_len(point.row()) {
+            *point.column_mut() += 1;
+        } else if point.row() < map.max_buffer_row() {
+            *point.row_mut() += 1;
+            *point.column_mut() = 0;
+        }
+        point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
+            let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
+            let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+
+            left_kind != right_kind && left_kind != CharKind::Whitespace
+        });
+
+        // find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know
+        // we have backtracked already
+        if !map
+            .chars_at(point)
+            .nth(1)
+            .map(|(c, _)| c == '\n')
+            .unwrap_or(true)
+        {
+            *point.column_mut() = point.column().saturating_sub(1);
+        }
+        point = map.clip_point(point, Bias::Left);
+    }
+    point
+}
+
+fn previous_word_start(
+    map: &DisplaySnapshot,
+    mut point: DisplayPoint,
+    ignore_punctuation: bool,
+    times: usize,
+) -> DisplayPoint {
+    let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
+    for _ in 0..times {
+        // This works even though find_preceding_boundary is called for every character in the line containing
+        // cursor because the newline is checked only once.
+        point =
+            movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
+                let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
+                let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+
+                (left_kind != right_kind && !right.is_whitespace()) || left == '\n'
+            });
+    }
+    point
+}
+
+pub(crate) fn first_non_whitespace(
+    map: &DisplaySnapshot,
+    display_lines: bool,
+    from: DisplayPoint,
+) -> DisplayPoint {
+    let mut last_point = start_of_line(map, display_lines, from);
+    let scope = map.buffer_snapshot.language_scope_at(from.to_point(map));
+    for (ch, point) in map.chars_at(last_point) {
+        if ch == '\n' {
+            return from;
+        }
+
+        last_point = point;
+
+        if char_kind(&scope, ch) != CharKind::Whitespace {
+            break;
+        }
+    }
+
+    map.clip_point(last_point, Bias::Left)
+}
+
+pub(crate) fn start_of_line(
+    map: &DisplaySnapshot,
+    display_lines: bool,
+    point: DisplayPoint,
+) -> DisplayPoint {
+    if display_lines {
+        map.clip_point(DisplayPoint::new(point.row(), 0), Bias::Right)
+    } else {
+        map.prev_line_boundary(point.to_point(map)).1
+    }
+}
+
+pub(crate) fn end_of_line(
+    map: &DisplaySnapshot,
+    display_lines: bool,
+    point: DisplayPoint,
+) -> DisplayPoint {
+    if display_lines {
+        map.clip_point(
+            DisplayPoint::new(point.row(), map.line_len(point.row())),
+            Bias::Left,
+        )
+    } else {
+        map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
+    }
+}
+
+fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
+    let mut new_point = Point::new((line - 1) as u32, 0).to_display_point(map);
+    *new_point.column_mut() = point.column();
+    map.clip_point(new_point, Bias::Left)
+}
+
+fn end_of_document(
+    map: &DisplaySnapshot,
+    point: DisplayPoint,
+    line: Option<usize>,
+) -> DisplayPoint {
+    let new_row = if let Some(line) = line {
+        (line - 1) as u32
+    } else {
+        map.max_buffer_row()
+    };
+
+    let new_point = Point::new(new_row, point.column());
+    map.clip_point(new_point.to_display_point(map), Bias::Left)
+}
+
+fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
+    // https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200
+    let point = display_point.to_point(map);
+    let offset = point.to_offset(&map.buffer_snapshot);
+
+    // Ensure the range is contained by the current line.
+    let mut line_end = map.next_line_boundary(point).0;
+    if line_end == point {
+        line_end = map.max_point().to_point(map);
+    }
+
+    let line_range = map.prev_line_boundary(point).0..line_end;
+    let visible_line_range =
+        line_range.start..Point::new(line_range.end.row, line_range.end.column.saturating_sub(1));
+    let ranges = map
+        .buffer_snapshot
+        .bracket_ranges(visible_line_range.clone());
+    if let Some(ranges) = ranges {
+        let line_range = line_range.start.to_offset(&map.buffer_snapshot)
+            ..line_range.end.to_offset(&map.buffer_snapshot);
+        let mut closest_pair_destination = None;
+        let mut closest_distance = usize::MAX;
+
+        for (open_range, close_range) in ranges {
+            if open_range.start >= offset && line_range.contains(&open_range.start) {
+                let distance = open_range.start - offset;
+                if distance < closest_distance {
+                    closest_pair_destination = Some(close_range.start);
+                    closest_distance = distance;
+                    continue;
+                }
+            }
+
+            if close_range.start >= offset && line_range.contains(&close_range.start) {
+                let distance = close_range.start - offset;
+                if distance < closest_distance {
+                    closest_pair_destination = Some(open_range.start);
+                    closest_distance = distance;
+                    continue;
+                }
+            }
+
+            continue;
+        }
+
+        closest_pair_destination
+            .map(|destination| destination.to_display_point(map))
+            .unwrap_or(display_point)
+    } else {
+        display_point
+    }
+}
+
+fn find_forward(
+    map: &DisplaySnapshot,
+    from: DisplayPoint,
+    before: bool,
+    target: char,
+    times: usize,
+) -> DisplayPoint {
+    let mut to = from;
+    let mut found = false;
+
+    for _ in 0..times {
+        found = false;
+        to = find_boundary(map, to, FindRange::SingleLine, |_, right| {
+            found = right == target;
+            found
+        });
+    }
+
+    if found {
+        if before && to.column() > 0 {
+            *to.column_mut() -= 1;
+            map.clip_point(to, Bias::Left)
+        } else {
+            to
+        }
+    } else {
+        from
+    }
+}
+
+fn find_backward(
+    map: &DisplaySnapshot,
+    from: DisplayPoint,
+    after: bool,
+    target: char,
+    times: usize,
+) -> DisplayPoint {
+    let mut to = from;
+
+    for _ in 0..times {
+        to = find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
+    }
+
+    if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) {
+        if after {
+            *to.column_mut() += 1;
+            map.clip_point(to, Bias::Right)
+        } else {
+            to
+        }
+    } else {
+        from
+    }
+}
+
+fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
+    let correct_line = start_of_relative_buffer_row(map, point, times as isize);
+    first_non_whitespace(map, false, correct_line)
+}
+
+fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
+    let correct_line = start_of_relative_buffer_row(map, point, 0);
+    right(map, correct_line, times.saturating_sub(1))
+}
+
+pub(crate) fn next_line_end(
+    map: &DisplaySnapshot,
+    mut point: DisplayPoint,
+    times: usize,
+) -> DisplayPoint {
+    if times > 1 {
+        point = start_of_relative_buffer_row(map, point, times as isize - 1);
+    }
+    end_of_line(map, false, point)
+}
+
+// #[cfg(test)]
+// mod test {
+
+//     use crate::test::NeovimBackedTestContext;
+//     use indoc::indoc;
+
+//     #[gpui::test]
+//     async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         let initial_state = indoc! {r"ˇabc
+//             def
+
+//             paragraph
+//             the second
+
+//             third and
+//             final"};
+
+//         // goes down once
+//         cx.set_shared_state(initial_state).await;
+//         cx.simulate_shared_keystrokes(["}"]).await;
+//         cx.assert_shared_state(indoc! {r"abc
+//             def
+//             ˇ
+//             paragraph
+//             the second
+
+//             third and
+//             final"})
+//             .await;
+
+//         // goes up once
+//         cx.simulate_shared_keystrokes(["{"]).await;
+//         cx.assert_shared_state(initial_state).await;
+
+//         // goes down twice
+//         cx.simulate_shared_keystrokes(["2", "}"]).await;
+//         cx.assert_shared_state(indoc! {r"abc
+//             def
+
+//             paragraph
+//             the second
+//             ˇ
+
+//             third and
+//             final"})
+//             .await;
+
+//         // goes down over multiple blanks
+//         cx.simulate_shared_keystrokes(["}"]).await;
+//         cx.assert_shared_state(indoc! {r"abc
+//                 def
+
+//                 paragraph
+//                 the second
+
+//                 third and
+//                 finaˇl"})
+//             .await;
+
+//         // goes up twice
+//         cx.simulate_shared_keystrokes(["2", "{"]).await;
+//         cx.assert_shared_state(indoc! {r"abc
+//                 def
+//                 ˇ
+//                 paragraph
+//                 the second
+
+//                 third and
+//                 final"})
+//             .await
+//     }
+
+//     #[gpui::test]
+//     async fn test_matching(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {r"func ˇ(a string) {
+//                 do(something(with<Types>.and_arrays[0, 2]))
+//             }"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["%"]).await;
+//         cx.assert_shared_state(indoc! {r"func (a stringˇ) {
+//                 do(something(with<Types>.and_arrays[0, 2]))
+//             }"})
+//             .await;
+
+//         // test it works on the last character of the line
+//         cx.set_shared_state(indoc! {r"func (a string) ˇ{
+//             do(something(with<Types>.and_arrays[0, 2]))
+//             }"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["%"]).await;
+//         cx.assert_shared_state(indoc! {r"func (a string) {
+//             do(something(with<Types>.and_arrays[0, 2]))
+//             ˇ}"})
+//             .await;
+
+//         // test it works on immediate nesting
+//         cx.set_shared_state("ˇ{()}").await;
+//         cx.simulate_shared_keystrokes(["%"]).await;
+//         cx.assert_shared_state("{()ˇ}").await;
+//         cx.simulate_shared_keystrokes(["%"]).await;
+//         cx.assert_shared_state("ˇ{()}").await;
+
+//         // test it works on immediate nesting inside braces
+//         cx.set_shared_state("{\n    ˇ{()}\n}").await;
+//         cx.simulate_shared_keystrokes(["%"]).await;
+//         cx.assert_shared_state("{\n    {()ˇ}\n}").await;
+
+//         // test it jumps to the next paren on a line
+//         cx.set_shared_state("func ˇboop() {\n}").await;
+//         cx.simulate_shared_keystrokes(["%"]).await;
+//         cx.assert_shared_state("func boop(ˇ) {\n}").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state("ˇone two three four").await;
+//         cx.simulate_shared_keystrokes(["f", "o"]).await;
+//         cx.assert_shared_state("one twˇo three four").await;
+//         cx.simulate_shared_keystrokes([","]).await;
+//         cx.assert_shared_state("ˇone two three four").await;
+//         cx.simulate_shared_keystrokes(["2", ";"]).await;
+//         cx.assert_shared_state("one two three fˇour").await;
+//         cx.simulate_shared_keystrokes(["shift-t", "e"]).await;
+//         cx.assert_shared_state("one two threeˇ four").await;
+//         cx.simulate_shared_keystrokes(["3", ";"]).await;
+//         cx.assert_shared_state("oneˇ two three four").await;
+//         cx.simulate_shared_keystrokes([","]).await;
+//         cx.assert_shared_state("one two thˇree four").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_next_line_start(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.set_shared_state("ˇone\n  two\nthree").await;
+//         cx.simulate_shared_keystrokes(["enter"]).await;
+//         cx.assert_shared_state("one\n  ˇtwo\nthree").await;
+//     }
+// }

crates/vim2/src/normal.rs 🔗

@@ -0,0 +1,907 @@
+mod case;
+mod change;
+mod delete;
+mod increment;
+mod paste;
+pub(crate) mod repeat;
+mod scroll;
+pub(crate) mod search;
+pub mod substitute;
+mod yank;
+
+use std::sync::Arc;
+
+use crate::{
+    motion::{self, first_non_whitespace, next_line_end, right, Motion},
+    object::Object,
+    state::{Mode, Operator},
+    Vim,
+};
+use collections::HashSet;
+use editor::scroll::autoscroll::Autoscroll;
+use editor::{Bias, DisplayPoint};
+use gpui::{actions, ViewContext, WindowContext};
+use language::SelectionGoal;
+use log::error;
+use workspace::Workspace;
+
+use self::{
+    case::change_case,
+    change::{change_motion, change_object},
+    delete::{delete_motion, delete_object},
+    yank::{yank_motion, yank_object},
+};
+
+actions!(
+    vim,
+    [
+        InsertAfter,
+        InsertBefore,
+        InsertFirstNonWhitespace,
+        InsertEndOfLine,
+        InsertLineAbove,
+        InsertLineBelow,
+        DeleteLeft,
+        DeleteRight,
+        ChangeToEndOfLine,
+        DeleteToEndOfLine,
+        Yank,
+        YankLine,
+        ChangeCase,
+        JoinLines,
+    ]
+);
+
+pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+    workspace.register_action(insert_after);
+    workspace.register_action(insert_before);
+    workspace.register_action(insert_first_non_whitespace);
+    workspace.register_action(insert_end_of_line);
+    workspace.register_action(insert_line_above);
+    workspace.register_action(insert_line_below);
+    workspace.register_action(change_case);
+    workspace.register_action(yank_line);
+
+    workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.record_current_action(cx);
+            let times = vim.take_count(cx);
+            delete_motion(vim, Motion::Left, times, cx);
+        })
+    });
+    workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.record_current_action(cx);
+            let times = vim.take_count(cx);
+            delete_motion(vim, Motion::Right, times, cx);
+        })
+    });
+    workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.start_recording(cx);
+            let times = vim.take_count(cx);
+            change_motion(
+                vim,
+                Motion::EndOfLine {
+                    display_lines: false,
+                },
+                times,
+                cx,
+            );
+        })
+    });
+    workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.record_current_action(cx);
+            let times = vim.take_count(cx);
+            delete_motion(
+                vim,
+                Motion::EndOfLine {
+                    display_lines: false,
+                },
+                times,
+                cx,
+            );
+        })
+    });
+    workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.record_current_action(cx);
+            let mut times = vim.take_count(cx).unwrap_or(1);
+            if vim.state().mode.is_visual() {
+                times = 1;
+            } else if times > 1 {
+                // 2J joins two lines together (same as J or 1J)
+                times -= 1;
+            }
+
+            vim.update_active_editor(cx, |editor, cx| {
+                editor.transact(cx, |editor, cx| {
+                    for _ in 0..times {
+                        editor.join_lines(&Default::default(), cx)
+                    }
+                })
+            })
+        });
+    });
+
+    paste::register(workspace, cx);
+    repeat::register(workspace, cx);
+    scroll::register(workspace, cx);
+    search::register(workspace, cx);
+    substitute::register(workspace, cx);
+    increment::register(workspace, cx);
+}
+
+pub fn normal_motion(
+    motion: Motion,
+    operator: Option<Operator>,
+    times: Option<usize>,
+    cx: &mut WindowContext,
+) {
+    Vim::update(cx, |vim, cx| {
+        match operator {
+            None => move_cursor(vim, motion, times, cx),
+            Some(Operator::Change) => change_motion(vim, motion, times, cx),
+            Some(Operator::Delete) => delete_motion(vim, motion, times, cx),
+            Some(Operator::Yank) => yank_motion(vim, motion, times, cx),
+            Some(operator) => {
+                // Can't do anything for text objects, Ignoring
+                error!("Unexpected normal mode motion operator: {:?}", operator)
+            }
+        }
+    });
+}
+
+pub fn normal_object(object: Object, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        match vim.maybe_pop_operator() {
+            Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
+                Some(Operator::Change) => change_object(vim, object, around, cx),
+                Some(Operator::Delete) => delete_object(vim, object, around, cx),
+                Some(Operator::Yank) => yank_object(vim, object, around, cx),
+                _ => {
+                    // Can't do anything for namespace operators. Ignoring
+                }
+            },
+            _ => {
+                // Can't do anything with change/delete/yank and text objects. Ignoring
+            }
+        }
+        vim.clear_operator(cx);
+    })
+}
+
+pub(crate) fn move_cursor(
+    vim: &mut Vim,
+    motion: Motion,
+    times: Option<usize>,
+    cx: &mut WindowContext,
+) {
+    vim.update_active_editor(cx, |editor, cx| {
+        let text_layout_details = editor.text_layout_details(cx);
+        editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+            s.move_cursors_with(|map, cursor, goal| {
+                motion
+                    .move_point(map, cursor, goal, times, &text_layout_details)
+                    .unwrap_or((cursor, goal))
+            })
+        })
+    });
+}
+
+fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.start_recording(cx);
+        vim.switch_mode(Mode::Insert, false, cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
+            });
+        });
+    });
+}
+
+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);
+    });
+}
+
+fn insert_first_non_whitespace(
+    _: &mut Workspace,
+    _: &InsertFirstNonWhitespace,
+    cx: &mut ViewContext<Workspace>,
+) {
+    Vim::update(cx, |vim, cx| {
+        vim.start_recording(cx);
+        vim.switch_mode(Mode::Insert, false, cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_cursors_with(|map, cursor, _| {
+                    (
+                        first_non_whitespace(map, false, cursor),
+                        SelectionGoal::None,
+                    )
+                });
+            });
+        });
+    });
+}
+
+fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.start_recording(cx);
+        vim.switch_mode(Mode::Insert, false, cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_cursors_with(|map, cursor, _| {
+                    (next_line_end(map, cursor, 1), SelectionGoal::None)
+                });
+            });
+        });
+    });
+}
+
+fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.start_recording(cx);
+        vim.switch_mode(Mode::Insert, false, cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.transact(cx, |editor, cx| {
+                let (map, old_selections) = editor.selections.all_display(cx);
+                let selection_start_rows: HashSet<u32> = old_selections
+                    .into_iter()
+                    .map(|selection| selection.start.row())
+                    .collect();
+                let edits = selection_start_rows.into_iter().map(|row| {
+                    let (indent, _) = map.line_indent(row);
+                    let start_of_line =
+                        motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
+                            .to_point(&map);
+                    let mut new_text = " ".repeat(indent as usize);
+                    new_text.push('\n');
+                    (start_of_line..start_of_line, new_text)
+                });
+                editor.edit_with_autoindent(edits, cx);
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.move_cursors_with(|map, cursor, _| {
+                        let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
+                        let insert_point = motion::end_of_line(map, false, previous_line);
+                        (insert_point, SelectionGoal::None)
+                    });
+                });
+            });
+        });
+    });
+}
+
+fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.start_recording(cx);
+        vim.switch_mode(Mode::Insert, false, cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            let text_layout_details = editor.text_layout_details(cx);
+            editor.transact(cx, |editor, cx| {
+                let (map, old_selections) = editor.selections.all_display(cx);
+
+                let selection_end_rows: HashSet<u32> = old_selections
+                    .into_iter()
+                    .map(|selection| selection.end.row())
+                    .collect();
+                let edits = selection_end_rows.into_iter().map(|row| {
+                    let (indent, _) = map.line_indent(row);
+                    let end_of_line =
+                        motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
+
+                    let mut new_text = "\n".to_string();
+                    new_text.push_str(&" ".repeat(indent as usize));
+                    (end_of_line..end_of_line, new_text)
+                });
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.maybe_move_cursors_with(|map, cursor, goal| {
+                        Motion::CurrentLine.move_point(
+                            map,
+                            cursor,
+                            goal,
+                            None,
+                            &text_layout_details,
+                        )
+                    });
+                });
+                editor.edit_with_autoindent(edits, cx);
+            });
+        });
+    });
+}
+
+fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        let count = vim.take_count(cx);
+        yank_motion(vim, motion::Motion::CurrentLine, count, cx)
+    })
+}
+
+pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        vim.stop_recording();
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.transact(cx, |editor, cx| {
+                editor.set_clip_at_line_ends(false, cx);
+                let (map, display_selections) = editor.selections.all_display(cx);
+                // Selections are biased right at the start. So we need to store
+                // anchors that are biased left so that we can restore the selections
+                // after the change
+                let stable_anchors = editor
+                    .selections
+                    .disjoint_anchors()
+                    .into_iter()
+                    .map(|selection| {
+                        let start = selection.start.bias_left(&map.buffer_snapshot);
+                        start..start
+                    })
+                    .collect::<Vec<_>>();
+
+                let edits = display_selections
+                    .into_iter()
+                    .map(|selection| {
+                        let mut range = selection.range();
+                        *range.end.column_mut() += 1;
+                        range.end = map.clip_point(range.end, Bias::Right);
+
+                        (
+                            range.start.to_offset(&map, Bias::Left)
+                                ..range.end.to_offset(&map, Bias::Left),
+                            text.clone(),
+                        )
+                    })
+                    .collect::<Vec<_>>();
+
+                editor.buffer().update(cx, |buffer, cx| {
+                    buffer.edit(edits, None, cx);
+                });
+                editor.set_clip_at_line_ends(true, cx);
+                editor.change_selections(None, cx, |s| {
+                    s.select_anchor_ranges(stable_anchors);
+                });
+            });
+        });
+        vim.pop_operator(cx)
+    });
+}
+
+// #[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/normal/case.rs 🔗

@@ -0,0 +1,116 @@
+use editor::scroll::autoscroll::Autoscroll;
+use gpui::ViewContext;
+use language::{Bias, Point};
+use workspace::Workspace;
+
+use crate::{normal::ChangeCase, state::Mode, Vim};
+
+pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.record_current_action(cx);
+        let count = vim.take_count(cx).unwrap_or(1) as u32;
+        vim.update_active_editor(cx, |editor, cx| {
+            let mut ranges = Vec::new();
+            let mut cursor_positions = Vec::new();
+            let snapshot = editor.buffer().read(cx).snapshot(cx);
+            for selection in editor.selections.all::<Point>(cx) {
+                match vim.state().mode {
+                    Mode::VisualLine => {
+                        let start = Point::new(selection.start.row, 0);
+                        let end =
+                            Point::new(selection.end.row, snapshot.line_len(selection.end.row));
+                        ranges.push(start..end);
+                        cursor_positions.push(start..start);
+                    }
+                    Mode::Visual => {
+                        ranges.push(selection.start..selection.end);
+                        cursor_positions.push(selection.start..selection.start);
+                    }
+                    Mode::VisualBlock => {
+                        ranges.push(selection.start..selection.end);
+                        if cursor_positions.len() == 0 {
+                            cursor_positions.push(selection.start..selection.start);
+                        }
+                    }
+                    Mode::Insert | Mode::Normal => {
+                        let start = selection.start;
+                        let mut end = start;
+                        for _ in 0..count {
+                            end = snapshot.clip_point(end + Point::new(0, 1), Bias::Right);
+                        }
+                        ranges.push(start..end);
+
+                        if end.column == snapshot.line_len(end.row) {
+                            end = snapshot.clip_point(end - Point::new(0, 1), Bias::Left);
+                        }
+                        cursor_positions.push(end..end)
+                    }
+                }
+            }
+            editor.transact(cx, |editor, cx| {
+                for range in ranges.into_iter().rev() {
+                    let snapshot = editor.buffer().read(cx).snapshot(cx);
+                    editor.buffer().update(cx, |buffer, cx| {
+                        let text = snapshot
+                            .text_for_range(range.start..range.end)
+                            .flat_map(|s| s.chars())
+                            .flat_map(|c| {
+                                if c.is_lowercase() {
+                                    c.to_uppercase().collect::<Vec<char>>()
+                                } else {
+                                    c.to_lowercase().collect::<Vec<char>>()
+                                }
+                            })
+                            .collect::<String>();
+
+                        buffer.edit([(range, text)], None, cx)
+                    })
+                }
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.select_ranges(cursor_positions)
+                })
+            });
+        });
+        vim.switch_mode(Mode::Normal, true, cx)
+    })
+}
+// #[cfg(test)]
+// mod test {
+//     use crate::{state::Mode, test::NeovimBackedTestContext};
+
+//     #[gpui::test]
+//     async fn test_change_case(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.set_shared_state("ˇabC\n").await;
+//         cx.simulate_shared_keystrokes(["~"]).await;
+//         cx.assert_shared_state("AˇbC\n").await;
+//         cx.simulate_shared_keystrokes(["2", "~"]).await;
+//         cx.assert_shared_state("ABˇc\n").await;
+
+//         // works in visual mode
+//         cx.set_shared_state("a😀C«dÉ1*fˇ»\n").await;
+//         cx.simulate_shared_keystrokes(["~"]).await;
+//         cx.assert_shared_state("a😀CˇDé1*F\n").await;
+
+//         // works with multibyte characters
+//         cx.simulate_shared_keystrokes(["~"]).await;
+//         cx.set_shared_state("aˇC😀é1*F\n").await;
+//         cx.simulate_shared_keystrokes(["4", "~"]).await;
+//         cx.assert_shared_state("ac😀É1ˇ*F\n").await;
+
+//         // works with line selections
+//         cx.set_shared_state("abˇC\n").await;
+//         cx.simulate_shared_keystrokes(["shift-v", "~"]).await;
+//         cx.assert_shared_state("ˇABc\n").await;
+
+//         // works in visual block mode
+//         cx.set_shared_state("ˇaa\nbb\ncc").await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "j", "~"]).await;
+//         cx.assert_shared_state("ˇAa\nBb\ncc").await;
+
+//         // works with multiple cursors (zed only)
+//         cx.set_state("aˇßcdˇe\n", Mode::Normal);
+//         cx.simulate_keystroke("~");
+//         cx.assert_state("aSSˇcdˇE\n", Mode::Normal);
+//     }
+// }

crates/vim2/src/normal/change.rs 🔗

@@ -0,0 +1,502 @@
+use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
+use editor::{
+    char_kind,
+    display_map::DisplaySnapshot,
+    movement::{self, FindRange, TextLayoutDetails},
+    scroll::autoscroll::Autoscroll,
+    CharKind, DisplayPoint,
+};
+use gpui::WindowContext;
+use language::Selection;
+
+pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
+    // Some motions ignore failure when switching to normal mode
+    let mut motion_succeeded = matches!(
+        motion,
+        Motion::Left
+            | Motion::Right
+            | Motion::EndOfLine { .. }
+            | Motion::Backspace
+            | Motion::StartOfLine { .. }
+    );
+    vim.update_active_editor(cx, |editor, cx| {
+        let text_layout_details = editor.text_layout_details(cx);
+        editor.transact(cx, |editor, cx| {
+            // We are swapping to insert mode anyway. Just set the line end clipping behavior now
+            editor.set_clip_at_line_ends(false, cx);
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_with(|map, selection| {
+                    motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion
+                    {
+                        expand_changed_word_selection(
+                            map,
+                            selection,
+                            times,
+                            ignore_punctuation,
+                            &text_layout_details,
+                        )
+                    } else {
+                        motion.expand_selection(map, selection, times, false, &text_layout_details)
+                    };
+                });
+            });
+            copy_selections_content(editor, motion.linewise(), cx);
+            editor.insert("", cx);
+        });
+    });
+
+    if motion_succeeded {
+        vim.switch_mode(Mode::Insert, false, cx)
+    } else {
+        vim.switch_mode(Mode::Normal, false, cx)
+    }
+}
+
+pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
+    let mut objects_found = false;
+    vim.update_active_editor(cx, |editor, cx| {
+        // We are swapping to insert mode anyway. Just set the line end clipping behavior now
+        editor.set_clip_at_line_ends(false, cx);
+        editor.transact(cx, |editor, cx| {
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_with(|map, selection| {
+                    objects_found |= object.expand_selection(map, selection, around);
+                });
+            });
+            if objects_found {
+                copy_selections_content(editor, false, cx);
+                editor.insert("", cx);
+            }
+        });
+    });
+
+    if objects_found {
+        vim.switch_mode(Mode::Insert, false, cx);
+    } else {
+        vim.switch_mode(Mode::Normal, false, cx);
+    }
+}
+
+// From the docs https://vimdoc.sourceforge.net/htmldoc/motion.html
+// Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is
+// on a non-blank.  This is because "cw" is interpreted as change-word, and a
+// word does not include the following white space.  {Vi: "cw" when on a blank
+//     followed by other blanks changes only the first blank; this is probably a
+//     bug, because "dw" deletes all the blanks}
+fn expand_changed_word_selection(
+    map: &DisplaySnapshot,
+    selection: &mut Selection<DisplayPoint>,
+    times: Option<usize>,
+    ignore_punctuation: bool,
+    text_layout_details: &TextLayoutDetails,
+) -> bool {
+    if times.is_none() || times.unwrap() == 1 {
+        let scope = map
+            .buffer_snapshot
+            .language_scope_at(selection.start.to_point(map));
+        let in_word = map
+            .chars_at(selection.head())
+            .next()
+            .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
+            .unwrap_or_default();
+
+        if in_word {
+            selection.end =
+                movement::find_boundary(map, selection.end, FindRange::MultiLine, |left, right| {
+                    let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
+                    let right_kind =
+                        char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+
+                    left_kind != right_kind && left_kind != CharKind::Whitespace
+                });
+            true
+        } else {
+            Motion::NextWordStart { ignore_punctuation }.expand_selection(
+                map,
+                selection,
+                None,
+                false,
+                &text_layout_details,
+            )
+        }
+    } else {
+        Motion::NextWordStart { ignore_punctuation }.expand_selection(
+            map,
+            selection,
+            times,
+            false,
+            &text_layout_details,
+        )
+    }
+}
+
+// #[cfg(test)]
+// mod test {
+//     use indoc::indoc;
+
+//     use crate::test::NeovimBackedTestContext;
+
+//     #[gpui::test]
+//     async fn test_change_h(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "h"]);
+//         cx.assert("Teˇst").await;
+//         cx.assert("Tˇest").await;
+//         cx.assert("ˇTest").await;
+//         cx.assert(indoc! {"
+//             Test
+//             ˇtest"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_backspace(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx)
+//             .await
+//             .binding(["c", "backspace"]);
+//         cx.assert("Teˇst").await;
+//         cx.assert("Tˇest").await;
+//         cx.assert("ˇTest").await;
+//         cx.assert(indoc! {"
+//             Test
+//             ˇtest"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_l(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "l"]);
+//         cx.assert("Teˇst").await;
+//         cx.assert("Tesˇt").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_w(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "w"]);
+//         cx.assert("Teˇst").await;
+//         cx.assert("Tˇest test").await;
+//         cx.assert("Testˇ  test").await;
+//         cx.assert(indoc! {"
+//                 Test teˇst
+//                 test"})
+//             .await;
+//         cx.assert(indoc! {"
+//                 Test tesˇt
+//                 test"})
+//             .await;
+//         cx.assert(indoc! {"
+//                 Test test
+//                 ˇ
+//                 test"})
+//             .await;
+
+//         let mut cx = cx.binding(["c", "shift-w"]);
+//         cx.assert("Test teˇst-test test").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_e(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "e"]);
+//         cx.assert("Teˇst Test").await;
+//         cx.assert("Tˇest test").await;
+//         cx.assert(indoc! {"
+//                 Test teˇst
+//                 test"})
+//             .await;
+//         cx.assert(indoc! {"
+//                 Test tesˇt
+//                 test"})
+//             .await;
+//         cx.assert(indoc! {"
+//                 Test test
+//                 ˇ
+//                 test"})
+//             .await;
+
+//         let mut cx = cx.binding(["c", "shift-e"]);
+//         cx.assert("Test teˇst-test test").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_b(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "b"]);
+//         cx.assert("Teˇst Test").await;
+//         cx.assert("Test ˇtest").await;
+//         cx.assert("Test1 test2 ˇtest3").await;
+//         cx.assert(indoc! {"
+//                 Test test
+//                 ˇtest"})
+//             .await;
+//         cx.assert(indoc! {"
+//                 Test test
+//                 ˇ
+//                 test"})
+//             .await;
+
+//         let mut cx = cx.binding(["c", "shift-b"]);
+//         cx.assert("Test test-test ˇtest").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "$"]);
+//         cx.assert(indoc! {"
+//             The qˇuick
+//             brown fox"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The quick
+//             ˇ
+//             brown fox"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_0(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The qˇuick
+//             brown fox"},
+//             ["c", "0"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             ˇ
+//             brown fox"},
+//             ["c", "0"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_k(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown ˇfox
+//             jumps over"},
+//             ["c", "k"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps ˇover"},
+//             ["c", "k"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The qˇuick
+//             brown fox
+//             jumps over"},
+//             ["c", "k"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             ˇ
+//             brown fox
+//             jumps over"},
+//             ["c", "k"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_j(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown ˇfox
+//             jumps over"},
+//             ["c", "j"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps ˇover"},
+//             ["c", "j"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The qˇuick
+//             brown fox
+//             jumps over"},
+//             ["c", "j"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             ˇ"},
+//             ["c", "j"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brownˇ fox
+//             jumps over
+//             the lazy"},
+//             ["c", "shift-g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brownˇ fox
+//             jumps over
+//             the lazy"},
+//             ["c", "shift-g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps over
+//             the lˇazy"},
+//             ["c", "shift-g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps over
+//             ˇ"},
+//             ["c", "shift-g"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_change_gg(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brownˇ fox
+//             jumps over
+//             the lazy"},
+//             ["c", "g", "g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps over
+//             the lˇazy"},
+//             ["c", "g", "g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The qˇuick
+//             brown fox
+//             jumps over
+//             the lazy"},
+//             ["c", "g", "g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             ˇ
+//             brown fox
+//             jumps over
+//             the lazy"},
+//             ["c", "g", "g"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeated_cj(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         for count in 1..=5 {
+//             cx.assert_binding_matches_all(
+//                 ["c", &count.to_string(), "j"],
+//                 indoc! {"
+//                     ˇThe quˇickˇ browˇn
+//                     ˇ
+//                     ˇfox ˇjumpsˇ-ˇoˇver
+//                     ˇthe lazy dog
+//                     "},
+//             )
+//             .await;
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeated_cl(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         for count in 1..=5 {
+//             cx.assert_binding_matches_all(
+//                 ["c", &count.to_string(), "l"],
+//                 indoc! {"
+//                     ˇThe quˇickˇ browˇn
+//                     ˇ
+//                     ˇfox ˇjumpsˇ-ˇoˇver
+//                     ˇthe lazy dog
+//                     "},
+//             )
+//             .await;
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeated_cb(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         for count in 1..=5 {
+//             for marked_text in cx.each_marked_position(indoc! {"
+//                 ˇThe quˇickˇ browˇn
+//                 ˇ
+//                 ˇfox ˇjumpsˇ-ˇoˇver
+//                 ˇthe lazy dog
+//                 "})
+//             {
+//                 cx.assert_neovim_compatible(&marked_text, ["c", &count.to_string(), "b"])
+//                     .await;
+//             }
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeated_ce(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         for count in 1..=5 {
+//             cx.assert_binding_matches_all(
+//                 ["c", &count.to_string(), "e"],
+//                 indoc! {"
+//                     ˇThe quˇickˇ browˇn
+//                     ˇ
+//                     ˇfox ˇjumpsˇ-ˇoˇver
+//                     ˇthe lazy dog
+//                     "},
+//             )
+//             .await;
+//         }
+//     }
+// }

crates/vim2/src/normal/delete.rs 🔗

@@ -0,0 +1,475 @@
+use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
+use collections::{HashMap, HashSet};
+use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
+use gpui::WindowContext;
+use language::Point;
+
+pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
+    vim.stop_recording();
+    vim.update_active_editor(cx, |editor, cx| {
+        let text_layout_details = editor.text_layout_details(cx);
+        editor.transact(cx, |editor, cx| {
+            editor.set_clip_at_line_ends(false, cx);
+            let mut original_columns: HashMap<_, _> = Default::default();
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_with(|map, selection| {
+                    let original_head = selection.head();
+                    original_columns.insert(selection.id, original_head.column());
+                    motion.expand_selection(map, selection, times, true, &text_layout_details);
+
+                    // Motion::NextWordStart on an empty line should delete it.
+                    if let Motion::NextWordStart {
+                        ignore_punctuation: _,
+                    } = motion
+                    {
+                        if selection.is_empty()
+                            && map
+                                .buffer_snapshot
+                                .line_len(selection.start.to_point(&map).row)
+                                == 0
+                        {
+                            selection.end = map
+                                .buffer_snapshot
+                                .clip_point(
+                                    Point::new(selection.start.to_point(&map).row + 1, 0),
+                                    Bias::Left,
+                                )
+                                .to_display_point(map)
+                        }
+                    }
+                });
+            });
+            copy_selections_content(editor, motion.linewise(), cx);
+            editor.insert("", cx);
+
+            // Fixup cursor position after the deletion
+            editor.set_clip_at_line_ends(true, cx);
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_with(|map, selection| {
+                    let mut cursor = selection.head();
+                    if motion.linewise() {
+                        if let Some(column) = original_columns.get(&selection.id) {
+                            *cursor.column_mut() = *column
+                        }
+                    }
+                    cursor = map.clip_point(cursor, Bias::Left);
+                    selection.collapse_to(cursor, selection.goal)
+                });
+            });
+        });
+    });
+}
+
+pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
+    vim.stop_recording();
+    vim.update_active_editor(cx, |editor, cx| {
+        editor.transact(cx, |editor, cx| {
+            editor.set_clip_at_line_ends(false, cx);
+            // Emulates behavior in vim where if we expanded backwards to include a newline
+            // the cursor gets set back to the start of the line
+            let mut should_move_to_start: HashSet<_> = Default::default();
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_with(|map, selection| {
+                    object.expand_selection(map, selection, around);
+                    let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range();
+                    let contains_only_newlines = map
+                        .chars_at(selection.start)
+                        .take_while(|(_, p)| p < &selection.end)
+                        .all(|(char, _)| char == '\n')
+                        && !offset_range.is_empty();
+                    let end_at_newline = map
+                        .chars_at(selection.end)
+                        .next()
+                        .map(|(c, _)| c == '\n')
+                        .unwrap_or(false);
+
+                    // If expanded range contains only newlines and
+                    // the object is around or sentence, expand to include a newline
+                    // at the end or start
+                    if (around || object == Object::Sentence) && contains_only_newlines {
+                        if end_at_newline {
+                            selection.end =
+                                (offset_range.end + '\n'.len_utf8()).to_display_point(map);
+                        } else if selection.start.row() > 0 {
+                            should_move_to_start.insert(selection.id);
+                            selection.start =
+                                (offset_range.start - '\n'.len_utf8()).to_display_point(map);
+                        }
+                    }
+                });
+            });
+            copy_selections_content(editor, false, cx);
+            editor.insert("", cx);
+
+            // Fixup cursor position after the deletion
+            editor.set_clip_at_line_ends(true, cx);
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                s.move_with(|map, selection| {
+                    let mut cursor = selection.head();
+                    if should_move_to_start.contains(&selection.id) {
+                        *cursor.column_mut() = 0;
+                    }
+                    cursor = map.clip_point(cursor, Bias::Left);
+                    selection.collapse_to(cursor, selection.goal)
+                });
+            });
+        });
+    });
+}
+
+// #[cfg(test)]
+// mod test {
+//     use indoc::indoc;
+
+//     use crate::{
+//         state::Mode,
+//         test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
+//     };
+
+//     #[gpui::test]
+//     async fn test_delete_h(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "h"]);
+//         cx.assert("Teˇst").await;
+//         cx.assert("Tˇest").await;
+//         cx.assert("ˇTest").await;
+//         cx.assert(indoc! {"
+//             Test
+//             ˇtest"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_l(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "l"]);
+//         cx.assert("ˇTest").await;
+//         cx.assert("Teˇst").await;
+//         cx.assert("Tesˇt").await;
+//         cx.assert(indoc! {"
+//                 Tesˇt
+//                 test"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_w(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             Test tesˇt
+//                 test"},
+//             ["d", "w"],
+//         )
+//         .await;
+
+//         cx.assert_neovim_compatible("Teˇst", ["d", "w"]).await;
+//         cx.assert_neovim_compatible("Tˇest test", ["d", "w"]).await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             Test teˇst
+//             test"},
+//             ["d", "w"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             Test tesˇt
+//             test"},
+//             ["d", "w"],
+//         )
+//         .await;
+
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             Test test
+//             ˇ
+//             test"},
+//             ["d", "w"],
+//         )
+//         .await;
+
+//         let mut cx = cx.binding(["d", "shift-w"]);
+//         cx.assert_neovim_compatible("Test teˇst-test test", ["d", "shift-w"])
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_next_word_end(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "e"]);
+//         // cx.assert("Teˇst Test").await;
+//         // cx.assert("Tˇest test").await;
+//         cx.assert(indoc! {"
+//             Test teˇst
+//             test"})
+//             .await;
+//         cx.assert(indoc! {"
+//             Test tesˇt
+//             test"})
+//             .await;
+//         cx.assert_exempted(
+//             indoc! {"
+//             Test test
+//             ˇ
+//             test"},
+//             ExemptionFeatures::OperatorLastNewlineRemains,
+//         )
+//         .await;
+
+//         let mut cx = cx.binding(["d", "shift-e"]);
+//         cx.assert("Test teˇst-test test").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_b(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "b"]);
+//         cx.assert("Teˇst Test").await;
+//         cx.assert("Test ˇtest").await;
+//         cx.assert("Test1 test2 ˇtest3").await;
+//         cx.assert(indoc! {"
+//             Test test
+//             ˇtest"})
+//             .await;
+//         cx.assert(indoc! {"
+//             Test test
+//             ˇ
+//             test"})
+//             .await;
+
+//         let mut cx = cx.binding(["d", "shift-b"]);
+//         cx.assert("Test test-test ˇtest").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "$"]);
+//         cx.assert(indoc! {"
+//             The qˇuick
+//             brown fox"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The quick
+//             ˇ
+//             brown fox"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_0(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "0"]);
+//         cx.assert(indoc! {"
+//             The qˇuick
+//             brown fox"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The quick
+//             ˇ
+//             brown fox"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_k(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "k"]);
+//         cx.assert(indoc! {"
+//             The quick
+//             brown ˇfox
+//             jumps over"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The quick
+//             brown fox
+//             jumps ˇover"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The qˇuick
+//             brown fox
+//             jumps over"})
+//             .await;
+//         cx.assert(indoc! {"
+//             ˇbrown fox
+//             jumps over"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_j(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "j"]);
+//         cx.assert(indoc! {"
+//             The quick
+//             brown ˇfox
+//             jumps over"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The quick
+//             brown fox
+//             jumps ˇover"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The qˇuick
+//             brown fox
+//             jumps over"})
+//             .await;
+//         cx.assert(indoc! {"
+//             The quick
+//             brown fox
+//             ˇ"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brownˇ fox
+//             jumps over
+//             the lazy"},
+//             ["d", "shift-g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brownˇ fox
+//             jumps over
+//             the lazy"},
+//             ["d", "shift-g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps over
+//             the lˇazy"},
+//             ["d", "shift-g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps over
+//             ˇ"},
+//             ["d", "shift-g"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_gg(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx)
+//             .await
+//             .binding(["d", "g", "g"]);
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brownˇ fox
+//             jumps over
+//             the lazy"},
+//             ["d", "g", "g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The quick
+//             brown fox
+//             jumps over
+//             the lˇazy"},
+//             ["d", "g", "g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             The qˇuick
+//             brown fox
+//             jumps over
+//             the lazy"},
+//             ["d", "g", "g"],
+//         )
+//         .await;
+//         cx.assert_neovim_compatible(
+//             indoc! {"
+//             ˇ
+//             brown fox
+//             jumps over
+//             the lazy"},
+//             ["d", "g", "g"],
+//         )
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_cancel_delete_operator(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,
+//         );
+
+//         // Canceling operator twice reverts to normal mode with no active operator
+//         cx.simulate_keystrokes(["d", "escape", "k"]);
+//         assert_eq!(cx.active_operator(), None);
+//         assert_eq!(cx.mode(), Mode::Normal);
+//         cx.assert_editor_state(indoc! {"
+//             The quˇick brown
+//             fox jumps over
+//             the lazy dog"});
+//     }
+
+//     #[gpui::test]
+//     async fn test_unbound_command_cancels_pending_operator(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,
+//         );
+
+//         // Canceling operator twice reverts to normal mode with no active operator
+//         cx.simulate_keystrokes(["d", "y"]);
+//         assert_eq!(cx.active_operator(), None);
+//         assert_eq!(cx.mode(), Mode::Normal);
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_with_counts(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.set_shared_state(indoc! {"
+//                 The ˇquick brown
+//                 fox jumps over
+//                 the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["d", "2", "d"]).await;
+//         cx.assert_shared_state(indoc! {"
+//         the ˇlazy dog"})
+//             .await;
+
+//         cx.set_shared_state(indoc! {"
+//                 The ˇquick brown
+//                 fox jumps over
+//                 the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["2", "d", "d"]).await;
+//         cx.assert_shared_state(indoc! {"
+//         the ˇlazy dog"})
+//             .await;
+
+//         cx.set_shared_state(indoc! {"
+//                 The ˇquick brown
+//                 fox jumps over
+//                 the moon,
+//                 a star, and
+//                 the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["2", "d", "2", "d"]).await;
+//         cx.assert_shared_state(indoc! {"
+//         the ˇlazy dog"})
+//             .await;
+//     }
+// }

crates/vim2/src/normal/increment.rs 🔗

@@ -0,0 +1,278 @@
+use std::ops::Range;
+
+use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
+use gpui::{impl_actions, ViewContext, WindowContext};
+use language::{Bias, Point};
+use serde::Deserialize;
+use workspace::Workspace;
+
+use crate::{state::Mode, Vim};
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Increment {
+    #[serde(default)]
+    step: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Decrement {
+    #[serde(default)]
+    step: bool,
+}
+
+impl_actions!(vim, [Increment, Decrement]);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.record_current_action(cx);
+            let count = vim.take_count(cx).unwrap_or(1);
+            let step = if action.step { 1 } else { 0 };
+            increment(vim, count as i32, step, cx)
+        })
+    });
+    workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.record_current_action(cx);
+            let count = vim.take_count(cx).unwrap_or(1);
+            let step = if action.step { -1 } else { 0 };
+            increment(vim, count as i32 * -1, step, cx)
+        })
+    });
+}
+
+fn increment(vim: &mut Vim, mut delta: i32, step: i32, cx: &mut WindowContext) {
+    vim.update_active_editor(cx, |editor, cx| {
+        let mut edits = Vec::new();
+        let mut new_anchors = Vec::new();
+
+        let snapshot = editor.buffer().read(cx).snapshot(cx);
+        for selection in editor.selections.all_adjusted(cx) {
+            if !selection.is_empty() {
+                if vim.state().mode != Mode::VisualBlock || new_anchors.is_empty() {
+                    new_anchors.push((true, snapshot.anchor_before(selection.start)))
+                }
+            }
+            for row in selection.start.row..=selection.end.row {
+                let start = if row == selection.start.row {
+                    selection.start
+                } else {
+                    Point::new(row, 0)
+                };
+
+                if let Some((range, num, radix)) = find_number(&snapshot, start) {
+                    if let Ok(val) = i32::from_str_radix(&num, radix) {
+                        let result = val + delta;
+                        delta += step;
+                        let replace = match radix {
+                            10 => format!("{}", result),
+                            16 => {
+                                if num.to_ascii_lowercase() == num {
+                                    format!("{:x}", result)
+                                } else {
+                                    format!("{:X}", result)
+                                }
+                            }
+                            2 => format!("{:b}", result),
+                            _ => unreachable!(),
+                        };
+                        edits.push((range.clone(), replace));
+                    }
+                    if selection.is_empty() {
+                        new_anchors.push((false, snapshot.anchor_after(range.end)))
+                    }
+                } else {
+                    if selection.is_empty() {
+                        new_anchors.push((true, snapshot.anchor_after(start)))
+                    }
+                }
+            }
+        }
+        editor.transact(cx, |editor, cx| {
+            editor.edit(edits, cx);
+
+            let snapshot = editor.buffer().read(cx).snapshot(cx);
+            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                let mut new_ranges = Vec::new();
+                for (visual, anchor) in new_anchors.iter() {
+                    let mut point = anchor.to_point(&snapshot);
+                    if !*visual && point.column > 0 {
+                        point.column -= 1;
+                        point = snapshot.clip_point(point, Bias::Left)
+                    }
+                    new_ranges.push(point..point);
+                }
+                s.select_ranges(new_ranges)
+            })
+        });
+    });
+    vim.switch_mode(Mode::Normal, true, cx)
+}
+
+fn find_number(
+    snapshot: &MultiBufferSnapshot,
+    start: Point,
+) -> Option<(Range<Point>, String, u32)> {
+    let mut offset = start.to_offset(snapshot);
+
+    // go backwards to the start of any number the selection is within
+    for ch in snapshot.reversed_chars_at(offset) {
+        if ch.is_ascii_digit() || ch == '-' || ch == 'b' || ch == 'x' {
+            offset -= ch.len_utf8();
+            continue;
+        }
+        break;
+    }
+
+    let mut begin = None;
+    let mut end = None;
+    let mut num = String::new();
+    let mut radix = 10;
+
+    let mut chars = snapshot.chars_at(offset).peekable();
+    // find the next number on the line (may start after the original cursor position)
+    while let Some(ch) = chars.next() {
+        if num == "0" && ch == 'b' && chars.peek().is_some() && chars.peek().unwrap().is_digit(2) {
+            radix = 2;
+            begin = None;
+            num = String::new();
+        }
+        if num == "0" && ch == 'x' && chars.peek().is_some() && chars.peek().unwrap().is_digit(16) {
+            radix = 16;
+            begin = None;
+            num = String::new();
+        }
+
+        if ch.is_digit(radix)
+            || (begin.is_none()
+                && ch == '-'
+                && chars.peek().is_some()
+                && chars.peek().unwrap().is_digit(radix))
+        {
+            if begin.is_none() {
+                begin = Some(offset);
+            }
+            num.push(ch);
+        } else {
+            if begin.is_some() {
+                end = Some(offset);
+                break;
+            } else if ch == '\n' {
+                break;
+            }
+        }
+        offset += ch.len_utf8();
+    }
+    if let Some(begin) = begin {
+        let end = end.unwrap_or(offset);
+        Some((begin.to_point(snapshot)..end.to_point(snapshot), num, radix))
+    } else {
+        None
+    }
+}
+
+// #[cfg(test)]
+// mod test {
+//     use indoc::indoc;
+
+//     use crate::test::NeovimBackedTestContext;
+
+//     #[gpui::test]
+//     async fn test_increment(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {"
+//             1ˇ2
+//             "})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["ctrl-a"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             1ˇ3
+//             "})
+//             .await;
+//         cx.simulate_shared_keystrokes(["ctrl-x"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             1ˇ2
+//             "})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["9", "9", "ctrl-a"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             11ˇ1
+//             "})
+//             .await;
+//         cx.simulate_shared_keystrokes(["1", "1", "1", "ctrl-x"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             ˇ0
+//             "})
+//             .await;
+//         cx.simulate_shared_keystrokes(["."]).await;
+//         cx.assert_shared_state(indoc! {"
+//             -11ˇ1
+//             "})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_increment_radix(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.assert_matches_neovim("ˇ total: 0xff", ["ctrl-a"], " total: 0x10ˇ0")
+//             .await;
+//         cx.assert_matches_neovim("ˇ total: 0xff", ["ctrl-x"], " total: 0xfˇe")
+//             .await;
+//         cx.assert_matches_neovim("ˇ total: 0xFF", ["ctrl-x"], " total: 0xFˇE")
+//             .await;
+//         cx.assert_matches_neovim("(ˇ0b10f)", ["ctrl-a"], "(0b1ˇ1f)")
+//             .await;
+//         cx.assert_matches_neovim("ˇ-1", ["ctrl-a"], "ˇ0").await;
+//         cx.assert_matches_neovim("banˇana", ["ctrl-a"], "banˇana")
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_increment_steps(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {"
+//             ˇ1
+//             1
+//             1  2
+//             1
+//             1"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["j", "v", "shift-g", "g", "ctrl-a"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             1
+//             ˇ2
+//             3  2
+//             4
+//             5"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["shift-g", "ctrl-v", "g", "g"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             «1ˇ»
+//             «2ˇ»
+//             «3ˇ»  2
+//             «4ˇ»
+//             «5ˇ»"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["g", "ctrl-x"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             ˇ0
+//             0
+//             0  2
+//             0
+//             0"})
+//             .await;
+//     }
+// }

crates/vim2/src/normal/paste.rs 🔗

@@ -0,0 +1,476 @@
+use std::{borrow::Cow, cmp};
+
+use editor::{
+    display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
+    DisplayPoint,
+};
+use gpui::{impl_actions, ViewContext};
+use language::{Bias, SelectionGoal};
+use serde::Deserialize;
+use workspace::Workspace;
+
+use crate::{state::Mode, utils::copy_selections_content, Vim};
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Paste {
+    #[serde(default)]
+    before: bool,
+    #[serde(default)]
+    preserve_clipboard: bool,
+}
+
+impl_actions!(vim, [Paste]);
+
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(paste);
+}
+
+fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.record_current_action(cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            let text_layout_details = editor.text_layout_details(cx);
+            editor.transact(cx, |editor, cx| {
+                editor.set_clip_at_line_ends(false, cx);
+
+                let Some(item) = cx.read_from_clipboard() else {
+                    return;
+                };
+                let clipboard_text = Cow::Borrowed(item.text());
+                if clipboard_text.is_empty() {
+                    return;
+                }
+
+                if !action.preserve_clipboard && vim.state().mode.is_visual() {
+                    copy_selections_content(editor, vim.state().mode == Mode::VisualLine, cx);
+                }
+
+                // if we are copying from multi-cursor (of visual block mode), we want
+                // to
+                let clipboard_selections =
+                    item.metadata::<Vec<ClipboardSelection>>()
+                        .filter(|clipboard_selections| {
+                            clipboard_selections.len() > 1 && vim.state().mode != Mode::VisualLine
+                        });
+
+                let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
+
+                // unlike zed, if you have a multi-cursor selection from vim block mode,
+                // pasting it will paste it on subsequent lines, even if you don't yet
+                // have a cursor there.
+                let mut selections_to_process = Vec::new();
+                let mut i = 0;
+                while i < current_selections.len() {
+                    selections_to_process
+                        .push((current_selections[i].start..current_selections[i].end, true));
+                    i += 1;
+                }
+                if let Some(clipboard_selections) = clipboard_selections.as_ref() {
+                    let left = current_selections
+                        .iter()
+                        .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
+                        .min()
+                        .unwrap();
+                    let mut row = current_selections.last().unwrap().end.row() + 1;
+                    while i < clipboard_selections.len() {
+                        let cursor =
+                            display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
+                        selections_to_process.push((cursor..cursor, false));
+                        i += 1;
+                        row += 1;
+                    }
+                }
+
+                let first_selection_indent_column =
+                    clipboard_selections.as_ref().and_then(|zed_selections| {
+                        zed_selections
+                            .first()
+                            .map(|selection| selection.first_line_indent)
+                    });
+                let before = action.before || vim.state().mode == Mode::VisualLine;
+
+                let mut edits = Vec::new();
+                let mut new_selections = Vec::new();
+                let mut original_indent_columns = Vec::new();
+                let mut start_offset = 0;
+
+                for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
+                    let (mut to_insert, original_indent_column) =
+                        if let Some(clipboard_selections) = &clipboard_selections {
+                            if let Some(clipboard_selection) = clipboard_selections.get(ix) {
+                                let end_offset = start_offset + clipboard_selection.len;
+                                let text = clipboard_text[start_offset..end_offset].to_string();
+                                start_offset = end_offset + 1;
+                                (text, Some(clipboard_selection.first_line_indent))
+                            } else {
+                                ("".to_string(), first_selection_indent_column)
+                            }
+                        } else {
+                            (clipboard_text.to_string(), first_selection_indent_column)
+                        };
+                    let line_mode = to_insert.ends_with("\n");
+                    let is_multiline = to_insert.contains("\n");
+
+                    if line_mode && !before {
+                        if selection.is_empty() {
+                            to_insert =
+                                "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
+                        } else {
+                            to_insert = "\n".to_owned() + &to_insert;
+                        }
+                    } else if !line_mode && vim.state().mode == Mode::VisualLine {
+                        to_insert = to_insert + "\n";
+                    }
+
+                    let display_range = if !selection.is_empty() {
+                        selection.start..selection.end
+                    } else if line_mode {
+                        let point = if before {
+                            movement::line_beginning(&display_map, selection.start, false)
+                        } else {
+                            movement::line_end(&display_map, selection.start, false)
+                        };
+                        point..point
+                    } else {
+                        let point = if before {
+                            selection.start
+                        } else {
+                            movement::saturating_right(&display_map, selection.start)
+                        };
+                        point..point
+                    };
+
+                    let point_range = display_range.start.to_point(&display_map)
+                        ..display_range.end.to_point(&display_map);
+                    let anchor = if is_multiline || vim.state().mode == Mode::VisualLine {
+                        display_map.buffer_snapshot.anchor_before(point_range.start)
+                    } else {
+                        display_map.buffer_snapshot.anchor_after(point_range.end)
+                    };
+
+                    if *preserve {
+                        new_selections.push((anchor, line_mode, is_multiline));
+                    }
+                    edits.push((point_range, to_insert));
+                    original_indent_columns.extend(original_indent_column);
+                }
+
+                editor.edit_with_block_indent(edits, original_indent_columns, cx);
+
+                // in line_mode vim will insert the new text on the next (or previous if before) line
+                // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
+                // otherwise vim will insert the next text at (or before) the current cursor position,
+                // the cursor will go to the last (or first, if is_multiline) inserted character.
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.replace_cursors_with(|map| {
+                        let mut cursors = Vec::new();
+                        for (anchor, line_mode, is_multiline) in &new_selections {
+                            let mut cursor = anchor.to_display_point(map);
+                            if *line_mode {
+                                if !before {
+                                    cursor = movement::down(
+                                        map,
+                                        cursor,
+                                        SelectionGoal::None,
+                                        false,
+                                        &text_layout_details,
+                                    )
+                                    .0;
+                                }
+                                cursor = movement::indented_line_beginning(map, cursor, true);
+                            } else if !is_multiline {
+                                cursor = movement::saturating_left(map, cursor)
+                            }
+                            cursors.push(cursor);
+                            if vim.state().mode == Mode::VisualBlock {
+                                break;
+                            }
+                        }
+
+                        cursors
+                    });
+                })
+            });
+        });
+        vim.switch_mode(Mode::Normal, true, cx);
+    });
+}
+
+// #[cfg(test)]
+// mod test {
+//     use crate::{
+//         state::Mode,
+//         test::{NeovimBackedTestContext, VimTestContext},
+//     };
+//     use indoc::indoc;
+
+//     #[gpui::test]
+//     async fn test_paste(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         // single line
+//         cx.set_shared_state(indoc! {"
+//             The quick brown
+//             fox ˇjumps over
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
+//         cx.assert_shared_clipboard("jumps o").await;
+//         cx.set_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps oveˇr
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystroke("p").await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps overjumps ˇo
+//             the lazy dog"})
+//             .await;
+
+//         cx.set_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps oveˇr
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystroke("shift-p").await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps ovejumps ˇor
+//             the lazy dog"})
+//             .await;
+
+//         // line mode
+//         cx.set_shared_state(indoc! {"
+//             The quick brown
+//             fox juˇmps over
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["d", "d"]).await;
+//         cx.assert_shared_clipboard("fox jumps over\n").await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             the laˇzy dog"})
+//             .await;
+//         cx.simulate_shared_keystroke("p").await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             the lazy dog
+//             ˇfox jumps over"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["k", "shift-p"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             ˇfox jumps over
+//             the lazy dog
+//             fox jumps over"})
+//             .await;
+
+//         // multiline, cursor to first character of pasted text.
+//         cx.set_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps ˇover
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "j", "y"]).await;
+//         cx.assert_shared_clipboard("over\nthe lazy do").await;
+
+//         cx.simulate_shared_keystroke("p").await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps oˇover
+//             the lazy dover
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["u", "shift-p"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             fox jumps ˇover
+//             the lazy doover
+//             the lazy dog"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         // copy in visual mode
+//         cx.set_shared_state(indoc! {"
+//                 The quick brown
+//                 fox jˇumps over
+//                 the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "w", "y"]).await;
+//         cx.assert_shared_state(indoc! {"
+//                 The quick brown
+//                 fox ˇjumps over
+//                 the lazy dog"})
+//             .await;
+//         // paste in visual mode
+//         cx.simulate_shared_keystrokes(["w", "v", "i", "w", "p"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//                 The quick brown
+//                 fox jumps jumpˇs
+//                 the lazy dog"})
+//             .await;
+//         cx.assert_shared_clipboard("over").await;
+//         // paste in visual line mode
+//         cx.simulate_shared_keystrokes(["up", "shift-v", "shift-p"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             ˇover
+//             fox jumps jumps
+//             the lazy dog"})
+//             .await;
+//         cx.assert_shared_clipboard("over").await;
+//         // paste in visual block mode
+//         cx.simulate_shared_keystrokes(["ctrl-v", "down", "down", "p"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             oveˇrver
+//             overox jumps jumps
+//             overhe lazy dog"})
+//             .await;
+
+//         // copy in visual line mode
+//         cx.set_shared_state(indoc! {"
+//                 The quick brown
+//                 fox juˇmps over
+//                 the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
+//         cx.assert_shared_state(indoc! {"
+//                 The quick brown
+//                 the laˇzy dog"})
+//             .await;
+//         // paste in visual mode
+//         cx.simulate_shared_keystrokes(["v", "i", "w", "p"]).await;
+//         cx.assert_shared_state(
+//             &indoc! {"
+//                 The quick brown
+//                 the_
+//                 ˇfox jumps over
+//                 _dog"}
+//             .replace("_", " "), // Hack for trailing whitespace
+//         )
+//         .await;
+//         cx.assert_shared_clipboard("lazy").await;
+//         cx.set_shared_state(indoc! {"
+//             The quick brown
+//             fox juˇmps over
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             the laˇzy dog"})
+//             .await;
+//         // paste in visual line mode
+//         cx.simulate_shared_keystrokes(["k", "shift-v", "p"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             ˇfox jumps over
+//             the lazy dog"})
+//             .await;
+//         cx.assert_shared_clipboard("The quick brown\n").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         // copy in visual block mode
+//         cx.set_shared_state(indoc! {"
+//             The ˇquick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "2", "j", "y"])
+//             .await;
+//         cx.assert_shared_clipboard("q\nj\nl").await;
+//         cx.simulate_shared_keystrokes(["p"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             The qˇquick brown
+//             fox jjumps over
+//             the llazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             The ˇq brown
+//             fox jjjumps over
+//             the lllazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
+//             .await;
+
+//         cx.set_shared_state(indoc! {"
+//             The ˇquick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "j", "y"]).await;
+//         cx.assert_shared_clipboard("q\nj").await;
+//         cx.simulate_shared_keystrokes(["l", "ctrl-v", "2", "j", "shift-p"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             The qˇqick brown
+//             fox jjmps over
+//             the lzy dog"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["shift-v", "p"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             ˇq
+//             j
+//             fox jjmps over
+//             the lzy dog"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new_typescript(cx).await;
+
+//         cx.set_state(
+//             indoc! {"
+//             class A {ˇ
+//             }
+//         "},
+//             Mode::Normal,
+//         );
+//         cx.simulate_keystrokes(["o", "a", "(", ")", "{", "escape"]);
+//         cx.assert_state(
+//             indoc! {"
+//             class A {
+//                 a()ˇ{}
+//             }
+//             "},
+//             Mode::Normal,
+//         );
+//         // cursor goes to the first non-blank character in the line;
+//         cx.simulate_keystrokes(["y", "y", "p"]);
+//         cx.assert_state(
+//             indoc! {"
+//             class A {
+//                 a(){}
+//                 ˇa(){}
+//             }
+//             "},
+//             Mode::Normal,
+//         );
+//         // indentation is preserved when pasting
+//         cx.simulate_keystrokes(["u", "shift-v", "up", "y", "shift-p"]);
+//         cx.assert_state(
+//             indoc! {"
+//                 ˇclass A {
+//                     a(){}
+//                 class A {
+//                     a(){}
+//                 }
+//                 "},
+//             Mode::Normal,
+//         );
+//     }
+// }

crates/vim2/src/normal/repeat.rs 🔗

@@ -0,0 +1,523 @@
+use crate::{
+    insert::NormalBefore,
+    motion::Motion,
+    state::{Mode, RecordedSelection, ReplayableAction},
+    visual::visual_motion,
+    Vim,
+};
+use gpui::{actions, Action, ViewContext, WindowContext};
+use workspace::Workspace;
+
+actions!(vim, [Repeat, EndRepeat]);
+
+fn should_replay(action: &Box<dyn Action>) -> bool {
+    // skip so that we don't leave the character palette open
+    if editor::ShowCharacterPalette.partial_eq(&**action) {
+        return false;
+    }
+    true
+}
+
+fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
+    match action {
+        ReplayableAction::Action(action) => {
+            if super::InsertBefore.partial_eq(&**action)
+                || super::InsertAfter.partial_eq(&**action)
+                || super::InsertFirstNonWhitespace.partial_eq(&**action)
+                || super::InsertEndOfLine.partial_eq(&**action)
+            {
+                Some(super::InsertBefore.boxed_clone())
+            } else if super::InsertLineAbove.partial_eq(&**action)
+                || super::InsertLineBelow.partial_eq(&**action)
+            {
+                Some(super::InsertLineBelow.boxed_clone())
+            } else {
+                None
+            }
+        }
+        ReplayableAction::Insertion { .. } => None,
+    }
+}
+
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.workspace_state.replaying = false;
+            vim.switch_mode(Mode::Normal, false, cx)
+        });
+    });
+
+    workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
+}
+
+pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
+    let Some((mut actions, editor, selection)) = Vim::update(cx, |vim, cx| {
+        let actions = vim.workspace_state.recorded_actions.clone();
+        if actions.is_empty() {
+            return None;
+        }
+
+        let Some(editor) = vim.active_editor.clone() else {
+            return None;
+        };
+        let count = vim.take_count(cx);
+
+        let selection = vim.workspace_state.recorded_selection.clone();
+        match selection {
+            RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
+                vim.workspace_state.recorded_count = None;
+                vim.switch_mode(Mode::Visual, false, cx)
+            }
+            RecordedSelection::VisualLine { .. } => {
+                vim.workspace_state.recorded_count = None;
+                vim.switch_mode(Mode::VisualLine, false, cx)
+            }
+            RecordedSelection::VisualBlock { .. } => {
+                vim.workspace_state.recorded_count = None;
+                vim.switch_mode(Mode::VisualBlock, false, cx)
+            }
+            RecordedSelection::None => {
+                if let Some(count) = count {
+                    vim.workspace_state.recorded_count = Some(count);
+                }
+            }
+        }
+
+        Some((actions, editor, selection))
+    }) else {
+        return;
+    };
+
+    match selection {
+        RecordedSelection::SingleLine { cols } => {
+            if cols > 1 {
+                visual_motion(Motion::Right, Some(cols as usize - 1), cx)
+            }
+        }
+        RecordedSelection::Visual { rows, cols } => {
+            visual_motion(
+                Motion::Down {
+                    display_lines: false,
+                },
+                Some(rows as usize),
+                cx,
+            );
+            visual_motion(
+                Motion::StartOfLine {
+                    display_lines: false,
+                },
+                None,
+                cx,
+            );
+            if cols > 1 {
+                visual_motion(Motion::Right, Some(cols as usize - 1), cx)
+            }
+        }
+        RecordedSelection::VisualBlock { rows, cols } => {
+            visual_motion(
+                Motion::Down {
+                    display_lines: false,
+                },
+                Some(rows as usize),
+                cx,
+            );
+            if cols > 1 {
+                visual_motion(Motion::Right, Some(cols as usize - 1), cx);
+            }
+        }
+        RecordedSelection::VisualLine { rows } => {
+            visual_motion(
+                Motion::Down {
+                    display_lines: false,
+                },
+                Some(rows as usize),
+                cx,
+            );
+        }
+        RecordedSelection::None => {}
+    }
+
+    // insert internally uses repeat to handle counts
+    // vim doesn't treat 3a1 as though you literally repeated a1
+    // 3 times, instead it inserts the content thrice at the insert position.
+    if let Some(to_repeat) = repeatable_insert(&actions[0]) {
+        if let Some(ReplayableAction::Action(action)) = actions.last() {
+            if NormalBefore.partial_eq(&**action) {
+                actions.pop();
+            }
+        }
+
+        let mut new_actions = actions.clone();
+        actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());
+
+        let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
+
+        // if we came from insert mode we're just doing repititions 2 onwards.
+        if from_insert_mode {
+            count -= 1;
+            new_actions[0] = actions[0].clone();
+        }
+
+        for _ in 1..count {
+            new_actions.append(actions.clone().as_mut());
+        }
+        new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));
+        actions = new_actions;
+    }
+
+    Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
+    let window = cx.window_handle();
+    cx.spawn(move |mut cx| async move {
+        editor.update(&mut cx, |editor, _| {
+            editor.show_local_selections = false;
+        })?;
+        for action in actions {
+            match action {
+                ReplayableAction::Action(action) => {
+                    if should_replay(&action) {
+                        window.update(&mut cx, |_, cx| cx.dispatch_action(action))
+                    } else {
+                        Ok(())
+                    }
+                }
+                ReplayableAction::Insertion {
+                    text,
+                    utf16_range_to_replace,
+                } => editor.update(&mut cx, |editor, cx| {
+                    editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
+                }),
+            }?
+        }
+        editor.update(&mut cx, |editor, _| {
+            editor.show_local_selections = true;
+        })?;
+        window.update(&mut cx, |_, cx| cx.dispatch_action(EndRepeat.boxed_clone()))
+    })
+    .detach_and_log_err(cx);
+}
+
+// #[cfg(test)]
+// mod test {
+//     use std::sync::Arc;
+
+//     use editor::test::editor_lsp_test_context::EditorLspTestContext;
+//     use futures::StreamExt;
+//     use indoc::indoc;
+
+//     use gpui::{executor::Deterministic, View};
+
+//     use crate::{
+//         state::Mode,
+//         test::{NeovimBackedTestContext, VimTestContext},
+//     };
+
+//     #[gpui::test]
+//     async fn test_dot_repeat(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         // "o"
+//         cx.set_shared_state("ˇhello").await;
+//         cx.simulate_shared_keystrokes(["o", "w", "o", "r", "l", "d", "escape"])
+//             .await;
+//         cx.assert_shared_state("hello\nworlˇd").await;
+//         cx.simulate_shared_keystrokes(["."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("hello\nworld\nworlˇd").await;
+
+//         // "d"
+//         cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;
+//         cx.simulate_shared_keystrokes(["g", "g", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("ˇ\nworld\nrld").await;
+
+//         // "p" (note that it pastes the current clipboard)
+//         cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;
+//         cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])
+//             .await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;
+
+//         // "~" (note that counts apply to the action taken, not . itself)
+//         cx.set_shared_state("ˇthe quick brown fox").await;
+//         cx.simulate_shared_keystrokes(["2", "~", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.set_shared_state("THE ˇquick brown fox").await;
+//         cx.simulate_shared_keystrokes(["3", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.set_shared_state("THE QUIˇck brown fox").await;
+//         deterministic.run_until_parked();
+//         cx.simulate_shared_keystrokes(["."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state("THE QUICK ˇbrown fox").await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeat_ime(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         cx.set_state("hˇllo", Mode::Normal);
+//         cx.simulate_keystrokes(["i"]);
+
+//         // simulate brazilian input for ä.
+//         cx.update_editor(|editor, cx| {
+//             editor.replace_and_mark_text_in_range(None, "\"", Some(1..1), cx);
+//             editor.replace_text_in_range(None, "ä", cx);
+//         });
+//         cx.simulate_keystrokes(["escape"]);
+//         cx.assert_state("hˇällo", Mode::Normal);
+//         cx.simulate_keystrokes(["."]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("hˇäällo", Mode::Normal);
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeat_completion(
+//         deterministic: Arc<Deterministic>,
+//         cx: &mut gpui::TestAppContext,
+//     ) {
+//         let cx = EditorLspTestContext::new_rust(
+//             lsp::ServerCapabilities {
+//                 completion_provider: Some(lsp::CompletionOptions {
+//                     trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
+//                     resolve_provider: Some(true),
+//                     ..Default::default()
+//                 }),
+//                 ..Default::default()
+//             },
+//             cx,
+//         )
+//         .await;
+//         let mut cx = VimTestContext::new_with_lsp(cx, true);
+
+//         cx.set_state(
+//             indoc! {"
+//             onˇe
+//             two
+//             three
+//         "},
+//             Mode::Normal,
+//         );
+
+//         let mut request =
+//             cx.handle_request::<lsp::request::Completion, _, _>(move |_, params, _| async move {
+//                 let position = params.text_document_position.position;
+//                 Ok(Some(lsp::CompletionResponse::Array(vec![
+//                     lsp::CompletionItem {
+//                         label: "first".to_string(),
+//                         text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+//                             range: lsp::Range::new(position.clone(), position.clone()),
+//                             new_text: "first".to_string(),
+//                         })),
+//                         ..Default::default()
+//                     },
+//                     lsp::CompletionItem {
+//                         label: "second".to_string(),
+//                         text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+//                             range: lsp::Range::new(position.clone(), position.clone()),
+//                             new_text: "second".to_string(),
+//                         })),
+//                         ..Default::default()
+//                     },
+//                 ])))
+//             });
+//         cx.simulate_keystrokes(["a", "."]);
+//         request.next().await;
+//         cx.condition(|editor, _| editor.context_menu_visible())
+//             .await;
+//         cx.simulate_keystrokes(["down", "enter", "!", "escape"]);
+
+//         cx.assert_state(
+//             indoc! {"
+//                 one.secondˇ!
+//                 two
+//                 three
+//             "},
+//             Mode::Normal,
+//         );
+//         cx.simulate_keystrokes(["j", "."]);
+//         deterministic.run_until_parked();
+//         cx.assert_state(
+//             indoc! {"
+//                 one.second!
+//                 two.secondˇ!
+//                 three
+//             "},
+//             Mode::Normal,
+//         );
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeat_visual(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         // single-line (3 columns)
+//         cx.set_shared_state(indoc! {
+//             "ˇthe quick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "w", "s", "o", "escape"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇo quick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["j", "w", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "o quick brown
+//             fox ˇops over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["f", "r", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "o quick brown
+//             fox ops oveˇothe lazy dog"
+//         })
+//         .await;
+
+//         // visual
+//         cx.set_shared_state(indoc! {
+//             "the ˇquick brown
+//             fox jumps over
+//             fox jumps over
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "j", "x"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "the ˇumps over
+//             fox jumps over
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "the ˇumps over
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["w", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "the umps ˇumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["j", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "the umps umps over
+//             the ˇog"
+//         })
+//         .await;
+
+//         // block mode (3 rows)
+//         cx.set_shared_state(indoc! {
+//             "ˇthe quick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "j", "j", "shift-i", "o", "escape"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇothe quick brown
+//             ofox jumps over
+//             othe lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "othe quick brown
+//             ofoxˇo jumps over
+//             otheo lazy dog"
+//         })
+//         .await;
+
+//         // line mode
+//         cx.set_shared_state(indoc! {
+//             "ˇthe quick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["shift-v", "shift-r", "o", "escape"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇo
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["j", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             "o
+//             ˇo
+//             the lazy dog"
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_repeat_motion_counts(
+//         deterministic: Arc<Deterministic>,
+//         cx: &mut gpui::TestAppContext,
+//     ) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "ˇthe quick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["3", "d", "3", "l"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇ brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["j", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             " brown
+//             ˇ over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["j", "2", "."]).await;
+//         deterministic.run_until_parked();
+//         cx.assert_shared_state(indoc! {
+//             " brown
+//              over
+//             ˇe lazy dog"
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_record_interrupted(
+//         deterministic: Arc<Deterministic>,
+//         cx: &mut gpui::TestAppContext,
+//     ) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         cx.set_state("ˇhello\n", Mode::Normal);
+//         cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape", "escape"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("ˇjhello\n", Mode::Normal);
+//     }
+// }

crates/vim2/src/normal/scroll.rs 🔗

@@ -0,0 +1,229 @@
+use crate::Vim;
+use editor::{
+    display_map::ToDisplayPoint,
+    scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
+    DisplayPoint, Editor,
+};
+use gpui::{actions, ViewContext};
+use language::Bias;
+use workspace::Workspace;
+
+actions!(
+    vim,
+    [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
+);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
+        scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
+    });
+    workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
+        scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
+    });
+    workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
+        scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
+    });
+    workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
+        scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
+    });
+    workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
+        scroll(cx, true, |c| {
+            if let Some(c) = c {
+                ScrollAmount::Line(c)
+            } else {
+                ScrollAmount::Page(0.5)
+            }
+        })
+    });
+    workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
+        scroll(cx, true, |c| {
+            if let Some(c) = c {
+                ScrollAmount::Line(-c)
+            } else {
+                ScrollAmount::Page(-0.5)
+            }
+        })
+    });
+}
+
+fn scroll(
+    cx: &mut ViewContext<Workspace>,
+    move_cursor: bool,
+    by: fn(c: Option<f32>) -> ScrollAmount,
+) {
+    Vim::update(cx, |vim, cx| {
+        let amount = by(vim.take_count(cx).map(|c| c as f32));
+        vim.update_active_editor(cx, |editor, cx| {
+            scroll_editor(editor, move_cursor, &amount, cx)
+        });
+    })
+}
+
+fn scroll_editor(
+    editor: &mut Editor,
+    preserve_cursor_position: bool,
+    amount: &ScrollAmount,
+    cx: &mut ViewContext<Editor>,
+) {
+    let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
+    let old_top_anchor = editor.scroll_manager.anchor().anchor;
+
+    editor.scroll_screen(amount, cx);
+    if should_move_cursor {
+        let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
+            visible_rows as u32
+        } else {
+            return;
+        };
+
+        let top_anchor = editor.scroll_manager.anchor().anchor;
+
+        editor.change_selections(None, cx, |s| {
+            s.move_with(|map, selection| {
+                let mut head = selection.head();
+                let top = top_anchor.to_display_point(map);
+
+                if preserve_cursor_position {
+                    let old_top = old_top_anchor.to_display_point(map);
+                    let new_row = top.row() + selection.head().row() - old_top.row();
+                    head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left)
+                }
+                let min_row = top.row() + VERTICAL_SCROLL_MARGIN as u32;
+                let max_row = top.row() + visible_rows - VERTICAL_SCROLL_MARGIN as u32 - 1;
+
+                let new_head = if head.row() < min_row {
+                    map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left)
+                } else if head.row() > max_row {
+                    map.clip_point(DisplayPoint::new(max_row, head.column()), Bias::Left)
+                } else {
+                    head
+                };
+                if selection.is_empty() {
+                    selection.collapse_to(new_head, selection.goal)
+                } else {
+                    selection.set_head(new_head, selection.goal)
+                };
+            })
+        });
+    }
+}
+
+// #[cfg(test)]
+// mod test {
+//     use crate::{
+//         state::Mode,
+//         test::{NeovimBackedTestContext, VimTestContext},
+//     };
+//     use gpui::geometry::vector::vec2f;
+//     use indoc::indoc;
+//     use language::Point;
+
+//     #[gpui::test]
+//     async fn test_scroll(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         let window = cx.window;
+//         let line_height = cx.editor(|editor, cx| editor.style().text.line_height(cx.font_cache()));
+//         window.simulate_resize(vec2f(1000., 8.0 * line_height - 1.0), &mut cx);
+
+//         cx.set_state(
+//             indoc!(
+//                 "ˇone
+//                 two
+//                 three
+//                 four
+//                 five
+//                 six
+//                 seven
+//                 eight
+//                 nine
+//                 ten
+//                 eleven
+//                 twelve
+//             "
+//             ),
+//             Mode::Normal,
+//         );
+
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+//         });
+//         cx.simulate_keystrokes(["ctrl-e"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.))
+//         });
+//         cx.simulate_keystrokes(["2", "ctrl-e"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.))
+//         });
+//         cx.simulate_keystrokes(["ctrl-y"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.))
+//         });
+
+//         // does not select in normal mode
+//         cx.simulate_keystrokes(["g", "g"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+//         });
+//         cx.simulate_keystrokes(["ctrl-d"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
+//             assert_eq!(
+//                 editor.selections.newest(cx).range(),
+//                 Point::new(6, 0)..Point::new(6, 0)
+//             )
+//         });
+
+//         // does select in visual mode
+//         cx.simulate_keystrokes(["g", "g"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+//         });
+//         cx.simulate_keystrokes(["v", "ctrl-d"]);
+//         cx.update_editor(|editor, cx| {
+//             assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
+//             assert_eq!(
+//                 editor.selections.newest(cx).range(),
+//                 Point::new(0, 0)..Point::new(6, 1)
+//             )
+//         });
+//     }
+//     #[gpui::test]
+//     async fn test_ctrl_d_u(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_scroll_height(10).await;
+
+//         pub fn sample_text(rows: usize, cols: usize, start_char: char) -> String {
+//             let mut text = String::new();
+//             for row in 0..rows {
+//                 let c: char = (start_char as u32 + row as u32) as u8 as char;
+//                 let mut line = c.to_string().repeat(cols);
+//                 if row < rows - 1 {
+//                     line.push('\n');
+//                 }
+//                 text += &line;
+//             }
+//             text
+//         }
+//         let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
+//         cx.set_shared_state(&content).await;
+
+//         // skip over the scrolloff at the top
+//         // test ctrl-d
+//         cx.simulate_shared_keystrokes(["4", "j", "ctrl-d"]).await;
+//         cx.assert_state_matches().await;
+//         cx.simulate_shared_keystrokes(["ctrl-d"]).await;
+//         cx.assert_state_matches().await;
+//         cx.simulate_shared_keystrokes(["g", "g", "ctrl-d"]).await;
+//         cx.assert_state_matches().await;
+
+//         // test ctrl-u
+//         cx.simulate_shared_keystrokes(["ctrl-u"]).await;
+//         cx.assert_state_matches().await;
+//         cx.simulate_shared_keystrokes(["ctrl-d", "ctrl-d", "4", "j", "ctrl-u", "ctrl-u"])
+//             .await;
+//         cx.assert_state_matches().await;
+//     }
+// }

crates/vim2/src/normal/search.rs 🔗

@@ -0,0 +1,495 @@
+use gpui::{actions, impl_actions, ViewContext};
+use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
+use serde_derive::Deserialize;
+use workspace::{searchable::Direction, Workspace};
+
+use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct MoveToNext {
+    #[serde(default)]
+    partial_word: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct MoveToPrev {
+    #[serde(default)]
+    partial_word: bool,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+pub(crate) struct Search {
+    #[serde(default)]
+    backwards: bool,
+}
+
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+pub struct FindCommand {
+    pub query: String,
+    pub backwards: bool,
+}
+
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+pub struct ReplaceCommand {
+    pub query: String,
+}
+
+#[derive(Debug, Default)]
+struct Replacement {
+    search: String,
+    replacement: String,
+    should_replace_all: bool,
+    is_case_sensitive: bool,
+}
+
+actions!(vim, [SearchSubmit]);
+impl_actions!(
+    vim,
+    [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
+);
+
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(move_to_next);
+    workspace.register_action(move_to_prev);
+    workspace.register_action(search);
+    workspace.register_action(search_submit);
+    workspace.register_action(search_deploy);
+
+    workspace.register_action(find_command);
+    workspace.register_action(replace_command);
+}
+
+fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
+    move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
+}
+
+fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {
+    move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
+}
+
+fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
+    let pane = workspace.active_pane().clone();
+    let direction = if action.backwards {
+        Direction::Prev
+    } else {
+        Direction::Next
+    };
+    Vim::update(cx, |vim, cx| {
+        let count = vim.take_count(cx).unwrap_or(1);
+        pane.update(cx, |pane, cx| {
+            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
+                search_bar.update(cx, |search_bar, cx| {
+                    if !search_bar.show(cx) {
+                        return;
+                    }
+                    let query = search_bar.query(cx);
+
+                    search_bar.select_query(cx);
+                    cx.focus_self();
+
+                    if query.is_empty() {
+                        search_bar.set_replacement(None, cx);
+                        search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);
+                        search_bar.activate_search_mode(SearchMode::Regex, cx);
+                    }
+                    vim.workspace_state.search = SearchState {
+                        direction,
+                        count,
+                        initial_query: query.clone(),
+                    };
+                });
+            }
+        })
+    })
+}
+
+// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
+fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
+    cx.propagate();
+}
+
+fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        let pane = workspace.active_pane().clone();
+        pane.update(cx, |pane, cx| {
+            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
+                search_bar.update(cx, |search_bar, cx| {
+                    let state = &mut vim.workspace_state.search;
+                    let mut count = state.count;
+                    let direction = state.direction;
+
+                    // in the case that the query has changed, the search bar
+                    // will have selected the next match already.
+                    if (search_bar.query(cx) != state.initial_query)
+                        && state.direction == Direction::Next
+                    {
+                        count = count.saturating_sub(1)
+                    }
+                    state.count = 1;
+                    search_bar.select_match(direction, count, cx);
+                    search_bar.focus_editor(&Default::default(), cx);
+                });
+            }
+        });
+    })
+}
+
+pub fn move_to_internal(
+    workspace: &mut Workspace,
+    direction: Direction,
+    whole_word: bool,
+    cx: &mut ViewContext<Workspace>,
+) {
+    Vim::update(cx, |vim, cx| {
+        let pane = workspace.active_pane().clone();
+        let count = vim.take_count(cx).unwrap_or(1);
+        pane.update(cx, |pane, cx| {
+            if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
+                let search = search_bar.update(cx, |search_bar, cx| {
+                    let mut options = SearchOptions::CASE_SENSITIVE;
+                    options.set(SearchOptions::WHOLE_WORD, whole_word);
+                    if search_bar.show(cx) {
+                        search_bar
+                            .query_suggestion(cx)
+                            .map(|query| search_bar.search(&query, Some(options), cx))
+                    } else {
+                        None
+                    }
+                });
+
+                if let Some(search) = search {
+                    let search_bar = search_bar.downgrade();
+                    cx.spawn(|_, mut cx| async move {
+                        search.await?;
+                        search_bar.update(&mut cx, |search_bar, cx| {
+                            search_bar.select_match(direction, count, cx)
+                        })?;
+                        anyhow::Ok(())
+                    })
+                    .detach_and_log_err(cx);
+                }
+            }
+        });
+        vim.clear_operator(cx);
+    });
+}
+
+fn find_command(workspace: &mut Workspace, action: &FindCommand, cx: &mut ViewContext<Workspace>) {
+    let pane = workspace.active_pane().clone();
+    pane.update(cx, |pane, cx| {
+        if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
+            let search = search_bar.update(cx, |search_bar, cx| {
+                if !search_bar.show(cx) {
+                    return None;
+                }
+                let mut query = action.query.clone();
+                if query == "" {
+                    query = search_bar.query(cx);
+                };
+
+                search_bar.activate_search_mode(SearchMode::Regex, cx);
+                Some(search_bar.search(&query, Some(SearchOptions::CASE_SENSITIVE), cx))
+            });
+            let Some(search) = search else { return };
+            let search_bar = search_bar.downgrade();
+            let direction = if action.backwards {
+                Direction::Prev
+            } else {
+                Direction::Next
+            };
+            cx.spawn(|_, mut cx| async move {
+                search.await?;
+                search_bar.update(&mut cx, |search_bar, cx| {
+                    search_bar.select_match(direction, 1, cx)
+                })?;
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
+        }
+    })
+}
+
+fn replace_command(
+    workspace: &mut Workspace,
+    action: &ReplaceCommand,
+    cx: &mut ViewContext<Workspace>,
+) {
+    let replacement = parse_replace_all(&action.query);
+    let pane = workspace.active_pane().clone();
+    pane.update(cx, |pane, cx| {
+        let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
+            return;
+        };
+        let search = search_bar.update(cx, |search_bar, cx| {
+            if !search_bar.show(cx) {
+                return None;
+            }
+
+            let mut options = SearchOptions::default();
+            if replacement.is_case_sensitive {
+                options.set(SearchOptions::CASE_SENSITIVE, true)
+            }
+            let search = if replacement.search == "" {
+                search_bar.query(cx)
+            } else {
+                replacement.search
+            };
+
+            search_bar.set_replacement(Some(&replacement.replacement), cx);
+            search_bar.activate_search_mode(SearchMode::Regex, cx);
+            Some(search_bar.search(&search, Some(options), cx))
+        });
+        let Some(search) = search else { return };
+        let search_bar = search_bar.downgrade();
+        cx.spawn(|_, mut cx| async move {
+            search.await?;
+            search_bar.update(&mut cx, |search_bar, cx| {
+                if replacement.should_replace_all {
+                    search_bar.select_last_match(cx);
+                    search_bar.replace_all(&Default::default(), cx);
+                    Vim::update(cx, |vim, cx| {
+                        move_cursor(
+                            vim,
+                            Motion::StartOfLine {
+                                display_lines: false,
+                            },
+                            None,
+                            cx,
+                        )
+                    })
+                }
+            })?;
+            anyhow::Ok(())
+        })
+        .detach_and_log_err(cx);
+    })
+}
+
+// convert a vim query into something more usable by zed.
+// we don't attempt to fully convert between the two regex syntaxes,
+// but we do flip \( and \) to ( and ) (and vice-versa) in the pattern,
+// and convert \0..\9 to $0..$9 in the replacement so that common idioms work.
+fn parse_replace_all(query: &str) -> Replacement {
+    let mut chars = query.chars();
+    if Some('%') != chars.next() || Some('s') != chars.next() {
+        return Replacement::default();
+    }
+
+    let Some(delimeter) = chars.next() else {
+        return Replacement::default();
+    };
+
+    let mut search = String::new();
+    let mut replacement = String::new();
+    let mut flags = String::new();
+
+    let mut buffer = &mut search;
+
+    let mut escaped = false;
+    // 0 - parsing search
+    // 1 - parsing replacement
+    // 2 - parsing flags
+    let mut phase = 0;
+
+    for c in chars {
+        if escaped {
+            escaped = false;
+            if phase == 1 && c.is_digit(10) {
+                buffer.push('$')
+            // unescape escaped parens
+            } else if phase == 0 && c == '(' || c == ')' {
+            } else if c != delimeter {
+                buffer.push('\\')
+            }
+            buffer.push(c)
+        } else if c == '\\' {
+            escaped = true;
+        } else if c == delimeter {
+            if phase == 0 {
+                buffer = &mut replacement;
+                phase = 1;
+            } else if phase == 1 {
+                buffer = &mut flags;
+                phase = 2;
+            } else {
+                break;
+            }
+        } else {
+            // escape unescaped parens
+            if phase == 0 && c == '(' || c == ')' {
+                buffer.push('\\')
+            }
+            buffer.push(c)
+        }
+    }
+
+    let mut replacement = Replacement {
+        search,
+        replacement,
+        should_replace_all: true,
+        is_case_sensitive: true,
+    };
+
+    for c in flags.chars() {
+        match c {
+            'g' | 'I' => {}
+            'c' | 'n' => replacement.should_replace_all = false,
+            'i' => replacement.is_case_sensitive = false,
+            _ => {}
+        }
+    }
+
+    replacement
+}
+
+// #[cfg(test)]
+// mod test {
+//     use std::sync::Arc;
+
+//     use editor::DisplayPoint;
+//     use search::BufferSearchBar;
+
+//     use crate::{state::Mode, test::VimTestContext};
+
+//     #[gpui::test]
+//     async fn test_move_to_next(
+//         cx: &mut gpui::TestAppContext,
+//         deterministic: Arc<gpui::executor::Deterministic>,
+//     ) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+//         cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["*"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["*"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["#"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["#"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["2", "*"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["g", "*"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["n"]);
+//         cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
+
+//         cx.simulate_keystrokes(["g", "#"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
+//     }
+
+//     #[gpui::test]
+//     async fn test_search(
+//         cx: &mut gpui::TestAppContext,
+//         deterministic: Arc<gpui::executor::Deterministic>,
+//     ) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         cx.set_state("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");
+//         });
+
+//         deterministic.run_until_parked();
+
+//         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("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
+
+//         // n to go to next/N to go to previous
+//         cx.simulate_keystrokes(["n"]);
+//         cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
+//         cx.simulate_keystrokes(["shift-n"]);
+//         cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
+
+//         // ?<enter> to go to previous
+//         cx.simulate_keystrokes(["?", "enter"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
+//         cx.simulate_keystrokes(["?", "enter"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
+
+//         // /<enter> to go to next
+//         cx.simulate_keystrokes(["/", "enter"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
+
+//         // ?{search}<enter> to search backwards
+//         cx.simulate_keystrokes(["?", "b", "enter"]);
+//         deterministic.run_until_parked();
+//         cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
+
+//         // works with counts
+//         cx.simulate_keystrokes(["4", "/", "c"]);
+//         deterministic.run_until_parked();
+//         cx.simulate_keystrokes(["enter"]);
+//         cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
+
+//         // check that searching resumes from cursor, not previous match
+//         cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
+//         cx.simulate_keystrokes(["/", "d"]);
+//         deterministic.run_until_parked();
+//         cx.simulate_keystrokes(["enter"]);
+//         cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
+//         cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
+//         cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
+//         cx.simulate_keystrokes(["/", "b"]);
+//         deterministic.run_until_parked();
+//         cx.simulate_keystrokes(["enter"]);
+//         cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
+//     }
+
+//     #[gpui::test]
+//     async fn test_non_vim_search(
+//         cx: &mut gpui::TestAppContext,
+//         deterministic: Arc<gpui::executor::Deterministic>,
+//     ) {
+//         let mut cx = VimTestContext::new(cx, false).await;
+//         cx.set_state("ˇone one one one", Mode::Normal);
+//         cx.simulate_keystrokes(["cmd-f"]);
+//         deterministic.run_until_parked();
+
+//         cx.assert_editor_state("«oneˇ» one one one");
+//         cx.simulate_keystrokes(["enter"]);
+//         cx.assert_editor_state("one «oneˇ» one one");
+//         cx.simulate_keystrokes(["shift-enter"]);
+//         cx.assert_editor_state("«oneˇ» one one one");
+//     }
+// }

crates/vim2/src/normal/substitute.rs 🔗

@@ -0,0 +1,276 @@
+use editor::movement;
+use gpui::{actions, ViewContext, WindowContext};
+use language::Point;
+use workspace::Workspace;
+
+use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
+
+actions!(vim, [Substitute, SubstituteLine]);
+
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.start_recording(cx);
+            let count = vim.take_count(cx);
+            substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);
+        })
+    });
+
+    workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.start_recording(cx);
+            if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
+                vim.switch_mode(Mode::VisualLine, false, cx)
+            }
+            let count = vim.take_count(cx);
+            substitute(vim, count, true, cx)
+        })
+    });
+}
+
+pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
+    vim.update_active_editor(cx, |editor, cx| {
+        editor.set_clip_at_line_ends(false, cx);
+        editor.transact(cx, |editor, cx| {
+            let text_layout_details = editor.text_layout_details(cx);
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|map, selection| {
+                    if selection.start == selection.end {
+                        Motion::Right.expand_selection(
+                            map,
+                            selection,
+                            count,
+                            true,
+                            &text_layout_details,
+                        );
+                    }
+                    if line_mode {
+                        // in Visual mode when the selection contains the newline at the end
+                        // of the line, we should exclude it.
+                        if !selection.is_empty() && selection.end.column() == 0 {
+                            selection.end = movement::left(map, selection.end);
+                        }
+                        Motion::CurrentLine.expand_selection(
+                            map,
+                            selection,
+                            None,
+                            false,
+                            &text_layout_details,
+                        );
+                        if let Some((point, _)) = (Motion::FirstNonWhitespace {
+                            display_lines: false,
+                        })
+                        .move_point(
+                            map,
+                            selection.start,
+                            selection.goal,
+                            None,
+                            &text_layout_details,
+                        ) {
+                            selection.start = point;
+                        }
+                    }
+                })
+            });
+            copy_selections_content(editor, line_mode, cx);
+            let selections = editor.selections.all::<Point>(cx).into_iter();
+            let edits = selections.map(|selection| (selection.start..selection.end, ""));
+            editor.edit(edits, cx);
+        });
+    });
+    vim.switch_mode(Mode::Insert, true, cx);
+}
+
+// #[cfg(test)]
+// mod test {
+//     use crate::{
+//         state::Mode,
+//         test::{NeovimBackedTestContext, VimTestContext},
+//     };
+//     use indoc::indoc;
+
+//     #[gpui::test]
+//     async fn test_substitute(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         // supports a single cursor
+//         cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
+//         cx.simulate_keystrokes(["s", "x"]);
+//         cx.assert_editor_state("xˇbc\n");
+
+//         // supports a selection
+//         cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
+//         cx.assert_editor_state("a«bcˇ»\n");
+//         cx.simulate_keystrokes(["s", "x"]);
+//         cx.assert_editor_state("axˇ\n");
+
+//         // supports counts
+//         cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
+//         cx.simulate_keystrokes(["2", "s", "x"]);
+//         cx.assert_editor_state("xˇc\n");
+
+//         // supports multiple cursors
+//         cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
+//         cx.simulate_keystrokes(["2", "s", "x"]);
+//         cx.assert_editor_state("axˇdexˇg\n");
+
+//         // does not read beyond end of line
+//         cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
+//         cx.simulate_keystrokes(["5", "s", "x"]);
+//         cx.assert_editor_state("xˇ\n");
+
+//         // it handles multibyte characters
+//         cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal);
+//         cx.simulate_keystrokes(["4", "s"]);
+//         cx.assert_editor_state("ˇ\n");
+
+//         // should transactionally undo selection changes
+//         cx.simulate_keystrokes(["escape", "u"]);
+//         cx.assert_editor_state("ˇcàfé\n");
+
+//         // it handles visual line mode
+//         cx.set_state(
+//             indoc! {"
+//             alpha
+//               beˇta
+//             gamma"},
+//             Mode::Normal,
+//         );
+//         cx.simulate_keystrokes(["shift-v", "s"]);
+//         cx.assert_editor_state(indoc! {"
+//             alpha
+//               ˇ
+//             gamma"});
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_change(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state("The quick ˇbrown").await;
+//         cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
+//         cx.assert_shared_state("The quick ˇ").await;
+
+//         cx.set_shared_state(indoc! {"
+//             The ˇquick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "w", "j", "c"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             The ˇver
+//             the lazy dog"})
+//             .await;
+
+//         let cases = cx.each_marked_position(indoc! {"
+//             The ˇquick brown
+//             fox jumps ˇover
+//             the ˇlazy dog"});
+//         for initial_state in cases {
+//             cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
+//                 .await;
+//             cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
+//                 .await;
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx)
+//             .await
+//             .binding(["shift-v", "c"]);
+//         cx.assert(indoc! {"
+//             The quˇick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         // Test pasting code copied on change
+//         cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
+//         cx.assert_state_matches().await;
+
+//         cx.assert_all(indoc! {"
+//             The quick brown
+//             fox juˇmps over
+//             the laˇzy dog"})
+//             .await;
+//         let mut cx = cx.binding(["shift-v", "j", "c"]);
+//         cx.assert(indoc! {"
+//             The quˇick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         // Test pasting code copied on delete
+//         cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
+//         cx.assert_state_matches().await;
+
+//         cx.assert_all(indoc! {"
+//             The quick brown
+//             fox juˇmps over
+//             the laˇzy dog"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_substitute_line(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         let initial_state = indoc! {"
+//                     The quick brown
+//                     fox juˇmps over
+//                     the lazy dog
+//                     "};
+
+//         // normal mode
+//         cx.set_shared_state(initial_state).await;
+//         cx.simulate_shared_keystrokes(["shift-s", "o"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             oˇ
+//             the lazy dog
+//             "})
+//             .await;
+
+//         // visual mode
+//         cx.set_shared_state(initial_state).await;
+//         cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             oˇ
+//             the lazy dog
+//             "})
+//             .await;
+
+//         // visual block mode
+//         cx.set_shared_state(initial_state).await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             oˇ
+//             "})
+//             .await;
+
+//         // visual mode including newline
+//         cx.set_shared_state(initial_state).await;
+//         cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//             oˇ
+//             the lazy dog
+//             "})
+//             .await;
+
+//         // indentation
+//         cx.set_neovim_option("shiftwidth=4").await;
+//         cx.set_shared_state(initial_state).await;
+//         cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//             The quick brown
+//                 oˇ
+//             the lazy dog
+//             "})
+//             .await;
+//     }
+// }

crates/vim2/src/normal/yank.rs 🔗

@@ -0,0 +1,50 @@
+use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
+use collections::HashMap;
+use gpui::WindowContext;
+
+pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
+    vim.update_active_editor(cx, |editor, cx| {
+        let text_layout_details = editor.text_layout_details(cx);
+        editor.transact(cx, |editor, cx| {
+            editor.set_clip_at_line_ends(false, cx);
+            let mut original_positions: HashMap<_, _> = Default::default();
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|map, selection| {
+                    let original_position = (selection.head(), selection.goal);
+                    original_positions.insert(selection.id, original_position);
+                    motion.expand_selection(map, selection, times, true, &text_layout_details);
+                });
+            });
+            copy_selections_content(editor, motion.linewise(), cx);
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|_, selection| {
+                    let (head, goal) = original_positions.remove(&selection.id).unwrap();
+                    selection.collapse_to(head, goal);
+                });
+            });
+        });
+    });
+}
+
+pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
+    vim.update_active_editor(cx, |editor, cx| {
+        editor.transact(cx, |editor, cx| {
+            editor.set_clip_at_line_ends(false, cx);
+            let mut original_positions: HashMap<_, _> = Default::default();
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|map, selection| {
+                    let original_position = (selection.head(), selection.goal);
+                    object.expand_selection(map, selection, around);
+                    original_positions.insert(selection.id, original_position);
+                });
+            });
+            copy_selections_content(editor, false, cx);
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|_, selection| {
+                    let (head, goal) = original_positions.remove(&selection.id).unwrap();
+                    selection.collapse_to(head, goal);
+                });
+            });
+        });
+    });
+}

crates/vim2/src/object.rs 🔗

@@ -0,0 +1,1025 @@
+use std::ops::Range;
+
+use editor::{
+    char_kind,
+    display_map::{DisplaySnapshot, ToDisplayPoint},
+    movement::{self, FindRange},
+    Bias, CharKind, DisplayPoint,
+};
+use gpui::{actions, impl_actions, ViewContext, WindowContext};
+use language::Selection;
+use serde::Deserialize;
+use workspace::Workspace;
+
+use crate::{motion::right, normal::normal_object, state::Mode, visual::visual_object, Vim};
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum Object {
+    Word { ignore_punctuation: bool },
+    Sentence,
+    Quotes,
+    BackQuotes,
+    DoubleQuotes,
+    VerticalBars,
+    Parentheses,
+    SquareBrackets,
+    CurlyBrackets,
+    AngleBrackets,
+}
+
+#[derive(Clone, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+struct Word {
+    #[serde(default)]
+    ignore_punctuation: bool,
+}
+
+impl_actions!(vim, [Word]);
+
+actions!(
+    vim,
+    [
+        Sentence,
+        Quotes,
+        BackQuotes,
+        DoubleQuotes,
+        VerticalBars,
+        Parentheses,
+        SquareBrackets,
+        CurlyBrackets,
+        AngleBrackets
+    ]
+);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(
+        |_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
+            object(Object::Word { ignore_punctuation }, cx)
+        },
+    );
+    workspace
+        .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
+    workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
+    workspace
+        .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
+    workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
+        object(Object::DoubleQuotes, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
+        object(Object::Parentheses, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
+        object(Object::SquareBrackets, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
+        object(Object::CurlyBrackets, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
+        object(Object::AngleBrackets, cx)
+    });
+    workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
+        object(Object::VerticalBars, cx)
+    });
+}
+
+fn object(object: Object, cx: &mut WindowContext) {
+    match Vim::read(cx).state().mode {
+        Mode::Normal => normal_object(object, cx),
+        Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_object(object, cx),
+        Mode::Insert => {
+            // Shouldn't execute a text object in insert mode. Ignoring
+        }
+    }
+}
+
+impl Object {
+    pub fn is_multiline(self) -> bool {
+        match self {
+            Object::Word { .. }
+            | Object::Quotes
+            | Object::BackQuotes
+            | Object::VerticalBars
+            | Object::DoubleQuotes => false,
+            Object::Sentence
+            | Object::Parentheses
+            | Object::AngleBrackets
+            | Object::CurlyBrackets
+            | Object::SquareBrackets => true,
+        }
+    }
+
+    pub fn always_expands_both_ways(self) -> bool {
+        match self {
+            Object::Word { .. } | Object::Sentence => false,
+            Object::Quotes
+            | Object::BackQuotes
+            | Object::DoubleQuotes
+            | Object::VerticalBars
+            | Object::Parentheses
+            | Object::SquareBrackets
+            | Object::CurlyBrackets
+            | Object::AngleBrackets => true,
+        }
+    }
+
+    pub fn target_visual_mode(self, current_mode: Mode) -> Mode {
+        match self {
+            Object::Word { .. } if current_mode == Mode::VisualLine => Mode::Visual,
+            Object::Word { .. } => current_mode,
+            Object::Sentence
+            | Object::Quotes
+            | Object::BackQuotes
+            | Object::DoubleQuotes
+            | Object::VerticalBars
+            | Object::Parentheses
+            | Object::SquareBrackets
+            | Object::CurlyBrackets
+            | Object::AngleBrackets => Mode::Visual,
+        }
+    }
+
+    pub fn range(
+        self,
+        map: &DisplaySnapshot,
+        relative_to: DisplayPoint,
+        around: bool,
+    ) -> Option<Range<DisplayPoint>> {
+        match self {
+            Object::Word { ignore_punctuation } => {
+                if around {
+                    around_word(map, relative_to, ignore_punctuation)
+                } else {
+                    in_word(map, relative_to, ignore_punctuation)
+                }
+            }
+            Object::Sentence => sentence(map, relative_to, around),
+            Object::Quotes => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'')
+            }
+            Object::BackQuotes => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')
+            }
+            Object::DoubleQuotes => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
+            }
+            Object::VerticalBars => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '|', '|')
+            }
+            Object::Parentheses => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
+            }
+            Object::SquareBrackets => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')
+            }
+            Object::CurlyBrackets => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '{', '}')
+            }
+            Object::AngleBrackets => {
+                surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
+            }
+        }
+    }
+
+    pub fn expand_selection(
+        self,
+        map: &DisplaySnapshot,
+        selection: &mut Selection<DisplayPoint>,
+        around: bool,
+    ) -> bool {
+        if let Some(range) = self.range(map, selection.head(), around) {
+            selection.start = range.start;
+            selection.end = range.end;
+            true
+        } else {
+            false
+        }
+    }
+}
+
+/// Return a range that surrounds the word relative_to is in
+/// If relative_to is at the start of a word, return the word.
+/// If relative_to is between words, return the space between
+fn in_word(
+    map: &DisplaySnapshot,
+    relative_to: DisplayPoint,
+    ignore_punctuation: bool,
+) -> Option<Range<DisplayPoint>> {
+    // Use motion::right so that we consider the character under the cursor when looking for the start
+    let scope = map
+        .buffer_snapshot
+        .language_scope_at(relative_to.to_point(map));
+    let start = movement::find_preceding_boundary(
+        map,
+        right(map, relative_to, 1),
+        movement::FindRange::SingleLine,
+        |left, right| {
+            char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
+                != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
+        },
+    );
+
+    let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
+        char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
+            != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
+    });
+
+    Some(start..end)
+}
+
+/// Return a range that surrounds the word and following whitespace
+/// relative_to is in.
+/// If relative_to is at the start of a word, return the word and following whitespace.
+/// If relative_to is between words, return the whitespace back and the following word
+
+/// if in word
+///   delete that word
+///   if there is whitespace following the word, delete that as well
+///   otherwise, delete any preceding whitespace
+/// otherwise
+///   delete whitespace around cursor
+///   delete word following the cursor
+fn around_word(
+    map: &DisplaySnapshot,
+    relative_to: DisplayPoint,
+    ignore_punctuation: bool,
+) -> Option<Range<DisplayPoint>> {
+    let scope = map
+        .buffer_snapshot
+        .language_scope_at(relative_to.to_point(map));
+    let in_word = map
+        .chars_at(relative_to)
+        .next()
+        .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
+        .unwrap_or(false);
+
+    if in_word {
+        around_containing_word(map, relative_to, ignore_punctuation)
+    } else {
+        around_next_word(map, relative_to, ignore_punctuation)
+    }
+}
+
+fn around_containing_word(
+    map: &DisplaySnapshot,
+    relative_to: DisplayPoint,
+    ignore_punctuation: bool,
+) -> Option<Range<DisplayPoint>> {
+    in_word(map, relative_to, ignore_punctuation)
+        .map(|range| expand_to_include_whitespace(map, range, true))
+}
+
+fn around_next_word(
+    map: &DisplaySnapshot,
+    relative_to: DisplayPoint,
+    ignore_punctuation: bool,
+) -> Option<Range<DisplayPoint>> {
+    let scope = map
+        .buffer_snapshot
+        .language_scope_at(relative_to.to_point(map));
+    // Get the start of the word
+    let start = movement::find_preceding_boundary(
+        map,
+        right(map, relative_to, 1),
+        FindRange::SingleLine,
+        |left, right| {
+            char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
+                != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
+        },
+    );
+
+    let mut word_found = false;
+    let end = movement::find_boundary(map, relative_to, FindRange::MultiLine, |left, right| {
+        let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
+        let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+
+        let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
+
+        if right_kind != CharKind::Whitespace {
+            word_found = true;
+        }
+
+        found
+    });
+
+    Some(start..end)
+}
+
+fn sentence(
+    map: &DisplaySnapshot,
+    relative_to: DisplayPoint,
+    around: bool,
+) -> Option<Range<DisplayPoint>> {
+    let mut start = None;
+    let mut previous_end = relative_to;
+
+    let mut chars = map.chars_at(relative_to).peekable();
+
+    // Search backwards for the previous sentence end or current sentence start. Include the character under relative_to
+    for (char, point) in chars
+        .peek()
+        .cloned()
+        .into_iter()
+        .chain(map.reverse_chars_at(relative_to))
+    {
+        if is_sentence_end(map, point) {
+            break;
+        }
+
+        if is_possible_sentence_start(char) {
+            start = Some(point);
+        }
+
+        previous_end = point;
+    }
+
+    // Search forward for the end of the current sentence or if we are between sentences, the start of the next one
+    let mut end = relative_to;
+    for (char, point) in chars {
+        if start.is_none() && is_possible_sentence_start(char) {
+            if around {
+                start = Some(point);
+                continue;
+            } else {
+                end = point;
+                break;
+            }
+        }
+
+        end = point;
+        *end.column_mut() += char.len_utf8() as u32;
+        end = map.clip_point(end, Bias::Left);
+
+        if is_sentence_end(map, end) {
+            break;
+        }
+    }
+
+    let mut range = start.unwrap_or(previous_end)..end;
+    if around {
+        range = expand_to_include_whitespace(map, range, false);
+    }
+
+    Some(range)
+}
+
+fn is_possible_sentence_start(character: char) -> bool {
+    !character.is_whitespace() && character != '.'
+}
+
+const SENTENCE_END_PUNCTUATION: &[char] = &['.', '!', '?'];
+const SENTENCE_END_FILLERS: &[char] = &[')', ']', '"', '\''];
+const SENTENCE_END_WHITESPACE: &[char] = &[' ', '\t', '\n'];
+fn is_sentence_end(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
+    let mut next_chars = map.chars_at(point).peekable();
+    if let Some((char, _)) = next_chars.next() {
+        // We are at a double newline. This position is a sentence end.
+        if char == '\n' && next_chars.peek().map(|(c, _)| c == &'\n').unwrap_or(false) {
+            return true;
+        }
+
+        // The next text is not a valid whitespace. This is not a sentence end
+        if !SENTENCE_END_WHITESPACE.contains(&char) {
+            return false;
+        }
+    }
+
+    for (char, _) in map.reverse_chars_at(point) {
+        if SENTENCE_END_PUNCTUATION.contains(&char) {
+            return true;
+        }
+
+        if !SENTENCE_END_FILLERS.contains(&char) {
+            return false;
+        }
+    }
+
+    return false;
+}
+
+/// Expands the passed range to include whitespace on one side or the other in a line. Attempts to add the
+/// whitespace to the end first and falls back to the start if there was none.
+fn expand_to_include_whitespace(
+    map: &DisplaySnapshot,
+    mut range: Range<DisplayPoint>,
+    stop_at_newline: bool,
+) -> Range<DisplayPoint> {
+    let mut whitespace_included = false;
+
+    let mut chars = map.chars_at(range.end).peekable();
+    while let Some((char, point)) = chars.next() {
+        if char == '\n' && stop_at_newline {
+            break;
+        }
+
+        if char.is_whitespace() {
+            // Set end to the next display_point or the character position after the current display_point
+            range.end = chars.peek().map(|(_, point)| *point).unwrap_or_else(|| {
+                let mut end = point;
+                *end.column_mut() += char.len_utf8() as u32;
+                map.clip_point(end, Bias::Left)
+            });
+
+            if char != '\n' {
+                whitespace_included = true;
+            }
+        } else {
+            // Found non whitespace. Quit out.
+            break;
+        }
+    }
+
+    if !whitespace_included {
+        for (char, point) in map.reverse_chars_at(range.start) {
+            if char == '\n' && stop_at_newline {
+                break;
+            }
+
+            if !char.is_whitespace() {
+                break;
+            }
+
+            range.start = point;
+        }
+    }
+
+    range
+}
+
+fn surrounding_markers(
+    map: &DisplaySnapshot,
+    relative_to: DisplayPoint,
+    around: bool,
+    search_across_lines: bool,
+    open_marker: char,
+    close_marker: char,
+) -> Option<Range<DisplayPoint>> {
+    let point = relative_to.to_offset(map, Bias::Left);
+
+    let mut matched_closes = 0;
+    let mut opening = None;
+
+    if let Some((ch, range)) = movement::chars_after(map, point).next() {
+        if ch == open_marker {
+            if open_marker == close_marker {
+                let mut total = 0;
+                for (ch, _) in movement::chars_before(map, point) {
+                    if ch == '\n' {
+                        break;
+                    }
+                    if ch == open_marker {
+                        total += 1;
+                    }
+                }
+                if total % 2 == 0 {
+                    opening = Some(range)
+                }
+            } else {
+                opening = Some(range)
+            }
+        }
+    }
+
+    if opening.is_none() {
+        for (ch, range) in movement::chars_before(map, point) {
+            if ch == '\n' && !search_across_lines {
+                break;
+            }
+
+            if ch == open_marker {
+                if matched_closes == 0 {
+                    opening = Some(range);
+                    break;
+                }
+                matched_closes -= 1;
+            } else if ch == close_marker {
+                matched_closes += 1
+            }
+        }
+    }
+
+    if opening.is_none() {
+        for (ch, range) in movement::chars_after(map, point) {
+            if ch == open_marker {
+                opening = Some(range);
+                break;
+            } else if ch == close_marker {
+                break;
+            }
+        }
+    }
+
+    let Some(mut opening) = opening else {
+        return None;
+    };
+
+    let mut matched_opens = 0;
+    let mut closing = None;
+
+    for (ch, range) in movement::chars_after(map, opening.end) {
+        if ch == '\n' && !search_across_lines {
+            break;
+        }
+
+        if ch == close_marker {
+            if matched_opens == 0 {
+                closing = Some(range);
+                break;
+            }
+            matched_opens -= 1;
+        } else if ch == open_marker {
+            matched_opens += 1;
+        }
+    }
+
+    let Some(mut closing) = closing else {
+        return None;
+    };
+
+    if around && !search_across_lines {
+        let mut found = false;
+
+        for (ch, range) in movement::chars_after(map, closing.end) {
+            if ch.is_whitespace() && ch != '\n' {
+                found = true;
+                closing.end = range.end;
+            } else {
+                break;
+            }
+        }
+
+        if !found {
+            for (ch, range) in movement::chars_before(map, opening.start) {
+                if ch.is_whitespace() && ch != '\n' {
+                    opening.start = range.start
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+
+    if !around && search_across_lines {
+        if let Some((ch, range)) = movement::chars_after(map, opening.end).next() {
+            if ch == '\n' {
+                opening.end = range.end
+            }
+        }
+
+        for (ch, range) in movement::chars_before(map, closing.start) {
+            if !ch.is_whitespace() {
+                break;
+            }
+            if ch != '\n' {
+                closing.start = range.start
+            }
+        }
+    }
+
+    let result = if around {
+        opening.start..closing.end
+    } else {
+        opening.end..closing.start
+    };
+
+    Some(
+        map.clip_point(result.start.to_display_point(map), Bias::Left)
+            ..map.clip_point(result.end.to_display_point(map), Bias::Right),
+    )
+}
+
+// #[cfg(test)]
+// mod test {
+//     use indoc::indoc;
+
+//     use crate::{
+//         state::Mode,
+//         test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
+//     };
+
+//     const WORD_LOCATIONS: &'static str = indoc! {"
+//         The quick ˇbrowˇnˇ•••
+//         fox ˇjuˇmpsˇ over
+//         the lazy dogˇ••
+//         ˇ
+//         ˇ
+//         ˇ
+//         Thˇeˇ-ˇquˇickˇ ˇbrownˇ•
+//         ˇ••
+//         ˇ••
+//         ˇ  fox-jumpˇs over
+//         the lazy dogˇ•
+//         ˇ
+//         "
+//     };
+
+//     #[gpui::test]
+//     async fn test_change_word_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.assert_binding_matches_all(["c", "i", "w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all(["c", "i", "shift-w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all(["c", "a", "w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all(["c", "a", "shift-w"], WORD_LOCATIONS)
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_word_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.assert_binding_matches_all(["d", "i", "w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all(["d", "i", "shift-w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all(["d", "a", "w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all(["d", "a", "shift-w"], WORD_LOCATIONS)
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         /*
+//                 cx.set_shared_state("The quick ˇbrown\nfox").await;
+//                 cx.simulate_shared_keystrokes(["v"]).await;
+//                 cx.assert_shared_state("The quick «bˇ»rown\nfox").await;
+//                 cx.simulate_shared_keystrokes(["i", "w"]).await;
+//                 cx.assert_shared_state("The quick «brownˇ»\nfox").await;
+//         */
+//         cx.set_shared_state("The quick brown\nˇ\nfox").await;
+//         cx.simulate_shared_keystrokes(["v"]).await;
+//         cx.assert_shared_state("The quick brown\n«\nˇ»fox").await;
+//         cx.simulate_shared_keystrokes(["i", "w"]).await;
+//         cx.assert_shared_state("The quick brown\n«\nˇ»fox").await;
+
+//         cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
+//             .await;
+//         cx.assert_binding_matches_all_exempted(
+//             ["v", "h", "i", "w"],
+//             WORD_LOCATIONS,
+//             ExemptionFeatures::NonEmptyVisualTextObjects,
+//         )
+//         .await;
+//         cx.assert_binding_matches_all_exempted(
+//             ["v", "l", "i", "w"],
+//             WORD_LOCATIONS,
+//             ExemptionFeatures::NonEmptyVisualTextObjects,
+//         )
+//         .await;
+//         cx.assert_binding_matches_all(["v", "i", "shift-w"], WORD_LOCATIONS)
+//             .await;
+
+//         cx.assert_binding_matches_all_exempted(
+//             ["v", "i", "h", "shift-w"],
+//             WORD_LOCATIONS,
+//             ExemptionFeatures::NonEmptyVisualTextObjects,
+//         )
+//         .await;
+//         cx.assert_binding_matches_all_exempted(
+//             ["v", "i", "l", "shift-w"],
+//             WORD_LOCATIONS,
+//             ExemptionFeatures::NonEmptyVisualTextObjects,
+//         )
+//         .await;
+
+//         cx.assert_binding_matches_all_exempted(
+//             ["v", "a", "w"],
+//             WORD_LOCATIONS,
+//             ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
+//         )
+//         .await;
+//         cx.assert_binding_matches_all_exempted(
+//             ["v", "a", "shift-w"],
+//             WORD_LOCATIONS,
+//             ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
+//         )
+//         .await;
+//     }
+
+//     const SENTENCE_EXAMPLES: &[&'static str] = &[
+//         "ˇThe quick ˇbrownˇ?ˇ ˇFox Jˇumpsˇ!ˇ Ovˇer theˇ lazyˇ.",
+//         indoc! {"
+//             ˇThe quick ˇbrownˇ
+//             fox jumps over
+//             the lazy doˇgˇ.ˇ ˇThe quick ˇ
+//             brown fox jumps over
+//         "},
+//         indoc! {"
+//             The quick brown fox jumps.
+//             Over the lazy dog
+//             ˇ
+//             ˇ
+//             ˇ  fox-jumpˇs over
+//             the lazy dog.ˇ
+//             ˇ
+//         "},
+//         r#"ˇThe ˇquick brownˇ.)ˇ]ˇ'ˇ" Brown ˇfox jumpsˇ.ˇ "#,
+//     ];
+
+//     #[gpui::test]
+//     async fn test_change_sentence_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx)
+//             .await
+//             .binding(["c", "i", "s"]);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown fox jumps.\nOver the lazy dog\nˇ\nˇ\n  fox-jumps over\nthe lazy dog.\n\n",
+//             ExemptionFeatures::SentenceOnEmptyLines);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown fox jumps.\nOver the lazy dog\n\n\nˇ  foxˇ-ˇjumpˇs over\nthe lazy dog.\n\n",
+//             ExemptionFeatures::SentenceAtStartOfLineWithWhitespace);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown fox jumps.\nOver the lazy dog\n\n\n  fox-jumps over\nthe lazy dog.ˇ\nˇ\n",
+//             ExemptionFeatures::SentenceAfterPunctuationAtEndOfFile);
+//         for sentence_example in SENTENCE_EXAMPLES {
+//             cx.assert_all(sentence_example).await;
+//         }
+
+//         let mut cx = cx.binding(["c", "a", "s"]);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown?ˇ Fox Jumps! Over the lazy.",
+//             ExemptionFeatures::IncorrectLandingPosition,
+//         );
+//         cx.add_initial_state_exemptions(
+//             "The quick brown.)]\'\" Brown fox jumps.ˇ ",
+//             ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
+//         );
+
+//         for sentence_example in SENTENCE_EXAMPLES {
+//             cx.assert_all(sentence_example).await;
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_sentence_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx)
+//             .await
+//             .binding(["d", "i", "s"]);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown fox jumps.\nOver the lazy dog\nˇ\nˇ\n  fox-jumps over\nthe lazy dog.\n\n",
+//             ExemptionFeatures::SentenceOnEmptyLines);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown fox jumps.\nOver the lazy dog\n\n\nˇ  foxˇ-ˇjumpˇs over\nthe lazy dog.\n\n",
+//             ExemptionFeatures::SentenceAtStartOfLineWithWhitespace);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown fox jumps.\nOver the lazy dog\n\n\n  fox-jumps over\nthe lazy dog.ˇ\nˇ\n",
+//             ExemptionFeatures::SentenceAfterPunctuationAtEndOfFile);
+
+//         for sentence_example in SENTENCE_EXAMPLES {
+//             cx.assert_all(sentence_example).await;
+//         }
+
+//         let mut cx = cx.binding(["d", "a", "s"]);
+//         cx.add_initial_state_exemptions(
+//             "The quick brown?ˇ Fox Jumps! Over the lazy.",
+//             ExemptionFeatures::IncorrectLandingPosition,
+//         );
+//         cx.add_initial_state_exemptions(
+//             "The quick brown.)]\'\" Brown fox jumps.ˇ ",
+//             ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
+//         );
+
+//         for sentence_example in SENTENCE_EXAMPLES {
+//             cx.assert_all(sentence_example).await;
+//         }
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_sentence_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx)
+//             .await
+//             .binding(["v", "i", "s"]);
+//         for sentence_example in SENTENCE_EXAMPLES {
+//             cx.assert_all_exempted(sentence_example, ExemptionFeatures::SentenceOnEmptyLines)
+//                 .await;
+//         }
+
+//         let mut cx = cx.binding(["v", "a", "s"]);
+//         for sentence_example in SENTENCE_EXAMPLES {
+//             cx.assert_all_exempted(
+//                 sentence_example,
+//                 ExemptionFeatures::AroundSentenceStartingBetweenIncludesWrongWhitespace,
+//             )
+//             .await;
+//         }
+//     }
+
+//     // Test string with "`" for opening surrounders and "'" for closing surrounders
+//     const SURROUNDING_MARKER_STRING: &str = indoc! {"
+//         ˇTh'ˇe ˇ`ˇ'ˇquˇi`ˇck broˇ'wn`
+//         'ˇfox juˇmps ovˇ`ˇer
+//         the ˇlazy dˇ'ˇoˇ`ˇg"};
+
+//     const SURROUNDING_OBJECTS: &[(char, char)] = &[
+//         ('\'', '\''), // Quote
+//         ('`', '`'),   // Back Quote
+//         ('"', '"'),   // Double Quote
+//         ('(', ')'),   // Parentheses
+//         ('[', ']'),   // SquareBrackets
+//         ('{', '}'),   // CurlyBrackets
+//         ('<', '>'),   // AngleBrackets
+//     ];
+
+//     #[gpui::test]
+//     async fn test_change_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         for (start, end) in SURROUNDING_OBJECTS {
+//             let marked_string = SURROUNDING_MARKER_STRING
+//                 .replace('`', &start.to_string())
+//                 .replace('\'', &end.to_string());
+
+//             cx.assert_binding_matches_all(["c", "i", &start.to_string()], &marked_string)
+//                 .await;
+//             cx.assert_binding_matches_all(["c", "i", &end.to_string()], &marked_string)
+//                 .await;
+//             cx.assert_binding_matches_all(["c", "a", &start.to_string()], &marked_string)
+//                 .await;
+//             cx.assert_binding_matches_all(["c", "a", &end.to_string()], &marked_string)
+//                 .await;
+//         }
+//     }
+//     #[gpui::test]
+//     async fn test_singleline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+//         cx.set_shared_wrap(12).await;
+
+//         cx.set_shared_state(indoc! {
+//             "helˇlo \"world\"!"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
+//         cx.assert_shared_state(indoc! {
+//             "hello \"«worldˇ»\"!"
+//         })
+//         .await;
+
+//         cx.set_shared_state(indoc! {
+//             "hello \"wˇorld\"!"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
+//         cx.assert_shared_state(indoc! {
+//             "hello \"«worldˇ»\"!"
+//         })
+//         .await;
+
+//         cx.set_shared_state(indoc! {
+//             "hello \"wˇorld\"!"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
+//         cx.assert_shared_state(indoc! {
+//             "hello« \"world\"ˇ»!"
+//         })
+//         .await;
+
+//         cx.set_shared_state(indoc! {
+//             "hello \"wˇorld\" !"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
+//         cx.assert_shared_state(indoc! {
+//             "hello «\"world\" ˇ»!"
+//         })
+//         .await;
+
+//         cx.set_shared_state(indoc! {
+//             "hello \"wˇorld\"•
+//             goodbye"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
+//         cx.assert_shared_state(indoc! {
+//             "hello «\"world\" ˇ»
+//             goodbye"
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "func empty(a string) bool {
+//                if a == \"\" {
+//                   return true
+//                }
+//                ˇreturn false
+//             }"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             func empty(a string) bool {
+//             «   if a == \"\" {
+//                   return true
+//                }
+//                return false
+//             ˇ»}"})
+//             .await;
+//         cx.set_shared_state(indoc! {
+//             "func empty(a string) bool {
+//                  if a == \"\" {
+//                      ˇreturn true
+//                  }
+//                  return false
+//             }"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             func empty(a string) bool {
+//                  if a == \"\" {
+//             «         return true
+//             ˇ»     }
+//                  return false
+//             }"})
+//             .await;
+
+//         cx.set_shared_state(indoc! {
+//             "func empty(a string) bool {
+//                  if a == \"\" ˇ{
+//                      return true
+//                  }
+//                  return false
+//             }"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             func empty(a string) bool {
+//                  if a == \"\" {
+//             «         return true
+//             ˇ»     }
+//                  return false
+//             }"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+//         cx.set_state(
+//             indoc! {"
+//             fn boop() {
+//                 baz(ˇ|a, b| { bar(|j, k| { })})
+//             }"
+//             },
+//             Mode::Normal,
+//         );
+//         cx.simulate_keystrokes(["c", "i", "|"]);
+//         cx.assert_state(
+//             indoc! {"
+//             fn boop() {
+//                 baz(|ˇ| { bar(|j, k| { })})
+//             }"
+//             },
+//             Mode::Insert,
+//         );
+//         cx.simulate_keystrokes(["escape", "1", "8", "|"]);
+//         cx.assert_state(
+//             indoc! {"
+//             fn boop() {
+//                 baz(|| { bar(ˇ|j, k| { })})
+//             }"
+//             },
+//             Mode::Normal,
+//         );
+
+//         cx.simulate_keystrokes(["v", "a", "|"]);
+//         cx.assert_state(
+//             indoc! {"
+//             fn boop() {
+//                 baz(|| { bar(«|j, k| ˇ»{ })})
+//             }"
+//             },
+//             Mode::Visual,
+//         );
+//     }
+
+//     #[gpui::test]
+//     async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         for (start, end) in SURROUNDING_OBJECTS {
+//             let marked_string = SURROUNDING_MARKER_STRING
+//                 .replace('`', &start.to_string())
+//                 .replace('\'', &end.to_string());
+
+//             cx.assert_binding_matches_all(["d", "i", &start.to_string()], &marked_string)
+//                 .await;
+//             cx.assert_binding_matches_all(["d", "i", &end.to_string()], &marked_string)
+//                 .await;
+//             cx.assert_binding_matches_all(["d", "a", &start.to_string()], &marked_string)
+//                 .await;
+//             cx.assert_binding_matches_all(["d", "a", &end.to_string()], &marked_string)
+//                 .await;
+//         }
+//     }
+// }

crates/vim2/src/state.rs 🔗

@@ -0,0 +1,234 @@
+use std::{ops::Range, sync::Arc};
+
+use gpui::{Action, KeyContext};
+use language::CursorShape;
+use serde::{Deserialize, Serialize};
+use workspace::searchable::Direction;
+
+use crate::motion::Motion;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
+pub enum Mode {
+    Normal,
+    Insert,
+    Visual,
+    VisualLine,
+    VisualBlock,
+}
+
+impl Mode {
+    pub fn is_visual(&self) -> bool {
+        match self {
+            Mode::Normal | Mode::Insert => false,
+            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
+        }
+    }
+}
+
+impl Default for Mode {
+    fn default() -> Self {
+        Self::Normal
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
+pub enum Operator {
+    Change,
+    Delete,
+    Yank,
+    Replace,
+    Object { around: bool },
+    FindForward { before: bool },
+    FindBackward { after: bool },
+}
+
+#[derive(Default, Clone)]
+pub struct EditorState {
+    pub mode: Mode,
+    pub last_mode: Mode,
+
+    /// pre_count is the number before an operator is specified (3 in 3d2d)
+    pub pre_count: Option<usize>,
+    /// post_count is the number after an operator is specified (2 in 3d2d)
+    pub post_count: Option<usize>,
+
+    pub operator_stack: Vec<Operator>,
+}
+
+#[derive(Default, Clone, Debug)]
+pub enum RecordedSelection {
+    #[default]
+    None,
+    Visual {
+        rows: u32,
+        cols: u32,
+    },
+    SingleLine {
+        cols: u32,
+    },
+    VisualBlock {
+        rows: u32,
+        cols: u32,
+    },
+    VisualLine {
+        rows: u32,
+    },
+}
+
+#[derive(Default, Clone)]
+pub struct WorkspaceState {
+    pub search: SearchState,
+    pub last_find: Option<Motion>,
+
+    pub recording: bool,
+    pub stop_recording_after_next_action: bool,
+    pub replaying: bool,
+    pub recorded_count: Option<usize>,
+    pub recorded_actions: Vec<ReplayableAction>,
+    pub recorded_selection: RecordedSelection,
+}
+
+#[derive(Debug)]
+pub enum ReplayableAction {
+    Action(Box<dyn Action>),
+    Insertion {
+        text: Arc<str>,
+        utf16_range_to_replace: Option<Range<isize>>,
+    },
+}
+
+impl Clone for ReplayableAction {
+    fn clone(&self) -> Self {
+        match self {
+            Self::Action(action) => Self::Action(action.boxed_clone()),
+            Self::Insertion {
+                text,
+                utf16_range_to_replace,
+            } => Self::Insertion {
+                text: text.clone(),
+                utf16_range_to_replace: utf16_range_to_replace.clone(),
+            },
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct SearchState {
+    pub direction: Direction,
+    pub count: usize,
+    pub initial_query: String,
+}
+
+impl Default for SearchState {
+    fn default() -> Self {
+        Self {
+            direction: Direction::Next,
+            count: 1,
+            initial_query: "".to_string(),
+        }
+    }
+}
+
+impl EditorState {
+    pub fn cursor_shape(&self) -> CursorShape {
+        match self.mode {
+            Mode::Normal => {
+                if self.operator_stack.is_empty() {
+                    CursorShape::Block
+                } else {
+                    CursorShape::Underscore
+                }
+            }
+            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
+            Mode::Insert => CursorShape::Bar,
+        }
+    }
+
+    pub fn vim_controlled(&self) -> bool {
+        !matches!(self.mode, Mode::Insert)
+            || matches!(
+                self.operator_stack.last(),
+                Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
+            )
+    }
+
+    pub fn should_autoindent(&self) -> bool {
+        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
+    }
+
+    pub fn clip_at_line_ends(&self) -> bool {
+        match self.mode {
+            Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false,
+            Mode::Normal => true,
+        }
+    }
+
+    pub fn active_operator(&self) -> Option<Operator> {
+        self.operator_stack.last().copied()
+    }
+
+    pub fn keymap_context_layer(&self) -> KeyContext {
+        let mut context = KeyContext::default();
+        context.add("VimEnabled");
+        context.set(
+            "vim_mode",
+            match self.mode {
+                Mode::Normal => "normal",
+                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
+                Mode::Insert => "insert",
+            },
+        );
+
+        if self.vim_controlled() {
+            context.add("VimControl");
+        }
+
+        if self.active_operator().is_none() && self.pre_count.is_some()
+            || self.active_operator().is_some() && self.post_count.is_some()
+        {
+            context.add("VimCount");
+        }
+
+        let active_operator = self.active_operator();
+
+        if let Some(active_operator) = active_operator {
+            for context_flag in active_operator.context_flags().into_iter() {
+                context.add(*context_flag);
+            }
+        }
+
+        context.set(
+            "vim_operator",
+            active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
+        );
+
+        context
+    }
+}
+
+impl Operator {
+    pub fn id(&self) -> &'static str {
+        match self {
+            Operator::Object { around: false } => "i",
+            Operator::Object { around: true } => "a",
+            Operator::Change => "c",
+            Operator::Delete => "d",
+            Operator::Yank => "y",
+            Operator::Replace => "r",
+            Operator::FindForward { before: false } => "f",
+            Operator::FindForward { before: true } => "t",
+            Operator::FindBackward { after: false } => "F",
+            Operator::FindBackward { after: true } => "T",
+        }
+    }
+
+    pub fn context_flags(&self) -> &'static [&'static str] {
+        match self {
+            Operator::Object { .. } => &["VimObject"],
+            Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
+                &["VimWaiting"]
+            }
+            _ => &[],
+        }
+    }
+}

crates/vim2/src/test.rs 🔗

@@ -0,0 +1,759 @@
+// 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,
+//     );
+// }

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

@@ -0,0 +1,95 @@
+use std::ops::{Deref, DerefMut};
+
+use crate::state::Mode;
+
+use super::{ExemptionFeatures, NeovimBackedTestContext, SUPPORTED_FEATURES};
+
+pub struct NeovimBackedBindingTestContext<'a, const COUNT: usize> {
+    cx: NeovimBackedTestContext<'a>,
+    keystrokes_under_test: [&'static str; COUNT],
+}
+
+impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> {
+    pub fn new(
+        keystrokes_under_test: [&'static str; COUNT],
+        cx: NeovimBackedTestContext<'a>,
+    ) -> Self {
+        Self {
+            cx,
+            keystrokes_under_test,
+        }
+    }
+
+    pub fn consume(self) -> NeovimBackedTestContext<'a> {
+        self.cx
+    }
+
+    pub fn binding<const NEW_COUNT: usize>(
+        self,
+        keystrokes: [&'static str; NEW_COUNT],
+    ) -> NeovimBackedBindingTestContext<'a, NEW_COUNT> {
+        self.consume().binding(keystrokes)
+    }
+
+    pub async fn assert(&mut self, marked_positions: &str) {
+        self.cx
+            .assert_binding_matches(self.keystrokes_under_test, marked_positions)
+            .await;
+    }
+
+    pub async fn assert_exempted(&mut self, marked_positions: &str, feature: ExemptionFeatures) {
+        if SUPPORTED_FEATURES.contains(&feature) {
+            self.cx
+                .assert_binding_matches(self.keystrokes_under_test, marked_positions)
+                .await
+        }
+    }
+
+    pub fn assert_manual(
+        &mut self,
+        initial_state: &str,
+        mode_before: Mode,
+        state_after: &str,
+        mode_after: Mode,
+    ) {
+        self.cx.assert_binding(
+            self.keystrokes_under_test,
+            initial_state,
+            mode_before,
+            state_after,
+            mode_after,
+        );
+    }
+
+    pub async fn assert_all(&mut self, marked_positions: &str) {
+        self.cx
+            .assert_binding_matches_all(self.keystrokes_under_test, marked_positions)
+            .await
+    }
+
+    pub async fn assert_all_exempted(
+        &mut self,
+        marked_positions: &str,
+        feature: ExemptionFeatures,
+    ) {
+        if SUPPORTED_FEATURES.contains(&feature) {
+            self.cx
+                .assert_binding_matches_all(self.keystrokes_under_test, marked_positions)
+                .await
+        }
+    }
+}
+
+impl<'a, const COUNT: usize> Deref for NeovimBackedBindingTestContext<'a, COUNT> {
+    type Target = NeovimBackedTestContext<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
+impl<'a, const COUNT: usize> DerefMut for NeovimBackedBindingTestContext<'a, COUNT> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cx
+    }
+}

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

@@ -0,0 +1,427 @@
+use editor::scroll::VERTICAL_SCROLL_MARGIN;
+use indoc::indoc;
+use settings::SettingsStore;
+use std::{
+    ops::{Deref, DerefMut},
+    panic, thread,
+};
+
+use collections::{HashMap, HashSet};
+use gpui::{geometry::vector::vec2f, ContextHandle};
+use language::language_settings::{AllLanguageSettings, SoftWrap};
+use util::test::marked_text_offsets;
+
+use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
+use crate::state::Mode;
+
+pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[];
+
+/// Enum representing features we have tests for but which don't work, yet. Used
+/// to add exemptions and automatically
+#[derive(PartialEq, Eq)]
+pub enum ExemptionFeatures {
+    // MOTIONS
+    // When an operator completes at the end of the file, an extra newline is left
+    OperatorLastNewlineRemains,
+
+    // OBJECTS
+    // Resulting position after the operation is slightly incorrect for unintuitive reasons.
+    IncorrectLandingPosition,
+    // Operator around the text object at the end of the line doesn't remove whitespace.
+    AroundObjectLeavesWhitespaceAtEndOfLine,
+    // Sentence object on empty lines
+    SentenceOnEmptyLines,
+    // Whitespace isn't included with text objects at the start of the line
+    SentenceAtStartOfLineWithWhitespace,
+    // Whitespace around sentences is slightly incorrect when starting between sentences
+    AroundSentenceStartingBetweenIncludesWrongWhitespace,
+    // Non empty selection with text objects in visual mode
+    NonEmptyVisualTextObjects,
+    // Sentence Doesn't backtrack when its at the end of the file
+    SentenceAfterPunctuationAtEndOfFile,
+}
+
+impl ExemptionFeatures {
+    pub fn supported(&self) -> bool {
+        SUPPORTED_FEATURES.contains(self)
+    }
+}
+
+pub struct NeovimBackedTestContext<'a> {
+    cx: VimTestContext<'a>,
+    // Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
+    // bindings are exempted. If None, all bindings are ignored for that insertion text.
+    exemptions: HashMap<String, Option<HashSet<String>>>,
+    neovim: NeovimConnection,
+
+    last_set_state: Option<String>,
+    recent_keystrokes: Vec<String>,
+
+    is_dirty: bool,
+}
+
+impl<'a> NeovimBackedTestContext<'a> {
+    pub async fn new(cx: &'a mut gpui::TestAppContext) -> NeovimBackedTestContext<'a> {
+        // rust stores the name of the test on the current thread.
+        // We use this to automatically name a file that will store
+        // the neovim connection's requests/responses so that we can
+        // run without neovim on CI.
+        let thread = thread::current();
+        let test_name = thread
+            .name()
+            .expect("thread is not named")
+            .split(":")
+            .last()
+            .unwrap()
+            .to_string();
+        Self {
+            cx: VimTestContext::new(cx, true).await,
+            exemptions: Default::default(),
+            neovim: NeovimConnection::new(test_name).await,
+
+            last_set_state: None,
+            recent_keystrokes: Default::default(),
+            is_dirty: false,
+        }
+    }
+
+    pub fn add_initial_state_exemptions(
+        &mut self,
+        marked_positions: &str,
+        missing_feature: ExemptionFeatures, // Feature required to support this exempted test case
+    ) {
+        if !missing_feature.supported() {
+            let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
+
+            for cursor_offset in cursor_offsets.iter() {
+                let mut marked_text = unmarked_text.clone();
+                marked_text.insert(*cursor_offset, 'ˇ');
+
+                // None represents all key bindings being exempted for that initial state
+                self.exemptions.insert(marked_text, None);
+            }
+        }
+    }
+
+    pub async fn simulate_shared_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
+        self.neovim.send_keystroke(keystroke_text).await;
+        self.simulate_keystroke(keystroke_text)
+    }
+
+    pub async fn simulate_shared_keystrokes<const COUNT: usize>(
+        &mut self,
+        keystroke_texts: [&str; COUNT],
+    ) {
+        for keystroke_text in keystroke_texts.into_iter() {
+            self.recent_keystrokes.push(keystroke_text.to_string());
+            self.neovim.send_keystroke(keystroke_text).await;
+        }
+        self.simulate_keystrokes(keystroke_texts);
+    }
+
+    pub async fn set_shared_state(&mut self, marked_text: &str) {
+        let mode = if marked_text.contains("»") {
+            Mode::Visual
+        } else {
+            Mode::Normal
+        };
+        self.set_state(marked_text, mode);
+        self.last_set_state = Some(marked_text.to_string());
+        self.recent_keystrokes = Vec::new();
+        self.neovim.set_state(marked_text).await;
+        self.is_dirty = true;
+    }
+
+    pub async fn set_shared_wrap(&mut self, columns: u32) {
+        if columns < 12 {
+            panic!("nvim doesn't support columns < 12")
+        }
+        self.neovim.set_option("wrap").await;
+        self.neovim
+            .set_option(&format!("columns={}", columns))
+            .await;
+
+        self.update(|cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                    settings.defaults.soft_wrap = Some(SoftWrap::PreferredLineLength);
+                    settings.defaults.preferred_line_length = Some(columns);
+                });
+            })
+        })
+    }
+
+    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;
+    }
+
+    pub async fn assert_shared_state(&mut self, marked_text: &str) {
+        self.is_dirty = false;
+        let marked_text = marked_text.replace("•", " ");
+        let neovim = self.neovim_state().await;
+        let editor = self.editor_state();
+        if neovim == marked_text && neovim == editor {
+            return;
+        }
+        let initial_state = self
+            .last_set_state
+            .as_ref()
+            .unwrap_or(&"N/A".to_string())
+            .clone();
+
+        let message = if neovim != marked_text {
+            "Test is incorrect (currently expected != neovim_state)"
+        } else {
+            "Editor does not match nvim behaviour"
+        };
+        panic!(
+            indoc! {"{}
+                # initial state:
+                {}
+                # keystrokes:
+                {}
+                # currently expected:
+                {}
+                # neovim state:
+                {}
+                # zed state:
+                {}"},
+            message,
+            initial_state,
+            self.recent_keystrokes.join(" "),
+            marked_text.replace(" \n", "•\n"),
+            neovim.replace(" \n", "•\n"),
+            editor.replace(" \n", "•\n")
+        )
+    }
+
+    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();
+
+        if text == neovim && text == editor {
+            return;
+        }
+
+        let message = if neovim != text {
+            "Test is incorrect (currently expected != neovim)"
+        } else {
+            "Editor does not match nvim behaviour"
+        };
+
+        let initial_state = self
+            .last_set_state
+            .as_ref()
+            .unwrap_or(&"N/A".to_string())
+            .clone();
+
+        panic!(
+            indoc! {"{}
+                # initial state:
+                {}
+                # keystrokes:
+                {}
+                # currently expected:
+                {}
+                # neovim clipboard:
+                {}
+                # zed clipboard:
+                {}"},
+            message,
+            initial_state,
+            self.recent_keystrokes.join(" "),
+            text,
+            neovim,
+            editor
+        )
+    }
+
+    pub async fn neovim_state(&mut self) -> String {
+        self.neovim.marked_text().await
+    }
+
+    pub async fn neovim_mode(&mut self) -> Mode {
+        self.neovim.mode().await.unwrap()
+    }
+
+    pub async fn assert_state_matches(&mut self) {
+        self.is_dirty = false;
+        let neovim = self.neovim_state().await;
+        let editor = self.editor_state();
+        let initial_state = self
+            .last_set_state
+            .as_ref()
+            .unwrap_or(&"N/A".to_string())
+            .clone();
+
+        if neovim != editor {
+            panic!(
+                indoc! {"Test failed (zed does not match nvim behaviour)
+                    # initial state:
+                    {}
+                    # keystrokes:
+                    {}
+                    # neovim state:
+                    {}
+                    # zed state:
+                    {}"},
+                initial_state,
+                self.recent_keystrokes.join(" "),
+                neovim,
+                editor,
+            )
+        }
+    }
+
+    pub async fn assert_binding_matches<const COUNT: usize>(
+        &mut self,
+        keystrokes: [&str; COUNT],
+        initial_state: &str,
+    ) {
+        if let Some(possible_exempted_keystrokes) = self.exemptions.get(initial_state) {
+            match possible_exempted_keystrokes {
+                Some(exempted_keystrokes) => {
+                    if exempted_keystrokes.contains(&format!("{keystrokes:?}")) {
+                        // This keystroke was exempted for this insertion text
+                        return;
+                    }
+                }
+                None => {
+                    // All keystrokes for this insertion text are exempted
+                    return;
+                }
+            }
+        }
+
+        let _state_context = self.set_shared_state(initial_state).await;
+        let _keystroke_context = self.simulate_shared_keystrokes(keystrokes).await;
+        self.assert_state_matches().await;
+    }
+
+    pub async fn assert_binding_matches_all<const COUNT: usize>(
+        &mut self,
+        keystrokes: [&str; COUNT],
+        marked_positions: &str,
+    ) {
+        let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
+
+        for cursor_offset in cursor_offsets.iter() {
+            let mut marked_text = unmarked_text.clone();
+            marked_text.insert(*cursor_offset, 'ˇ');
+
+            self.assert_binding_matches(keystrokes, &marked_text).await;
+        }
+    }
+
+    pub fn each_marked_position(&self, marked_positions: &str) -> Vec<String> {
+        let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
+        let mut ret = Vec::with_capacity(cursor_offsets.len());
+
+        for cursor_offset in cursor_offsets.iter() {
+            let mut marked_text = unmarked_text.clone();
+            marked_text.insert(*cursor_offset, 'ˇ');
+            ret.push(marked_text)
+        }
+
+        ret
+    }
+
+    pub async fn assert_neovim_compatible<const COUNT: usize>(
+        &mut self,
+        marked_positions: &str,
+        keystrokes: [&str; COUNT],
+    ) {
+        self.set_shared_state(&marked_positions).await;
+        self.simulate_shared_keystrokes(keystrokes).await;
+        self.assert_state_matches().await;
+    }
+
+    pub async fn assert_matches_neovim<const COUNT: usize>(
+        &mut self,
+        marked_positions: &str,
+        keystrokes: [&str; COUNT],
+        result: &str,
+    ) {
+        self.set_shared_state(marked_positions).await;
+        self.simulate_shared_keystrokes(keystrokes).await;
+        self.assert_shared_state(result).await;
+    }
+
+    pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
+        &mut self,
+        keystrokes: [&str; COUNT],
+        marked_positions: &str,
+        feature: ExemptionFeatures,
+    ) {
+        if SUPPORTED_FEATURES.contains(&feature) {
+            self.assert_binding_matches_all(keystrokes, marked_positions)
+                .await
+        }
+    }
+
+    pub fn binding<const COUNT: usize>(
+        self,
+        keystrokes: [&'static str; COUNT],
+    ) -> NeovimBackedBindingTestContext<'a, COUNT> {
+        NeovimBackedBindingTestContext::new(keystrokes, self)
+    }
+}
+
+impl<'a> Deref for NeovimBackedTestContext<'a> {
+    type Target = VimTestContext<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
+impl<'a> DerefMut for NeovimBackedTestContext<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cx
+    }
+}
+
+// a common mistake in tests is to call set_shared_state when
+// you mean asswert_shared_state. This notices that and lets
+// you know.
+impl<'a> Drop for NeovimBackedTestContext<'a> {
+    fn drop(&mut self) {
+        if self.is_dirty {
+            panic!("Test context was dropped after set_shared_state before assert_shared_state")
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use gpui::TestAppContext;
+
+    use crate::test::NeovimBackedTestContext;
+
+    #[gpui::test]
+    async fn neovim_backed_test_context_works(cx: &mut TestAppContext) {
+        let mut cx = NeovimBackedTestContext::new(cx).await;
+        cx.assert_state_matches().await;
+        cx.set_shared_state("This is a tesˇt").await;
+        cx.assert_state_matches().await;
+    }
+}

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

@@ -0,0 +1,591 @@
+use std::path::PathBuf;
+#[cfg(feature = "neovim")]
+use std::{
+    cmp,
+    ops::{Deref, DerefMut, Range},
+};
+
+#[cfg(feature = "neovim")]
+use async_compat::Compat;
+#[cfg(feature = "neovim")]
+use async_trait::async_trait;
+#[cfg(feature = "neovim")]
+use gpui::keymap_matcher::Keystroke;
+
+#[cfg(feature = "neovim")]
+use language::Point;
+
+#[cfg(feature = "neovim")]
+use nvim_rs::{
+    create::tokio::new_child_cmd, error::LoopError, Handler, Neovim, UiAttachOptions, Value,
+};
+#[cfg(feature = "neovim")]
+use parking_lot::ReentrantMutex;
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "neovim")]
+use tokio::{
+    process::{Child, ChildStdin, Command},
+    task::JoinHandle,
+};
+
+use crate::state::Mode;
+use collections::VecDeque;
+
+// Neovim doesn't like to be started simultaneously from multiple threads. We use this lock
+// to ensure we are only constructing one neovim connection at a time.
+#[cfg(feature = "neovim")]
+static NEOVIM_LOCK: ReentrantMutex<()> = ReentrantMutex::new(());
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub enum NeovimData {
+    Put { state: String },
+    Key(String),
+    Get { state: String, mode: Option<Mode> },
+    ReadRegister { name: char, value: String },
+    SetOption { value: String },
+}
+
+pub struct NeovimConnection {
+    data: VecDeque<NeovimData>,
+    #[cfg(feature = "neovim")]
+    test_case_id: String,
+    #[cfg(feature = "neovim")]
+    nvim: Neovim<nvim_rs::compat::tokio::Compat<ChildStdin>>,
+    #[cfg(feature = "neovim")]
+    _join_handle: JoinHandle<Result<(), Box<LoopError>>>,
+    #[cfg(feature = "neovim")]
+    _child: Child,
+}
+
+impl NeovimConnection {
+    pub async fn new(test_case_id: String) -> Self {
+        #[cfg(feature = "neovim")]
+        let handler = NvimHandler {};
+        #[cfg(feature = "neovim")]
+        let (nvim, join_handle, child) = Compat::new(async {
+            // Ensure we don't create neovim connections in parallel
+            let _lock = NEOVIM_LOCK.lock();
+            let (nvim, join_handle, child) = new_child_cmd(
+                &mut Command::new("nvim")
+                    .arg("--embed")
+                    .arg("--clean")
+                    // disable swap (otherwise after about 1000 test runs you run out of swap file names)
+                    .arg("-n")
+                    // disable writing files (just in case)
+                    .arg("-m"),
+                handler,
+            )
+            .await
+            .expect("Could not connect to neovim process");
+
+            nvim.ui_attach(100, 100, &UiAttachOptions::default())
+                .await
+                .expect("Could not attach to ui");
+
+            // Makes system act a little more like zed in terms of indentation
+            nvim.set_option("smartindent", nvim_rs::Value::Boolean(true))
+                .await
+                .expect("Could not set smartindent on startup");
+
+            (nvim, join_handle, child)
+        })
+        .await;
+
+        Self {
+            #[cfg(feature = "neovim")]
+            data: Default::default(),
+            #[cfg(not(feature = "neovim"))]
+            data: Self::read_test_data(&test_case_id),
+            #[cfg(feature = "neovim")]
+            test_case_id,
+            #[cfg(feature = "neovim")]
+            nvim,
+            #[cfg(feature = "neovim")]
+            _join_handle: join_handle,
+            #[cfg(feature = "neovim")]
+            _child: child,
+        }
+    }
+
+    // Sends a keystroke to the neovim process.
+    #[cfg(feature = "neovim")]
+    pub async fn send_keystroke(&mut self, keystroke_text: &str) {
+        let mut keystroke = Keystroke::parse(keystroke_text).unwrap();
+
+        if keystroke.key == "<" {
+            keystroke.key = "lt".to_string()
+        }
+
+        let special = keystroke.shift
+            || keystroke.ctrl
+            || keystroke.alt
+            || keystroke.cmd
+            || 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 end = if special { ">" } else { "" };
+
+        let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);
+
+        self.data
+            .push_back(NeovimData::Key(keystroke_text.to_string()));
+        self.nvim
+            .input(&key)
+            .await
+            .expect("Could not input keystroke");
+    }
+
+    #[cfg(not(feature = "neovim"))]
+    pub async fn send_keystroke(&mut self, keystroke_text: &str) {
+        if matches!(self.data.front(), Some(NeovimData::Get { .. })) {
+            self.data.pop_front();
+        }
+        assert_eq!(
+            self.data.pop_front(),
+            Some(NeovimData::Key(keystroke_text.to_string())),
+            "operation does not match recorded script. re-record with --features=neovim"
+        );
+    }
+
+    #[cfg(feature = "neovim")]
+    pub async fn set_state(&mut self, marked_text: &str) {
+        let (text, selections) = parse_state(&marked_text);
+
+        let nvim_buffer = self
+            .nvim
+            .get_current_buf()
+            .await
+            .expect("Could not get neovim buffer");
+        let lines = text
+            .split('\n')
+            .map(|line| line.to_string())
+            .collect::<Vec<_>>();
+
+        nvim_buffer
+            .set_lines(0, -1, false, lines)
+            .await
+            .expect("Could not set nvim buffer text");
+
+        self.nvim
+            .input("<escape>")
+            .await
+            .expect("Could not send escape to nvim");
+        self.nvim
+            .input("<escape>")
+            .await
+            .expect("Could not send escape to nvim");
+
+        let nvim_window = self
+            .nvim
+            .get_current_win()
+            .await
+            .expect("Could not get neovim window");
+
+        if selections.len() != 1 {
+            panic!("must have one selection");
+        }
+        let selection = &selections[0];
+
+        let cursor = selection.start;
+        nvim_window
+            .set_cursor((cursor.row as i64 + 1, cursor.column as i64))
+            .await
+            .expect("Could not set nvim cursor position");
+
+        if !selection.is_empty() {
+            self.nvim
+                .input("v")
+                .await
+                .expect("could not enter visual mode");
+
+            let cursor = selection.end;
+            nvim_window
+                .set_cursor((cursor.row as i64 + 1, cursor.column as i64))
+                .await
+                .expect("Could not set nvim cursor position");
+        }
+
+        if let Some(NeovimData::Get { mode, state }) = self.data.back() {
+            if *mode == Some(Mode::Normal) && *state == marked_text {
+                return;
+            }
+        }
+        self.data.push_back(NeovimData::Put {
+            state: marked_text.to_string(),
+        })
+    }
+
+    #[cfg(not(feature = "neovim"))]
+    pub async fn set_state(&mut self, marked_text: &str) {
+        if let Some(NeovimData::Get { mode, state: text }) = self.data.front() {
+            if *mode == Some(Mode::Normal) && *text == marked_text {
+                return;
+            }
+            self.data.pop_front();
+        }
+        assert_eq!(
+            self.data.pop_front(),
+            Some(NeovimData::Put {
+                state: marked_text.to_string()
+            }),
+            "operation does not match recorded script. re-record with --features=neovim"
+        );
+    }
+
+    #[cfg(feature = "neovim")]
+    pub async fn set_option(&mut self, value: &str) {
+        self.nvim
+            .command_output(format!("set {}", value).as_str())
+            .await
+            .unwrap();
+
+        self.data.push_back(NeovimData::SetOption {
+            value: value.to_string(),
+        })
+    }
+
+    #[cfg(not(feature = "neovim"))]
+    pub async fn set_option(&mut self, value: &str) {
+        if let Some(NeovimData::Get { .. }) = self.data.front() {
+            self.data.pop_front();
+        };
+        assert_eq!(
+            self.data.pop_front(),
+            Some(NeovimData::SetOption {
+                value: value.to_string(),
+            }),
+            "operation does not match recorded script. re-record with --features=neovim"
+        );
+    }
+
+    #[cfg(not(feature = "neovim"))]
+    pub async fn read_register(&mut self, register: char) -> String {
+        if let Some(NeovimData::Get { .. }) = self.data.front() {
+            self.data.pop_front();
+        };
+        if let Some(NeovimData::ReadRegister { name, value }) = self.data.pop_front() {
+            if name == register {
+                return value;
+            }
+        }
+
+        panic!("operation does not match recorded script. re-record with --features=neovim")
+    }
+
+    #[cfg(feature = "neovim")]
+    pub async fn read_register(&mut self, name: char) -> String {
+        let value = self
+            .nvim
+            .command_output(format!("echo getreg('{}')", name).as_str())
+            .await
+            .unwrap();
+
+        self.data.push_back(NeovimData::ReadRegister {
+            name,
+            value: value.clone(),
+        });
+
+        value
+    }
+
+    #[cfg(feature = "neovim")]
+    async fn read_position(&mut self, cmd: &str) -> u32 {
+        self.nvim
+            .command_output(cmd)
+            .await
+            .unwrap()
+            .parse::<u32>()
+            .unwrap()
+    }
+
+    #[cfg(feature = "neovim")]
+    pub async fn state(&mut self) -> (Option<Mode>, String) {
+        let nvim_buffer = self
+            .nvim
+            .get_current_buf()
+            .await
+            .expect("Could not get neovim buffer");
+        let text = nvim_buffer
+            .get_lines(0, -1, false)
+            .await
+            .expect("Could not get buffer text")
+            .join("\n");
+
+        // nvim columns are 1-based, so -1.
+        let mut cursor_row = self.read_position("echo line('.')").await - 1;
+        let mut cursor_col = self.read_position("echo col('.')").await - 1;
+        let mut selection_row = self.read_position("echo line('v')").await - 1;
+        let mut selection_col = self.read_position("echo col('v')").await - 1;
+        let total_rows = self.read_position("echo line('$')").await - 1;
+
+        let nvim_mode_text = self
+            .nvim
+            .get_mode()
+            .await
+            .expect("Could not get mode")
+            .into_iter()
+            .find_map(|(key, value)| {
+                if key.as_str() == Some("mode") {
+                    Some(value.as_str().unwrap().to_owned())
+                } else {
+                    None
+                }
+            })
+            .expect("Could not find mode value");
+
+        let mode = match nvim_mode_text.as_ref() {
+            "i" => Some(Mode::Insert),
+            "n" => Some(Mode::Normal),
+            "v" => Some(Mode::Visual),
+            "V" => Some(Mode::VisualLine),
+            "\x16" => Some(Mode::VisualBlock),
+            _ => None,
+        };
+
+        let mut selections = Vec::new();
+        // Vim uses the index of the first and last character in the selection
+        // Zed uses the index of the positions between the characters, so we need
+        // to add one to the end in visual mode.
+        match mode {
+            Some(Mode::VisualBlock) if selection_row != cursor_row => {
+                // in zed we fake a block selecrtion by using multiple cursors (one per line)
+                // this code emulates that.
+                // to deal with casees where the selection is not perfectly rectangular we extract
+                // the content of the selection via the "a register to get the shape correctly.
+                self.nvim.input("\"aygv").await.unwrap();
+                let content = self.nvim.command_output("echo getreg('a')").await.unwrap();
+                let lines = content.split("\n").collect::<Vec<_>>();
+                let top = cmp::min(selection_row, cursor_row);
+                let left = cmp::min(selection_col, cursor_col);
+                for row in top..=cmp::max(selection_row, cursor_row) {
+                    let content = if row - top >= lines.len() as u32 {
+                        ""
+                    } else {
+                        lines[(row - top) as usize]
+                    };
+                    let line_len = self
+                        .read_position(format!("echo strlen(getline({}))", row + 1).as_str())
+                        .await;
+
+                    if left > line_len {
+                        continue;
+                    }
+
+                    let start = Point::new(row, left);
+                    let end = Point::new(row, left + content.len() as u32);
+                    if cursor_col >= selection_col {
+                        selections.push(start..end)
+                    } else {
+                        selections.push(end..start)
+                    }
+                }
+            }
+            Some(Mode::Visual) | Some(Mode::VisualLine) | Some(Mode::VisualBlock) => {
+                if selection_col > cursor_col {
+                    let selection_line_length =
+                        self.read_position("echo strlen(getline(line('v')))").await;
+                    if selection_line_length > selection_col {
+                        selection_col += 1;
+                    } else if selection_row < total_rows {
+                        selection_col = 0;
+                        selection_row += 1;
+                    }
+                } else {
+                    let cursor_line_length =
+                        self.read_position("echo strlen(getline(line('.')))").await;
+                    if cursor_line_length > cursor_col {
+                        cursor_col += 1;
+                    } else if cursor_row < total_rows {
+                        cursor_col = 0;
+                        cursor_row += 1;
+                    }
+                }
+                selections.push(
+                    Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col),
+                )
+            }
+            Some(Mode::Insert) | Some(Mode::Normal) | None => selections
+                .push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
+        }
+
+        let ranges = encode_ranges(&text, &selections);
+        let state = NeovimData::Get {
+            mode,
+            state: ranges.clone(),
+        };
+
+        if self.data.back() != Some(&state) {
+            self.data.push_back(state.clone());
+        }
+
+        (mode, ranges)
+    }
+
+    #[cfg(not(feature = "neovim"))]
+    pub async fn state(&mut self) -> (Option<Mode>, String) {
+        if let Some(NeovimData::Get { state: raw, mode }) = self.data.front() {
+            (*mode, raw.to_string())
+        } else {
+            panic!("operation does not match recorded script. re-record with --features=neovim");
+        }
+    }
+
+    pub async fn mode(&mut self) -> Option<Mode> {
+        self.state().await.0
+    }
+
+    pub async fn marked_text(&mut self) -> String {
+        self.state().await.1
+    }
+
+    fn test_data_path(test_case_id: &str) -> PathBuf {
+        let mut data_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+        data_path.push("test_data");
+        data_path.push(format!("{}.json", test_case_id));
+        data_path
+    }
+
+    #[cfg(not(feature = "neovim"))]
+    fn read_test_data(test_case_id: &str) -> VecDeque<NeovimData> {
+        let path = Self::test_data_path(test_case_id);
+        let json = std::fs::read_to_string(path).expect(
+            "Could not read test data. Is it generated? Try running test with '--features neovim'",
+        );
+
+        let mut result = VecDeque::new();
+        for line in json.lines() {
+            result.push_back(
+                serde_json::from_str(line)
+                    .expect("invalid test data. regenerate it with '--features neovim'"),
+            );
+        }
+        result
+    }
+
+    #[cfg(feature = "neovim")]
+    fn write_test_data(test_case_id: &str, data: &VecDeque<NeovimData>) {
+        let path = Self::test_data_path(test_case_id);
+        let mut json = Vec::new();
+        for entry in data {
+            serde_json::to_writer(&mut json, entry).unwrap();
+            json.push(b'\n');
+        }
+        std::fs::create_dir_all(path.parent().unwrap())
+            .expect("could not create test data directory");
+        std::fs::write(path, json).expect("could not write out test data");
+    }
+}
+
+#[cfg(feature = "neovim")]
+impl Deref for NeovimConnection {
+    type Target = Neovim<nvim_rs::compat::tokio::Compat<ChildStdin>>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.nvim
+    }
+}
+
+#[cfg(feature = "neovim")]
+impl DerefMut for NeovimConnection {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.nvim
+    }
+}
+
+#[cfg(feature = "neovim")]
+impl Drop for NeovimConnection {
+    fn drop(&mut self) {
+        Self::write_test_data(&self.test_case_id, &self.data);
+    }
+}
+
+#[cfg(feature = "neovim")]
+#[derive(Clone)]
+struct NvimHandler {}
+
+#[cfg(feature = "neovim")]
+#[async_trait]
+impl Handler for NvimHandler {
+    type Writer = nvim_rs::compat::tokio::Compat<ChildStdin>;
+
+    async fn handle_request(
+        &self,
+        _event_name: String,
+        _arguments: Vec<Value>,
+        _neovim: Neovim<Self::Writer>,
+    ) -> Result<Value, Value> {
+        unimplemented!();
+    }
+
+    async fn handle_notify(
+        &self,
+        _event_name: String,
+        _arguments: Vec<Value>,
+        _neovim: Neovim<Self::Writer>,
+    ) {
+    }
+}
+
+#[cfg(feature = "neovim")]
+fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) {
+    let (text, ranges) = util::test::marked_text_ranges(marked_text, true);
+    let point_ranges = ranges
+        .into_iter()
+        .map(|byte_range| {
+            let mut point_range = Point::zero()..Point::zero();
+            let mut ix = 0;
+            let mut position = Point::zero();
+            for c in text.chars().chain(['\0']) {
+                if ix == byte_range.start {
+                    point_range.start = position;
+                }
+                if ix == byte_range.end {
+                    point_range.end = position;
+                }
+                let len_utf8 = c.len_utf8();
+                ix += len_utf8;
+                if c == '\n' {
+                    position.row += 1;
+                    position.column = 0;
+                } else {
+                    position.column += len_utf8 as u32;
+                }
+            }
+            point_range
+        })
+        .collect::<Vec<_>>();
+    (text, point_ranges)
+}
+
+#[cfg(feature = "neovim")]
+fn encode_ranges(text: &str, point_ranges: &Vec<Range<Point>>) -> String {
+    let byte_ranges = point_ranges
+        .into_iter()
+        .map(|range| {
+            let mut byte_range = 0..0;
+            let mut ix = 0;
+            let mut position = Point::zero();
+            for c in text.chars().chain(['\0']) {
+                if position == range.start {
+                    byte_range.start = ix;
+                }
+                if position == range.end {
+                    byte_range.end = ix;
+                }
+                let len_utf8 = c.len_utf8();
+                ix += len_utf8;
+                if c == '\n' {
+                    position.row += 1;
+                    position.column = 0;
+                } else {
+                    position.column += len_utf8 as u32;
+                }
+            }
+            byte_range
+        })
+        .collect::<Vec<_>>();
+    util::test::generate_marked_text(text, &byte_ranges[..], true)
+}

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

@@ -0,0 +1,165 @@
+use std::ops::{Deref, DerefMut};
+
+use editor::test::{
+    editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
+};
+use futures::Future;
+use gpui::{Context, View, VisualContext};
+use lsp::request;
+use search::BufferSearchBar;
+
+use crate::{state::Operator, *};
+
+pub struct VimTestContext<'a> {
+    cx: EditorLspTestContext<'a>,
+}
+
+impl<'a> VimTestContext<'a> {
+    pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
+        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> {
+        Self::new_with_lsp(
+            EditorLspTestContext::new_typescript(Default::default(), cx).await,
+            true,
+        )
+    }
+
+    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));
+            });
+            settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap();
+            settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
+        });
+
+        // Setup search toolbars and keypress hook
+        cx.update_workspace(|workspace, cx| {
+            observe_keystrokes(cx);
+            workspace.active_pane().update(cx, |pane, cx| {
+                pane.toolbar().update(cx, |toolbar, cx| {
+                    let buffer_search_bar = cx.build_view(BufferSearchBar::new);
+                    toolbar.add_item(buffer_search_bar, cx);
+                    // todo!();
+                    // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+                    // toolbar.add_item(project_search_bar, cx);
+                })
+            });
+            workspace.status_bar().update(cx, |status_bar, cx| {
+                let vim_mode_indicator = cx.build_view(ModeIndicator::new);
+                status_bar.add_right_item(vim_mode_indicator, cx);
+            });
+        });
+
+        Self { cx }
+    }
+
+    pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
+    where
+        F: FnOnce(&mut T, &mut ViewContext<T>) -> R,
+    {
+        self.update_window(self.window, |_, cx| view.update(cx, update))
+            .unwrap()
+    }
+
+    pub fn workspace<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+    {
+        self.update_window(self.window, |_, cx| self.cx.workspace.update(cx, update))
+            .unwrap()
+    }
+
+    pub fn enable_vim(&mut self) {
+        self.cx.update(|cx| {
+            cx.update_global(|store: &mut SettingsStore, cx| {
+                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true));
+            });
+        })
+    }
+
+    pub fn disable_vim(&mut self) {
+        self.cx.update(|cx| {
+            cx.update_global(|store: &mut SettingsStore, cx| {
+                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false));
+            });
+        })
+    }
+
+    pub fn mode(&mut self) -> Mode {
+        self.cx.read(|cx| cx.global::<Vim>().state().mode)
+    }
+
+    pub fn active_operator(&mut self) -> Option<Operator> {
+        self.cx
+            .read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
+    }
+
+    pub fn set_state(&mut self, text: &str, mode: Mode) {
+        let window = self.window;
+        self.cx.set_state(text);
+        self.update_window(window, |_, cx| {
+            Vim::update(cx, |vim, cx| {
+                vim.switch_mode(mode, true, cx);
+            })
+        });
+        self.cx.cx.cx.run_until_parked();
+    }
+
+    #[track_caller]
+    pub fn assert_state(&mut self, text: &str, mode: Mode) {
+        self.assert_editor_state(text);
+        assert_eq!(self.mode(), mode, "{}", self.assertion_context());
+    }
+
+    pub fn assert_binding<const COUNT: usize>(
+        &mut self,
+        keystrokes: [&str; COUNT],
+        initial_state: &str,
+        initial_mode: Mode,
+        state_after: &str,
+        mode_after: Mode,
+    ) {
+        self.set_state(initial_state, initial_mode);
+        self.cx.simulate_keystrokes(keystrokes);
+        self.cx.assert_editor_state(state_after);
+        assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
+        assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
+    }
+
+    pub fn handle_request<T, F, Fut>(
+        &self,
+        handler: F,
+    ) -> futures::channel::mpsc::UnboundedReceiver<()>
+    where
+        T: 'static + request::Request,
+        T::Params: 'static + Send,
+        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
+        Fut: 'static + Send + Future<Output = Result<T::Result>>,
+    {
+        self.cx.handle_request::<T, F, Fut>(handler)
+    }
+}
+
+impl<'a> Deref for VimTestContext<'a> {
+    type Target = EditorTestContext<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
+impl<'a> DerefMut for VimTestContext<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cx
+    }
+}

crates/vim2/src/utils.rs 🔗

@@ -0,0 +1,50 @@
+use editor::{ClipboardSelection, Editor};
+use gpui::{AppContext, ClipboardItem};
+use language::Point;
+
+pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
+    let selections = editor.selections.all_adjusted(cx);
+    let buffer = editor.buffer().read(cx).snapshot(cx);
+    let mut text = String::new();
+    let mut clipboard_selections = Vec::with_capacity(selections.len());
+    {
+        let mut is_first = true;
+        for selection in selections.iter() {
+            let mut start = selection.start;
+            let end = selection.end;
+            if is_first {
+                is_first = false;
+            } else {
+                text.push_str("\n");
+            }
+            let initial_len = text.len();
+
+            // if the file does not end with \n, and our line-mode selection ends on
+            // that line, we will have expanded the start of the selection to ensure it
+            // contains a newline (so that delete works as expected). We undo that change
+            // here.
+            let is_last_line = linewise
+                && end.row == buffer.max_buffer_row()
+                && buffer.max_point().column > 0
+                && start.row < buffer.max_buffer_row()
+                && start == Point::new(start.row, buffer.line_len(start.row));
+
+            if is_last_line {
+                start = Point::new(start.row + 1, 0);
+            }
+            for chunk in buffer.text_for_range(start..end) {
+                text.push_str(chunk);
+            }
+            if is_last_line {
+                text.push_str("\n");
+            }
+            clipboard_selections.push(ClipboardSelection {
+                len: text.len() - initial_len,
+                is_entire_line: linewise,
+                first_line_indent: buffer.indent_size_for_line(start.row).len,
+            });
+        }
+    }
+
+    cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
+}

crates/vim2/src/vim.rs 🔗

@@ -0,0 +1,607 @@
+#[cfg(test)]
+mod test;
+
+mod command;
+mod editor_events;
+mod insert;
+mod mode_indicator;
+mod motion;
+mod normal;
+mod object;
+mod state;
+mod utils;
+mod visual;
+
+use anyhow::Result;
+use collections::{CommandPaletteFilter, HashMap};
+use command_palette::CommandPaletteInterceptor;
+use editor::{movement, Editor, EditorEvent, EditorMode};
+use gpui::{
+    actions, impl_actions, Action, AppContext, EntityId, KeyContext, Subscription, View,
+    ViewContext, WeakView, WindowContext,
+};
+use language::{CursorShape, Point, Selection, SelectionGoal};
+pub use mode_indicator::ModeIndicator;
+use motion::Motion;
+use normal::normal_replace;
+use serde::Deserialize;
+use settings::{update_settings_file, Settings, SettingsStore};
+use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
+use std::{ops::Range, sync::Arc};
+use visual::{visual_block_motion, visual_replace};
+use workspace::{self, Workspace};
+
+use crate::state::ReplayableAction;
+
+pub struct VimModeSetting(pub bool);
+
+#[derive(Clone, Deserialize, PartialEq)]
+pub struct SwitchMode(pub Mode);
+
+#[derive(Clone, Deserialize, PartialEq)]
+pub struct PushOperator(pub Operator);
+
+#[derive(Clone, Deserialize, PartialEq)]
+struct Number(usize);
+
+actions!(
+    vim,
+    [Tab, Enter, Object, InnerObject, FindForward, FindBackward]
+);
+// in the workspace namespace so it's not filtered out when vim is disabled.
+actions!(workspace, [ToggleVimMode]);
+
+impl_actions!(vim, [SwitchMode, PushOperator, Number]);
+
+pub fn init(cx: &mut AppContext) {
+    cx.set_global(Vim::default());
+    VimModeSetting::register(cx);
+
+    editor_events::init(cx);
+
+    cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
+        .detach();
+
+    // Any time settings change, update vim mode to match. The Vim struct
+    // will be initialized as disabled by default, so we filter its commands
+    // out when starting up.
+    cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
+        filter.hidden_namespaces.insert("vim");
+    });
+    cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
+        vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+    });
+    cx.observe_global::<SettingsStore>(|cx| {
+        cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
+            vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+        });
+    })
+    .detach();
+}
+
+fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
+        Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
+    });
+    workspace.register_action(
+        |_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
+            Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
+        },
+    );
+    workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
+        Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
+    });
+
+    workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
+        Vim::active_editor_input_ignored(" ".into(), cx)
+    });
+
+    workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
+        Vim::active_editor_input_ignored("\n".into(), cx)
+    });
+
+    workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
+        let fs = workspace.app_state().fs.clone();
+        let currently_enabled = VimModeSetting::get_global(cx).0;
+        update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
+            *setting = Some(!currently_enabled)
+        })
+    });
+
+    normal::register(workspace, cx);
+    insert::register(workspace, cx);
+    motion::register(workspace, cx);
+    command::register(workspace, cx);
+    object::register(workspace, cx);
+    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()
+}
+
+#[derive(Default)]
+pub struct Vim {
+    active_editor: Option<WeakView<Editor>>,
+    editor_subscription: Option<Subscription>,
+    enabled: bool,
+    editor_states: HashMap<EntityId, EditorState>,
+    workspace_state: WorkspaceState,
+    default_state: EditorState,
+}
+
+impl Vim {
+    fn read(cx: &mut AppContext) -> &Self {
+        cx.global::<Self>()
+    }
+
+    fn update<F, S>(cx: &mut WindowContext, update: F) -> S
+    where
+        F: FnOnce(&mut Self, &mut WindowContext) -> S,
+    {
+        cx.update_global(update)
+    }
+
+    fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
+        self.active_editor = Some(editor.clone().downgrade());
+        self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
+            EditorEvent::SelectionsChanged { local: true } => {
+                let editor = editor.read(cx);
+                if editor.leader_peer_id().is_none() {
+                    let newest = editor.selections.newest::<usize>(cx);
+                    local_selections_changed(newest, cx);
+                }
+            }
+            EditorEvent::InputIgnored { text } => {
+                Vim::active_editor_input_ignored(text.clone(), cx);
+                Vim::record_insertion(text, None, cx)
+            }
+            EditorEvent::InputHandled {
+                text,
+                utf16_range_to_replace: range_to_replace,
+            } => Vim::record_insertion(text, range_to_replace.clone(), cx),
+            _ => {}
+        }));
+
+        if self.enabled {
+            let editor = editor.read(cx);
+            let editor_mode = editor.mode();
+            let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
+
+            if editor_mode == EditorMode::Full
+                && !newest_selection_empty
+                && self.state().mode == Mode::Normal
+                // When following someone, don't switch vim mode.
+                && editor.leader_peer_id().is_none()
+            {
+                self.switch_mode(Mode::Visual, true, cx);
+            }
+        }
+
+        self.sync_vim_settings(cx);
+    }
+
+    fn record_insertion(
+        text: &Arc<str>,
+        range_to_replace: Option<Range<isize>>,
+        cx: &mut WindowContext,
+    ) {
+        Vim::update(cx, |vim, _| {
+            if vim.workspace_state.recording {
+                vim.workspace_state
+                    .recorded_actions
+                    .push(ReplayableAction::Insertion {
+                        text: text.clone(),
+                        utf16_range_to_replace: range_to_replace,
+                    });
+                if vim.workspace_state.stop_recording_after_next_action {
+                    vim.workspace_state.recording = false;
+                    vim.workspace_state.stop_recording_after_next_action = false;
+                }
+            }
+        });
+    }
+
+    fn update_active_editor<S>(
+        &self,
+        cx: &mut WindowContext,
+        update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
+    ) -> Option<S> {
+        let editor = self.active_editor.clone()?.upgrade()?;
+        Some(editor.update(cx, update))
+    }
+
+    pub fn start_recording(&mut self, cx: &mut WindowContext) {
+        if !self.workspace_state.replaying {
+            self.workspace_state.recording = true;
+            self.workspace_state.recorded_actions = Default::default();
+            self.workspace_state.recorded_count = None;
+
+            let selections = self
+                .active_editor
+                .as_ref()
+                .and_then(|editor| editor.upgrade())
+                .map(|editor| {
+                    let editor = editor.read(cx);
+                    (
+                        editor.selections.oldest::<Point>(cx),
+                        editor.selections.newest::<Point>(cx),
+                    )
+                });
+
+            if let Some((oldest, newest)) = selections {
+                self.workspace_state.recorded_selection = match self.state().mode {
+                    Mode::Visual if newest.end.row == newest.start.row => {
+                        RecordedSelection::SingleLine {
+                            cols: newest.end.column - newest.start.column,
+                        }
+                    }
+                    Mode::Visual => RecordedSelection::Visual {
+                        rows: newest.end.row - newest.start.row,
+                        cols: newest.end.column,
+                    },
+                    Mode::VisualLine => RecordedSelection::VisualLine {
+                        rows: newest.end.row - newest.start.row,
+                    },
+                    Mode::VisualBlock => RecordedSelection::VisualBlock {
+                        rows: newest.end.row.abs_diff(oldest.start.row),
+                        cols: newest.end.column.abs_diff(oldest.start.column),
+                    },
+                    _ => RecordedSelection::None,
+                }
+            } else {
+                self.workspace_state.recorded_selection = RecordedSelection::None;
+            }
+        }
+    }
+
+    pub fn stop_recording(&mut self) {
+        if self.workspace_state.recording {
+            self.workspace_state.stop_recording_after_next_action = true;
+        }
+    }
+
+    pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
+        if self.workspace_state.recording {
+            self.workspace_state
+                .recorded_actions
+                .push(ReplayableAction::Action(action.boxed_clone()));
+            self.workspace_state.recording = false;
+            self.workspace_state.stop_recording_after_next_action = false;
+        }
+    }
+
+    pub fn record_current_action(&mut self, cx: &mut WindowContext) {
+        self.start_recording(cx);
+        self.stop_recording();
+    }
+
+    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
+        let state = self.state();
+        let last_mode = state.mode;
+        let prior_mode = state.last_mode;
+        self.update_state(|state| {
+            state.last_mode = last_mode;
+            state.mode = mode;
+            state.operator_stack.clear();
+        });
+        if mode != Mode::Insert {
+            self.take_count(cx);
+        }
+
+        // Sync editor settings like clip mode
+        self.sync_vim_settings(cx);
+
+        if leave_selections {
+            return;
+        }
+
+        // Adjust selections
+        self.update_active_editor(cx, |editor, cx| {
+            if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
+            {
+                visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
+            }
+
+            editor.change_selections(None, cx, |s| {
+                // we cheat with visual block mode and use multiple cursors.
+                // the cost of this cheat is we need to convert back to a single
+                // cursor whenever vim would.
+                if last_mode == Mode::VisualBlock
+                    && (mode != Mode::VisualBlock && mode != Mode::Insert)
+                {
+                    let tail = s.oldest_anchor().tail();
+                    let head = s.newest_anchor().head();
+                    s.select_anchor_ranges(vec![tail..head]);
+                } else if last_mode == Mode::Insert
+                    && prior_mode == Mode::VisualBlock
+                    && mode != Mode::VisualBlock
+                {
+                    let pos = s.first_anchor().head();
+                    s.select_anchor_ranges(vec![pos..pos])
+                }
+
+                s.move_with(|map, selection| {
+                    if last_mode.is_visual() && !mode.is_visual() {
+                        let mut point = selection.head();
+                        if !selection.reversed && !selection.is_empty() {
+                            point = movement::left(map, selection.head());
+                        }
+                        selection.collapse_to(point, selection.goal)
+                    } else if !last_mode.is_visual() && mode.is_visual() {
+                        if selection.is_empty() {
+                            selection.end = movement::right(map, selection.start);
+                        }
+                    }
+                });
+            })
+        });
+    }
+
+    fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
+        if self.active_operator().is_some() {
+            self.update_state(|state| {
+                state.post_count = Some(state.post_count.unwrap_or(0) * 10 + number)
+            })
+        } else {
+            self.update_state(|state| {
+                state.pre_count = Some(state.pre_count.unwrap_or(0) * 10 + number)
+            })
+        }
+        // update the keymap so that 0 works
+        self.sync_vim_settings(cx)
+    }
+
+    fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
+        if self.workspace_state.replaying {
+            return self.workspace_state.recorded_count;
+        }
+
+        let count = if self.state().post_count == None && self.state().pre_count == None {
+            return None;
+        } else {
+            Some(self.update_state(|state| {
+                state.post_count.take().unwrap_or(1) * state.pre_count.take().unwrap_or(1)
+            }))
+        };
+        if self.workspace_state.recording {
+            self.workspace_state.recorded_count = count;
+        }
+        self.sync_vim_settings(cx);
+        count
+    }
+
+    fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
+        if matches!(
+            operator,
+            Operator::Change | Operator::Delete | Operator::Replace
+        ) {
+            self.start_recording(cx)
+        };
+        self.update_state(|state| state.operator_stack.push(operator));
+        self.sync_vim_settings(cx);
+    }
+
+    fn maybe_pop_operator(&mut self) -> Option<Operator> {
+        self.update_state(|state| state.operator_stack.pop())
+    }
+
+    fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
+        let popped_operator = self.update_state( |state| state.operator_stack.pop()
+        )            .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
+        self.sync_vim_settings(cx);
+        popped_operator
+    }
+    fn clear_operator(&mut self, cx: &mut WindowContext) {
+        self.take_count(cx);
+        self.update_state(|state| state.operator_stack.clear());
+        self.sync_vim_settings(cx);
+    }
+
+    fn active_operator(&self) -> Option<Operator> {
+        self.state().operator_stack.last().copied()
+    }
+
+    fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
+        if text.is_empty() {
+            return;
+        }
+
+        match Vim::read(cx).active_operator() {
+            Some(Operator::FindForward { before }) => {
+                let find = Motion::FindForward {
+                    before,
+                    char: text.chars().next().unwrap(),
+                };
+                Vim::update(cx, |vim, _| {
+                    vim.workspace_state.last_find = Some(find.clone())
+                });
+                motion::motion(find, cx)
+            }
+            Some(Operator::FindBackward { after }) => {
+                let find = Motion::FindBackward {
+                    after,
+                    char: text.chars().next().unwrap(),
+                };
+                Vim::update(cx, |vim, _| {
+                    vim.workspace_state.last_find = Some(find.clone())
+                });
+                motion::motion(find, cx)
+            }
+            Some(Operator::Replace) => match Vim::read(cx).state().mode {
+                Mode::Normal => normal_replace(text, cx),
+                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_replace(text, cx),
+                _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
+            },
+            _ => {}
+        }
+    }
+
+    fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
+        if self.enabled != enabled {
+            self.enabled = enabled;
+
+            cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
+                if self.enabled {
+                    filter.hidden_namespaces.remove("vim");
+                } else {
+                    filter.hidden_namespaces.insert("vim");
+                }
+            });
+
+            if self.enabled {
+                cx.set_global::<CommandPaletteInterceptor>(Box::new(command::command_interceptor));
+            } else if cx.has_global::<CommandPaletteInterceptor>() {
+                let _ = cx.remove_global::<CommandPaletteInterceptor>();
+            }
+
+            if let Some(active_window) = cx.active_window() {
+                active_window
+                    .update(cx, |root_view, cx| {
+                        if self.enabled {
+                            let active_editor = root_view
+                                .downcast::<Workspace>()
+                                .ok()
+                                .and_then(|workspace| workspace.read(cx).active_item(cx))
+                                .and_then(|item| item.downcast::<Editor>());
+                            if let Some(active_editor) = active_editor {
+                                self.set_active_editor(active_editor, cx);
+                            }
+                            self.switch_mode(Mode::Normal, false, cx);
+                        }
+                        self.sync_vim_settings(cx);
+                    })
+                    .ok();
+            }
+        }
+    }
+
+    pub fn state(&self) -> &EditorState {
+        if let Some(active_editor) = self.active_editor.as_ref() {
+            if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
+                return state;
+            }
+        }
+
+        &self.default_state
+    }
+
+    pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
+        let mut state = self.state().clone();
+        let ret = func(&mut state);
+
+        if let Some(active_editor) = self.active_editor.as_ref() {
+            self.editor_states.insert(active_editor.entity_id(), state);
+        }
+
+        ret
+    }
+
+    fn sync_vim_settings(&self, cx: &mut WindowContext) {
+        let state = self.state();
+        let cursor_shape = state.cursor_shape();
+
+        self.update_active_editor(cx, |editor, cx| {
+            if self.enabled && editor.mode() == EditorMode::Full {
+                editor.set_cursor_shape(cursor_shape, cx);
+                editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
+                editor.set_collapse_matches(true);
+                editor.set_input_enabled(!state.vim_controlled());
+                editor.set_autoindent(state.should_autoindent());
+                editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
+                let context_layer = state.keymap_context_layer();
+                editor.set_keymap_context_layer::<Self>(context_layer, cx);
+            } else {
+                // Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur,
+                // but we need collapse_matches to persist when the search bar is focused.
+                editor.set_collapse_matches(false);
+                self.unhook_vim_settings(editor, cx);
+            }
+        });
+    }
+
+    fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+        editor.set_cursor_shape(CursorShape::Bar, cx);
+        editor.set_clip_at_line_ends(false, cx);
+        editor.set_input_enabled(true);
+        editor.set_autoindent(true);
+        editor.selections.line_mode = false;
+
+        // we set the VimEnabled context on all editors so that we
+        // can distinguish between vim mode and non-vim mode in the BufferSearchBar.
+        // This is a bit of a hack, but currently the search crate does not depend on vim,
+        // and it seems nice to keep it that way.
+        if self.enabled {
+            let mut context = KeyContext::default();
+            context.add("VimEnabled");
+            editor.set_keymap_context_layer::<Self>(context, cx)
+        } else {
+            editor.remove_keymap_context_layer::<Self>(cx);
+        }
+    }
+}
+
+impl Settings for VimModeSetting {
+    const KEY: Option<&'static str> = Some("vim_mode");
+
+    type FileContent = Option<bool>;
+
+    fn load(
+        default_value: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        _: &mut AppContext,
+    ) -> Result<Self> {
+        Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
+            default_value.ok_or_else(Self::missing_default)?,
+        )))
+    }
+}
+
+fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
+            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
+                vim.switch_mode(Mode::VisualBlock, false, cx);
+            } else {
+                vim.switch_mode(Mode::Visual, false, cx)
+            }
+        }
+    })
+}

crates/vim2/src/visual.rs 🔗

@@ -0,0 +1,1029 @@
+use anyhow::Result;
+use std::sync::Arc;
+
+use collections::HashMap;
+use editor::{
+    display_map::{DisplaySnapshot, ToDisplayPoint},
+    movement,
+    scroll::autoscroll::Autoscroll,
+    Bias, DisplayPoint, Editor,
+};
+use gpui::{actions, ViewContext, WindowContext};
+use language::{Selection, SelectionGoal};
+use workspace::Workspace;
+
+use crate::{
+    motion::{start_of_line, Motion},
+    object::Object,
+    state::{Mode, Operator},
+    utils::copy_selections_content,
+    Vim,
+};
+
+actions!(
+    vim,
+    [
+        ToggleVisual,
+        ToggleVisualLine,
+        ToggleVisualBlock,
+        VisualDelete,
+        VisualYank,
+        OtherEnd,
+        SelectNext,
+        SelectPrevious,
+    ]
+);
+
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+    workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
+        toggle_mode(Mode::Visual, cx)
+    });
+    workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
+        toggle_mode(Mode::VisualLine, cx)
+    });
+    workspace.register_action(
+        |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
+            toggle_mode(Mode::VisualBlock, cx)
+        },
+    );
+    workspace.register_action(other_end);
+    workspace.register_action(delete);
+    workspace.register_action(yank);
+
+    workspace.register_action(|workspace, action, cx| {
+        select_next(workspace, action, cx).ok();
+    });
+    workspace.register_action(|workspace, action, cx| {
+        select_previous(workspace, action, cx).ok();
+    });
+}
+
+pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        vim.update_active_editor(cx, |editor, cx| {
+            let text_layout_details = editor.text_layout_details(cx);
+            if vim.state().mode == Mode::VisualBlock
+                && !matches!(
+                    motion,
+                    Motion::EndOfLine {
+                        display_lines: false
+                    }
+                )
+            {
+                let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
+                visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
+                    motion.move_point(map, point, goal, times, &text_layout_details)
+                })
+            } else {
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.move_with(|map, selection| {
+                        let was_reversed = selection.reversed;
+                        let mut current_head = selection.head();
+
+                        // our motions assume the current character is after the cursor,
+                        // but in (forward) visual mode the current character is just
+                        // before the end of the selection.
+
+                        // If the file ends with a newline (which is common) we don't do this.
+                        // so that if you go to the end of such a file you can use "up" to go
+                        // to the previous line and have it work somewhat as expected.
+                        if !selection.reversed
+                            && !selection.is_empty()
+                            && !(selection.end.column() == 0 && selection.end == map.max_point())
+                        {
+                            current_head = movement::left(map, selection.end)
+                        }
+
+                        let Some((new_head, goal)) = motion.move_point(
+                            map,
+                            current_head,
+                            selection.goal,
+                            times,
+                            &text_layout_details,
+                        ) else {
+                            return;
+                        };
+
+                        selection.set_head(new_head, goal);
+
+                        // ensure the current character is included in the selection.
+                        if !selection.reversed {
+                            let next_point = if vim.state().mode == Mode::VisualBlock {
+                                movement::saturating_right(map, selection.end)
+                            } else {
+                                movement::right(map, selection.end)
+                            };
+
+                            if !(next_point.column() == 0 && next_point == map.max_point()) {
+                                selection.end = next_point;
+                            }
+                        }
+
+                        // vim always ensures the anchor character stays selected.
+                        // if our selection has reversed, we need to move the opposite end
+                        // to ensure the anchor is still selected.
+                        if was_reversed && !selection.reversed {
+                            selection.start = movement::left(map, selection.start);
+                        } else if !was_reversed && selection.reversed {
+                            selection.end = movement::right(map, selection.end);
+                        }
+                    })
+                });
+            }
+        });
+    });
+}
+
+pub fn visual_block_motion(
+    preserve_goal: bool,
+    editor: &mut Editor,
+    cx: &mut ViewContext<Editor>,
+    mut move_selection: impl FnMut(
+        &DisplaySnapshot,
+        DisplayPoint,
+        SelectionGoal,
+    ) -> Option<(DisplayPoint, SelectionGoal)>,
+) {
+    let text_layout_details = editor.text_layout_details(cx);
+    editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+        let map = &s.display_map();
+        let mut head = s.newest_anchor().head().to_display_point(map);
+        let mut tail = s.oldest_anchor().tail().to_display_point(map);
+
+        let mut head_x = map.x_for_display_point(head, &text_layout_details);
+        let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
+
+        let (start, end) = match s.newest_anchor().goal {
+            SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
+            SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
+            _ => (tail_x.0, head_x.0),
+        };
+        let mut goal = SelectionGoal::HorizontalRange { start, end };
+
+        let was_reversed = tail_x > head_x;
+        if !was_reversed && !preserve_goal {
+            head = movement::saturating_left(map, head);
+        }
+
+        let Some((new_head, _)) = move_selection(&map, head, goal) else {
+            return;
+        };
+        head = new_head;
+        head_x = map.x_for_display_point(head, &text_layout_details);
+
+        let is_reversed = tail_x > head_x;
+        if was_reversed && !is_reversed {
+            tail = movement::saturating_left(map, tail);
+            tail_x = map.x_for_display_point(tail, &text_layout_details);
+        } else if !was_reversed && is_reversed {
+            tail = movement::saturating_right(map, tail);
+            tail_x = map.x_for_display_point(tail, &text_layout_details);
+        }
+        if !is_reversed && !preserve_goal {
+            head = movement::saturating_right(map, head);
+            head_x = map.x_for_display_point(head, &text_layout_details);
+        }
+
+        let positions = if is_reversed {
+            head_x..tail_x
+        } else {
+            tail_x..head_x
+        };
+
+        if !preserve_goal {
+            goal = SelectionGoal::HorizontalRange {
+                start: positions.start.0,
+                end: positions.end.0,
+            };
+        }
+
+        let mut selections = Vec::new();
+        let mut row = tail.row();
+
+        loop {
+            let layed_out_line = map.layout_row(row, &text_layout_details);
+            let start = DisplayPoint::new(
+                row,
+                layed_out_line.closest_index_for_x(positions.start) as u32,
+            );
+            let mut end = DisplayPoint::new(
+                row,
+                layed_out_line.closest_index_for_x(positions.end) as u32,
+            );
+            if end <= start {
+                if start.column() == map.line_len(start.row()) {
+                    end = start;
+                } else {
+                    end = movement::saturating_right(map, start);
+                }
+            }
+
+            if positions.start <= layed_out_line.width {
+                let selection = Selection {
+                    id: s.new_selection_id(),
+                    start: start.to_point(map),
+                    end: end.to_point(map),
+                    reversed: is_reversed,
+                    goal: goal.clone(),
+                };
+
+                selections.push(selection);
+            }
+            if row == head.row() {
+                break;
+            }
+            if tail.row() > head.row() {
+                row -= 1
+            } else {
+                row += 1
+            }
+        }
+
+        s.select(selections);
+    })
+}
+
+pub fn visual_object(object: Object, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        if let Some(Operator::Object { around }) = vim.active_operator() {
+            vim.pop_operator(cx);
+            let current_mode = vim.state().mode;
+            let target_mode = object.target_visual_mode(current_mode);
+            if target_mode != current_mode {
+                vim.switch_mode(target_mode, true, cx);
+            }
+
+            vim.update_active_editor(cx, |editor, cx| {
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.move_with(|map, selection| {
+                        let mut head = selection.head();
+
+                        // all our motions assume that the current character is
+                        // after the cursor; however in the case of a visual selection
+                        // the current character is before the cursor.
+                        if !selection.reversed {
+                            head = movement::left(map, head);
+                        }
+
+                        if let Some(range) = object.range(map, head, around) {
+                            if !range.is_empty() {
+                                let expand_both_ways = object.always_expands_both_ways()
+                                    || selection.is_empty()
+                                    || movement::right(map, selection.start) == selection.end;
+
+                                if expand_both_ways {
+                                    selection.start = range.start;
+                                    selection.end = range.end;
+                                } else if selection.reversed {
+                                    selection.start = range.start;
+                                } else {
+                                    selection.end = range.end;
+                                }
+                            }
+                        }
+                    });
+                });
+            });
+        }
+    });
+}
+
+fn toggle_mode(mode: Mode, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        if vim.state().mode == mode {
+            vim.switch_mode(Mode::Normal, false, cx);
+        } else {
+            vim.switch_mode(mode, false, cx);
+        }
+    })
+}
+
+pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|_, selection| {
+                    selection.reversed = !selection.reversed;
+                })
+            })
+        })
+    });
+}
+
+pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.record_current_action(cx);
+        vim.update_active_editor(cx, |editor, cx| {
+            let mut original_columns: HashMap<_, _> = Default::default();
+            let line_mode = editor.selections.line_mode;
+
+            editor.transact(cx, |editor, cx| {
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.move_with(|map, selection| {
+                        if line_mode {
+                            let mut position = selection.head();
+                            if !selection.reversed {
+                                position = movement::left(map, position);
+                            }
+                            original_columns.insert(selection.id, position.to_point(map).column);
+                        }
+                        selection.goal = SelectionGoal::None;
+                    });
+                });
+                copy_selections_content(editor, line_mode, cx);
+                editor.insert("", cx);
+
+                // Fixup cursor position after the deletion
+                editor.set_clip_at_line_ends(true, cx);
+                editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                    s.move_with(|map, selection| {
+                        let mut cursor = selection.head().to_point(map);
+
+                        if let Some(column) = original_columns.get(&selection.id) {
+                            cursor.column = *column
+                        }
+                        let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
+                        selection.collapse_to(cursor, selection.goal)
+                    });
+                    if vim.state().mode == Mode::VisualBlock {
+                        s.select_anchors(vec![s.first_anchor()])
+                    }
+                });
+            })
+        });
+        vim.switch_mode(Mode::Normal, true, cx);
+    });
+}
+
+pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
+    Vim::update(cx, |vim, cx| {
+        vim.update_active_editor(cx, |editor, cx| {
+            let line_mode = editor.selections.line_mode;
+            copy_selections_content(editor, line_mode, cx);
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|map, selection| {
+                    if line_mode {
+                        selection.start = start_of_line(map, false, selection.start);
+                    };
+                    selection.collapse_to(selection.start, SelectionGoal::None)
+                });
+                if vim.state().mode == Mode::VisualBlock {
+                    s.select_anchors(vec![s.first_anchor()])
+                }
+            });
+        });
+        vim.switch_mode(Mode::Normal, true, cx);
+    });
+}
+
+pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        vim.stop_recording();
+        vim.update_active_editor(cx, |editor, cx| {
+            editor.transact(cx, |editor, cx| {
+                let (display_map, selections) = editor.selections.all_adjusted_display(cx);
+
+                // Selections are biased right at the start. So we need to store
+                // anchors that are biased left so that we can restore the selections
+                // after the change
+                let stable_anchors = editor
+                    .selections
+                    .disjoint_anchors()
+                    .into_iter()
+                    .map(|selection| {
+                        let start = selection.start.bias_left(&display_map.buffer_snapshot);
+                        start..start
+                    })
+                    .collect::<Vec<_>>();
+
+                let mut edits = Vec::new();
+                for selection in selections.iter() {
+                    let selection = selection.clone();
+                    for row_range in
+                        movement::split_display_range_by_lines(&display_map, selection.range())
+                    {
+                        let range = row_range.start.to_offset(&display_map, Bias::Right)
+                            ..row_range.end.to_offset(&display_map, Bias::Right);
+                        let text = text.repeat(range.len());
+                        edits.push((range, text));
+                    }
+                }
+
+                editor.buffer().update(cx, |buffer, cx| {
+                    buffer.edit(edits, None, cx);
+                });
+                editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors));
+            });
+        });
+        vim.switch_mode(Mode::Normal, false, cx);
+    });
+}
+
+pub fn select_next(
+    _: &mut Workspace,
+    _: &SelectNext,
+    cx: &mut ViewContext<Workspace>,
+) -> Result<()> {
+    Vim::update(cx, |vim, cx| {
+        let count =
+            vim.take_count(cx)
+                .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
+        vim.update_active_editor(cx, |editor, cx| {
+            for _ in 0..count {
+                match editor.select_next(&Default::default(), cx) {
+                    Err(a) => return Err(a),
+                    _ => {}
+                }
+            }
+            Ok(())
+        })
+    })
+    .unwrap_or(Ok(()))
+}
+
+pub fn select_previous(
+    _: &mut Workspace,
+    _: &SelectPrevious,
+    cx: &mut ViewContext<Workspace>,
+) -> Result<()> {
+    Vim::update(cx, |vim, cx| {
+        let count =
+            vim.take_count(cx)
+                .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
+        vim.update_active_editor(cx, |editor, cx| {
+            for _ in 0..count {
+                match editor.select_previous(&Default::default(), cx) {
+                    Err(a) => return Err(a),
+                    _ => {}
+                }
+            }
+            Ok(())
+        })
+    })
+    .unwrap_or(Ok(()))
+}
+
+// #[cfg(test)]
+// mod test {
+//     use indoc::indoc;
+//     use workspace::item::Item;
+
+//     use crate::{
+//         state::Mode,
+//         test::{NeovimBackedTestContext, VimTestContext},
+//     };
+
+//     #[gpui::test]
+//     async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "The ˇquick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
+
+//         // entering visual mode should select the character
+//         // under cursor
+//         cx.simulate_shared_keystrokes(["v"]).await;
+//         cx.assert_shared_state(indoc! { "The «qˇ»uick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
+
+//         // forwards motions should extend the selection
+//         cx.simulate_shared_keystrokes(["w", "j"]).await;
+//         cx.assert_shared_state(indoc! { "The «quick brown
+//             fox jumps oˇ»ver
+//             the lazy dog"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["escape"]).await;
+//         assert_eq!(Mode::Normal, cx.neovim_mode().await);
+//         cx.assert_shared_state(indoc! { "The quick brown
+//             fox jumps ˇover
+//             the lazy dog"})
+//             .await;
+
+//         // motions work backwards
+//         cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
+//         cx.assert_shared_state(indoc! { "The «ˇquick brown
+//             fox jumps o»ver
+//             the lazy dog"})
+//             .await;
+
+//         // works on empty lines
+//         cx.set_shared_state(indoc! {"
+//             a
+//             ˇ
+//             b
+//             "})
+//             .await;
+//         let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
+//         cx.simulate_shared_keystrokes(["v"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             «
+//             ˇ»b
+//         "})
+//             .await;
+//         cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
+
+//         // toggles off again
+//         cx.simulate_shared_keystrokes(["v"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             ˇ
+//             b
+//             "})
+//             .await;
+
+//         // works at the end of a document
+//         cx.set_shared_state(indoc! {"
+//             a
+//             b
+//             ˇ"})
+//             .await;
+
+//         cx.simulate_shared_keystrokes(["v"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             b
+//             ˇ"})
+//             .await;
+//         assert_eq!(cx.mode(), cx.neovim_mode().await);
+//     }
+
+//     #[gpui::test]
+//     async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "The ˇquick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["shift-v"]).await;
+//         cx.assert_shared_state(indoc! { "The «qˇ»uick brown
+//             fox jumps over
+//             the lazy dog"})
+//             .await;
+//         assert_eq!(cx.mode(), cx.neovim_mode().await);
+//         cx.simulate_shared_keystrokes(["x"]).await;
+//         cx.assert_shared_state(indoc! { "fox ˇjumps over
+//         the lazy dog"})
+//             .await;
+
+//         // it should work on empty lines
+//         cx.set_shared_state(indoc! {"
+//             a
+//             ˇ
+//             b"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["shift-v"]).await;
+//         cx.assert_shared_state(indoc! { "
+//             a
+//             «
+//             ˇ»b"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["x"]).await;
+//         cx.assert_shared_state(indoc! { "
+//             a
+//             ˇb"})
+//             .await;
+
+//         // it should work at the end of the document
+//         cx.set_shared_state(indoc! {"
+//             a
+//             b
+//             ˇ"})
+//             .await;
+//         let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
+//         cx.simulate_shared_keystrokes(["shift-v"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             b
+//             ˇ"})
+//             .await;
+//         assert_eq!(cx.mode(), cx.neovim_mode().await);
+//         cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
+//         cx.simulate_shared_keystrokes(["x"]).await;
+//         cx.assert_shared_state(indoc! {"
+//             a
+//             ˇb"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
+//             .await;
+
+//         cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
+//             .await;
+//         cx.assert_binding_matches(
+//             ["v", "w", "j", "x"],
+//             indoc! {"
+//                 The ˇquick brown
+//                 fox jumps over
+//                 the lazy dog"},
+//         )
+//         .await;
+//         // Test pasting code copied on delete
+//         cx.simulate_shared_keystrokes(["j", "p"]).await;
+//         cx.assert_state_matches().await;
+
+//         let mut cx = cx.binding(["v", "w", "j", "x"]);
+//         cx.assert_all(indoc! {"
+//                 The ˇquick brown
+//                 fox jumps over
+//                 the ˇlazy dog"})
+//             .await;
+//         let mut cx = cx.binding(["v", "b", "k", "x"]);
+//         cx.assert_all(indoc! {"
+//                 The ˇquick brown
+//                 fox jumps ˇover
+//                 the ˇlazy dog"})
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_line_delete(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(["shift-v", "x"]).await;
+//         cx.assert_state_matches().await;
+
+//         // Test pasting code copied on delete
+//         cx.simulate_shared_keystroke("p").await;
+//         cx.assert_state_matches().await;
+
+//         cx.set_shared_state(indoc! {"
+//                 The quick brown
+//                 fox jumps over
+//                 the laˇzy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
+//         cx.assert_state_matches().await;
+//         cx.assert_shared_clipboard("the lazy dog\n").await;
+
+//         for marked_text in cx.each_marked_position(indoc! {"
+//                         The quˇick brown
+//                         fox jumps over
+//                         the lazy dog"})
+//         {
+//             cx.set_shared_state(&marked_text).await;
+//             cx.simulate_shared_keystrokes(["shift-v", "j", "x"]).await;
+//             cx.assert_state_matches().await;
+//             // Test pasting code copied on delete
+//             cx.simulate_shared_keystroke("p").await;
+//             cx.assert_state_matches().await;
+//         }
+
+//         cx.set_shared_state(indoc! {"
+//             The ˇlong line
+//             should not
+//             crash
+//             "})
+//             .await;
+//         cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
+//         cx.assert_state_matches().await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state("The quick ˇbrown").await;
+//         cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
+//         cx.assert_shared_state("The quick ˇbrown").await;
+//         cx.assert_shared_clipboard("brown").await;
+
+//         cx.set_shared_state(indoc! {"
+//                 The ˇquick brown
+//                 fox jumps over
+//                 the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
+//         cx.assert_shared_state(indoc! {"
+//                     The ˇquick brown
+//                     fox jumps over
+//                     the lazy dog"})
+//             .await;
+//         cx.assert_shared_clipboard(indoc! {"
+//                 quick brown
+//                 fox jumps o"})
+//             .await;
+
+//         cx.set_shared_state(indoc! {"
+//                     The quick brown
+//                     fox jumps over
+//                     the ˇlazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
+//         cx.assert_shared_state(indoc! {"
+//                     The quick brown
+//                     fox jumps over
+//                     the ˇlazy dog"})
+//             .await;
+//         cx.assert_shared_clipboard("lazy d").await;
+//         cx.simulate_shared_keystrokes(["shift-v", "y"]).await;
+//         cx.assert_shared_clipboard("the lazy dog\n").await;
+
+//         let mut cx = cx.binding(["v", "b", "k", "y"]);
+//         cx.set_shared_state(indoc! {"
+//                     The ˇquick brown
+//                     fox jumps over
+//                     the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["v", "b", "k", "y"]).await;
+//         cx.assert_shared_state(indoc! {"
+//                     ˇThe quick brown
+//                     fox jumps over
+//                     the lazy dog"})
+//             .await;
+//         cx.assert_clipboard_content(Some("The q"));
+
+//         cx.set_shared_state(indoc! {"
+//                     The quick brown
+//                     fox ˇjumps over
+//                     the lazy dog"})
+//             .await;
+//         cx.simulate_shared_keystrokes(["shift-v", "shift-g", "shift-y"])
+//             .await;
+//         cx.assert_shared_state(indoc! {"
+//                     The quick brown
+//                     ˇfox jumps over
+//                     the lazy dog"})
+//             .await;
+//         cx.assert_shared_clipboard("fox jumps over\nthe lazy dog\n")
+//             .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_block_mode(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "The ˇquick brown
+//              fox jumps over
+//              the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «qˇ»uick brown
+//             fox jumps over
+//             the lazy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["2", "down"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «qˇ»uick brown
+//             fox «jˇ»umps over
+//             the «lˇ»azy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["e"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «quicˇ»k brown
+//             fox «jumpˇ»s over
+//             the «lazyˇ» dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["^"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "«ˇThe q»uick brown
+//             «ˇfox j»umps over
+//             «ˇthe l»azy dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["$"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «quick brownˇ»
+//             fox «jumps overˇ»
+//             the «lazy dogˇ»"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["shift-f", " "]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «quickˇ» brown
+//             fox «jumpsˇ» over
+//             the «lazy ˇ»dog"
+//         })
+//         .await;
+
+//         // toggling through visual mode works as expected
+//         cx.simulate_shared_keystrokes(["v"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «quick brown
+//             fox jumps over
+//             the lazy ˇ»dog"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «quickˇ» brown
+//             fox «jumpsˇ» over
+//             the «lazy ˇ»dog"
+//         })
+//         .await;
+
+//         cx.set_shared_state(indoc! {
+//             "The ˇquick
+//              brown
+//              fox
+//              jumps over the
+
+//              lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "down", "down"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "The«ˇ q»uick
+//             bro«ˇwn»
+//             foxˇ
+//             jumps over the
+
+//             lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["down"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "The «qˇ»uick
+//             brow«nˇ»
+//             fox
+//             jump«sˇ» over the
+
+//             lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystroke("left").await;
+//         cx.assert_shared_state(indoc! {
+//             "The«ˇ q»uick
+//             bro«ˇwn»
+//             foxˇ
+//             jum«ˇps» over the
+
+//             lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["s", "o", "escape"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "Theˇouick
+//             broo
+//             foxo
+//             jumo over the
+
+//             lazy dog
+//             "
+//         })
+//         .await;
+
+//         //https://github.com/zed-industries/community/issues/1950
+//         cx.set_shared_state(indoc! {
+//             "Theˇ quick brown
+
+//             fox jumps over
+//             the lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["l", "ctrl-v", "j", "j"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "The «qˇ»uick brown
+
+//             fox «jˇ»umps over
+//             the lazy dog
+//             "
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_block_issue_2123(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "The ˇquick brown
+//             fox jumps over
+//             the lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "right", "down"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "The «quˇ»ick brown
+//             fox «juˇ»mps over
+//             the lazy dog
+//             "
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state(indoc! {
+//             "ˇThe quick brown
+//             fox jumps over
+//             the lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "«Tˇ»he quick brown
+//             «fˇ»ox jumps over
+//             «tˇ»he lazy dog
+//             ˇ"
+//         })
+//         .await;
+
+//         cx.simulate_shared_keystrokes(["shift-i", "k", "escape"])
+//             .await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇkThe quick brown
+//             kfox jumps over
+//             kthe lazy dog
+//             k"
+//         })
+//         .await;
+
+//         cx.set_shared_state(indoc! {
+//             "ˇThe quick brown
+//             fox jumps over
+//             the lazy dog
+//             "
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "«Tˇ»he quick brown
+//             «fˇ»ox jumps over
+//             «tˇ»he lazy dog
+//             ˇ"
+//         })
+//         .await;
+//         cx.simulate_shared_keystrokes(["c", "k", "escape"]).await;
+//         cx.assert_shared_state(indoc! {
+//             "ˇkhe quick brown
+//             kox jumps over
+//             khe lazy dog
+//             k"
+//         })
+//         .await;
+//     }
+
+//     #[gpui::test]
+//     async fn test_visual_object(cx: &mut gpui::TestAppContext) {
+//         let mut cx = NeovimBackedTestContext::new(cx).await;
+
+//         cx.set_shared_state("hello (in [parˇens] o)").await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "l"]).await;
+//         cx.simulate_shared_keystrokes(["a", "]"]).await;
+//         cx.assert_shared_state("hello (in «[parens]ˇ» o)").await;
+//         assert_eq!(cx.mode(), Mode::Visual);
+//         cx.simulate_shared_keystrokes(["i", "("]).await;
+//         cx.assert_shared_state("hello («in [parens] oˇ»)").await;
+
+//         cx.set_shared_state("hello in a wˇord again.").await;
+//         cx.simulate_shared_keystrokes(["ctrl-v", "l", "i", "w"])
+//             .await;
+//         cx.assert_shared_state("hello in a w«ordˇ» again.").await;
+//         assert_eq!(cx.mode(), Mode::VisualBlock);
+//         cx.simulate_shared_keystrokes(["o", "a", "s"]).await;
+//         cx.assert_shared_state("«ˇhello in a word» again.").await;
+//         assert_eq!(cx.mode(), Mode::Visual);
+//     }
+
+//     #[gpui::test]
+//     async fn test_mode_across_command(cx: &mut gpui::TestAppContext) {
+//         let mut cx = VimTestContext::new(cx, true).await;
+
+//         cx.set_state("aˇbc", Mode::Normal);
+//         cx.simulate_keystrokes(["ctrl-v"]);
+//         assert_eq!(cx.mode(), Mode::VisualBlock);
+//         cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
+//         assert_eq!(cx.mode(), Mode::VisualBlock);
+//     }
+// }

crates/vim2/test_data/test_a.json 🔗

@@ -0,0 +1,6 @@
+{"Put":{"state":"The qˇuick"}}
+{"Key":"a"}
+{"Get":{"state":"The quˇick","mode":"Insert"}}
+{"Put":{"state":"The quicˇk"}}
+{"Key":"a"}
+{"Get":{"state":"The quickˇ","mode":"Insert"}}

crates/vim2/test_data/test_b.json 🔗

@@ -0,0 +1,54 @@
+{"Put":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe"}}
+{"Key":"b"}
+{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe"}}
+{"Key":"b"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe"}}
+{"Key":"shift-b"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}

crates/vim2/test_data/test_backspace.json 🔗

@@ -0,0 +1,9 @@
+{"Put":{"state":"ˇThe quick\nbrown"}}
+{"Key":"backspace"}
+{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown"}}
+{"Key":"backspace"}
+{"Get":{"state":"The ˇquick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇbrown"}}
+{"Key":"backspace"}
+{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}

crates/vim2/test_data/test_capital_f_and_capital_t.json 🔗

@@ -0,0 +1,570 @@
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Key":"1"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Key":"1"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Key":"2"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Key":"2"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Key":"3"}
+{"Key":"shift-f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Key":"3"}
+{"Key":"shift-t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}

crates/vim2/test_data/test_cc.json 🔗

@@ -0,0 +1,24 @@
+{"Put":{"state":"ˇ"}}
+{"Key":"c"}
+{"Key":"c"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The ˇquick"}}
+{"Key":"c"}
+{"Key":"c"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quˇick\nbrown fox\njumps over"}}
+{"Key":"c"}
+{"Key":"c"}
+{"Get":{"state":"ˇ\nbrown fox\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"c"}
+{"Key":"c"}
+{"Get":{"state":"The quick\nˇ\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"c"}
+{"Key":"c"}
+{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"c"}
+{"Key":"c"}
+{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_change_0.json 🔗

@@ -0,0 +1,8 @@
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"c"}
+{"Key":"0"}
+{"Get":{"state":"ˇuick\nbrown fox","mode":"Insert"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"c"}
+{"Key":"0"}
+{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_change_b.json 🔗

@@ -0,0 +1,24 @@
+{"Put":{"state":"Teˇst Test"}}
+{"Key":"c"}
+{"Key":"b"}
+{"Get":{"state":"ˇst Test","mode":"Insert"}}
+{"Put":{"state":"Test ˇtest"}}
+{"Key":"c"}
+{"Key":"b"}
+{"Get":{"state":"ˇtest","mode":"Insert"}}
+{"Put":{"state":"Test1 test2 ˇtest3"}}
+{"Key":"c"}
+{"Key":"b"}
+{"Get":{"state":"Test1 ˇtest3","mode":"Insert"}}
+{"Put":{"state":"Test test\nˇtest"}}
+{"Key":"c"}
+{"Key":"b"}
+{"Get":{"state":"Test ˇ\ntest","mode":"Insert"}}
+{"Put":{"state":"Test test\nˇ\ntest"}}
+{"Key":"c"}
+{"Key":"b"}
+{"Get":{"state":"Test ˇ\n\ntest","mode":"Insert"}}
+{"Put":{"state":"Test test-test ˇtest"}}
+{"Key":"c"}
+{"Key":"shift-b"}
+{"Get":{"state":"Test ˇtest","mode":"Insert"}}

crates/vim2/test_data/test_change_backspace.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"Teˇst"}}
+{"Key":"c"}
+{"Key":"backspace"}
+{"Get":{"state":"Tˇst","mode":"Insert"}}
+{"Put":{"state":"Tˇest"}}
+{"Key":"c"}
+{"Key":"backspace"}
+{"Get":{"state":"ˇest","mode":"Insert"}}
+{"Put":{"state":"ˇTest"}}
+{"Key":"c"}
+{"Key":"backspace"}
+{"Get":{"state":"ˇTest","mode":"Insert"}}
+{"Put":{"state":"Test\nˇtest"}}
+{"Key":"c"}
+{"Key":"backspace"}
+{"Get":{"state":"Testˇtest","mode":"Insert"}}

crates/vim2/test_data/test_change_case.json 🔗

@@ -0,0 +1,23 @@
+{"Put":{"state":"ˇabC\n"}}
+{"Key":"~"}
+{"Get":{"state":"AˇbC\n","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"~"}
+{"Get":{"state":"ABˇc\n","mode":"Normal"}}
+{"Put":{"state":"a😀C«dÉ1*fˇ»\n"}}
+{"Key":"~"}
+{"Get":{"state":"a😀CˇDé1*F\n","mode":"Normal"}}
+{"Key":"~"}
+{"Put":{"state":"aˇC😀é1*F\n"}}
+{"Key":"4"}
+{"Key":"~"}
+{"Get":{"state":"ac😀É1ˇ*F\n","mode":"Normal"}}
+{"Put":{"state":"abˇC\n"}}
+{"Key":"shift-v"}
+{"Key":"~"}
+{"Get":{"state":"ˇABc\n","mode":"Normal"}}
+{"Put":{"state":"ˇaa\nbb\ncc"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"~"}
+{"Get":{"state":"ˇAa\nBb\ncc","mode":"Normal"}}

crates/vim2/test_data/test_change_e.json 🔗

@@ -0,0 +1,24 @@
+{"Put":{"state":"Teˇst Test"}}
+{"Key":"c"}
+{"Key":"e"}
+{"Get":{"state":"Teˇ Test","mode":"Insert"}}
+{"Put":{"state":"Tˇest test"}}
+{"Key":"c"}
+{"Key":"e"}
+{"Get":{"state":"Tˇ test","mode":"Insert"}}
+{"Put":{"state":"Test teˇst\ntest"}}
+{"Key":"c"}
+{"Key":"e"}
+{"Get":{"state":"Test teˇ\ntest","mode":"Insert"}}
+{"Put":{"state":"Test tesˇt\ntest"}}
+{"Key":"c"}
+{"Key":"e"}
+{"Get":{"state":"Test tesˇ","mode":"Insert"}}
+{"Put":{"state":"Test test\nˇ\ntest"}}
+{"Key":"c"}
+{"Key":"e"}
+{"Get":{"state":"Test test\nˇ","mode":"Insert"}}
+{"Put":{"state":"Test teˇst-test test"}}
+{"Key":"c"}
+{"Key":"shift-e"}
+{"Get":{"state":"Test teˇ test","mode":"Insert"}}

crates/vim2/test_data/test_change_end_of_document.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
+{"Key":"c"}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
+{"Key":"c"}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
+{"Key":"c"}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps over\nˇ"}}
+{"Key":"c"}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}

crates/vim2/test_data/test_change_end_of_line.json 🔗

@@ -0,0 +1,8 @@
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"c"}
+{"Key":"$"}
+{"Get":{"state":"The qˇ\nbrown fox","mode":"Insert"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"c"}
+{"Key":"$"}
+{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_change_gg.json 🔗

@@ -0,0 +1,20 @@
+{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
+{"Key":"c"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇ\njumps over\nthe lazy","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
+{"Key":"c"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over\nthe lazy"}}
+{"Key":"c"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇ\nbrown fox\njumps over\nthe lazy","mode":"Insert"}}
+{"Put":{"state":"ˇ\nbrown fox\njumps over\nthe lazy"}}
+{"Key":"c"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇ\nbrown fox\njumps over\nthe lazy","mode":"Insert"}}

crates/vim2/test_data/test_change_h.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"Teˇst"}}
+{"Key":"c"}
+{"Key":"h"}
+{"Get":{"state":"Tˇst","mode":"Insert"}}
+{"Put":{"state":"Tˇest"}}
+{"Key":"c"}
+{"Key":"h"}
+{"Get":{"state":"ˇest","mode":"Insert"}}
+{"Put":{"state":"ˇTest"}}
+{"Key":"c"}
+{"Key":"h"}
+{"Get":{"state":"ˇTest","mode":"Insert"}}
+{"Put":{"state":"Test\nˇtest"}}
+{"Key":"c"}
+{"Key":"h"}
+{"Get":{"state":"Test\nˇtest","mode":"Insert"}}

crates/vim2/test_data/test_change_j.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"c"}
+{"Key":"j"}
+{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"c"}
+{"Key":"j"}
+{"Get":{"state":"The quick\nbrown fox\njumps ˇover","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"c"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\nˇ"}}
+{"Key":"c"}
+{"Key":"j"}
+{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Normal"}}

crates/vim2/test_data/test_change_k.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"c"}
+{"Key":"k"}
+{"Get":{"state":"ˇ\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"c"}
+{"Key":"k"}
+{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"c"}
+{"Key":"k"}
+{"Get":{"state":"The qˇuick\nbrown fox\njumps over","mode":"Normal"}}
+{"Put":{"state":"ˇ\nbrown fox\njumps over"}}
+{"Key":"c"}
+{"Key":"k"}
+{"Get":{"state":"ˇ\nbrown fox\njumps over","mode":"Normal"}}

crates/vim2/test_data/test_change_l.json 🔗

@@ -0,0 +1,8 @@
+{"Put":{"state":"Teˇst"}}
+{"Key":"c"}
+{"Key":"l"}
+{"Get":{"state":"Teˇt","mode":"Insert"}}
+{"Put":{"state":"Tesˇt"}}
+{"Key":"c"}
+{"Key":"l"}
+{"Get":{"state":"Tesˇ","mode":"Insert"}}

crates/vim2/test_data/test_change_sentence_object.json 🔗

@@ -0,0 +1,270 @@
+{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown?ˇ Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown?ˇFox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇOver the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'\" Brown fox jumps.ˇ "}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" Brown fox jumps.ˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" ˇ","mode":"Insert"}}

crates/vim2/test_data/test_change_surrounding_character_objects.json 🔗

@@ -0,0 +1,2380 @@
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck broˇ\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck broˇ\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck broˇ\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck broˇ\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck broˇ\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck broˇ\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}

crates/vim2/test_data/test_change_w.json 🔗

@@ -0,0 +1,28 @@
+{"Put":{"state":"Teˇst"}}
+{"Key":"c"}
+{"Key":"w"}
+{"Get":{"state":"Teˇ","mode":"Insert"}}
+{"Put":{"state":"Tˇest test"}}
+{"Key":"c"}
+{"Key":"w"}
+{"Get":{"state":"Tˇ test","mode":"Insert"}}
+{"Put":{"state":"Testˇ  test"}}
+{"Key":"c"}
+{"Key":"w"}
+{"Get":{"state":"Testˇtest","mode":"Insert"}}
+{"Put":{"state":"Test teˇst\ntest"}}
+{"Key":"c"}
+{"Key":"w"}
+{"Get":{"state":"Test teˇ\ntest","mode":"Insert"}}
+{"Put":{"state":"Test tesˇt\ntest"}}
+{"Key":"c"}
+{"Key":"w"}
+{"Get":{"state":"Test tesˇ\ntest","mode":"Insert"}}
+{"Put":{"state":"Test test\nˇ\ntest"}}
+{"Key":"c"}
+{"Key":"w"}
+{"Get":{"state":"Test test\nˇ\ntest","mode":"Insert"}}
+{"Put":{"state":"Test teˇst-test test"}}
+{"Key":"c"}
+{"Key":"shift-w"}
+{"Get":{"state":"Test teˇ test","mode":"Insert"}}

crates/vim2/test_data/test_change_word_object.json 🔗

@@ -0,0 +1,460 @@
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumpsˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ\n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ\n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ\n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-ˇ over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ\n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumpsˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ\n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ\n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ\n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  ˇ over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ\n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"c"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumpsˇ\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇ\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-ˇover\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumpsˇ\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇ\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ over\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  ˇover\nthe lazy dog \n\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ","mode":"Insert"}}

crates/vim2/test_data/test_clear_counts.json 🔗

@@ -0,0 +1,7 @@
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
+{"Key":"4"}
+{"Key":"escape"}
+{"Key":"3"}
+{"Key":"d"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\nfox juˇ over\nthe lazy dog","mode":"Normal"}}

crates/vim2/test_data/test_comma_semicolon.json 🔗

@@ -0,0 +1,17 @@
+{"Put":{"state":"ˇone two three four"}}
+{"Key":"f"}
+{"Key":"o"}
+{"Get":{"state":"one twˇo three four","mode":"Normal"}}
+{"Key":","}
+{"Get":{"state":"ˇone two three four","mode":"Normal"}}
+{"Key":"2"}
+{"Key":";"}
+{"Get":{"state":"one two three fˇour","mode":"Normal"}}
+{"Key":"shift-t"}
+{"Key":"e"}
+{"Get":{"state":"one two threeˇ four","mode":"Normal"}}
+{"Key":"3"}
+{"Key":";"}
+{"Get":{"state":"oneˇ two three four","mode":"Normal"}}
+{"Key":","}
+{"Get":{"state":"one two thˇree four","mode":"Normal"}}

crates/vim2/test_data/test_command_replace.json 🔗

@@ -0,0 +1,22 @@
+{"Put":{"state":"ˇa\nb\nc"}}
+{"Key":":"}
+{"Key":"%"}
+{"Key":"s"}
+{"Key":"/"}
+{"Key":"b"}
+{"Key":"/"}
+{"Key":"d"}
+{"Key":"enter"}
+{"Get":{"state":"a\nˇd\nc","mode":"Normal"}}
+{"Key":":"}
+{"Key":"%"}
+{"Key":"s"}
+{"Key":":"}
+{"Key":"."}
+{"Key":":"}
+{"Key":"\\"}
+{"Key":"0"}
+{"Key":"\\"}
+{"Key":"0"}
+{"Key":"enter"}
+{"Get":{"state":"aa\ndd\nˇcc","mode":"Normal"}}

crates/vim2/test_data/test_command_search.json 🔗

@@ -0,0 +1,11 @@
+{"Put":{"state":"ˇa\nb\na\nc"}}
+{"Key":":"}
+{"Key":"/"}
+{"Key":"b"}
+{"Key":"enter"}
+{"Get":{"state":"a\nˇb\na\nc","mode":"Normal"}}
+{"Key":":"}
+{"Key":"?"}
+{"Key":"a"}
+{"Key":"enter"}
+{"Get":{"state":"ˇa\nb\na\nc","mode":"Normal"}}

crates/vim2/test_data/test_ctrl_d_u.json 🔗

@@ -0,0 +1,22 @@
+{"SetOption":{"value":"scrolloff=3"}}
+{"SetOption":{"value":"lines=12"}}
+{"Put":{"state":"ˇaa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz"}}
+{"Key":"4"}
+{"Key":"j"}
+{"Key":"ctrl-d"}
+{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\nˇjj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
+{"Key":"ctrl-d"}
+{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\nˇoo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"g"}
+{"Key":"ctrl-d"}
+{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nˇii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
+{"Key":"ctrl-u"}
+{"Get":{"state":"aa\nbb\ncc\nˇdd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
+{"Key":"ctrl-d"}
+{"Key":"ctrl-d"}
+{"Key":"4"}
+{"Key":"j"}
+{"Key":"ctrl-u"}
+{"Key":"ctrl-u"}
+{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nˇhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}

crates/vim2/test_data/test_dd.json 🔗

@@ -0,0 +1,24 @@
+{"Put":{"state":"ˇ"}}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"ˇ","mode":"Normal"}}
+{"Put":{"state":"The ˇquick"}}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"ˇ","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"brownˇ fox\njumps over","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"The quick\njumps ˇover","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"The quick\nbrown ˇfox","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"The quick\nˇbrown fox","mode":"Normal"}}

crates/vim2/test_data/test_delete_0.json 🔗

@@ -0,0 +1,8 @@
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"d"}
+{"Key":"0"}
+{"Get":{"state":"ˇuick\nbrown fox","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"d"}
+{"Key":"0"}
+{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Normal"}}

crates/vim2/test_data/test_delete_b.json 🔗

@@ -0,0 +1,24 @@
+{"Put":{"state":"Teˇst Test"}}
+{"Key":"d"}
+{"Key":"b"}
+{"Get":{"state":"ˇst Test","mode":"Normal"}}
+{"Put":{"state":"Test ˇtest"}}
+{"Key":"d"}
+{"Key":"b"}
+{"Get":{"state":"ˇtest","mode":"Normal"}}
+{"Put":{"state":"Test1 test2 ˇtest3"}}
+{"Key":"d"}
+{"Key":"b"}
+{"Get":{"state":"Test1 ˇtest3","mode":"Normal"}}
+{"Put":{"state":"Test test\nˇtest"}}
+{"Key":"d"}
+{"Key":"b"}
+{"Get":{"state":"Testˇ \ntest","mode":"Normal"}}
+{"Put":{"state":"Test test\nˇ\ntest"}}
+{"Key":"d"}
+{"Key":"b"}
+{"Get":{"state":"Testˇ \n\ntest","mode":"Normal"}}
+{"Put":{"state":"Test test-test ˇtest"}}
+{"Key":"d"}
+{"Key":"shift-b"}
+{"Get":{"state":"Test ˇtest","mode":"Normal"}}

crates/vim2/test_data/test_delete_end_of_document.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
+{"Key":"d"}
+{"Key":"shift-g"}
+{"Get":{"state":"The qˇuick","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
+{"Key":"d"}
+{"Key":"shift-g"}
+{"Get":{"state":"The qˇuick","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
+{"Key":"d"}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\nbrown fox\njumpsˇ over","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\njumps over\nˇ"}}
+{"Key":"d"}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\nbrown fox\nˇjumps over","mode":"Normal"}}

crates/vim2/test_data/test_delete_end_of_line.json 🔗

@@ -0,0 +1,8 @@
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"d"}
+{"Key":"$"}
+{"Get":{"state":"The ˇq\nbrown fox","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"d"}
+{"Key":"$"}
+{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Normal"}}

crates/vim2/test_data/test_delete_gg.json 🔗

@@ -0,0 +1,20 @@
+{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
+{"Key":"d"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"jumpsˇ over\nthe lazy","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
+{"Key":"d"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇ","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over\nthe lazy"}}
+{"Key":"d"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"brownˇ fox\njumps over\nthe lazy","mode":"Normal"}}
+{"Put":{"state":"ˇ\nbrown fox\njumps over\nthe lazy"}}
+{"Key":"d"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇbrown fox\njumps over\nthe lazy","mode":"Normal"}}

crates/vim2/test_data/test_delete_h.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"Teˇst"}}
+{"Key":"d"}
+{"Key":"h"}
+{"Get":{"state":"Tˇst","mode":"Normal"}}
+{"Put":{"state":"Tˇest"}}
+{"Key":"d"}
+{"Key":"h"}
+{"Get":{"state":"ˇest","mode":"Normal"}}
+{"Put":{"state":"ˇTest"}}
+{"Key":"d"}
+{"Key":"h"}
+{"Get":{"state":"ˇTest","mode":"Normal"}}
+{"Put":{"state":"Test\nˇtest"}}
+{"Key":"d"}
+{"Key":"h"}
+{"Get":{"state":"Test\nˇtest","mode":"Normal"}}

crates/vim2/test_data/test_delete_j.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"d"}
+{"Key":"j"}
+{"Get":{"state":"The quˇick","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"d"}
+{"Key":"j"}
+{"Get":{"state":"The quick\nbrown fox\njumps ˇover","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"d"}
+{"Key":"j"}
+{"Get":{"state":"jumpsˇ over","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\nˇ"}}
+{"Key":"d"}
+{"Key":"j"}
+{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Normal"}}

crates/vim2/test_data/test_delete_k.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"d"}
+{"Key":"k"}
+{"Get":{"state":"jumps ˇover","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"d"}
+{"Key":"k"}
+{"Get":{"state":"The quˇick","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"d"}
+{"Key":"k"}
+{"Get":{"state":"The qˇuick\nbrown fox\njumps over","mode":"Normal"}}
+{"Put":{"state":"ˇbrown fox\njumps over"}}
+{"Key":"d"}
+{"Key":"k"}
+{"Get":{"state":"ˇbrown fox\njumps over","mode":"Normal"}}

crates/vim2/test_data/test_delete_l.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"ˇTest"}}
+{"Key":"d"}
+{"Key":"l"}
+{"Get":{"state":"ˇest","mode":"Normal"}}
+{"Put":{"state":"Teˇst"}}
+{"Key":"d"}
+{"Key":"l"}
+{"Get":{"state":"Teˇt","mode":"Normal"}}
+{"Put":{"state":"Tesˇt"}}
+{"Key":"d"}
+{"Key":"l"}
+{"Get":{"state":"Teˇs","mode":"Normal"}}
+{"Put":{"state":"Tesˇt\ntest"}}
+{"Key":"d"}
+{"Key":"l"}
+{"Get":{"state":"Teˇs\ntest","mode":"Normal"}}

crates/vim2/test_data/test_delete_left.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"ˇTest"}}
+{"Key":"shift-x"}
+{"Get":{"state":"ˇTest","mode":"Normal"}}
+{"Put":{"state":"Tˇest"}}
+{"Key":"shift-x"}
+{"Get":{"state":"ˇest","mode":"Normal"}}
+{"Put":{"state":"Teˇst"}}
+{"Key":"shift-x"}
+{"Get":{"state":"Tˇst","mode":"Normal"}}
+{"Put":{"state":"Tesˇt"}}
+{"Key":"shift-x"}
+{"Get":{"state":"Teˇt","mode":"Normal"}}
+{"Put":{"state":"Test\nˇtest"}}
+{"Key":"shift-x"}
+{"Get":{"state":"Test\nˇtest","mode":"Normal"}}

crates/vim2/test_data/test_delete_next_word_end.json 🔗

@@ -0,0 +1,12 @@
+{"Put":{"state":"Test teˇst\ntest"}}
+{"Key":"d"}
+{"Key":"e"}
+{"Get":{"state":"Test tˇe\ntest","mode":"Normal"}}
+{"Put":{"state":"Test tesˇt\ntest"}}
+{"Key":"d"}
+{"Key":"e"}
+{"Get":{"state":"Test teˇs","mode":"Normal"}}
+{"Put":{"state":"Test teˇst-test test"}}
+{"Key":"d"}
+{"Key":"shift-e"}
+{"Get":{"state":"Test teˇ test","mode":"Normal"}}

crates/vim2/test_data/test_delete_sentence_object.json 🔗

@@ -0,0 +1,270 @@
+{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown?ˇ Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown?ˇFox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇOver the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'\" Brown fox jumps.ˇ "}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\" Brown fox jumpsˇ.","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
+{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\"ˇ ","mode":"Normal"}}
+{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"The quick brown.)]'\"ˇ ","mode":"Normal"}}

crates/vim2/test_data/test_delete_surrounding_character_objects.json 🔗

@@ -0,0 +1,2372 @@
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck brˇo\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck brˇo\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"'"}
+{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck brˇo\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck brˇo\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"`"}
+{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck brˇo\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck brˇo\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
+{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"("}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
+{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":")"}
+{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"["}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
+{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"{"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
+{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"}"}
+{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"<"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
+{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
+{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":">"}
+{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}

crates/vim2/test_data/test_delete_to_end_of_line.json 🔗

@@ -0,0 +1,6 @@
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"shift-d"}
+{"Get":{"state":"The ˇq\nbrown fox","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"shift-d"}
+{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Normal"}}

crates/vim2/test_data/test_delete_w.json 🔗

@@ -0,0 +1,28 @@
+{"Put":{"state":"Test tesˇt\n    test"}}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"Test teˇs\n    test","mode":"Normal"}}
+{"Put":{"state":"Teˇst"}}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"Tˇe","mode":"Normal"}}
+{"Put":{"state":"Tˇest test"}}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"Tˇtest","mode":"Normal"}}
+{"Put":{"state":"Test teˇst\ntest"}}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"Test tˇe\ntest","mode":"Normal"}}
+{"Put":{"state":"Test tesˇt\ntest"}}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"Test teˇs\ntest","mode":"Normal"}}
+{"Put":{"state":"Test test\nˇ\ntest"}}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"Test test\nˇtest","mode":"Normal"}}
+{"Put":{"state":"Test teˇst-test test"}}
+{"Key":"d"}
+{"Key":"shift-w"}
+{"Get":{"state":"Test teˇtest","mode":"Normal"}}

crates/vim2/test_data/test_delete_with_counts.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"d"}
+{"Key":"2"}
+{"Key":"d"}
+{"Get":{"state":"the ˇlazy dog","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"2"}
+{"Key":"d"}
+{"Key":"d"}
+{"Get":{"state":"the ˇlazy dog","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe moon,\na star, and\nthe lazy dog"}}
+{"Key":"2"}
+{"Key":"d"}
+{"Key":"2"}
+{"Key":"d"}
+{"Get":{"state":"the ˇlazy dog","mode":"Normal"}}

crates/vim2/test_data/test_delete_word_object.json 🔗

@@ -0,0 +1,460 @@
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick browˇn\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumpsˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg\n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick browˇn\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ\n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ\n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-ˇ over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy doˇg\n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick ˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick browˇn\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumpsˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg\n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick browˇn\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ\n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ\n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  ˇ over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy doˇg\n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumpˇs\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-ˇover\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy doˇg\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nˇthe lazy dog ","mode":"Normal"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox ˇover\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumpˇs\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ over\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  ˇover\nthe lazy dog \n\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy doˇg\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"d"}
+{"Key":"a"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nˇthe lazy dog ","mode":"Normal"}}

crates/vim2/test_data/test_dot_repeat.json 🔗

@@ -0,0 +1,38 @@
+{"Put":{"state":"ˇhello"}}
+{"Key":"o"}
+{"Key":"w"}
+{"Key":"o"}
+{"Key":"r"}
+{"Key":"l"}
+{"Key":"d"}
+{"Key":"escape"}
+{"Get":{"state":"hello\nworlˇd","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"hello\nworld\nworlˇd","mode":"Normal"}}
+{"Key":"^"}
+{"Key":"d"}
+{"Key":"f"}
+{"Key":"o"}
+{"Key":"g"}
+{"Key":"g"}
+{"Key":"."}
+{"Get":{"state":"ˇ\nworld\nrld","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"y"}
+{"Key":"y"}
+{"Key":"p"}
+{"Key":"shift-g"}
+{"Key":"y"}
+{"Key":"y"}
+{"Key":"."}
+{"Get":{"state":"\nworld\nworld\nrld\nˇrld","mode":"Normal"}}
+{"Put":{"state":"ˇthe quick brown fox"}}
+{"Key":"2"}
+{"Key":"~"}
+{"Key":"."}
+{"Put":{"state":"THE ˇquick brown fox"}}
+{"Key":"3"}
+{"Key":"."}
+{"Put":{"state":"THE QUIˇck brown fox"}}
+{"Key":"."}
+{"Get":{"state":"THE QUICK ˇbrown fox","mode":"Normal"}}

crates/vim2/test_data/test_end_of_document.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrown fox jumps\nover ˇthe lazy dog","mode":"Normal"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrown fox jumps\nover ˇthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick\n\nbrown fox jumps\nover the laˇzy dog"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrown fox jumps\nover the laˇzy dog","mode":"Normal"}}
+{"Put":{"state":"\n\nbrown fox jumps\nover the laˇzy dog"}}
+{"Key":"shift-g"}
+{"Get":{"state":"\n\nbrown fox jumps\nover the laˇzy dog","mode":"Normal"}}
+{"Put":{"state":"ˇ\n\nbrown fox jumps\nover the lazydog"}}
+{"Key":"2"}
+{"Key":"shift-g"}
+{"Get":{"state":"\nˇ\nbrown fox jumps\nover the lazydog","mode":"Normal"}}

crates/vim2/test_data/test_end_of_word.json 🔗

@@ -0,0 +1,32 @@
+{"Put":{"state":"Thˇe quick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"e"}
+{"Get":{"state":"The quicˇk-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"e"}
+{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"e"}
+{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumpˇs over\nthe","mode":"Normal"}}
+{"Key":"e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps oveˇr\nthe","mode":"Normal"}}
+{"Key":"e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
+{"Key":"e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
+{"Put":{"state":"Thˇe quick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quicˇk-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumpˇs over\nthe","mode":"Normal"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps oveˇr\nthe","mode":"Normal"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
+{"Key":"shift-e"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}

crates/vim2/test_data/test_enter.json 🔗

@@ -0,0 +1,11 @@
+{"Put":{"state":"ˇThe quick brown\nfox jumps"}}
+{"Key":"enter"}
+{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
+{"Put":{"state":"The qˇuick brown\nfox jumps"}}
+{"Key":"enter"}
+{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
+{"Put":{"state":"The quick broˇwn\nfox jumps"}}
+{"Key":"enter"}
+{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
+{"Key":"enter"}
+{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}

crates/vim2/test_data/test_enter_visual_line_mode.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"VisualLine"}}
+{"Key":"x"}
+{"Get":{"state":"fox ˇjumps over\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"a\nˇ\nb"}}
+{"Key":"shift-v"}
+{"Get":{"state":"a\n«\nˇ»b","mode":"VisualLine"}}
+{"Key":"x"}
+{"Get":{"state":"a\nˇb","mode":"Normal"}}
+{"Put":{"state":"a\nb\nˇ"}}
+{"Key":"shift-v"}
+{"Get":{"state":"a\nb\nˇ","mode":"VisualLine"}}
+{"Key":"x"}
+{"Get":{"state":"a\nˇb","mode":"Normal"}}

crates/vim2/test_data/test_enter_visual_mode.json 🔗

@@ -0,0 +1,20 @@
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"Visual"}}
+{"Key":"w"}
+{"Key":"j"}
+{"Get":{"state":"The «quick brown\nfox jumps oˇ»ver\nthe lazy dog","mode":"Visual"}}
+{"Key":"escape"}
+{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog","mode":"Normal"}}
+{"Key":"v"}
+{"Key":"k"}
+{"Key":"b"}
+{"Get":{"state":"The «ˇquick brown\nfox jumps o»ver\nthe lazy dog","mode":"Visual"}}
+{"Put":{"state":"a\nˇ\nb\n"}}
+{"Key":"v"}
+{"Get":{"state":"a\n«\nˇ»b\n","mode":"Visual"}}
+{"Key":"v"}
+{"Get":{"state":"a\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"a\nb\nˇ"}}
+{"Key":"v"}
+{"Get":{"state":"a\nb\nˇ","mode":"Visual"}}

crates/vim2/test_data/test_f_and_t.json 🔗

@@ -0,0 +1,557 @@
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
+{"Key":"1"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaˇab b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n   ˇ baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
+{"Key":"1"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
+{"Key":"2"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
+{"Key":"2"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
+{"Key":"3"}
+{"Key":"f"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
+{"Key":"3"}
+{"Key":"t"}
+{"Key":"b"}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}

crates/vim2/test_data/test_folds.json 🔗

@@ -0,0 +1,23 @@
+{"SetOption":{"value":"foldmethod=manual"}}
+{"Put":{"state":"fn boop() {\n  ˇbarp()\n  bazp()\n}\n"}}
+{"Key":"shift-v"}
+{"Key":"j"}
+{"Key":"z"}
+{"Key":"f"}
+{"Key":"escape"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇfn boop() {\n  barp()\n  bazp()\n}\n","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"j"}
+{"Get":{"state":"fn boop() {\n  barp()\n  bazp()\nˇ}\n","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"k"}
+{"Get":{"state":"ˇfn boop() {\n  barp()\n  bazp()\n}\n","mode":"Normal"}}
+{"Key":"down"}
+{"Key":"y"}
+{"Key":"y"}
+{"ReadRegister":{"name":"\"","value":"  barp()\n  bazp()\n"}}
+{"Key":"z"}
+{"Key":"o"}
+{"Get":{"state":"fn boop() {\nˇ  barp()\n  bazp()\n}\n","mode":"Normal"}}

crates/vim2/test_data/test_folds_panic.json 🔗

@@ -0,0 +1,13 @@
+{"SetOption":{"value":"foldmethod=manual"}}
+{"Put":{"state":"fn boop() {\n  ˇbarp()\n  bazp()\n}\n"}}
+{"Key":"shift-v"}
+{"Key":"j"}
+{"Key":"z"}
+{"Key":"f"}
+{"Key":"escape"}
+{"Key":"g"}
+{"Key":"g"}
+{"Key":"5"}
+{"Key":"d"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Normal"}}

crates/vim2/test_data/test_gg.json 🔗

@@ -0,0 +1,21 @@
+{"Put":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog"}}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick\n\nbrown fox jumps\nover ˇthe lazy dog"}}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick\n\nbrown fox jumps\nover the laˇzy dog"}}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"The quicˇk\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
+{"Put":{"state":"\n\nbrown fox jumps\nover the laˇzy dog"}}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"ˇ\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
+{"Put":{"state":"ˇ\n\nbrown fox jumps\nover the lazydog"}}
+{"Key":"2"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"\nˇ\nbrown fox jumps\nover the lazydog","mode":"Normal"}}

crates/vim2/test_data/test_h.json 🔗

@@ -0,0 +1,9 @@
+{"Put":{"state":"ˇThe quick\nbrown"}}
+{"Key":"h"}
+{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown"}}
+{"Key":"h"}
+{"Get":{"state":"The ˇquick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇbrown"}}
+{"Key":"h"}
+{"Get":{"state":"The quick\nˇbrown","mode":"Normal"}}

crates/vim2/test_data/test_h_through_unicode.json 🔗

@@ -0,0 +1,12 @@
+{"Put":{"state":"Testˇ├──┐Test"}}
+{"Key":"h"}
+{"Get":{"state":"Tesˇt├──┐Test","mode":"Normal"}}
+{"Put":{"state":"Test├ˇ──┐Test"}}
+{"Key":"h"}
+{"Get":{"state":"Testˇ├──┐Test","mode":"Normal"}}
+{"Put":{"state":"Test├──ˇ┐Test"}}
+{"Key":"h"}
+{"Get":{"state":"Test├─ˇ─┐Test","mode":"Normal"}}
+{"Put":{"state":"Test├──┐ˇTest"}}
+{"Key":"h"}
+{"Get":{"state":"Test├──ˇ┐Test","mode":"Normal"}}

crates/vim2/test_data/test_increment.json 🔗

@@ -0,0 +1,16 @@
+{"Put":{"state":"1ˇ2\n"}}
+{"Key":"ctrl-a"}
+{"Get":{"state":"1ˇ3\n","mode":"Normal"}}
+{"Key":"ctrl-x"}
+{"Get":{"state":"1ˇ2\n","mode":"Normal"}}
+{"Key":"9"}
+{"Key":"9"}
+{"Key":"ctrl-a"}
+{"Get":{"state":"11ˇ1\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"1"}
+{"Key":"1"}
+{"Key":"ctrl-x"}
+{"Get":{"state":"ˇ0\n","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"-11ˇ1\n","mode":"Normal"}}

crates/vim2/test_data/test_increment_radix.json 🔗

@@ -0,0 +1,18 @@
+{"Put":{"state":"ˇ total: 0xff"}}
+{"Key":"ctrl-a"}
+{"Get":{"state":" total: 0x10ˇ0","mode":"Normal"}}
+{"Put":{"state":"ˇ total: 0xff"}}
+{"Key":"ctrl-x"}
+{"Get":{"state":" total: 0xfˇe","mode":"Normal"}}
+{"Put":{"state":"ˇ total: 0xFF"}}
+{"Key":"ctrl-x"}
+{"Get":{"state":" total: 0xFˇE","mode":"Normal"}}
+{"Put":{"state":"(ˇ0b10f)"}}
+{"Key":"ctrl-a"}
+{"Get":{"state":"(0b1ˇ1f)","mode":"Normal"}}
+{"Put":{"state":"ˇ-1"}}
+{"Key":"ctrl-a"}
+{"Get":{"state":"ˇ0","mode":"Normal"}}
+{"Put":{"state":"banˇana"}}
+{"Key":"ctrl-a"}
+{"Get":{"state":"banˇana","mode":"Normal"}}

crates/vim2/test_data/test_increment_steps.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"ˇ1\n1\n1  2\n1\n1"}}
+{"Key":"j"}
+{"Key":"v"}
+{"Key":"shift-g"}
+{"Key":"g"}
+{"Key":"ctrl-a"}
+{"Get":{"state":"1\nˇ2\n3  2\n4\n5","mode":"Normal"}}
+{"Key":"shift-g"}
+{"Key":"ctrl-v"}
+{"Key":"g"}
+{"Key":"g"}
+{"Get":{"state":"«1ˇ»\n«2ˇ»\n«3ˇ»  2\n«4ˇ»\n«5ˇ»","mode":"VisualBlock"}}
+{"Key":"g"}
+{"Key":"ctrl-x"}
+{"Get":{"state":"ˇ0\n0\n0  2\n0\n0","mode":"Normal"}}

crates/vim2/test_data/test_insert_end_of_line.json 🔗

@@ -0,0 +1,9 @@
+{"Put":{"state":"ˇ\nThe quick\nbrown fox "}}
+{"Key":"shift-a"}
+{"Get":{"state":"ˇ\nThe quick\nbrown fox ","mode":"Insert"}}
+{"Put":{"state":"\nThe qˇuick\nbrown fox "}}
+{"Key":"shift-a"}
+{"Get":{"state":"\nThe quickˇ\nbrown fox ","mode":"Insert"}}
+{"Put":{"state":"\nThe quick\nbrown ˇfox "}}
+{"Key":"shift-a"}
+{"Get":{"state":"\nThe quick\nbrown fox ˇ","mode":"Insert"}}

crates/vim2/test_data/test_insert_first_non_whitespace.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"The qˇuick"}}
+{"Key":"shift-i"}
+{"Get":{"state":"ˇThe quick","mode":"Insert"}}
+{"Put":{"state":" The qˇuick"}}
+{"Key":"shift-i"}
+{"Get":{"state":" ˇThe quick","mode":"Insert"}}
+{"Put":{"state":"ˇ"}}
+{"Key":"shift-i"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"shift-i"}
+{"Get":{"state":"ˇThe quick\nbrown fox","mode":"Insert"}}
+{"Put":{"state":"ˇ\nThe quick"}}
+{"Key":"shift-i"}
+{"Get":{"state":"ˇ\nThe quick","mode":"Insert"}}

crates/vim2/test_data/test_insert_line_above.json 🔗

@@ -0,0 +1,18 @@
+{"Put":{"state":"ˇ"}}
+{"Key":"shift-o"}
+{"Get":{"state":"ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The ˇquick"}}
+{"Key":"shift-o"}
+{"Get":{"state":"ˇ\nThe quick","mode":"Insert"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"shift-o"}
+{"Get":{"state":"ˇ\nThe quick\nbrown fox\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"shift-o"}
+{"Get":{"state":"The quick\nˇ\nbrown fox\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"shift-o"}
+{"Get":{"state":"The quick\nbrown fox\nˇ\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"shift-o"}
+{"Get":{"state":"The quick\nˇ\n\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_insert_with_counts.json 🔗

@@ -0,0 +1,36 @@
+{"Put":{"state":"ˇhello\n"}}
+{"Key":"5"}
+{"Key":"i"}
+{"Key":"-"}
+{"Key":"escape"}
+{"Get":{"state":"----ˇ-hello\n","mode":"Normal"}}
+{"Put":{"state":"ˇhello\n"}}
+{"Key":"5"}
+{"Key":"a"}
+{"Key":"-"}
+{"Key":"escape"}
+{"Get":{"state":"h----ˇ-ello\n","mode":"Normal"}}
+{"Key":"4"}
+{"Key":"shift-i"}
+{"Key":"-"}
+{"Key":"escape"}
+{"Get":{"state":"---ˇ-h-----ello\n","mode":"Normal"}}
+{"Key":"3"}
+{"Key":"shift-a"}
+{"Key":"-"}
+{"Key":"escape"}
+{"Get":{"state":"----h-----ello--ˇ-\n","mode":"Normal"}}
+{"Put":{"state":"ˇhello\n"}}
+{"Key":"3"}
+{"Key":"o"}
+{"Key":"o"}
+{"Key":"i"}
+{"Key":"escape"}
+{"Get":{"state":"hello\noi\noi\noˇi\n","mode":"Normal"}}
+{"Put":{"state":"ˇhello\n"}}
+{"Key":"3"}
+{"Key":"shift-o"}
+{"Key":"o"}
+{"Key":"i"}
+{"Key":"escape"}
+{"Get":{"state":"oi\noi\noˇi\nhello\n","mode":"Normal"}}

crates/vim2/test_data/test_insert_with_repeat.json 🔗

@@ -0,0 +1,23 @@
+{"Put":{"state":"ˇhello\n"}}
+{"Key":"3"}
+{"Key":"i"}
+{"Key":"-"}
+{"Key":"escape"}
+{"Get":{"state":"--ˇ-hello\n","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"----ˇ--hello\n","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"."}
+{"Get":{"state":"-----ˇ---hello\n","mode":"Normal"}}
+{"Put":{"state":"ˇhello\n"}}
+{"Key":"2"}
+{"Key":"o"}
+{"Key":"k"}
+{"Key":"k"}
+{"Key":"escape"}
+{"Get":{"state":"hello\nkk\nkˇk\n","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"hello\nkk\nkk\nkk\nkˇk\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"."}
+{"Get":{"state":"hello\nkk\nkk\nkk\nkk\nkˇk\n","mode":"Normal"}}

crates/vim2/test_data/test_j.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"aaˇaa\n😃😃"}}
+{"Key":"j"}
+{"Get":{"state":"aaaa\n😃ˇ😃","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps"}}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
+{"Put":{"state":"The qˇuick brown\nfox jumps"}}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nfox jˇumps","mode":"Normal"}}
+{"Put":{"state":"The quick broˇwn\nfox jumps"}}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nfox jumpˇs","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nˇfox jumps"}}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}

crates/vim2/test_data/test_join_lines.json 🔗

@@ -0,0 +1,13 @@
+{"Put":{"state":"ˇone\ntwo\nthree\nfour\nfive\nsix\n"}}
+{"Key":"shift-j"}
+{"Get":{"state":"oneˇ two\nthree\nfour\nfive\nsix\n","mode":"Normal"}}
+{"Key":"3"}
+{"Key":"shift-j"}
+{"Get":{"state":"one two threeˇ four\nfive\nsix\n","mode":"Normal"}}
+{"Put":{"state":"ˇone\ntwo\nthree\nfour\nfive\nsix\n"}}
+{"Key":"j"}
+{"Key":"v"}
+{"Key":"3"}
+{"Key":"j"}
+{"Key":"shift-j"}
+{"Get":{"state":"one\ntwo three fourˇ five\nsix\n","mode":"Normal"}}

crates/vim2/test_data/test_jump_to_end.json 🔗

@@ -0,0 +1,14 @@
+{"Put":{"state":"The ˇquick\n\nbrown fox jumps\nover the lazy dog"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrown fox jumps\noverˇ the lazy dog","mode":"Normal"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrown fox jumps\noverˇ the lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick\n\nbrown fox jumps\nover the lazy doˇg"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrown fox jumps\nover the lazy doˇg","mode":"Normal"}}
+{"Put":{"state":"The quiˇck\n\nbrown"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nbrowˇn","mode":"Normal"}}
+{"Put":{"state":"The quiˇck\n\n"}}
+{"Key":"shift-g"}
+{"Get":{"state":"The quick\n\nˇ","mode":"Normal"}}

crates/vim2/test_data/test_jump_to_first_non_whitespace.json 🔗

@@ -0,0 +1,18 @@
+{"Put":{"state":"The qˇuick"}}
+{"Key":"^"}
+{"Get":{"state":"ˇThe quick","mode":"Normal"}}
+{"Put":{"state":" The qˇuick"}}
+{"Key":"^"}
+{"Get":{"state":" ˇThe quick","mode":"Normal"}}
+{"Put":{"state":"ˇ"}}
+{"Key":"^"}
+{"Get":{"state":"ˇ","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox"}}
+{"Key":"^"}
+{"Get":{"state":"ˇThe quick\nbrown fox","mode":"Normal"}}
+{"Put":{"state":"ˇ\nThe quick"}}
+{"Key":"^"}
+{"Get":{"state":"ˇ\nThe quick","mode":"Normal"}}
+{"Put":{"state":"   ˇ \nThe quick"}}
+{"Key":"^"}
+{"Get":{"state":"   ˇ \nThe quick","mode":"Normal"}}

crates/vim2/test_data/test_jump_to_line_boundaries.json 🔗

@@ -0,0 +1,28 @@
+{"Put":{"state":"ˇThe quick\nbrown"}}
+{"Key":"$"}
+{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown"}}
+{"Key":"$"}
+{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
+{"Key":"$"}
+{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇbrown"}}
+{"Key":"$"}
+{"Get":{"state":"The quick\nbrowˇn","mode":"Normal"}}
+{"Key":"$"}
+{"Get":{"state":"The quick\nbrowˇn","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick\nbrown"}}
+{"Key":"0"}
+{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown"}}
+{"Key":"0"}
+{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quicˇk\nbrown"}}
+{"Key":"0"}
+{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇbrown"}}
+{"Key":"0"}
+{"Get":{"state":"The quick\nˇbrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrowˇn"}}
+{"Key":"0"}
+{"Get":{"state":"The quick\nˇbrown","mode":"Normal"}}

crates/vim2/test_data/test_k.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"ˇThe quick\nbrown fox jumps"}}
+{"Key":"k"}
+{"Get":{"state":"ˇThe quick\nbrown fox jumps","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown fox jumps"}}
+{"Key":"k"}
+{"Get":{"state":"The qˇuick\nbrown fox jumps","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇbrown fox jumps"}}
+{"Key":"k"}
+{"Get":{"state":"ˇThe quick\nbrown fox jumps","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fˇox jumps"}}
+{"Key":"k"}
+{"Get":{"state":"The quiˇck\nbrown fox jumps","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrown fox jumˇps"}}
+{"Key":"k"}
+{"Get":{"state":"The quicˇk\nbrown fox jumps","mode":"Normal"}}

crates/vim2/test_data/test_l.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"ˇThe quick\nbrown"}}
+{"Key":"l"}
+{"Get":{"state":"Tˇhe quick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The qˇuick\nbrown"}}
+{"Key":"l"}
+{"Get":{"state":"The quˇick\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quicˇk\nbrown"}}
+{"Key":"l"}
+{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nˇbrown"}}
+{"Key":"l"}
+{"Get":{"state":"The quick\nbˇrown","mode":"Normal"}}
+{"Put":{"state":"The quick\nbrowˇn"}}
+{"Key":"l"}
+{"Get":{"state":"The quick\nbrowˇn","mode":"Normal"}}

crates/vim2/test_data/test_matching.json 🔗

@@ -0,0 +1,17 @@
+{"Put":{"state":"func ˇ(a string) {\n    do(something(with<Types>.and_arrays[0, 2]))\n}"}}
+{"Key":"%"}
+{"Get":{"state":"func (a stringˇ) {\n    do(something(with<Types>.and_arrays[0, 2]))\n}","mode":"Normal"}}
+{"Put":{"state":"func (a string) ˇ{\ndo(something(with<Types>.and_arrays[0, 2]))\n}"}}
+{"Key":"%"}
+{"Get":{"state":"func (a string) {\ndo(something(with<Types>.and_arrays[0, 2]))\nˇ}","mode":"Normal"}}
+{"Put":{"state":"ˇ{()}"}}
+{"Key":"%"}
+{"Get":{"state":"{()ˇ}","mode":"Normal"}}
+{"Key":"%"}
+{"Get":{"state":"ˇ{()}","mode":"Normal"}}
+{"Put":{"state":"{\n    ˇ{()}\n}"}}
+{"Key":"%"}
+{"Get":{"state":"{\n    {()ˇ}\n}","mode":"Normal"}}
+{"Put":{"state":"func ˇboop() {\n}"}}
+{"Key":"%"}
+{"Get":{"state":"func boop(ˇ) {\n}","mode":"Normal"}}

crates/vim2/test_data/test_multiline_surrounding_character_objects.json 🔗

@@ -0,0 +1,15 @@
+{"Put":{"state":"func empty(a string) bool {\n   if a == \"\" {\n      return true\n   }\n   ˇreturn false\n}"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"func empty(a string) bool {\n«   if a == \"\" {\n      return true\n   }\n   return false\nˇ»}","mode":"Visual"}}
+{"Put":{"state":"func empty(a string) bool {\n     if a == \"\" {\n         ˇreturn true\n     }\n     return false\n}"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"func empty(a string) bool {\n     if a == \"\" {\n«         return true\nˇ»     }\n     return false\n}","mode":"Visual"}}
+{"Put":{"state":"func empty(a string) bool {\n     if a == \"\" ˇ{\n         return true\n     }\n     return false\n}"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"{"}
+{"Get":{"state":"func empty(a string) bool {\n     if a == \"\" {\n«         return true\nˇ»     }\n     return false\n}","mode":"Visual"}}

crates/vim2/test_data/test_neovim.json 🔗

@@ -0,0 +1,16 @@
+{"Key":"i"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Key":"shift-T"}
+{"Key":"e"}
+{"Key":"s"}
+{"Key":"t"}
+{"Key":" "}
+{"Key":"t"}
+{"Key":"e"}
+{"Key":"s"}
+{"Key":"t"}
+{"Key":"escape"}
+{"Key":"0"}
+{"Key":"d"}
+{"Key":"w"}
+{"Get":{"state":"ˇtest","mode":"Normal"}}

crates/vim2/test_data/test_o.json 🔗

@@ -0,0 +1,18 @@
+{"Put":{"state":"ˇ"}}
+{"Key":"o"}
+{"Get":{"state":"\nˇ","mode":"Insert"}}
+{"Put":{"state":"The ˇquick"}}
+{"Key":"o"}
+{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
+{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
+{"Key":"o"}
+{"Get":{"state":"The quick\nˇ\nbrown fox\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
+{"Key":"o"}
+{"Get":{"state":"The quick\nbrown fox\nˇ\njumps over","mode":"Insert"}}
+{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
+{"Key":"o"}
+{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick\nˇ\nbrown fox"}}
+{"Key":"o"}
+{"Get":{"state":"The quick\n\nˇ\nbrown fox","mode":"Insert"}}

crates/vim2/test_data/test_paste.json 🔗

@@ -0,0 +1,31 @@
+{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"y"}
+{"ReadRegister":{"name":"\"","value":"jumps o"}}
+{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
+{"Key":"p"}
+{"Get":{"state":"The quick brown\nfox jumps overjumps ˇo\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
+{"Key":"shift-p"}
+{"Get":{"state":"The quick brown\nfox jumps ovejumps ˇor\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
+{"Key":"d"}
+{"Key":"d"}
+{"ReadRegister":{"name":"\"","value":"fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
+{"Key":"p"}
+{"Get":{"state":"The quick brown\nthe lazy dog\nˇfox jumps over","mode":"Normal"}}
+{"Key":"k"}
+{"Key":"shift-p"}
+{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog\nfox jumps over","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"j"}
+{"Key":"y"}
+{"ReadRegister":{"name":"\"","value":"over\nthe lazy do"}}
+{"Key":"p"}
+{"Get":{"state":"The quick brown\nfox jumps oˇover\nthe lazy dover\nthe lazy dog","mode":"Normal"}}
+{"Key":"u"}
+{"Key":"shift-p"}
+{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy doover\nthe lazy dog","mode":"Normal"}}

crates/vim2/test_data/test_paste_visual.json 🔗

@@ -0,0 +1,42 @@
+{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Key":"y"}
+{"Get":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"w"}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Key":"p"}
+{"Get":{"state":"The quick brown\nfox jumps jumpˇs\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"over"}}
+{"Key":"up"}
+{"Key":"shift-v"}
+{"Key":"shift-p"}
+{"Get":{"state":"ˇover\nfox jumps jumps\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"over"}}
+{"Key":"ctrl-v"}
+{"Key":"down"}
+{"Key":"down"}
+{"Key":"p"}
+{"Get":{"state":"oveˇrver\noverox jumps jumps\noverhe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"d"}
+{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Key":"p"}
+{"Get":{"state":"The quick brown\nthe \nˇfox jumps over\n dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"lazy"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"d"}
+{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
+{"Key":"k"}
+{"Key":"shift-v"}
+{"Key":"p"}
+{"Get":{"state":"ˇfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"The quick brown\n"}}

crates/vim2/test_data/test_paste_visual_block.json 🔗

@@ -0,0 +1,31 @@
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"ctrl-v"}
+{"Key":"2"}
+{"Key":"j"}
+{"Key":"y"}
+{"ReadRegister":{"name":"\"","value":"q\nj\nl"}}
+{"Key":"p"}
+{"Get":{"state":"The qˇquick brown\nfox jjumps over\nthe llazy dog","mode":"Normal"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Key":"shift-p"}
+{"Get":{"state":"The ˇq brown\nfox jjjumps over\nthe lllazy dog","mode":"Normal"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Key":"shift-p"}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"y"}
+{"ReadRegister":{"name":"\"","value":"q\nj"}}
+{"Key":"l"}
+{"Key":"ctrl-v"}
+{"Key":"2"}
+{"Key":"j"}
+{"Key":"shift-p"}
+{"Get":{"state":"The qˇqick brown\nfox jjmps over\nthe lzy dog","mode":"Normal"}}
+{"Key":"shift-v"}
+{"Key":"p"}
+{"Get":{"state":"ˇq\nj\nfox jjmps over\nthe lzy dog","mode":"Normal"}}

crates/vim2/test_data/test_percent.json 🔗

@@ -0,0 +1,58 @@
+{"Put":{"state":"ˇconsole.log(var);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log(varˇ);","mode":"Normal"}}
+{"Put":{"state":"console.logˇ(var);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log(varˇ);","mode":"Normal"}}
+{"Put":{"state":"console.log(ˇvar);"}}
+{"Key":"%"}
+{"Get":{"state":"console.logˇ(var);","mode":"Normal"}}
+{"Put":{"state":"console.log(vaˇr);"}}
+{"Key":"%"}
+{"Get":{"state":"console.logˇ(var);","mode":"Normal"}}
+{"Put":{"state":"console.log(varˇ);"}}
+{"Key":"%"}
+{"Get":{"state":"console.logˇ(var);","mode":"Normal"}}
+{"Put":{"state":"console.log(var)ˇ;"}}
+{"Key":"%"}
+{"Get":{"state":"console.log(var)ˇ;","mode":"Normal"}}
+{"Put":{"state":"ˇconsole.log('var', [1, 2, 3]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', [1, 2, 3]ˇ);","mode":"Normal"}}
+{"Put":{"state":"console.logˇ('var', [1, 2, 3]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', [1, 2, 3]ˇ);","mode":"Normal"}}
+{"Put":{"state":"console.log(ˇ'var', [1, 2, 3]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', [1, 2, 3ˇ]);","mode":"Normal"}}
+{"Put":{"state":"console.log('var', ˇ[1, 2, 3]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', [1, 2, 3ˇ]);","mode":"Normal"}}
+{"Put":{"state":"console.log('var', [ˇ1, 2, 3]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', ˇ[1, 2, 3]);","mode":"Normal"}}
+{"Put":{"state":"console.log('var', [1, ˇ2, 3]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', ˇ[1, 2, 3]);","mode":"Normal"}}
+{"Put":{"state":"console.log('var', [1, 2, 3ˇ]);"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', ˇ[1, 2, 3]);","mode":"Normal"}}
+{"Put":{"state":"console.log('var', [1, 2, 3]ˇ);"}}
+{"Key":"%"}
+{"Get":{"state":"console.logˇ('var', [1, 2, 3]);","mode":"Normal"}}
+{"Put":{"state":"console.log('var', [1, 2, 3])ˇ;"}}
+{"Key":"%"}
+{"Get":{"state":"console.log('var', [1, 2, 3])ˇ;","mode":"Normal"}}
+{"Put":{"state":"let result = curried_funˇ()();"}}
+{"Key":"%"}
+{"Get":{"state":"let result = curried_fun(ˇ)();","mode":"Normal"}}
+{"Key":"%"}
+{"Get":{"state":"let result = curried_funˇ()();","mode":"Normal"}}
+{"Put":{"state":"let result = curried_fun()ˇ();"}}
+{"Key":"%"}
+{"Get":{"state":"let result = curried_fun()(ˇ);","mode":"Normal"}}
+{"Key":"%"}
+{"Get":{"state":"let result = curried_fun()ˇ();","mode":"Normal"}}
+{"Put":{"state":"let result = curried_fun()()ˇ;"}}
+{"Key":"%"}
+{"Get":{"state":"let result = curried_fun()()ˇ;","mode":"Normal"}}

crates/vim2/test_data/test_repeat_motion_counts.json 🔗

@@ -0,0 +1,13 @@
+{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"3"}
+{"Key":"d"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"ˇ brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"."}
+{"Get":{"state":" brown\nˇ over\nthe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"2"}
+{"Key":"."}
+{"Get":{"state":" brown\n over\nˇe lazy dog","mode":"Normal"}}

crates/vim2/test_data/test_repeat_visual.json 🔗

@@ -0,0 +1,51 @@
+{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Key":"s"}
+{"Key":"o"}
+{"Key":"escape"}
+{"Get":{"state":"ˇo quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"w"}
+{"Key":"."}
+{"Get":{"state":"o quick brown\nfox ˇops over\nthe lazy dog","mode":"Normal"}}
+{"Key":"f"}
+{"Key":"r"}
+{"Key":"."}
+{"Get":{"state":"o quick brown\nfox ops oveˇothe lazy dog","mode":"Normal"}}
+{"Put":{"state":"the ˇquick brown\nfox jumps over\nfox jumps over\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"j"}
+{"Key":"x"}
+{"Get":{"state":"the ˇumps over\nfox jumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"the ˇumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"w"}
+{"Key":"."}
+{"Get":{"state":"the umps ˇumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"."}
+{"Get":{"state":"the umps umps over\nthe ˇog","mode":"Normal"}}
+{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"j"}
+{"Key":"shift-i"}
+{"Key":"o"}
+{"Key":"escape"}
+{"Get":{"state":"ˇothe quick brown\nofox jumps over\nothe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"4"}
+{"Key":"l"}
+{"Key":"."}
+{"Get":{"state":"othe quick brown\nofoxˇo jumps over\notheo lazy dog","mode":"Normal"}}
+{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"shift-r"}
+{"Key":"o"}
+{"Key":"escape"}
+{"Get":{"state":"ˇo\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"."}
+{"Get":{"state":"o\nˇo\nthe lazy dog","mode":"Normal"}}

crates/vim2/test_data/test_repeated_cb.json 🔗

@@ -0,0 +1,275 @@
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox ˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The quick ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\nˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox ˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"The ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"The quick ˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox ˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nfox ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"The ˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"The quick ˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\nˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\n\nˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"ˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"The ˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"The quick ˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\nˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"b"}
+{"Get":{"state":"The quick brown\nˇ\nthe lazy dog\n","mode":"Insert"}}

crates/vim2/test_data/test_repeated_ce.json 🔗

@@ -0,0 +1,275 @@
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"ˇ quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quickˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick browˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\nˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox ˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quickˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick browˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox ˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quickˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick browˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\nˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"ˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quickˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick browˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\nˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox ˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"ˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quickˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick browˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\nˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox ˇ dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"e"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}

crates/vim2/test_data/test_repeated_cj.json 🔗

@@ -0,0 +1,275 @@
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"ˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"j"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}

crates/vim2/test_data/test_repeated_cl.json 🔗

@@ -0,0 +1,275 @@
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"ˇhe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quˇck brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quickˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nˇox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox ˇumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇover\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇer\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"1"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇhe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"ˇe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quˇk brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quickˇrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nˇx jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox ˇmps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇver\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇer\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇr\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"2"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"ˇ quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quickˇown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox ˇps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇer\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇr\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"3"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"ˇquick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quickˇwn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox ˇs-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇr\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"4"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇlazy dog\n","mode":"Insert"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"ˇuick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quˇrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quickˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nˇumps-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox ˇ-over\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"c"}
+{"Key":"5"}
+{"Key":"l"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇazy dog\n","mode":"Insert"}}

crates/vim2/test_data/test_repeated_word.json 🔗

@@ -0,0 +1,214 @@
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The ˇquick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick ˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"2"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"3"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"4"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
+{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
+{"Key":"5"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}

crates/vim2/test_data/test_selection_goal.json 🔗

@@ -0,0 +1,8 @@
+{"Put":{"state":";;ˇ;\nLorem Ipsum"}}
+{"Key":"a"}
+{"Key":"down"}
+{"Key":"up"}
+{"Key":";"}
+{"Key":"down"}
+{"Key":"up"}
+{"Get":{"state":";;;;ˇ\nLorem Ipsum","mode":"Insert"}}

crates/vim2/test_data/test_singleline_surrounding_character_objects.json 🔗

@@ -0,0 +1,27 @@
+{"SetOption":{"value":"wrap"}}
+{"SetOption":{"value":"columns=12"}}
+{"Put":{"state":"helˇlo \"world\"!"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"hello \"«worldˇ»\"!","mode":"Visual"}}
+{"Put":{"state":"hello \"wˇorld\"!"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"\""}
+{"Get":{"state":"hello \"«worldˇ»\"!","mode":"Visual"}}
+{"Put":{"state":"hello \"wˇorld\"!"}}
+{"Key":"v"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"hello« \"world\"ˇ»!","mode":"Visual"}}
+{"Put":{"state":"hello \"wˇorld\" !"}}
+{"Key":"v"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"hello «\"world\" ˇ»!","mode":"Visual"}}
+{"Put":{"state":"hello \"wˇorld\"•\ngoodbye"}}
+{"Key":"v"}
+{"Key":"a"}
+{"Key":"\""}
+{"Get":{"state":"hello «\"world\" ˇ»\ngoodbye","mode":"Visual"}}

crates/vim2/test_data/test_start_end_of_paragraph.json 🔗

@@ -0,0 +1,13 @@
+{"Put":{"state":"ˇabc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinal"}}
+{"Key":"}"}
+{"Get":{"state":"abc\ndef\nˇ\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}}
+{"Key":"{"}
+{"Get":{"state":"ˇabc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"}"}
+{"Get":{"state":"abc\ndef\n\nparagraph\nthe second\nˇ\n\n\nthird and\nfinal","mode":"Normal"}}
+{"Key":"}"}
+{"Get":{"state":"abc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinaˇl","mode":"Normal"}}
+{"Key":"2"}
+{"Key":"{"}
+{"Get":{"state":"abc\ndef\nˇ\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}}

crates/vim2/test_data/test_substitute_line.json 🔗

@@ -0,0 +1,29 @@
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
+{"Key":"shift-s"}
+{"Key":"o"}
+{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
+{"Key":"v"}
+{"Key":"k"}
+{"Key":"shift-s"}
+{"Key":"o"}
+{"Get":{"state":"oˇ\nthe lazy dog\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"shift-s"}
+{"Key":"o"}
+{"Get":{"state":"The quick brown\noˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
+{"Key":"v"}
+{"Key":"$"}
+{"Key":"shift-s"}
+{"Key":"o"}
+{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}}
+{"SetOption":{"value":"shiftwidth=4"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
+{"Key":">"}
+{"Key":">"}
+{"Key":"shift-s"}
+{"Key":"o"}
+{"Get":{"state":"The quick brown\n    oˇ\nthe lazy dog\n","mode":"Insert"}}

crates/vim2/test_data/test_visual_block_insert.json 🔗

@@ -0,0 +1,18 @@
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"9"}
+{"Key":"down"}
+{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
+{"Key":"shift-i"}
+{"Key":"k"}
+{"Key":"escape"}
+{"Get":{"state":"ˇkThe quick brown\nkfox jumps over\nkthe lazy dog\nk","mode":"Normal"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"9"}
+{"Key":"down"}
+{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
+{"Key":"c"}
+{"Key":"k"}
+{"Key":"escape"}
+{"Get":{"state":"ˇkhe quick brown\nkox jumps over\nkhe lazy dog\nk","mode":"Normal"}}

crates/vim2/test_data/test_visual_block_issue_2123.json 🔗

@@ -0,0 +1,5 @@
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"right"}
+{"Key":"down"}
+{"Get":{"state":"The «quˇ»ick brown\nfox «juˇ»mps over\nthe lazy dog\n","mode":"VisualBlock"}}

crates/vim2/test_data/test_visual_block_mode.json 🔗

@@ -0,0 +1,38 @@
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"ctrl-v"}
+{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"VisualBlock"}}
+{"Key":"2"}
+{"Key":"down"}
+{"Get":{"state":"The «qˇ»uick brown\nfox «jˇ»umps over\nthe «lˇ»azy dog","mode":"VisualBlock"}}
+{"Key":"e"}
+{"Get":{"state":"The «quicˇ»k brown\nfox «jumpˇ»s over\nthe «lazyˇ» dog","mode":"VisualBlock"}}
+{"Key":"^"}
+{"Get":{"state":"«ˇThe q»uick brown\n«ˇfox j»umps over\n«ˇthe l»azy dog","mode":"VisualBlock"}}
+{"Key":"$"}
+{"Get":{"state":"The «quick brownˇ»\nfox «jumps overˇ»\nthe «lazy dogˇ»","mode":"VisualBlock"}}
+{"Key":"shift-f"}
+{"Key":" "}
+{"Get":{"state":"The «quickˇ» brown\nfox «jumpsˇ» over\nthe «lazy ˇ»dog","mode":"VisualBlock"}}
+{"Key":"v"}
+{"Get":{"state":"The «quick brown\nfox jumps over\nthe lazy ˇ»dog","mode":"Visual"}}
+{"Key":"ctrl-v"}
+{"Get":{"state":"The «quickˇ» brown\nfox «jumpsˇ» over\nthe «lazy ˇ»dog","mode":"VisualBlock"}}
+{"Put":{"state":"The ˇquick\nbrown\nfox\njumps over the\n\nlazy dog\n"}}
+{"Key":"ctrl-v"}
+{"Key":"down"}
+{"Key":"down"}
+{"Get":{"state":"The«ˇ q»uick\nbro«ˇwn»\nfoxˇ\njumps over the\n\nlazy dog\n","mode":"VisualBlock"}}
+{"Key":"down"}
+{"Get":{"state":"The «qˇ»uick\nbrow«nˇ»\nfox\njump«sˇ» over the\n\nlazy dog\n","mode":"VisualBlock"}}
+{"Key":"left"}
+{"Get":{"state":"The«ˇ q»uick\nbro«ˇwn»\nfoxˇ\njum«ˇps» over the\n\nlazy dog\n","mode":"VisualBlock"}}
+{"Key":"s"}
+{"Key":"o"}
+{"Key":"escape"}
+{"Get":{"state":"Theˇouick\nbroo\nfoxo\njumo over the\n\nlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"Theˇ quick brown\n\nfox jumps over\nthe lazy dog\n"}}
+{"Key":"l"}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"j"}
+{"Get":{"state":"The «qˇ»uick brown\n\nfox «jˇ»umps over\nthe lazy dog\n","mode":"VisualBlock"}}

crates/vim2/test_data/test_visual_change.json 🔗

@@ -0,0 +1,47 @@
+{"Put":{"state":"The quick ˇbrown"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"c"}
+{"Get":{"state":"The quick ˇ","mode":"Insert"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"k"}
+{"Key":"c"}
+{"Get":{"state":"The ˇrown\nfox jumps over\nthe lazy dog","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nfox jumps ˇhe lazy dog","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"k"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nˇver\nthe lazy dog","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"k"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nfox jumpsˇazy dog","mode":"Insert"}}

crates/vim2/test_data/test_visual_delete.json 🔗

@@ -0,0 +1,48 @@
+{"Put":{"state":"The quick ˇbrown"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Get":{"state":"The quick «brownˇ»","mode":"Visual"}}
+{"Put":{"state":"The quick ˇbrown"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"x"}
+{"Get":{"state":"The quickˇ ","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"x"}
+{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"p"}
+{"Get":{"state":"The ver\nthe lˇquick brown\nfox jumps oazy dog","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"x"}
+{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"x"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Normal"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"b"}
+{"Key":"k"}
+{"Key":"x"}
+{"Get":{"state":"ˇuick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"b"}
+{"Key":"k"}
+{"Key":"x"}
+{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
+{"Key":"v"}
+{"Key":"b"}
+{"Key":"k"}
+{"Key":"x"}
+{"Get":{"state":"The quick brown\nˇazy dog","mode":"Normal"}}

crates/vim2/test_data/test_visual_line_change.json 🔗

@@ -0,0 +1,35 @@
+{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"c"}
+{"Get":{"state":"ˇ\nfox jumps over\nthe lazy dog","mode":"Insert"}}
+{"Key":"escape"}
+{"Key":"j"}
+{"Key":"p"}
+{"Get":{"state":"\nfox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nˇ\nthe lazy dog","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
+{"Key":"shift-v"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nfox jumps over\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"ˇ\nthe lazy dog","mode":"Insert"}}
+{"Key":"escape"}
+{"Key":"j"}
+{"Key":"p"}
+{"Get":{"state":"\nthe lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
+{"Key":"shift-v"}
+{"Key":"j"}
+{"Key":"c"}
+{"Get":{"state":"The quick brown\nfox jumps over\nˇ","mode":"Insert"}}

crates/vim2/test_data/test_visual_line_delete.json 🔗

@@ -0,0 +1,23 @@
+{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"x"}
+{"Get":{"state":"fox juˇmps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"p"}
+{"Get":{"state":"fox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
+{"Key":"shift-v"}
+{"Key":"x"}
+{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
+{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"j"}
+{"Key":"x"}
+{"Get":{"state":"the laˇzy dog","mode":"Normal"}}
+{"Key":"p"}
+{"Get":{"state":"the lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
+{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
+{"Key":"shift-v"}
+{"Key":"$"}
+{"Key":"x"}
+{"Get":{"state":"should noˇt\ncrash\n","mode":"Normal"}}

crates/vim2/test_data/test_visual_object.json 🔗

@@ -0,0 +1,19 @@
+{"Put":{"state":"hello (in [parˇens] o)"}}
+{"Key":"ctrl-v"}
+{"Key":"l"}
+{"Key":"a"}
+{"Key":"]"}
+{"Get":{"state":"hello (in «[parens]ˇ» o)","mode":"Visual"}}
+{"Key":"i"}
+{"Key":"("}
+{"Get":{"state":"hello («in [parens] oˇ»)","mode":"Visual"}}
+{"Put":{"state":"hello in a wˇord again."}}
+{"Key":"ctrl-v"}
+{"Key":"l"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"hello in a w«ordˇ» again.","mode":"VisualBlock"}}
+{"Key":"o"}
+{"Key":"a"}
+{"Key":"s"}
+{"Get":{"state":"«ˇhello in a word» again.","mode":"VisualBlock"}}

crates/vim2/test_data/test_visual_word_object.json 🔗

@@ -0,0 +1,236 @@
+{"Put":{"state":"The quick brown\nˇ\nfox"}}
+{"Key":"v"}
+{"Get":{"state":"The quick brown\n«\nˇ»fox","mode":"Visual"}}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown\n«\nˇ»fox","mode":"Visual"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick «brownˇ»   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick «brownˇ»   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown«   ˇ»\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox «jumpsˇ» over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox «jumpsˇ» over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps« ˇ»over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog«  ˇ»\n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n«\nˇ»\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n«\nˇ»\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n«\nˇ»The-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\n«Theˇ»-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe«-ˇ»quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-«quickˇ» brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-«quickˇ» brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick« ˇ»brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick «brownˇ» \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown« ˇ»\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n«  ˇ»\n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n«  ˇ»\n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n«  ˇ»fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-«jumpsˇ» over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n«\nˇ»","mode":"Visual"}}
+{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick «brownˇ»   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick browˇn   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick «brownˇ»   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown«   ˇ»\nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox ˇjumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox «jumpsˇ» over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox juˇmps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox «jumpsˇ» over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumpsˇ over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps« ˇ»over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog«  ˇ»\n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \nˇ\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n«\nˇ»\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\nˇ\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n«\nˇ»\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\nˇ\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n«\nˇ»The-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThˇe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\n«The-quickˇ» brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nTheˇ-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\n«The-quickˇ» brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-ˇquick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\n«The-quickˇ» brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quˇick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\n«The-quickˇ» brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quickˇ brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick« ˇ»brown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick ˇbrown \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick «brownˇ» \n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brownˇ \n  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown« ˇ»\n  \n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \nˇ  \n  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n«  ˇ»\n  \n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \nˇ  \n  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n«  ˇ»\n  fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \nˇ  fox-jumps over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n«  ˇ»fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumpˇs over\nthe lazy dog \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  «fox-jumpsˇ» over\nthe lazy dog \n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dogˇ \n\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":"Visual"}}
+{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \nˇ\n"}}
+{"Key":"v"}
+{"Key":"i"}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog  \n\n\n\nThe-quick brown \n  \n  \n  fox-jumps over\nthe lazy dog \n«\nˇ»","mode":"Visual"}}

crates/vim2/test_data/test_visual_yank.json 🔗

@@ -0,0 +1,35 @@
+{"Put":{"state":"The quick ˇbrown"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"y"}
+{"Get":{"state":"The quick ˇbrown","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"brown"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"y"}
+{"Get":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"quick brown\nfox jumps o"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
+{"Key":"v"}
+{"Key":"w"}
+{"Key":"j"}
+{"Key":"y"}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"lazy d"}}
+{"Key":"shift-v"}
+{"Key":"y"}
+{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
+{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"v"}
+{"Key":"b"}
+{"Key":"k"}
+{"Key":"y"}
+{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
+{"Key":"shift-v"}
+{"Key":"shift-g"}
+{"Key":"shift-y"}
+{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"ReadRegister":{"name":"\"","value":"fox jumps over\nthe lazy dog\n"}}

crates/vim2/test_data/test_w.json 🔗

@@ -0,0 +1,40 @@
+{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"w"}
+{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
+{"Key":"w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
+{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe","mode":"Normal"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
+{"Key":"shift-w"}
+{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}

crates/vim2/test_data/test_wrapped_lines.json 🔗

@@ -0,0 +1,61 @@
+{"SetOption":{"value":"wrap"}}
+{"SetOption":{"value":"columns=12"}}
+{"Put":{"state":"tˇwelve char twelve char\ntwelve char\n"}}
+{"Key":"j"}
+{"Get":{"state":"twelve char twelve char\ntˇwelve char\n","mode":"Normal"}}
+{"Key":"k"}
+{"Get":{"state":"tˇwelve char twelve char\ntwelve char\n","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"j"}
+{"Get":{"state":"twelve char tˇwelve char\ntwelve char\n","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"j"}
+{"Get":{"state":"twelve char twelve char\ntˇwelve char\n","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"k"}
+{"Get":{"state":"twelve char tˇwelve char\ntwelve char\n","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"^"}
+{"Get":{"state":"twelve char ˇtwelve char\ntwelve char\n","mode":"Normal"}}
+{"Key":"^"}
+{"Get":{"state":"ˇtwelve char twelve char\ntwelve char\n","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"$"}
+{"Get":{"state":"twelve charˇ twelve char\ntwelve char\n","mode":"Normal"}}
+{"Key":"$"}
+{"Get":{"state":"twelve char twelve chaˇr\ntwelve char\n","mode":"Normal"}}
+{"Put":{"state":"tˇwelve char twelve char\ntwelve char\n"}}
+{"Key":"enter"}
+{"Get":{"state":"twelve char twelve char\nˇtwelve char\n","mode":"Normal"}}
+{"Put":{"state":"twelve char\ntˇwelve char twelve char\ntwelve char\n"}}
+{"Key":"o"}
+{"Key":"o"}
+{"Key":"escape"}
+{"Get":{"state":"twelve char\ntwelve char twelve char\nˇo\ntwelve char\n","mode":"Normal"}}
+{"Put":{"state":"twelve char\ntˇwelve char twelve char\ntwelve char\n"}}
+{"Key":"shift-a"}
+{"Key":"a"}
+{"Key":"escape"}
+{"Get":{"state":"twelve char\ntwelve char twelve charˇa\ntwelve char\n","mode":"Normal"}}
+{"Key":"shift-i"}
+{"Key":"i"}
+{"Key":"escape"}
+{"Get":{"state":"twelve char\nˇitwelve char twelve chara\ntwelve char\n","mode":"Normal"}}
+{"Key":"shift-d"}
+{"Get":{"state":"twelve char\nˇ\ntwelve char\n","mode":"Normal"}}
+{"Put":{"state":"twelve char\ntwelve char tˇwelve char\ntwelve char\n"}}
+{"Key":"shift-o"}
+{"Key":"o"}
+{"Key":"escape"}
+{"Get":{"state":"twelve char\nˇo\ntwelve char twelve char\ntwelve char\n","mode":"Normal"}}
+{"Put":{"state":"fourteen chaˇr\nfourteen char\n"}}
+{"Key":"d"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"fourteenˇ \nfourteen char\n","mode":"Normal"}}
+{"Key":"j"}
+{"Key":"shift-f"}
+{"Key":"e"}
+{"Key":"f"}
+{"Key":"r"}
+{"Get":{"state":"fourteen \nfourteen chaˇr\n","mode":"Normal"}}

crates/vim2/test_data/test_wrapped_motions.json 🔗

@@ -0,0 +1,15 @@
+{"SetOption":{"value":"wrap"}}
+{"SetOption":{"value":"columns=12"}}
+{"Put":{"state":"aaˇaa\n😃😃"}}
+{"Key":"j"}
+{"Get":{"state":"aaaa\n😃ˇ😃","mode":"Normal"}}
+{"Put":{"state":"123456789012aaˇaa\n123456789012😃😃"}}
+{"Key":"j"}
+{"Get":{"state":"123456789012aaaa\n123456789012😃ˇ😃","mode":"Normal"}}
+{"Put":{"state":"123456789012aaˇaa\n123456789012😃😃"}}
+{"Key":"j"}
+{"Get":{"state":"123456789012aaaa\n123456789012😃ˇ😃","mode":"Normal"}}
+{"Put":{"state":"123456789012aaaaˇaaaaaaaa123456789012\nwow\n123456789012😃😃😃😃😃😃123456789012"}}
+{"Key":"j"}
+{"Key":"j"}
+{"Get":{"state":"123456789012aaaaaaaaaaaa123456789012\nwow\n123456789012😃😃ˇ😃😃😃😃123456789012","mode":"Normal"}}

crates/vim2/test_data/test_x.json 🔗

@@ -0,0 +1,12 @@
+{"Put":{"state":"ˇTest"}}
+{"Key":"x"}
+{"Get":{"state":"ˇest","mode":"Normal"}}
+{"Put":{"state":"Teˇst"}}
+{"Key":"x"}
+{"Get":{"state":"Teˇt","mode":"Normal"}}
+{"Put":{"state":"Tesˇt"}}
+{"Key":"x"}
+{"Get":{"state":"Teˇs","mode":"Normal"}}
+{"Put":{"state":"Tesˇt\ntest"}}
+{"Key":"x"}
+{"Get":{"state":"Teˇs\ntest","mode":"Normal"}}

crates/vim2/test_data/test_zero.json 🔗

@@ -0,0 +1,7 @@
+{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
+{"Key":"0"}
+{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
+{"Key":"1"}
+{"Key":"0"}
+{"Key":"l"}
+{"Get":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog","mode":"Normal"}}

crates/zed2/Cargo.toml 🔗

@@ -69,7 +69,7 @@ theme = { package = "theme2", path = "../theme2" }
 theme_selector = { package = "theme_selector2", path = "../theme_selector2" }
 util = { path = "../util" }
 semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
-# vim = { path = "../vim" }
+vim = { package = "vim2", path = "../vim2" }
 workspace = { package = "workspace2", path = "../workspace2" }
 welcome = { package = "welcome2", path = "../welcome2" }
 zed_actions = {package = "zed_actions2", path = "../zed_actions2"}

crates/zed2/src/app_menus.rs 🔗

@@ -2,6 +2,8 @@ use gpui::{Menu, MenuItem, OsAction};
 
 #[cfg(target_os = "macos")]
 pub fn app_menus() -> Vec<Menu<'static>> {
+    use zed_actions::Quit;
+
     vec![
         Menu {
             name: "Zed",
@@ -25,7 +27,7 @@ pub fn app_menus() -> Vec<Menu<'static>> {
                 MenuItem::action("Hide Zed", super::Hide),
                 MenuItem::action("Hide Others", super::HideOthers),
                 MenuItem::action("Show All", super::ShowAll),
-                MenuItem::action("Quit", super::Quit),
+                MenuItem::action("Quit", Quit),
             ],
         },
         Menu {

crates/zed2/src/main.rs 🔗

@@ -211,7 +211,7 @@ fn main() {
         // diagnostics::init(cx);
         search::init(cx);
         semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
-        // vim::init(cx);
+        vim::init(cx);
         terminal_view::init(cx);
 
         // journal2::init(app_state.clone(), cx);

crates/zed2/src/zed2.rs 🔗

@@ -41,7 +41,7 @@ use workspace::{
     notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
     NewWindow, Workspace, WorkspaceSettings,
 };
-use zed_actions::{OpenBrowser, OpenZedURL};
+use zed_actions::{OpenBrowser, OpenZedURL, Quit};
 
 actions!(
     zed,
@@ -61,7 +61,6 @@ actions!(
         OpenLog,
         OpenSettings,
         OpenTelemetryLog,
-        Quit,
         ResetBufferFontSize,
         ResetDatabase,
         ShowAll,
@@ -128,7 +127,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
         let active_buffer_language =
             cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
-        //     let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
+        let vim_mode_indicator = cx.build_view(|cx| vim::ModeIndicator::new(cx));
         let feedback_button = cx
             .build_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace));
         //     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
@@ -140,13 +139,13 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             // status_bar.add_right_item(copilot, cx);
             status_bar.add_right_item(copilot, cx);
             status_bar.add_right_item(active_buffer_language, cx);
-            // status_bar.add_right_item(vim_mode_indicator, cx);
+            status_bar.add_right_item(vim_mode_indicator, cx);
             status_bar.add_right_item(cursor_position, cx);
         });
 
         auto_update::notify_of_any_new_update(cx);
 
-        //     vim::observe_keystrokes(cx);
+        vim::observe_keystrokes(cx);
 
         let handle = cx.view().downgrade();
         cx.on_window_should_close(move |cx| {

crates/zed_actions2/src/lib.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::impl_actions;
+use gpui::{actions, impl_actions};
 use serde::Deserialize;
 
 // If the zed binary doesn't use anything in this crate, it will be optimized away
@@ -21,3 +21,5 @@ pub struct OpenZedURL {
 }
 
 impl_actions!(zed, [OpenBrowser, OpenZedURL]);
+
+actions!(zed, [Quit]);