From 673041d1f54577bc68e9229bbc9d6310096e175b Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 11 Oct 2022 15:17:29 -0700 Subject: [PATCH] working quote and bracket text objects --- assets/keymaps/vim.json | 13 +- crates/vim/src/normal/change.rs | 22 +- crates/vim/src/normal/delete.rs | 2 +- crates/vim/src/object.rs | 208 ++++++++++++++++-- crates/vim/src/visual.rs | 27 +-- ..._change_surrounding_character_objects.json | 1 + ..._delete_surrounding_character_objects.json | 1 + 7 files changed, 237 insertions(+), 37 deletions(-) create mode 100644 crates/vim/test_data/test_change_surrounding_character_objects.json create mode 100644 crates/vim/test_data/test_delete_surrounding_character_objects.json diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 1c2f8ac24d..94729af21f 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -198,7 +198,18 @@ "ignorePunctuation": true } ], - "s": "vim::Sentence" + "s": "vim::Sentence", + "'": "vim::Quotes", + "`": "vim::BackQuotes", + "\"": "vim::DoubleQuotes", + "(": "vim::Parentheses", + ")": "vim::Parentheses", + "[": "vim::SquareBrackets", + "]": "vim::SquareBrackets", + "{": "vim::CurlyBrackets", + "}": "vim::CurlyBrackets", + "<": "vim::AngleBrackets", + ">": "vim::AngleBrackets" } }, { diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index cc62ce8db0..a343d43ba8 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -1,5 +1,5 @@ use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim}; -use editor::{char_kind, display_map::DisplaySnapshot, movement, Autoscroll, DisplayPoint}; +use editor::{char_kind, display_map::DisplaySnapshot, movement, Autoscroll, Bias, DisplayPoint}; use gpui::MutableAppContext; use language::Selection; @@ -25,20 +25,28 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut Mutab } pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut MutableAppContext) { + 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| { - // 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| { - object.expand_selection(map, selection, around); + objects_found |= object.expand_selection(map, selection, around); }); }); - copy_selections_content(editor, false, cx); - editor.insert("", cx); + if objects_found { + copy_selections_content(editor, false, cx); + editor.insert("", cx); + } }); }); - vim.switch_mode(Mode::Insert, false, cx); + + if objects_found { + vim.switch_mode(Mode::Insert, false, cx); + } else { + vim.switch_mode(Mode::Normal, false, cx); + } } // From the docs https://vimhelp.org/change.txt.html#cw diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 1465e3e377..a2c540a59c 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -51,7 +51,7 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Mutab .chars_at(selection.start) .take_while(|(_, p)| p < &selection.end) .all(|(char, _)| char == '\n') - || offset_range.is_empty(); + && !offset_range.is_empty(); let end_at_newline = map .chars_at(selection.end) .next() diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 5adc8397fa..ca7aff06dc 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -12,6 +12,13 @@ use crate::{motion, normal::normal_object, state::Mode, visual::visual_object, V pub enum Object { Word { ignore_punctuation: bool }, Sentence, + Quotes, + BackQuotes, + DoubleQuotes, + Parentheses, + SquareBrackets, + CurlyBrackets, + AngleBrackets, } #[derive(Clone, Deserialize, PartialEq)] @@ -21,7 +28,19 @@ struct Word { ignore_punctuation: bool, } -actions!(vim, [Sentence]); +actions!( + vim, + [ + Sentence, + Quotes, + BackQuotes, + DoubleQuotes, + Parentheses, + SquareBrackets, + CurlyBrackets, + AngleBrackets + ] +); impl_actions!(vim, [Word]); pub fn init(cx: &mut MutableAppContext) { @@ -31,6 +50,15 @@ pub fn init(cx: &mut MutableAppContext) { }, ); cx.add_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx)); + cx.add_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx)); + cx.add_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx)); + cx.add_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| object(Object::DoubleQuotes, cx)); + cx.add_action(|_: &mut Workspace, _: &Parentheses, cx: _| object(Object::Parentheses, cx)); + cx.add_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| { + object(Object::SquareBrackets, cx) + }); + cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx)); + cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx)); } fn object(object: Object, cx: &mut MutableAppContext) { @@ -49,7 +77,7 @@ impl Object { map: &DisplaySnapshot, relative_to: DisplayPoint, around: bool, - ) -> Range { + ) -> Option> { match self { Object::Word { ignore_punctuation } => { if around { @@ -59,6 +87,13 @@ impl Object { } } Object::Sentence => sentence(map, relative_to, around), + Object::Quotes => surrounding_markers(map, relative_to, around, false, '\'', '\''), + Object::BackQuotes => surrounding_markers(map, relative_to, around, false, '`', '`'), + Object::DoubleQuotes => surrounding_markers(map, relative_to, around, false, '"', '"'), + Object::Parentheses => surrounding_markers(map, relative_to, around, true, '(', ')'), + Object::SquareBrackets => surrounding_markers(map, relative_to, around, true, '[', ']'), + Object::CurlyBrackets => surrounding_markers(map, relative_to, around, true, '{', '}'), + Object::AngleBrackets => surrounding_markers(map, relative_to, around, true, '<', '>'), } } @@ -67,10 +102,14 @@ impl Object { map: &DisplaySnapshot, selection: &mut Selection, around: bool, - ) { - let range = self.range(map, selection.head(), around); - selection.start = range.start; - selection.end = range.end; + ) -> bool { + if let Some(range) = self.range(map, selection.head(), around) { + selection.start = range.start; + selection.end = range.end; + true + } else { + false + } } } @@ -81,7 +120,7 @@ fn in_word( map: &DisplaySnapshot, relative_to: DisplayPoint, ignore_punctuation: bool, -) -> Range { +) -> Option> { // Use motion::right so that we consider the character under the cursor when looking for the start let start = movement::find_preceding_boundary_in_line( map, @@ -96,7 +135,7 @@ fn in_word( != char_kind(right).coerce_punctuation(ignore_punctuation) }); - start..end + Some(start..end) } /// Return a range that surrounds the word and following whitespace @@ -115,7 +154,7 @@ fn around_word( map: &DisplaySnapshot, relative_to: DisplayPoint, ignore_punctuation: bool, -) -> Range { +) -> Option> { let in_word = map .chars_at(relative_to) .next() @@ -133,15 +172,16 @@ fn around_containing_word( map: &DisplaySnapshot, relative_to: DisplayPoint, ignore_punctuation: bool, -) -> Range { - expand_to_include_whitespace(map, in_word(map, relative_to, ignore_punctuation), true) +) -> Option> { + 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, -) -> Range { +) -> Option> { // Get the start of the word let start = movement::find_preceding_boundary_in_line( map, @@ -166,10 +206,14 @@ fn around_next_word( found }); - start..end + Some(start..end) } -fn sentence(map: &DisplaySnapshot, relative_to: DisplayPoint, around: bool) -> Range { +fn sentence( + map: &DisplaySnapshot, + relative_to: DisplayPoint, + around: bool, +) -> Option> { let mut start = None; let mut previous_end = relative_to; @@ -220,7 +264,7 @@ fn sentence(map: &DisplaySnapshot, relative_to: DisplayPoint, around: bool) -> R range = expand_to_include_whitespace(map, range, false); } - range + Some(range) } fn is_possible_sentence_start(character: char) -> bool { @@ -306,6 +350,83 @@ fn expand_to_include_whitespace( range } +fn surrounding_markers( + map: &DisplaySnapshot, + relative_to: DisplayPoint, + around: bool, + search_across_lines: bool, + start_marker: char, + end_marker: char, +) -> Option> { + let mut matched_ends = 0; + let mut start = None; + for (char, mut point) in map.reverse_chars_at(relative_to) { + if char == start_marker { + if matched_ends > 0 { + matched_ends -= 1; + } else { + if around { + start = Some(point) + } else { + *point.column_mut() += char.len_utf8() as u32; + start = Some(point); + } + break; + } + } else if char == end_marker { + matched_ends += 1; + } else if char == '\n' && !search_across_lines { + break; + } + } + + let mut matched_starts = 0; + let mut end = None; + for (char, mut point) in map.chars_at(relative_to) { + if char == end_marker { + if start.is_none() { + break; + } + + if matched_starts > 0 { + matched_starts -= 1; + } else { + if around { + *point.column_mut() += char.len_utf8() as u32; + end = Some(point); + } else { + end = Some(point); + } + + break; + } + } + + if char == start_marker { + if start.is_none() { + if around { + start = Some(point); + } else { + *point.column_mut() += char.len_utf8() as u32; + start = Some(point); + } + } else { + matched_starts += 1; + } + } + + if char == '\n' && !search_across_lines { + break; + } + } + + if let (Some(start), Some(end)) = (start, end) { + Some(start..end) + } else { + None + } +} + #[cfg(test)] mod test { use indoc::indoc; @@ -459,4 +580,61 @@ mod test { // cx.assert_all(sentence_example).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 + // ('"', '"'), // 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_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; + } + } } diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 4fa195a2fa..1351439d7b 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -60,22 +60,23 @@ pub fn visual_object(object: Object, cx: &mut MutableAppContext) { editor.change_selections(Some(Autoscroll::Fit), cx, |s| { s.move_with(|map, selection| { let head = selection.head(); - let mut range = object.range(map, head, around); - if !range.is_empty() { - if let Some((_, end)) = map.reverse_chars_at(range.end).next() { - range.end = end; - } + if let Some(mut range) = object.range(map, head, around) { + if !range.is_empty() { + if let Some((_, end)) = map.reverse_chars_at(range.end).next() { + range.end = end; + } - if selection.is_empty() { - selection.start = range.start; - selection.end = range.end; - } else if selection.reversed { - selection.start = range.start; - } else { - selection.end = range.end; + if selection.is_empty() { + selection.start = range.start; + selection.end = range.end; + } else if selection.reversed { + selection.start = range.start; + } else { + selection.end = range.end; + } } } - }) + }); }); }); } diff --git a/crates/vim/test_data/test_change_surrounding_character_objects.json b/crates/vim/test_data/test_change_surrounding_character_objects.json new file mode 100644 index 0000000000..8a66f5b144 --- /dev/null +++ b/crates/vim/test_data/test_change_surrounding_character_objects.json @@ -0,0 +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