From 21ad375b422236d1b1e19ec5fb8b7b33a1ee7769 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 24 Oct 2022 18:27:56 -0700 Subject: [PATCH] Fix panic in vim motion when not listed as exclusive and add features enum to capture why tests are ignored --- crates/vim/src/motion.rs | 59 +- crates/vim/src/normal.rs | 11 +- crates/vim/src/normal/change.rs | 504 +++++++----------- crates/vim/src/normal/delete.rs | 486 +++++++---------- crates/vim/src/object.rs | 176 ++++-- .../neovim_backed_binding_test_context.rs | 28 +- .../src/test/neovim_backed_test_context.rs | 71 ++- crates/vim/test_data/test_change_0.json | 1 + crates/vim/test_data/test_change_b.json | 1 + .../vim/test_data/test_change_backspace.json | 1 + crates/vim/test_data/test_change_e.json | 1 + .../test_change_end_of_document.json | 1 + .../test_data/test_change_end_of_line.json | 1 + crates/vim/test_data/test_change_gg.json | 1 + crates/vim/test_data/test_change_h.json | 1 + crates/vim/test_data/test_change_j.json | 1 + crates/vim/test_data/test_change_k.json | 1 + crates/vim/test_data/test_change_l.json | 1 + ..._change_surrounding_character_objects.json | 2 +- crates/vim/test_data/test_change_w.json | 1 + crates/vim/test_data/test_dd.json | 2 +- crates/vim/test_data/test_delete_0.json | 1 + crates/vim/test_data/test_delete_b.json | 1 + crates/vim/test_data/test_delete_e.json | 1 + .../test_delete_end_of_document.json | 1 + .../test_data/test_delete_end_of_line.json | 1 + crates/vim/test_data/test_delete_gg.json | 1 + crates/vim/test_data/test_delete_h.json | 1 + crates/vim/test_data/test_delete_j.json | 1 + crates/vim/test_data/test_delete_k.json | 1 + crates/vim/test_data/test_delete_l.json | 1 + ..._delete_surrounding_character_objects.json | 2 +- crates/vim/test_data/test_delete_w.json | 1 + .../test_visual_sentence_object.json | 2 +- 34 files changed, 678 insertions(+), 688 deletions(-) create mode 100644 crates/vim/test_data/test_change_0.json create mode 100644 crates/vim/test_data/test_change_b.json create mode 100644 crates/vim/test_data/test_change_backspace.json create mode 100644 crates/vim/test_data/test_change_e.json create mode 100644 crates/vim/test_data/test_change_end_of_document.json create mode 100644 crates/vim/test_data/test_change_end_of_line.json create mode 100644 crates/vim/test_data/test_change_gg.json create mode 100644 crates/vim/test_data/test_change_h.json create mode 100644 crates/vim/test_data/test_change_j.json create mode 100644 crates/vim/test_data/test_change_k.json create mode 100644 crates/vim/test_data/test_change_l.json create mode 100644 crates/vim/test_data/test_change_w.json create mode 100644 crates/vim/test_data/test_delete_0.json create mode 100644 crates/vim/test_data/test_delete_b.json create mode 100644 crates/vim/test_data/test_delete_e.json create mode 100644 crates/vim/test_data/test_delete_end_of_document.json create mode 100644 crates/vim/test_data/test_delete_end_of_line.json create mode 100644 crates/vim/test_data/test_delete_gg.json create mode 100644 crates/vim/test_data/test_delete_h.json create mode 100644 crates/vim/test_data/test_delete_j.json create mode 100644 crates/vim/test_data/test_delete_k.json create mode 100644 crates/vim/test_data/test_delete_l.json create mode 100644 crates/vim/test_data/test_delete_w.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 860b61a4ef..d9613bc865 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -15,7 +15,7 @@ use crate::{ Vim, }; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Motion { Left, Backspace, @@ -139,14 +139,22 @@ impl Motion { pub fn inclusive(self) -> bool { use Motion::*; - if self.linewise() { - return true; - } - match self { - EndOfLine | NextWordEnd { .. } | Matching => true, - Left | Right | StartOfLine | NextWordStart { .. } | PreviousWordStart { .. } => false, - _ => panic!("Exclusivity not defined for {self:?}"), + Down + | Up + | StartOfDocument + | EndOfDocument + | CurrentLine + | EndOfLine + | NextWordEnd { .. } + | Matching => true, + Left + | Backspace + | Right + | StartOfLine + | NextWordStart { .. } + | PreviousWordStart { .. } + | FirstNonWhitespace => false, } } @@ -194,27 +202,29 @@ impl Motion { times: usize, expand_to_surrounding_newline: bool, ) { - let (head, goal) = self.move_point(map, selection.head(), selection.goal, times); - selection.set_head(head, goal); + let (new_head, goal) = self.move_point(map, selection.head(), selection.goal, times); + selection.set_head(new_head, goal); if self.linewise() { - selection.start = map.prev_line_boundary(selection.start.to_point(map)).1; + if selection.start != selection.end { + 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; - } 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); + 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; + } 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)); + (_, selection.end) = map.next_line_boundary(selection.end.to_point(map)); + } } else { // 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 @@ -222,6 +232,7 @@ impl Motion { // 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 && selection.end.row() > 0 diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 894b77e6e8..4b786c4a5f 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -372,7 +372,7 @@ mod test { Mode::{self, *}, Namespace, Operator, }, - test::{NeovimBackedTestContext, VimTestContext}, + test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext}, }; #[gpui::test] @@ -741,11 +741,14 @@ mod test { brown ˇfox jumps ˇover"}) .await; - cx.assert(indoc! {" + cx.assert_exempted( + indoc! {" The quick ˇ - brown fox"}) - .await; + brown fox"}, + ExemptionFeatures::DeletionOnEmptyLine, + ) + .await; } #[gpui::test] diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index ee83c3490d..4e2d4c80e4 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -85,372 +85,271 @@ fn expand_changed_word_selection( mod test { use indoc::indoc; - use crate::{ - state::Mode, - test::{NeovimBackedTestContext, VimTestContext}, - }; + use crate::test::{ExemptionFeatures, NeovimBackedTestContext}; #[gpui::test] async fn test_change_h(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "h"]).mode_after(Mode::Insert); - cx.assert("Teˇst", "Tˇst"); - cx.assert("Tˇest", "ˇest"); - cx.assert("ˇTest", "ˇTest"); - cx.assert( - indoc! {" - Test - ˇtest"}, - indoc! {" - Test - ˇtest"}, - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "l"]).mode_after(Mode::Insert); - cx.assert("Teˇst", "Teˇt"); - cx.assert("Tesˇt", "Tesˇ"); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "w"]).mode_after(Mode::Insert); - cx.assert("Teˇst", "Teˇ"); - cx.assert("Tˇest test", "Tˇ test"); - cx.assert("Testˇ test", "Testˇtest"); - cx.assert( - indoc! {" + 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"}, - indoc! {" - Test teˇ - test"}, - ); - cx.assert( - indoc! {" + test"}) + .await; + cx.assert(indoc! {" Test tesˇt - test"}, - indoc! {" - Test tesˇ - test"}, - ); - cx.assert( - indoc! {" + test"}) + .await; + cx.assert(indoc! {" Test test ˇ - test"}, - indoc! {" - Test test - ˇ - test"}, - ); + test"}) + .await; let mut cx = cx.binding(["c", "shift-w"]); - cx.assert("Test teˇst-test test", "Test teˇ test"); + cx.assert("Test teˇst-test test").await; } #[gpui::test] async fn test_change_e(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "e"]).mode_after(Mode::Insert); - cx.assert("Teˇst Test", "Teˇ Test"); - cx.assert("Tˇest test", "Tˇ test"); - cx.assert( - indoc! {" + 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"}, - indoc! {" - Test teˇ - test"}, - ); - cx.assert( - indoc! {" + test"}) + .await; + cx.assert(indoc! {" Test tesˇt - test"}, - "Test tesˇ", - ); - cx.assert( - indoc! {" + test"}) + .await; + cx.assert(indoc! {" Test test ˇ - test"}, - indoc! {" - Test test - ˇ"}, - ); + test"}) + .await; let mut cx = cx.binding(["c", "shift-e"]); - cx.assert("Test teˇst-test test", "Test teˇ test"); + cx.assert("Test teˇst-test test").await; } #[gpui::test] async fn test_change_b(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "b"]).mode_after(Mode::Insert); - cx.assert("Teˇst Test", "ˇst Test"); - cx.assert("Test ˇtest", "ˇtest"); - cx.assert("Test1 test2 ˇtest3", "Test1 ˇtest3"); - cx.assert( - indoc! {" + 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"}, - indoc! {" - Test ˇ - test"}, - ); + ˇtest"}) + .await; println!("Marker"); - cx.assert( - indoc! {" + cx.assert(indoc! {" Test test ˇ - test"}, - indoc! {" - Test ˇ - - test"}, - ); + test"}) + .await; let mut cx = cx.binding(["c", "shift-b"]); - cx.assert("Test test-test ˇtest", "Test ˇtest"); + cx.assert("Test test-test ˇtest").await; } #[gpui::test] async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "$"]).mode_after(Mode::Insert); - cx.assert( - indoc! {" - The qˇuick - brown fox"}, - indoc! {" - The qˇ - brown fox"}, - ); - cx.assert( - indoc! {" - The quick - ˇ - brown fox"}, - indoc! {" - The quick - ˇ - brown fox"}, - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "0"]).mode_after(Mode::Insert); - cx.assert( - indoc! {" - The qˇuick - brown fox"}, - indoc! {" - ˇuick - brown fox"}, - ); - cx.assert( - indoc! {" - The quick - ˇ - brown fox"}, - indoc! {" - The quick - ˇ - brown fox"}, - ); + let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "0"]); + cx.assert(indoc! {" + The qˇuick + brown fox"}) + .await; + cx.assert(indoc! {" + The quick + ˇ + brown fox"}) + .await; } #[gpui::test] async fn test_change_k(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "k"]).mode_after(Mode::Insert); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "k"]); + cx.assert(indoc! {" + The quick + brown ˇfox + jumps over"}) + .await; + cx.assert(indoc! {" + The quick + brown fox + jumps ˇover"}) + .await; + cx.assert_exempted( indoc! {" - The quick - brown ˇfox - jumps over"}, + The qˇuick + brown fox + jumps over"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; + cx.assert_exempted( indoc! {" - ˇ - jumps over"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps ˇover"}, - indoc! {" - The quick - ˇ"}, - ); - cx.assert( - indoc! {" - The qˇuick - brown fox - jumps over"}, - indoc! {" - ˇ - brown fox - jumps over"}, - ); - cx.assert( - indoc! {" - ˇ - brown fox - jumps over"}, - indoc! {" - ˇ - brown fox - jumps over"}, - ); + ˇ + brown fox + jumps over"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; } #[gpui::test] async fn test_change_j(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "j"]).mode_after(Mode::Insert); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "j"]); + cx.assert(indoc! {" + The quick + brown ˇfox + jumps over"}) + .await; + cx.assert_exempted( indoc! {" - The quick - brown ˇfox - jumps over"}, + The quick + brown fox + jumps ˇover"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; + cx.assert(indoc! {" + The qˇuick + brown fox + jumps over"}) + .await; + cx.assert_exempted( indoc! {" - The quick - ˇ"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps ˇover"}, - indoc! {" - The quick - brown fox - ˇ"}, - ); - cx.assert( - indoc! {" - The qˇuick - brown fox - jumps over"}, - indoc! {" - ˇ - jumps over"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - ˇ"}, - indoc! {" - The quick - brown fox - ˇ"}, - ); + The quick + brown fox + ˇ"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; } #[gpui::test] async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "shift-g"]).mode_after(Mode::Insert); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx) + .await + .binding(["c", "shift-g"]); + cx.assert(indoc! {" + The quick + brownˇ fox + jumps over + the lazy"}) + .await; + cx.assert(indoc! {" + The quick + brownˇ fox + jumps over + the lazy"}) + .await; + cx.assert_exempted( indoc! {" - The quick - brownˇ fox - jumps over - the lazy"}, + The quick + brown fox + jumps over + the lˇazy"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; + cx.assert_exempted( indoc! {" - The quick - ˇ"}, - ); - cx.assert( - indoc! {" - The quick - brownˇ fox - jumps over - the lazy"}, - indoc! {" - The quick - ˇ"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps over - the lˇazy"}, - indoc! {" - The quick - brown fox - jumps over - ˇ"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps over - ˇ"}, - indoc! {" - The quick - brown fox - jumps over - ˇ"}, - ); + The quick + brown fox + jumps over + ˇ"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; } #[gpui::test] async fn test_change_gg(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "g", "g"]).mode_after(Mode::Insert); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx) + .await + .binding(["c", "g", "g"]); + cx.assert(indoc! {" + The quick + brownˇ fox + jumps over + the lazy"}) + .await; + cx.assert(indoc! {" + The quick + brown fox + jumps over + the lˇazy"}) + .await; + cx.assert_exempted( indoc! {" - The quick - brownˇ fox - jumps over - the lazy"}, + The qˇuick + brown fox + jumps over + the lazy"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; + cx.assert_exempted( indoc! {" - ˇ - jumps over - the lazy"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps over - the lˇazy"}, - "ˇ", - ); - cx.assert( - indoc! {" - The qˇuick - brown fox - jumps over - the lazy"}, - indoc! {" - ˇ - brown fox - jumps over - the lazy"}, - ); - cx.assert( - indoc! {" - ˇ - brown fox - jumps over - the lazy"}, - indoc! {" - ˇ - brown fox - jumps over - the lazy"}, - ); + ˇ + brown fox + jumps over + the lazy"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; } #[gpui::test] @@ -493,14 +392,15 @@ mod test { async fn test_repeated_cb(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; - // Changing back any number of times from the start of the file doesn't - // switch to insert mode in vim. This is weird and painful to implement - cx.add_initial_state_exemption(indoc! {" + cx.add_initial_state_exemptions( + indoc! {" ˇThe quick brown - + fox jumps-over the lazy dog - "}); + "}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ); for count in 1..=5 { cx.assert_binding_matches_all( diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index a2c540a59c..b0ff7b5f0e 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -96,354 +96,256 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Mutab mod test { use indoc::indoc; - use crate::{state::Mode, test::VimTestContext}; + use crate::{ + state::Mode, + test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext}, + }; #[gpui::test] async fn test_delete_h(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "h"]); - cx.assert("Teˇst", "Tˇst"); - cx.assert("Tˇest", "ˇest"); - cx.assert("ˇTest", "ˇTest"); - cx.assert( - indoc! {" - Test - ˇtest"}, - indoc! {" - Test - ˇtest"}, - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "l"]); - cx.assert("ˇTest", "ˇest"); - cx.assert("Teˇst", "Teˇt"); - cx.assert("Tesˇt", "Teˇs"); - cx.assert( - indoc! {" + 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"}, - indoc! {" - Teˇs - test"}, - ); + test"}) + .await; } #[gpui::test] async fn test_delete_w(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "w"]); - cx.assert("Teˇst", "Tˇe"); - cx.assert("Tˇest test", "Tˇtest"); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "w"]); + cx.assert("Teˇst").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 teˇst - test"}, - indoc! {" - Test tˇe - test"}, - ); - cx.assert( - indoc! {" - Test tesˇt - test"}, - indoc! {" - Test teˇs - test"}, - ); - cx.assert( - indoc! {" - Test test - ˇ - test"}, - indoc! {" - Test test - ˇ - test"}, - ); + Test test + ˇ + test"}, + ExemptionFeatures::DeletionOnEmptyLine, + ) + .await; let mut cx = cx.binding(["d", "shift-w"]); - cx.assert("Test teˇst-test test", "Test teˇtest"); + cx.assert("Test teˇst-test test").await; } #[gpui::test] async fn test_delete_e(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "e"]); - cx.assert("Teˇst Test", "Teˇ Test"); - cx.assert("Tˇest test", "Tˇ test"); - cx.assert( + 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 teˇst - test"}, - indoc! {" - Test tˇe - test"}, - ); - cx.assert( - indoc! {" - Test tesˇt - test"}, - "Test teˇs", - ); - cx.assert( - indoc! {" - Test test - ˇ - test"}, - indoc! {" - Test test - ˇ"}, - ); + Test test + ˇ + test"}, + ExemptionFeatures::DeletionOnEmptyLine, + ) + .await; let mut cx = cx.binding(["d", "shift-e"]); - cx.assert("Test teˇst-test test", "Test teˇ test"); + cx.assert("Test teˇst-test test").await; } #[gpui::test] async fn test_delete_b(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "b"]); - cx.assert("Teˇst Test", "ˇst Test"); - cx.assert("Test ˇtest", "ˇtest"); - cx.assert("Test1 test2 ˇtest3", "Test1 ˇtest3"); - cx.assert( - indoc! {" - Test test - ˇtest"}, - // Trailing whitespace after cursor - indoc! {" - Testˇ - test"}, - ); - cx.assert( - indoc! {" - Test test - ˇ - test"}, - // Trailing whitespace after cursor - indoc! {" - Testˇ - - test"}, - ); + 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", "Test ˇtest"); + cx.assert("Test test-test ˇtest").await; } #[gpui::test] async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "$"]); - cx.assert( - indoc! {" - The qˇuick - brown fox"}, - indoc! {" - The ˇq - brown fox"}, - ); - cx.assert( - indoc! {" - The quick - ˇ - brown fox"}, - indoc! {" - The quick - ˇ - brown fox"}, - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "0"]); - cx.assert( - indoc! {" - The qˇuick - brown fox"}, - indoc! {" - ˇuick - brown fox"}, - ); - cx.assert( - indoc! {" - The quick - ˇ - brown fox"}, - indoc! {" - The quick - ˇ - brown fox"}, - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "k"]); - cx.assert( - indoc! {" - The quick - brown ˇfox - jumps over"}, - "jumps ˇover", - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps ˇover"}, - "The quˇick", - ); - cx.assert( - indoc! {" - The qˇuick - brown fox - jumps over"}, - indoc! {" - brownˇ fox - jumps over"}, - ); - cx.assert( - indoc! {" - ˇbrown fox - jumps over"}, - "ˇjumps over", - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "j"]); - cx.assert( - indoc! {" - The quick - brown ˇfox - jumps over"}, - "The quˇick", - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps ˇover"}, - indoc! {" - The quick - brown ˇfox"}, - ); - cx.assert( - indoc! {" - The qˇuick - brown fox - jumps over"}, - "jumpsˇ over", - ); - cx.assert( - indoc! {" - The quick - brown fox - ˇ"}, - indoc! {" - The quick - ˇbrown fox"}, - ); + 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 cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "shift-g"]); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx) + .await + .binding(["d", "shift-g"]); + cx.assert(indoc! {" + The quick + brownˇ fox + jumps over + the lazy"}) + .await; + cx.assert(indoc! {" + The quick + brownˇ fox + jumps over + the lazy"}) + .await; + cx.assert_exempted( indoc! {" - The quick - brownˇ fox - jumps over - the lazy"}, - "The qˇuick", - ); - cx.assert( + The quick + brown fox + jumps over + the lˇazy"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; + cx.assert_exempted( indoc! {" - The quick - brownˇ fox - jumps over - the lazy"}, - "The qˇuick", - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps over - the lˇazy"}, - indoc! {" - The quick - brown fox - jumpsˇ over"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps over - ˇ"}, - indoc! {" - The quick - brown fox - ˇjumps over"}, - ); + The quick + brown fox + jumps over + ˇ"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; } #[gpui::test] async fn test_delete_gg(cx: &mut gpui::TestAppContext) { - let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "g", "g"]); - cx.assert( + let mut cx = NeovimBackedTestContext::new(cx) + .await + .binding(["d", "g", "g"]); + cx.assert(indoc! {" + The quick + brownˇ fox + jumps over + the lazy"}) + .await; + cx.assert(indoc! {" + The quick + brown fox + jumps over + the lˇazy"}) + .await; + cx.assert_exempted( indoc! {" - The quick - brownˇ fox - jumps over - the lazy"}, + The qˇuick + brown fox + jumps over + the lazy"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; + cx.assert_exempted( indoc! {" - jumpsˇ over - the lazy"}, - ); - cx.assert( - indoc! {" - The quick - brown fox - jumps over - the lˇazy"}, - "ˇ", - ); - cx.assert( - indoc! {" - The qˇuick - brown fox - jumps over - the lazy"}, - indoc! {" - brownˇ fox - jumps over - the lazy"}, - ); - cx.assert( - indoc! {" - ˇ - brown fox - jumps over - the lazy"}, - indoc! {" - ˇbrown fox - jumps over - the lazy"}, - ); + ˇ + brown fox + jumps over + the lazy"}, + ExemptionFeatures::OperatorAbortsOnFailedMotion, + ) + .await; } #[gpui::test] diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index b39dc6790b..81aee220a9 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -431,7 +431,7 @@ fn surrounding_markers( mod test { use indoc::indoc; - use crate::test::NeovimBackedTestContext; + use crate::test::{ExemptionFeatures, NeovimBackedTestContext}; const WORD_LOCATIONS: &'static str = indoc! {" The quick ˇbrowˇnˇ @@ -482,25 +482,46 @@ mod test { cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS) .await; - // Visual text objects are slightly broken when used with non empty selections - // cx.assert_binding_matches_all(["v", "h", "i", "w"], WORD_LOCATIONS) - // .await; - // cx.assert_binding_matches_all(["v", "l", "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; - // Visual text objects are slightly broken when used with non empty selections - // cx.assert_binding_matches_all(["v", "i", "h", "shift-w"], WORD_LOCATIONS) - // .await; - // cx.assert_binding_matches_all(["v", "i", "l", "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; - // Visual around words is somewhat broken right now when it comes to newlines - // cx.assert_binding_matches_all(["v", "a", "w"], WORD_LOCATIONS) - // .await; - // cx.assert_binding_matches_all(["v", "a", "shift-w"], WORD_LOCATIONS) - // .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] = &[ @@ -511,17 +532,15 @@ mod test { the lazy doˇgˇ.ˇ ˇThe quick ˇ brown fox jumps over "}, - // Position of the cursor after deletion between lines isn't quite right. - // Deletion in a sentence at the start of a line with whitespace is incorrect. - // indoc! {" - // The quick brown fox jumps. - // Over the lazy dog - // ˇ - // ˇ - // ˇ fox-jumpˇs over - // the lazy dog.ˇ - // ˇ - // "}, + indoc! {" + The quick brown fox jumps. + Over the lazy dog + ˇ + ˇ + ˇ fox-jumpˇs over + the lazy dog.ˇ + ˇ + "}, r#"ˇThe ˇquick brownˇ.)ˇ]ˇ'ˇ" Brown ˇfox jumpsˇ.ˇ "#, ]; @@ -530,15 +549,28 @@ mod test { 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"]); - // Resulting position is slightly incorrect for unintuitive reasons. - cx.add_initial_state_exemption("The quick brown?ˇ Fox Jumps! Over the lazy."); - // Changing around the sentence at the end of the line doesn't remove whitespace.' - cx.add_initial_state_exemption("The quick brown.)]\'\" Brown fox jumps.ˇ "); + 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; @@ -550,15 +582,29 @@ mod test { 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"]); - // Resulting position is slightly incorrect for unintuitive reasons. - cx.add_initial_state_exemption("The quick brown?ˇ Fox Jumps! Over the lazy."); - // Changing around the sentence at the end of the line doesn't remove whitespace.' - cx.add_initial_state_exemption("The quick brown.)]\'\" Brown fox jumps.ˇ "); + 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; @@ -571,14 +617,18 @@ mod test { .await .binding(["v", "i", "s"]); for sentence_example in SENTENCE_EXAMPLES { - cx.assert_all(sentence_example).await; + cx.assert_all_exempted(sentence_example, ExemptionFeatures::SentenceOnEmptyLines) + .await; } - // Visual around sentences is somewhat broken right now when it comes to newlines - // let mut cx = cx.binding(["d", "a", "s"]); - // for sentence_example in SENTENCE_EXAMPLES { - // cx.assert_all(sentence_example).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 @@ -588,14 +638,13 @@ mod test { the ˇlazy dˇ'ˇoˇ`ˇg"}; const SURROUNDING_OBJECTS: &[(char, char)] = &[ - // ('\'', '\''), // Quote, - // ('`', '`'), // Back Quote - // ('"', '"'), // Double Quote - // ('"', '"'), // Double Quote - ('(', ')'), // Parentheses - ('[', ']'), // SquareBrackets - ('{', '}'), // CurlyBrackets - ('<', '>'), // AngleBrackets + ('\'', '\''), // Quote + ('`', '`'), // Back Quote + ('"', '"'), // Double Quote + ('(', ')'), // Parentheses + ('[', ']'), // SquareBrackets + ('{', '}'), // CurlyBrackets + ('<', '>'), // AngleBrackets ]; #[gpui::test] @@ -603,16 +652,23 @@ mod test { let mut cx = NeovimBackedTestContext::new(cx).await; for (start, end) in SURROUNDING_OBJECTS { + if ((start == &'\'' || start == &'`' || start == &'"') + && !ExemptionFeatures::QuotesSeekForward.supported()) + || (start == &'<' && !ExemptionFeatures::AngleBracketsFreezeNeovim.supported()) + { + continue; + } + 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", &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", &start.to_string()], &marked_string) + .await; cx.assert_binding_matches_all(["c", "a", &end.to_string()], &marked_string) .await; } @@ -623,16 +679,22 @@ mod test { let mut cx = NeovimBackedTestContext::new(cx).await; for (start, end) in SURROUNDING_OBJECTS { + if ((start == &'\'' || start == &'`' || start == &'"') + && !ExemptionFeatures::QuotesSeekForward.supported()) + || (start == &'<' && !ExemptionFeatures::AngleBracketsFreezeNeovim.supported()) + { + continue; + } 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", &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", &start.to_string()], &marked_string) + .await; cx.assert_binding_matches_all(["d", "a", &end.to_string()], &marked_string) .await; } diff --git a/crates/vim/src/test/neovim_backed_binding_test_context.rs b/crates/vim/src/test/neovim_backed_binding_test_context.rs index a768aff59d..18de029fdc 100644 --- a/crates/vim/src/test/neovim_backed_binding_test_context.rs +++ b/crates/vim/src/test/neovim_backed_binding_test_context.rs @@ -4,7 +4,7 @@ use gpui::ContextHandle; use crate::state::Mode; -use super::NeovimBackedTestContext; +use super::{ExemptionFeatures, NeovimBackedTestContext, SUPPORTED_FEATURES}; pub struct NeovimBackedBindingTestContext<'a, const COUNT: usize> { cx: NeovimBackedTestContext<'a>, @@ -42,6 +42,20 @@ impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> { .await } + pub async fn assert_exempted( + &mut self, + marked_positions: &str, + feature: ExemptionFeatures, + ) -> Option<(ContextHandle, ContextHandle)> { + if SUPPORTED_FEATURES.contains(&feature) { + self.cx + .assert_binding_matches(self.keystrokes_under_test, marked_positions) + .await + } else { + None + } + } + pub fn assert_manual( &mut self, initial_state: &str, @@ -63,6 +77,18 @@ impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> { .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> { diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index e66099963b..c499aafa08 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -8,6 +8,45 @@ 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 + // Deletions on empty lines miss some newlines + DeletionOnEmptyLine, + // When a motion fails, it should should not apply linewise operations + OperatorAbortsOnFailedMotion, + + // 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, + // Quote style surrounding text objects don't seek forward properly + QuotesSeekForward, + // Neovim freezes up for some reason with angle brackets + AngleBracketsFreezeNeovim, + // 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 @@ -27,10 +66,22 @@ impl<'a> NeovimBackedTestContext<'a> { } } - pub fn add_initial_state_exemption(&mut self, initial_state: &str) { - let initial_state = initial_state.to_string(); - // None represents all keybindings being exempted for that initial state - self.exemptions.insert(initial_state, None); + 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 keybindings being exempted for that initial state + self.exemptions.insert(marked_text, None); + } + } } pub async fn simulate_shared_keystroke(&mut self, keystroke_text: &str) -> ContextHandle { @@ -120,6 +171,18 @@ impl<'a> NeovimBackedTestContext<'a> { } } + pub async fn assert_binding_matches_all_exempted( + &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( self, keystrokes: [&'static str; COUNT], diff --git a/crates/vim/test_data/test_change_0.json b/crates/vim/test_data/test_change_0.json new file mode 100644 index 0000000000..fdc7632f01 --- /dev/null +++ b/crates/vim/test_data/test_change_0.json @@ -0,0 +1 @@ +[{"Text":"uick\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\n\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_b.json b/crates/vim/test_data/test_change_b.json new file mode 100644 index 0000000000..b1dadfdd64 --- /dev/null +++ b/crates/vim/test_data/test_change_b.json @@ -0,0 +1 @@ +[{"Text":"st Test"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"test"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"Test1 test3"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Test \ntest"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Test \n\ntest"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Test test"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_backspace.json b/crates/vim/test_data/test_change_backspace.json new file mode 100644 index 0000000000..f2e2f66b35 --- /dev/null +++ b/crates/vim/test_data/test_change_backspace.json @@ -0,0 +1 @@ +[{"Text":"Tst"},{"Mode":"Insert"},{"Selection":{"start":[0,1],"end":[0,1]}},{"Mode":"Insert"},{"Text":"est"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"Test"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"Testtest"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_e.json b/crates/vim/test_data/test_change_e.json new file mode 100644 index 0000000000..bd91ec3d63 --- /dev/null +++ b/crates/vim/test_data/test_change_e.json @@ -0,0 +1 @@ +[{"Text":"Te Test"},{"Mode":"Insert"},{"Selection":{"start":[0,2],"end":[0,2]}},{"Mode":"Insert"},{"Text":"T test"},{"Mode":"Insert"},{"Selection":{"start":[0,1],"end":[0,1]}},{"Mode":"Insert"},{"Text":"Test te\ntest"},{"Mode":"Insert"},{"Selection":{"start":[0,7],"end":[0,7]}},{"Mode":"Insert"},{"Text":"Test tes"},{"Mode":"Insert"},{"Selection":{"start":[0,8],"end":[0,8]}},{"Mode":"Insert"},{"Text":"Test test\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"Test te test"},{"Mode":"Insert"},{"Selection":{"start":[0,7],"end":[0,7]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_end_of_document.json b/crates/vim/test_data/test_change_end_of_document.json new file mode 100644 index 0000000000..dcd810910c --- /dev/null +++ b/crates/vim/test_data/test_change_end_of_document.json @@ -0,0 +1 @@ +[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_end_of_line.json b/crates/vim/test_data/test_change_end_of_line.json new file mode 100644 index 0000000000..6d6f45a3a2 --- /dev/null +++ b/crates/vim/test_data/test_change_end_of_line.json @@ -0,0 +1 @@ +[{"Text":"The q\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"The quick\n\nbrown fox"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_gg.json b/crates/vim/test_data/test_change_gg.json new file mode 100644 index 0000000000..dceb783e9c --- /dev/null +++ b/crates/vim/test_data/test_change_gg.json @@ -0,0 +1 @@ +[{"Text":"\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":""},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_h.json b/crates/vim/test_data/test_change_h.json new file mode 100644 index 0000000000..75da235bd2 --- /dev/null +++ b/crates/vim/test_data/test_change_h.json @@ -0,0 +1 @@ +[{"Text":"Tst"},{"Mode":"Insert"},{"Selection":{"start":[0,1],"end":[0,1]}},{"Mode":"Insert"},{"Text":"est"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"Test"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"Test\ntest"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_j.json b/crates/vim/test_data/test_change_j.json new file mode 100644 index 0000000000..3057656a99 --- /dev/null +++ b/crates/vim/test_data/test_change_j.json @@ -0,0 +1 @@ +[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_k.json b/crates/vim/test_data/test_change_k.json new file mode 100644 index 0000000000..7352c6052b --- /dev/null +++ b/crates/vim/test_data/test_change_k.json @@ -0,0 +1 @@ +[{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_l.json b/crates/vim/test_data/test_change_l.json new file mode 100644 index 0000000000..a280506d56 --- /dev/null +++ b/crates/vim/test_data/test_change_l.json @@ -0,0 +1 @@ +[{"Text":"Tet"},{"Mode":"Insert"},{"Selection":{"start":[0,2],"end":[0,2]}},{"Mode":"Insert"},{"Text":"Tes"},{"Mode":"Insert"},{"Selection":{"start":[0,3],"end":[0,3]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_surrounding_character_objects.json b/crates/vim/test_data/test_change_surrounding_character_objects.json index 8a66f5b144..452b0fde10 100644 --- a/crates/vim/test_data/test_change_surrounding_character_objects.json +++ b/crates/vim/test_data/test_change_surrounding_character_objects.json @@ -1 +1 @@ -[{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th)e ()qui()wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th)e ()qui()wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th)e ()qui()wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th)e ()qui()wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov()o(g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov()o(g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov()o(g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov()o(g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov()o(g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov()o(g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[2,11],"end":[2,11]}},{"Mode":"Normal"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[2,12],"end":[2,12]}},{"Mode":"Normal"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[2,13],"end":[2,13]}},{"Mode":"Normal"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"Th)e qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th)e qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th)e qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th)e ()quiwn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th)e ()quiwn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th)e ()quiwn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th)e ()quiwn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ovo(g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ovo(g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ovo(g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ovo(g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ovo(g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ovo(g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[2,11],"end":[2,11]}},{"Mode":"Normal"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[2,12],"end":[2,12]}},{"Mode":"Normal"},{"Text":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"},{"Mode":"Normal"},{"Selection":{"start":[2,13],"end":[2,13]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th]e []qui[]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th]e []qui[]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th]e []qui[]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th]e []qui[]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[]o[g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[]o[g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[]o[g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[]o[g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[]o[g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[]o[g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[2,11],"end":[2,11]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[2,12],"end":[2,12]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[2,13],"end":[2,13]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"Th]e qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th]e qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th]e qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th]e []quiwn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th]e []quiwn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th]e []quiwn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th]e []quiwn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ovo[g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ovo[g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ovo[g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ovo[g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ovo[g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ovo[g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[2,11],"end":[2,11]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[2,12],"end":[2,12]}},{"Mode":"Normal"},{"Text":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"},{"Mode":"Normal"},{"Selection":{"start":[2,13],"end":[2,13]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,6],"end":[0,6]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,11],"end":[0,11]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{}o{g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{}o{g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{}o{g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{}o{g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{}o{g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{}o{g"},{"Mode":"Insert"},{"Selection":{"start":[1,14],"end":[1,14]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[2,11],"end":[2,11]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[2,12],"end":[2,12]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[2,13],"end":[2,13]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"Th}e qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th}e qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th}e qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Insert"},{"Text":"Th}e {}quiwn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th}e {}quiwn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th}e {}quiwn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th}e {}quiwn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ovo{g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ovo{g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ovo{g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ovo{g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ovo{g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ovo{g"},{"Mode":"Insert"},{"Selection":{"start":[1,13],"end":[1,13]}},{"Mode":"Insert"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[2,11],"end":[2,11]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[2,12],"end":[2,12]}},{"Mode":"Normal"},{"Text":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"},{"Mode":"Normal"},{"Selection":{"start":[2,13],"end":[2,13]}},{"Mode":"Normal"},{"Text":"Th>e <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe quiwn<\n>fox jumps ovoe quiwn<\n>fox jumps ovoe quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>qui<>wn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ov<>oe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe quiwn<\n>fox jumps ovoe quiwn<\n>fox jumps ovoe quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovoe <>quiwn<\n>fox jumps ovo