Detailed changes
@@ -1906,27 +1906,6 @@ dependencies = [
[[package]]
name = "command_palette"
version = "0.1.0"
-dependencies = [
- "collections",
- "ctor",
- "editor",
- "env_logger",
- "fuzzy",
- "gpui",
- "language",
- "picker",
- "project",
- "serde_json",
- "settings",
- "theme",
- "util",
- "workspace",
- "zed-actions",
-]
-
-[[package]]
-name = "command_palette2"
-version = "0.1.0"
dependencies = [
"anyhow",
"collections",
@@ -1934,7 +1913,7 @@ dependencies = [
"editor2",
"env_logger",
"fuzzy2",
- "go_to_line2",
+ "go_to_line",
"gpui2",
"language2",
"menu2",
@@ -2623,33 +2602,6 @@ dependencies = [
[[package]]
name = "diagnostics"
version = "0.1.0"
-dependencies = [
- "anyhow",
- "client",
- "collections",
- "editor",
- "futures 0.3.28",
- "gpui",
- "language",
- "log",
- "lsp",
- "postage",
- "project",
- "schemars",
- "serde",
- "serde_derive",
- "serde_json",
- "settings",
- "smallvec",
- "theme",
- "unindent",
- "util",
- "workspace",
-]
-
-[[package]]
-name = "diagnostics2"
-version = "0.1.0"
dependencies = [
"anyhow",
"client2",
@@ -3166,29 +3118,6 @@ dependencies = [
[[package]]
name = "file_finder"
version = "0.1.0"
-dependencies = [
- "collections",
- "ctor",
- "editor",
- "env_logger",
- "fuzzy",
- "gpui",
- "language",
- "menu",
- "picker",
- "postage",
- "project",
- "serde_json",
- "settings",
- "text",
- "theme",
- "util",
- "workspace",
-]
-
-[[package]]
-name = "file_finder2"
-version = "0.1.0"
dependencies = [
"collections",
"ctor",
@@ -3745,21 +3674,6 @@ dependencies = [
[[package]]
name = "go_to_line"
version = "0.1.0"
-dependencies = [
- "editor",
- "gpui",
- "menu",
- "postage",
- "settings",
- "text",
- "theme",
- "util",
- "workspace",
-]
-
-[[package]]
-name = "go_to_line2"
-version = "0.1.0"
dependencies = [
"editor2",
"gpui2",
@@ -10540,40 +10454,6 @@ dependencies = [
"collections",
"command_palette",
"diagnostics",
- "editor",
- "futures 0.3.28",
- "gpui",
- "indoc",
- "itertools 0.10.5",
- "language",
- "language_selector",
- "log",
- "lsp",
- "nvim-rs",
- "parking_lot 0.11.2",
- "project",
- "search",
- "serde",
- "serde_derive",
- "serde_json",
- "settings",
- "theme",
- "tokio",
- "util",
- "workspace",
- "zed-actions",
-]
-
-[[package]]
-name = "vim2"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-compat",
- "async-trait",
- "collections",
- "command_palette2",
- "diagnostics2",
"editor2",
"futures 0.3.28",
"gpui2",
@@ -11007,7 +10887,7 @@ dependencies = [
"theme_selector",
"ui2",
"util",
- "vim2",
+ "vim",
"workspace2",
]
@@ -11429,21 +11309,21 @@ dependencies = [
"client2",
"collab_ui",
"collections",
- "command_palette2",
+ "command_palette",
"copilot2",
"copilot_button2",
"ctor",
"db2",
- "diagnostics2",
+ "diagnostics",
"editor2",
"env_logger",
"feature_flags2",
"feedback2",
- "file_finder2",
+ "file_finder",
"fs2",
"fsevent",
"futures 0.3.28",
- "go_to_line2",
+ "go_to_line",
"gpui2",
"ignore",
"image",
@@ -11530,7 +11410,7 @@ dependencies = [
"urlencoding",
"util",
"uuid 1.4.1",
- "vim2",
+ "vim",
"welcome",
"workspace2",
"zed_actions2",
@@ -24,7 +24,6 @@ members = [
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
- "crates/command_palette2",
"crates/component_test",
"crates/context_menu",
"crates/copilot",
@@ -35,7 +34,6 @@ members = [
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/diagnostics",
- "crates/diagnostics2",
"crates/drag_and_drop",
"crates/editor",
"crates/feature_flags",
@@ -49,7 +47,6 @@ members = [
"crates/fuzzy2",
"crates/git",
"crates/go_to_line",
- "crates/go_to_line2",
"crates/gpui",
"crates/gpui_macros",
"crates/gpui2",
@@ -10,23 +10,28 @@ doctest = false
[dependencies]
collections = { path = "../collections" }
-editor = { path = "../editor" }
-fuzzy = { path = "../fuzzy" }
-gpui = { path = "../gpui" }
-picker = { path = "../picker" }
-project = { path = "../project" }
-settings = { path = "../settings" }
+editor = { package = "editor2", path = "../editor2" }
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+picker = { package = "picker2", path = "../picker2" }
+project = { package = "project2", path = "../project2" }
+settings = { package = "settings2", path = "../settings2" }
+ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
-theme = { path = "../theme" }
-workspace = { path = "../workspace" }
-zed-actions = { path = "../zed-actions" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package="workspace2", path = "../workspace2" }
+zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
+anyhow.workspace = true
+serde.workspace = true
[dev-dependencies]
-gpui = { path = "../gpui", features = ["test-support"] }
-editor = { path = "../editor", features = ["test-support"] }
-language = { path = "../language", features = ["test-support"] }
-project = { path = "../project", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+language = { package="language2", path = "../language2", features = ["test-support"] }
+project = { package="project2", path = "../project2", features = ["test-support"] }
+menu = { package = "menu2", path = "../menu2" }
+go_to_line = { path = "../go_to_line" }
serde_json.workspace = true
-workspace = { path = "../workspace", features = ["test-support"] }
+workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
@@ -1,26 +1,92 @@
+use std::{
+ cmp::{self, Reverse},
+ sync::Arc,
+};
+
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
- AppContext, Element, MouseState, ViewContext,
+ actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
+ ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
};
-use picker::{Picker, PickerDelegate, PickerEvent};
-use std::cmp::{self, Reverse};
+use picker::{Picker, PickerDelegate};
+
+use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
};
-use workspace::Workspace;
+use workspace::{ModalView, Workspace};
use zed_actions::OpenZedURL;
+actions!(command_palette, [Toggle]);
+
pub fn init(cx: &mut AppContext) {
- cx.add_action(toggle_command_palette);
- CommandPalette::init(cx);
+ cx.set_global(HitCounts::default());
+ cx.set_global(CommandPaletteFilter::default());
+ cx.observe_new_views(CommandPalette::register).detach();
}
-actions!(command_palette, [Toggle]);
+impl ModalView for CommandPalette {}
+
+pub struct CommandPalette {
+ picker: View<Picker<CommandPaletteDelegate>>,
+}
+
+impl CommandPalette {
+ fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|workspace, _: &Toggle, cx| {
+ let Some(previous_focus_handle) = cx.focused() else {
+ return;
+ };
+ workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx));
+ });
+ }
+
+ fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
+ let filter = cx.try_global::<CommandPaletteFilter>();
+
+ let commands = cx
+ .available_actions()
+ .into_iter()
+ .filter_map(|action| {
+ let name = action.name();
+ let namespace = name.split("::").next().unwrap_or("malformed action name");
+ if filter.is_some_and(|f| {
+ f.hidden_namespaces.contains(namespace)
+ || f.hidden_action_types.contains(&action.type_id())
+ }) {
+ return None;
+ }
+
+ Some(Command {
+ name: humanize_action_name(&name),
+ action,
+ })
+ })
+ .collect();
+
+ let delegate =
+ CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
+
+ let picker = cx.new_view(|cx| Picker::new(delegate, cx));
+ Self { picker }
+ }
+}
+
+impl EventEmitter<DismissEvent> for CommandPalette {}
-pub type CommandPalette = Picker<CommandPaletteDelegate>;
+impl FocusableView for CommandPalette {
+ fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+ self.picker.focus_handle(cx)
+ }
+}
+
+impl Render for CommandPalette {
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+ v_stack().w(rems(34.)).child(self.picker.clone())
+ }
+}
pub type CommandPaletteInterceptor =
Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>;
@@ -32,24 +98,26 @@ pub struct CommandInterceptResult {
}
pub struct CommandPaletteDelegate {
- actions: Vec<Command>,
+ command_palette: WeakView<CommandPalette>,
+ all_commands: Vec<Command>,
+ commands: Vec<Command>,
matches: Vec<StringMatch>,
selected_ix: usize,
- focused_view_id: usize,
+ previous_focus_handle: FocusHandle,
}
-pub enum Event {
- Dismissed,
- Confirmed {
- window: AnyWindowHandle,
- focused_view_id: usize,
- action: Box<dyn Action>,
- },
-}
struct Command {
name: String,
action: Box<dyn Action>,
- keystrokes: Vec<Keystroke>,
+}
+
+impl Clone for Command {
+ fn clone(&self) -> Self {
+ Self {
+ name: self.name.clone(),
+ action: self.action.boxed_clone(),
+ }
+ }
}
/// Hit count for each command in the palette.
@@ -58,26 +126,27 @@ struct Command {
#[derive(Default)]
struct HitCounts(HashMap<String, usize>);
-fn toggle_command_palette(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- let focused_view_id = cx.focused_view_id().unwrap_or_else(|| cx.view_id());
- workspace.toggle_modal(cx, |_, cx| {
- cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id), cx))
- });
-}
-
impl CommandPaletteDelegate {
- pub fn new(focused_view_id: usize) -> Self {
+ fn new(
+ command_palette: WeakView<CommandPalette>,
+ commands: Vec<Command>,
+ previous_focus_handle: FocusHandle,
+ ) -> Self {
Self {
- actions: Default::default(),
+ command_palette,
+ all_commands: commands.clone(),
matches: vec![],
+ commands,
selected_ix: 0,
- focused_view_id,
+ previous_focus_handle,
}
}
}
impl PickerDelegate for CommandPaletteDelegate {
- fn placeholder_text(&self) -> std::sync::Arc<str> {
+ type ListItem = ListItem;
+
+ fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into()
}
@@ -98,49 +167,20 @@ impl PickerDelegate for CommandPaletteDelegate {
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
- let view_id = self.focused_view_id;
- let window = cx.window();
+ let mut commands = self.all_commands.clone();
+
cx.spawn(move |picker, mut cx| async move {
- let mut actions = window
- .available_actions(view_id, &cx)
- .into_iter()
- .flatten()
- .filter_map(|(name, action, bindings)| {
- let filtered = cx.read(|cx| {
- if cx.has_global::<CommandPaletteFilter>() {
- let filter = cx.global::<CommandPaletteFilter>();
- filter.hidden_namespaces.contains(action.namespace())
- } else {
- false
- }
- });
-
- if filtered {
- None
- } else {
- Some(Command {
- name: humanize_action_name(name),
- action,
- keystrokes: bindings
- .iter()
- .map(|binding| binding.keystrokes())
- .last()
- .map_or(Vec::new(), |keystrokes| keystrokes.to_vec()),
- })
- }
- })
- .collect::<Vec<_>>();
- let mut actions = cx.read(move |cx| {
- let hit_counts = cx.optional_global::<HitCounts>();
- actions.sort_by_key(|action| {
+ cx.read_global::<HitCounts, _>(|hit_counts, _| {
+ commands.sort_by_key(|action| {
(
- Reverse(hit_counts.and_then(|map| map.0.get(&action.name)).cloned()),
+ Reverse(hit_counts.0.get(&action.name).cloned()),
action.name.clone(),
)
});
- actions
- });
- let candidates = actions
+ })
+ .ok();
+
+ let candidates = commands
.iter()
.enumerate()
.map(|(ix, command)| StringMatchCandidate {
@@ -167,17 +207,17 @@ impl PickerDelegate for CommandPaletteDelegate {
true,
10000,
&Default::default(),
- cx.background(),
+ cx.background_executor().clone(),
)
.await
};
- let mut intercept_result = cx.read(|cx| {
- if cx.has_global::<CommandPaletteInterceptor>() {
- cx.global::<CommandPaletteInterceptor>()(&query, cx)
- } else {
- None
- }
- });
+
+ let mut intercept_result = cx
+ .try_read_global(|interceptor: &CommandPaletteInterceptor, cx| {
+ (interceptor)(&query, cx)
+ })
+ .flatten();
+
if *RELEASE_CHANNEL == ReleaseChannel::Dev {
if parse_zed_link(&query).is_some() {
intercept_result = Some(CommandInterceptResult {
@@ -187,6 +227,7 @@ impl PickerDelegate for CommandPaletteDelegate {
})
}
}
+
if let Some(CommandInterceptResult {
action,
string,
@@ -195,29 +236,29 @@ impl PickerDelegate for CommandPaletteDelegate {
{
if let Some(idx) = matches
.iter()
- .position(|m| actions[m.candidate_id].action.id() == action.id())
+ .position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
{
matches.remove(idx);
}
- actions.push(Command {
+ commands.push(Command {
name: string.clone(),
action,
- keystrokes: vec![],
});
matches.insert(
0,
StringMatch {
- candidate_id: actions.len() - 1,
+ candidate_id: commands.len() - 1,
string,
positions,
score: 0.0,
},
)
}
+
picker
.update(&mut cx, |picker, _| {
- let delegate = picker.delegate_mut();
- delegate.actions = actions;
+ let delegate = &mut picker.delegate;
+ delegate.commands = commands;
delegate.matches = matches;
if delegate.matches.is_empty() {
delegate.selected_ix = 0;
@@ -230,83 +271,60 @@ impl PickerDelegate for CommandPaletteDelegate {
})
}
- fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+ self.command_palette
+ .update(cx, |_, cx| cx.emit(DismissEvent))
+ .log_err();
+ }
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
- if !self.matches.is_empty() {
- let window = cx.window();
- let focused_view_id = self.focused_view_id;
- let action_ix = self.matches[self.selected_ix].candidate_id;
- let command = self.actions.remove(action_ix);
- cx.update_default_global(|hit_counts: &mut HitCounts, _| {
- *hit_counts.0.entry(command.name).or_default() += 1;
- });
- let action = command.action;
-
- cx.app_context()
- .spawn(move |mut cx| async move {
- window
- .dispatch_action(focused_view_id, action.as_ref(), &mut cx)
- .ok_or_else(|| anyhow!("window was closed"))
- })
- .detach_and_log_err(cx);
+ if self.matches.is_empty() {
+ self.dismissed(cx);
+ return;
}
- cx.emit(PickerEvent::Dismiss);
+ let action_ix = self.matches[self.selected_ix].candidate_id;
+ let command = self.commands.swap_remove(action_ix);
+ self.matches.clear();
+ self.commands.clear();
+ cx.update_global(|hit_counts: &mut HitCounts, _| {
+ *hit_counts.0.entry(command.name).or_default() += 1;
+ });
+ let action = command.action;
+ cx.focus(&self.previous_focus_handle);
+ cx.window_context()
+ .spawn(move |mut cx| async move { cx.update(|_, cx| cx.dispatch_action(action)) })
+ .detach_and_log_err(cx);
+ self.dismissed(cx);
}
fn render_match(
&self,
ix: usize,
- mouse_state: &mut MouseState,
selected: bool,
- cx: &gpui::AppContext,
- ) -> AnyElement<Picker<Self>> {
- let mat = &self.matches[ix];
- let command = &self.actions[mat.candidate_id];
- let theme = theme::current(cx);
- let style = theme.picker.item.in_state(selected).style_for(mouse_state);
- let key_style = &theme.command_palette.key.in_state(selected);
- let keystroke_spacing = theme.command_palette.keystroke_spacing;
-
- Flex::row()
- .with_child(
- Label::new(mat.string.clone(), style.label.clone())
- .with_highlights(mat.positions.clone()),
- )
- .with_children(command.keystrokes.iter().map(|keystroke| {
- Flex::row()
- .with_children(
- [
- (keystroke.ctrl, "^"),
- (keystroke.alt, "⌥"),
- (keystroke.cmd, "⌘"),
- (keystroke.shift, "⇧"),
- ]
- .into_iter()
- .filter_map(|(modifier, label)| {
- if modifier {
- Some(
- Label::new(label, key_style.label.clone())
- .contained()
- .with_style(key_style.container),
- )
- } else {
- None
- }
- }),
- )
- .with_child(
- Label::new(keystroke.key.clone(), key_style.label.clone())
- .contained()
- .with_style(key_style.container),
- )
- .contained()
- .with_margin_left(keystroke_spacing)
- .flex_float()
- }))
- .contained()
- .with_style(style.container)
- .into_any()
+ cx: &mut ViewContext<Picker<Self>>,
+ ) -> Option<Self::ListItem> {
+ let r#match = self.matches.get(ix)?;
+ let command = self.commands.get(r#match.candidate_id)?;
+ Some(
+ ListItem::new(ix)
+ .inset(true)
+ .spacing(ListItemSpacing::Sparse)
+ .selected(selected)
+ .child(
+ h_stack()
+ .w_full()
+ .justify_between()
+ .child(HighlightedLabel::new(
+ command.name.clone(),
+ r#match.positions.clone(),
+ ))
+ .children(KeyBinding::for_action_in(
+ &*command.action,
+ &self.previous_focus_handle,
+ cx,
+ )),
+ ),
+ )
}
}
@@ -338,8 +356,7 @@ impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Command")
.field("name", &self.name)
- .field("keystrokes", &self.keystrokes)
- .finish()
+ .finish_non_exhaustive()
}
}
@@ -349,7 +366,9 @@ mod tests {
use super::*;
use editor::Editor;
- use gpui::{executor::Deterministic, TestAppContext};
+ use go_to_line::GoToLine;
+ use gpui::TestAppContext;
+ use language::Point;
use project::Project;
use workspace::{AppState, Workspace};
@@ -370,101 +389,121 @@ mod tests {
}
#[gpui::test]
- async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
+ async fn test_command_palette(cx: &mut TestAppContext) {
let app_state = init_test(cx);
-
let project = Project::test(app_state.fs.clone(), [], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
- let editor = window.add_view(cx, |cx| {
- let mut editor = Editor::single_line(None, cx);
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+ let editor = cx.new_view(|cx| {
+ let mut editor = Editor::single_line(cx);
editor.set_text("abc", cx);
editor
});
workspace.update(cx, |workspace, cx| {
- cx.focus(&editor);
- workspace.add_item(Box::new(editor.clone()), cx)
+ workspace.add_item(Box::new(editor.clone()), cx);
+ editor.update(cx, |editor, cx| editor.focus(cx))
});
- workspace.update(cx, |workspace, cx| {
- toggle_command_palette(workspace, &Toggle, cx);
- });
+ cx.simulate_keystrokes("cmd-shift-p");
- let palette = workspace.read_with(cx, |workspace, _| {
- workspace.modal::<CommandPalette>().unwrap()
+ let palette = workspace.update(cx, |workspace, cx| {
+ workspace
+ .active_modal::<CommandPalette>(cx)
+ .unwrap()
+ .read(cx)
+ .picker
+ .clone()
});
- palette
- .update(cx, |palette, cx| {
- // Fill up palette's command list by running an empty query;
- // we only need it to subsequently assert that the palette is initially
- // sorted by command's name.
- palette.delegate_mut().update_matches("".to_string(), cx)
- })
- .await;
-
palette.update(cx, |palette, _| {
+ assert!(palette.delegate.commands.len() > 5);
let is_sorted =
|actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
- assert!(is_sorted(&palette.delegate().actions));
+ assert!(is_sorted(&palette.delegate.commands));
});
- palette
- .update(cx, |palette, cx| {
- palette
- .delegate_mut()
- .update_matches("bcksp".to_string(), cx)
- })
- .await;
+ cx.simulate_input("bcksp");
- palette.update(cx, |palette, cx| {
- assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
- palette.confirm(&Default::default(), cx);
+ palette.update(cx, |palette, _| {
+ assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
});
- deterministic.run_until_parked();
- editor.read_with(cx, |editor, cx| {
- assert_eq!(editor.text(cx), "ab");
+
+ cx.simulate_keystrokes("enter");
+
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.active_modal::<CommandPalette>(cx).is_none());
+ assert_eq!(editor.read(cx).text(cx), "ab")
});
// Add namespace filter, and redeploy the palette
cx.update(|cx| {
- cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
+ cx.set_global(CommandPaletteFilter::default());
+ cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
filter.hidden_namespaces.insert("editor");
})
});
- workspace.update(cx, |workspace, cx| {
- toggle_command_palette(workspace, &Toggle, cx);
+ cx.simulate_keystrokes("cmd-shift-p");
+ cx.simulate_input("bcksp");
+
+ let palette = workspace.update(cx, |workspace, cx| {
+ workspace
+ .active_modal::<CommandPalette>(cx)
+ .unwrap()
+ .read(cx)
+ .picker
+ .clone()
+ });
+ palette.update(cx, |palette, _| {
+ assert!(palette.delegate.matches.is_empty())
+ });
+ }
+
+ #[gpui::test]
+ async fn test_go_to_line(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+ cx.simulate_keystrokes("cmd-n");
+
+ let editor = workspace.update(cx, |workspace, cx| {
+ workspace.active_item_as::<Editor>(cx).unwrap()
});
+ editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx));
+
+ cx.simulate_keystrokes("cmd-shift-p");
+ cx.simulate_input("go to line: Toggle");
+ cx.simulate_keystrokes("enter");
- // Assert editor command not present
- let palette = workspace.read_with(cx, |workspace, _| {
- workspace.modal::<CommandPalette>().unwrap()
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.active_modal::<GoToLine>(cx).is_some())
});
- palette
- .update(cx, |palette, cx| {
- palette
- .delegate_mut()
- .update_matches("bcksp".to_string(), cx)
- })
- .await;
+ cx.simulate_keystrokes("3 enter");
- palette.update(cx, |palette, _| {
- assert!(palette.delegate().matches.is_empty())
+ editor.update(cx, |editor, cx| {
+ assert!(editor.focus_handle(cx).is_focused(cx));
+ assert_eq!(
+ editor.selections.last::<Point>(cx).range().start,
+ Point::new(2, 0)
+ );
});
}
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
cx.update(|cx| {
let app_state = AppState::test(cx);
- theme::init((), cx);
+ theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx);
editor::init(cx);
+ menu::init();
+ go_to_line::init(cx);
workspace::init(app_state.clone(), cx);
init(cx);
Project::init_settings(cx);
+ settings::load_default_keymap(cx);
app_state
})
}
@@ -1,36 +0,0 @@
-[package]
-name = "command_palette2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/command_palette.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-picker = { package = "picker2", path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-theme = { package = "theme2", path = "../theme2" }
-workspace = { package="workspace2", path = "../workspace2" }
-zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
-anyhow.workspace = true
-serde.workspace = true
-[dev-dependencies]
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-language = { package="language2", path = "../language2", features = ["test-support"] }
-project = { package="project2", path = "../project2", features = ["test-support"] }
-menu = { package = "menu2", path = "../menu2" }
-go_to_line = { package = "go_to_line2", path = "../go_to_line2" }
-serde_json.workspace = true
-workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
-ctor.workspace = true
-env_logger.workspace = true
@@ -1,510 +0,0 @@
-use std::{
- cmp::{self, Reverse},
- sync::Arc,
-};
-
-use collections::{CommandPaletteFilter, HashMap};
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{
- actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
- ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
-};
-use picker::{Picker, PickerDelegate};
-
-use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
-use util::{
- channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
- ResultExt,
-};
-use workspace::{ModalView, Workspace};
-use zed_actions::OpenZedURL;
-
-actions!(command_palette, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
- cx.set_global(HitCounts::default());
- cx.set_global(CommandPaletteFilter::default());
- cx.observe_new_views(CommandPalette::register).detach();
-}
-
-impl ModalView for CommandPalette {}
-
-pub struct CommandPalette {
- picker: View<Picker<CommandPaletteDelegate>>,
-}
-
-impl CommandPalette {
- fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|workspace, _: &Toggle, cx| {
- let Some(previous_focus_handle) = cx.focused() else {
- return;
- };
- workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx));
- });
- }
-
- fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
- let filter = cx.try_global::<CommandPaletteFilter>();
-
- let commands = cx
- .available_actions()
- .into_iter()
- .filter_map(|action| {
- let name = action.name();
- let namespace = name.split("::").next().unwrap_or("malformed action name");
- if filter.is_some_and(|f| {
- f.hidden_namespaces.contains(namespace)
- || f.hidden_action_types.contains(&action.type_id())
- }) {
- return None;
- }
-
- Some(Command {
- name: humanize_action_name(&name),
- action,
- })
- })
- .collect();
-
- let delegate =
- CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
-
- let picker = cx.new_view(|cx| Picker::new(delegate, cx));
- Self { picker }
- }
-}
-
-impl EventEmitter<DismissEvent> for CommandPalette {}
-
-impl FocusableView for CommandPalette {
- fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
- self.picker.focus_handle(cx)
- }
-}
-
-impl Render for CommandPalette {
- fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
- v_stack().w(rems(34.)).child(self.picker.clone())
- }
-}
-
-pub type CommandPaletteInterceptor =
- Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>;
-
-pub struct CommandInterceptResult {
- pub action: Box<dyn Action>,
- pub string: String,
- pub positions: Vec<usize>,
-}
-
-pub struct CommandPaletteDelegate {
- command_palette: WeakView<CommandPalette>,
- all_commands: Vec<Command>,
- commands: Vec<Command>,
- matches: Vec<StringMatch>,
- selected_ix: usize,
- previous_focus_handle: FocusHandle,
-}
-
-struct Command {
- name: String,
- action: Box<dyn Action>,
-}
-
-impl Clone for Command {
- fn clone(&self) -> Self {
- Self {
- name: self.name.clone(),
- action: self.action.boxed_clone(),
- }
- }
-}
-
-/// Hit count for each command in the palette.
-/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
-/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
-#[derive(Default)]
-struct HitCounts(HashMap<String, usize>);
-
-impl CommandPaletteDelegate {
- fn new(
- command_palette: WeakView<CommandPalette>,
- commands: Vec<Command>,
- previous_focus_handle: FocusHandle,
- ) -> Self {
- Self {
- command_palette,
- all_commands: commands.clone(),
- matches: vec![],
- commands,
- selected_ix: 0,
- previous_focus_handle,
- }
- }
-}
-
-impl PickerDelegate for CommandPaletteDelegate {
- type ListItem = ListItem;
-
- fn placeholder_text(&self) -> Arc<str> {
- "Execute a command...".into()
- }
-
- fn match_count(&self) -> usize {
- self.matches.len()
- }
-
- fn selected_index(&self) -> usize {
- self.selected_ix
- }
-
- fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
- self.selected_ix = ix;
- }
-
- fn update_matches(
- &mut self,
- query: String,
- cx: &mut ViewContext<Picker<Self>>,
- ) -> gpui::Task<()> {
- let mut commands = self.all_commands.clone();
-
- cx.spawn(move |picker, mut cx| async move {
- cx.read_global::<HitCounts, _>(|hit_counts, _| {
- commands.sort_by_key(|action| {
- (
- Reverse(hit_counts.0.get(&action.name).cloned()),
- action.name.clone(),
- )
- });
- })
- .ok();
-
- let candidates = commands
- .iter()
- .enumerate()
- .map(|(ix, command)| StringMatchCandidate {
- id: ix,
- string: command.name.to_string(),
- char_bag: command.name.chars().collect(),
- })
- .collect::<Vec<_>>();
- let mut matches = if query.is_empty() {
- candidates
- .into_iter()
- .enumerate()
- .map(|(index, candidate)| StringMatch {
- candidate_id: index,
- string: candidate.string,
- positions: Vec::new(),
- score: 0.0,
- })
- .collect()
- } else {
- fuzzy::match_strings(
- &candidates,
- &query,
- true,
- 10000,
- &Default::default(),
- cx.background_executor().clone(),
- )
- .await
- };
-
- let mut intercept_result = cx
- .try_read_global(|interceptor: &CommandPaletteInterceptor, cx| {
- (interceptor)(&query, cx)
- })
- .flatten();
-
- if *RELEASE_CHANNEL == ReleaseChannel::Dev {
- if parse_zed_link(&query).is_some() {
- intercept_result = Some(CommandInterceptResult {
- action: OpenZedURL { url: query.clone() }.boxed_clone(),
- string: query.clone(),
- positions: vec![],
- })
- }
- }
-
- if let Some(CommandInterceptResult {
- action,
- string,
- positions,
- }) = intercept_result
- {
- if let Some(idx) = matches
- .iter()
- .position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
- {
- matches.remove(idx);
- }
- commands.push(Command {
- name: string.clone(),
- action,
- });
- matches.insert(
- 0,
- StringMatch {
- candidate_id: commands.len() - 1,
- string,
- positions,
- score: 0.0,
- },
- )
- }
-
- picker
- .update(&mut cx, |picker, _| {
- let delegate = &mut picker.delegate;
- delegate.commands = commands;
- delegate.matches = matches;
- if delegate.matches.is_empty() {
- delegate.selected_ix = 0;
- } else {
- delegate.selected_ix =
- cmp::min(delegate.selected_ix, delegate.matches.len() - 1);
- }
- })
- .log_err();
- })
- }
-
- fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
- self.command_palette
- .update(cx, |_, cx| cx.emit(DismissEvent))
- .log_err();
- }
-
- fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
- if self.matches.is_empty() {
- self.dismissed(cx);
- return;
- }
- let action_ix = self.matches[self.selected_ix].candidate_id;
- let command = self.commands.swap_remove(action_ix);
- self.matches.clear();
- self.commands.clear();
- cx.update_global(|hit_counts: &mut HitCounts, _| {
- *hit_counts.0.entry(command.name).or_default() += 1;
- });
- let action = command.action;
- cx.focus(&self.previous_focus_handle);
- cx.window_context()
- .spawn(move |mut cx| async move { cx.update(|_, cx| cx.dispatch_action(action)) })
- .detach_and_log_err(cx);
- self.dismissed(cx);
- }
-
- fn render_match(
- &self,
- ix: usize,
- selected: bool,
- cx: &mut ViewContext<Picker<Self>>,
- ) -> Option<Self::ListItem> {
- let r#match = self.matches.get(ix)?;
- let command = self.commands.get(r#match.candidate_id)?;
- Some(
- ListItem::new(ix)
- .inset(true)
- .spacing(ListItemSpacing::Sparse)
- .selected(selected)
- .child(
- h_stack()
- .w_full()
- .justify_between()
- .child(HighlightedLabel::new(
- command.name.clone(),
- r#match.positions.clone(),
- ))
- .children(KeyBinding::for_action_in(
- &*command.action,
- &self.previous_focus_handle,
- cx,
- )),
- ),
- )
- }
-}
-
-fn humanize_action_name(name: &str) -> String {
- let capacity = name.len() + name.chars().filter(|c| c.is_uppercase()).count();
- let mut result = String::with_capacity(capacity);
- for char in name.chars() {
- if char == ':' {
- if result.ends_with(':') {
- result.push(' ');
- } else {
- result.push(':');
- }
- } else if char == '_' {
- result.push(' ');
- } else if char.is_uppercase() {
- if !result.ends_with(' ') {
- result.push(' ');
- }
- result.extend(char.to_lowercase());
- } else {
- result.push(char);
- }
- }
- result
-}
-
-impl std::fmt::Debug for Command {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Command")
- .field("name", &self.name)
- .finish_non_exhaustive()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::sync::Arc;
-
- use super::*;
- use editor::Editor;
- use go_to_line::GoToLine;
- use gpui::TestAppContext;
- use language::Point;
- use project::Project;
- use workspace::{AppState, Workspace};
-
- #[test]
- fn test_humanize_action_name() {
- assert_eq!(
- humanize_action_name("editor::GoToDefinition"),
- "editor: go to definition"
- );
- assert_eq!(
- humanize_action_name("editor::Backspace"),
- "editor: backspace"
- );
- assert_eq!(
- humanize_action_name("go_to_line::Deploy"),
- "go to line: deploy"
- );
- }
-
- #[gpui::test]
- async fn test_command_palette(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- let project = Project::test(app_state.fs.clone(), [], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-
- let editor = cx.new_view(|cx| {
- let mut editor = Editor::single_line(cx);
- editor.set_text("abc", cx);
- editor
- });
-
- workspace.update(cx, |workspace, cx| {
- workspace.add_item(Box::new(editor.clone()), cx);
- editor.update(cx, |editor, cx| editor.focus(cx))
- });
-
- cx.simulate_keystrokes("cmd-shift-p");
-
- let palette = workspace.update(cx, |workspace, cx| {
- workspace
- .active_modal::<CommandPalette>(cx)
- .unwrap()
- .read(cx)
- .picker
- .clone()
- });
-
- palette.update(cx, |palette, _| {
- assert!(palette.delegate.commands.len() > 5);
- let is_sorted =
- |actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
- assert!(is_sorted(&palette.delegate.commands));
- });
-
- cx.simulate_input("bcksp");
-
- palette.update(cx, |palette, _| {
- assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
- });
-
- cx.simulate_keystrokes("enter");
-
- workspace.update(cx, |workspace, cx| {
- assert!(workspace.active_modal::<CommandPalette>(cx).is_none());
- assert_eq!(editor.read(cx).text(cx), "ab")
- });
-
- // Add namespace filter, and redeploy the palette
- cx.update(|cx| {
- cx.set_global(CommandPaletteFilter::default());
- cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
- filter.hidden_namespaces.insert("editor");
- })
- });
-
- cx.simulate_keystrokes("cmd-shift-p");
- cx.simulate_input("bcksp");
-
- let palette = workspace.update(cx, |workspace, cx| {
- workspace
- .active_modal::<CommandPalette>(cx)
- .unwrap()
- .read(cx)
- .picker
- .clone()
- });
- palette.update(cx, |palette, _| {
- assert!(palette.delegate.matches.is_empty())
- });
- }
-
- #[gpui::test]
- async fn test_go_to_line(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- let project = Project::test(app_state.fs.clone(), [], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-
- cx.simulate_keystrokes("cmd-n");
-
- let editor = workspace.update(cx, |workspace, cx| {
- workspace.active_item_as::<Editor>(cx).unwrap()
- });
- editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx));
-
- cx.simulate_keystrokes("cmd-shift-p");
- cx.simulate_input("go to line: Toggle");
- cx.simulate_keystrokes("enter");
-
- workspace.update(cx, |workspace, cx| {
- assert!(workspace.active_modal::<GoToLine>(cx).is_some())
- });
-
- cx.simulate_keystrokes("3 enter");
-
- editor.update(cx, |editor, cx| {
- assert!(editor.focus_handle(cx).is_focused(cx));
- assert_eq!(
- editor.selections.last::<Point>(cx).range().start,
- Point::new(2, 0)
- );
- });
- }
-
- fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
- cx.update(|cx| {
- let app_state = AppState::test(cx);
- theme::init(theme::LoadThemes::JustBase, cx);
- language::init(cx);
- editor::init(cx);
- menu::init();
- go_to_line::init(cx);
- workspace::init(app_state.clone(), cx);
- init(cx);
- Project::init_settings(cx);
- settings::load_default_keymap(cx);
- app_state
- })
- }
-}
@@ -10,15 +10,16 @@ doctest = false
[dependencies]
collections = { path = "../collections" }
-editor = { path = "../editor" }
-gpui = { path = "../gpui" }
-language = { path = "../language" }
-lsp = { path = "../lsp" }
-project = { path = "../project" }
-settings = { path = "../settings" }
-theme = { path = "../theme" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+ui = { package = "ui2", path = "../ui2" }
+language = { package = "language2", path = "../language2" }
+lsp = { package = "lsp2", path = "../lsp2" }
+project = { package = "project2", path = "../project2" }
+settings = { package = "settings2", path = "../settings2" }
+theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }
-workspace = { path = "../workspace" }
+workspace = { package = "workspace2", path = "../workspace2" }
log.workspace = true
anyhow.workspace = true
@@ -30,13 +31,13 @@ smallvec.workspace = true
postage.workspace = true
[dev-dependencies]
-client = { path = "../client", features = ["test-support"] }
-editor = { path = "../editor", features = ["test-support"] }
-language = { path = "../language", features = ["test-support"] }
-lsp = { path = "../lsp", features = ["test-support"] }
-gpui = { path = "../gpui", features = ["test-support"] }
-workspace = { path = "../workspace", features = ["test-support"] }
-theme = { path = "../theme", features = ["test-support"] }
+client = { package = "client2", path = "../client2", features = ["test-support"] }
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+language = { package = "language2", path = "../language2", features = ["test-support"] }
+lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
serde_json.workspace = true
unindent.workspace = true
@@ -2,19 +2,21 @@ pub mod items;
mod project_diagnostics_settings;
mod toolbar_controls;
-use anyhow::{Context, Result};
+use anyhow::{Context as _, Result};
use collections::{HashMap, HashSet};
use editor::{
diagnostic_block_renderer,
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
highlight_diagnostic_message,
scroll::autoscroll::Autoscroll,
- Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
+ Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
};
use futures::future::try_join_all;
use gpui::{
- actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
- ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
+ FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
+ SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
+ WeakView, WindowContext,
};
use language::{
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -23,23 +25,22 @@ use language::{
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
use project_diagnostics_settings::ProjectDiagnosticsSettings;
-use serde_json::json;
-use smallvec::SmallVec;
+use settings::Settings;
use std::{
any::{Any, TypeId},
- borrow::Cow,
cmp::Ordering,
mem,
ops::Range,
path::PathBuf,
sync::Arc,
};
-use theme::ThemeSettings;
+use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
+use ui::{h_stack, prelude::*, Icon, IconElement, Label};
use util::TryFutureExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
- ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
+ ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
};
actions!(diagnostics, [Deploy, ToggleWarnings]);
@@ -47,20 +48,18 @@ actions!(diagnostics, [Deploy, ToggleWarnings]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut AppContext) {
- settings::register::<ProjectDiagnosticsSettings>(cx);
- cx.add_action(ProjectDiagnosticsEditor::deploy);
- cx.add_action(ProjectDiagnosticsEditor::toggle_warnings);
- items::init(cx);
+ ProjectDiagnosticsSettings::register(cx);
+ cx.observe_new_views(ProjectDiagnosticsEditor::register)
+ .detach();
}
-type Event = editor::Event;
-
struct ProjectDiagnosticsEditor {
- project: ModelHandle<Project>,
- workspace: WeakViewHandle<Workspace>,
- editor: ViewHandle<Editor>,
+ project: Model<Project>,
+ workspace: WeakView<Workspace>,
+ focus_handle: FocusHandle,
+ editor: View<Editor>,
summary: DiagnosticSummary,
- excerpts: ModelHandle<MultiBuffer>,
+ excerpts: Model<MultiBuffer>,
path_states: Vec<PathState>,
paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
@@ -89,71 +88,38 @@ struct DiagnosticGroupState {
block_count: usize,
}
-impl Entity for ProjectDiagnosticsEditor {
- type Event = Event;
-}
-
-impl View for ProjectDiagnosticsEditor {
- fn ui_name() -> &'static str {
- "ProjectDiagnosticsEditor"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- if self.path_states.is_empty() {
- let theme = &theme::current(cx).project_diagnostics;
- PaneBackdrop::new(
- cx.view_id(),
- Label::new("No problems in workspace", theme.empty_message.clone())
- .aligned()
- .contained()
- .with_style(theme.container)
- .into_any(),
- )
- .into_any()
+impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
+
+impl Render for ProjectDiagnosticsEditor {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
+ let child = if self.path_states.is_empty() {
+ div()
+ .bg(cx.theme().colors().editor_background)
+ .flex()
+ .items_center()
+ .justify_center()
+ .size_full()
+ .child(Label::new("No problems in workspace"))
} else {
- ChildView::new(&self.editor, cx).into_any()
- }
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() && !self.path_states.is_empty() {
- cx.focus(&self.editor);
- }
- }
+ div().size_full().child(self.editor.clone())
+ };
- fn debug_json(&self, cx: &AppContext) -> serde_json::Value {
- let project = self.project.read(cx);
- json!({
- "project": json!({
- "language_servers": project.language_server_statuses().collect::<Vec<_>>(),
- "summary": project.diagnostic_summary(false, cx),
- }),
- "summary": self.summary,
- "paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
- (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
- ).collect::<HashMap<_, _>>(),
- "current_diagnostics": self.current_diagnostics.iter().map(|(server_id, paths)|
- (server_id.0, paths.into_iter().map(|path| path.path.to_string_lossy()).collect::<Vec<_>>())
- ).collect::<HashMap<_, _>>(),
- "paths_states": self.path_states.iter().map(|state|
- json!({
- "path": state.path.path.to_string_lossy(),
- "groups": state.diagnostic_groups.iter().map(|group|
- json!({
- "block_count": group.blocks.len(),
- "excerpt_count": group.excerpts.len(),
- })
- ).collect::<Vec<_>>(),
- })
- ).collect::<Vec<_>>(),
- })
+ div()
+ .track_focus(&self.focus_handle)
+ .size_full()
+ .on_action(cx.listener(Self::toggle_warnings))
+ .child(child)
}
}
impl ProjectDiagnosticsEditor {
+ fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(Self::deploy);
+ }
+
fn new(
- project_handle: ModelHandle<Project>,
- workspace: WeakViewHandle<Workspace>,
+ project_handle: Model<Project>,
+ workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
let project_event_subscription =
@@ -180,19 +146,25 @@ impl ProjectDiagnosticsEditor {
_ => {}
});
- let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
- let editor = cx.add_view(|cx| {
+ let focus_handle = cx.focus_handle();
+
+ let focus_in_subscription =
+ cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx));
+
+ let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
+ let editor = cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx);
editor.set_vertical_scroll_margin(5, cx);
editor
});
- let editor_event_subscription = cx.subscribe(&editor, |this, _, event, cx| {
- cx.emit(event.clone());
- if event == &editor::Event::Focused && this.path_states.is_empty() {
- cx.focus_self()
- }
- });
+ let editor_event_subscription =
+ cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
+ cx.emit(event.clone());
+ if event == &EditorEvent::Focused && this.path_states.is_empty() {
+ cx.focus(&this.focus_handle);
+ }
+ });
let project = project_handle.read(cx);
let summary = project.diagnostic_summary(false, cx);
@@ -201,12 +173,17 @@ impl ProjectDiagnosticsEditor {
summary,
workspace,
excerpts,
+ focus_handle,
editor,
path_states: Default::default(),
paths_to_update: HashMap::default(),
- include_warnings: settings::get::<ProjectDiagnosticsSettings>(cx).include_warnings,
+ include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
current_diagnostics: HashMap::default(),
- _subscriptions: vec![project_event_subscription, editor_event_subscription],
+ _subscriptions: vec![
+ project_event_subscription,
+ editor_event_subscription,
+ focus_in_subscription,
+ ],
};
this.update_excerpts(None, cx);
this
@@ -216,8 +193,8 @@ impl ProjectDiagnosticsEditor {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, cx);
} else {
- let workspace_handle = cx.weak_handle();
- let diagnostics = cx.add_view(|cx| {
+ let workspace_handle = cx.view().downgrade();
+ let diagnostics = cx.new_view(|cx| {
ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
});
workspace.add_item(Box::new(diagnostics), cx);
@@ -231,6 +208,12 @@ impl ProjectDiagnosticsEditor {
cx.notify();
}
+ fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+ if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
+ self.editor.focus_handle(cx).focus(cx)
+ }
+ }
+
fn update_excerpts(
&mut self,
language_server_id: Option<LanguageServerId>,
@@ -304,9 +287,10 @@ impl ProjectDiagnosticsEditor {
let _: Vec<()> = try_join_all(paths_to_recheck.into_iter().map(|path| {
let mut cx = cx.clone();
let project = project.clone();
+ let this = this.clone();
async move {
let buffer = project
- .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
+ .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
.await
.with_context(|| format!("opening buffer for path {path:?}"))?;
this.update(&mut cx, |this, cx| {
@@ -321,7 +305,7 @@ impl ProjectDiagnosticsEditor {
this.update(&mut cx, |this, cx| {
this.summary = this.project.read(cx).diagnostic_summary(false, cx);
- cx.emit(Event::TitleChanged);
+ cx.emit(EditorEvent::TitleChanged);
})?;
anyhow::Ok(())
}
@@ -334,7 +318,7 @@ impl ProjectDiagnosticsEditor {
&mut self,
path: ProjectPath,
language_server_id: Option<LanguageServerId>,
- buffer: ModelHandle<Buffer>,
+ buffer: Model<Buffer>,
cx: &mut ViewContext<Self>,
) {
let was_empty = self.path_states.is_empty();
@@ -618,41 +602,32 @@ impl ProjectDiagnosticsEditor {
});
if self.path_states.is_empty() {
- if self.editor.is_focused(cx) {
- cx.focus_self();
+ if self.editor.focus_handle(cx).is_focused(cx) {
+ cx.focus(&self.focus_handle);
}
- } else if cx.handle().is_focused(cx) {
- cx.focus(&self.editor);
+ } else if self.focus_handle.is_focused(cx) {
+ let focus_handle = self.editor.focus_handle(cx);
+ cx.focus(&focus_handle);
}
cx.notify();
}
}
-impl Item for ProjectDiagnosticsEditor {
- fn tab_content<T: 'static>(
- &self,
- _detail: Option<usize>,
- style: &theme::Tab,
- cx: &AppContext,
- ) -> AnyElement<T> {
- render_summary(
- &self.summary,
- &style.label.text,
- &theme::current(cx).project_diagnostics,
- )
+impl FocusableView for ProjectDiagnosticsEditor {
+ fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+ self.focus_handle.clone()
}
+}
- fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
- self.editor.for_each_project_item(cx, f)
- }
+impl Item for ProjectDiagnosticsEditor {
+ type Event = EditorEvent;
- fn is_singleton(&self, _: &AppContext) -> bool {
- false
+ fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
+ Editor::to_item_events(event, f)
}
- fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
- self.editor
- .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ self.editor.update(cx, |editor, cx| editor.deactivated(cx));
}
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
@@ -660,10 +635,82 @@ impl Item for ProjectDiagnosticsEditor {
.update(cx, |editor, cx| editor.navigate(data, cx))
}
- fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+ fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
Some("Project Diagnostics".into())
}
+ fn tab_content(&self, _detail: Option<usize>, selected: bool, _: &WindowContext) -> AnyElement {
+ if self.summary.error_count == 0 && self.summary.warning_count == 0 {
+ let label = Label::new("No problems");
+ label.into_any_element()
+ } else {
+ h_stack()
+ .gap_1()
+ .when(self.summary.error_count > 0, |then| {
+ then.child(
+ h_stack()
+ .gap_1()
+ .child(IconElement::new(Icon::XCircle).color(Color::Error))
+ .child(Label::new(self.summary.error_count.to_string()).color(
+ if selected {
+ Color::Default
+ } else {
+ Color::Muted
+ },
+ )),
+ )
+ })
+ .when(self.summary.warning_count > 0, |then| {
+ then.child(
+ h_stack()
+ .gap_1()
+ .child(
+ IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
+ )
+ .child(Label::new(self.summary.warning_count.to_string()).color(
+ if selected {
+ Color::Default
+ } else {
+ Color::Muted
+ },
+ )),
+ )
+ })
+ .into_any_element()
+ }
+ }
+
+ fn for_each_project_item(
+ &self,
+ cx: &AppContext,
+ f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
+ ) {
+ self.editor.for_each_project_item(cx, f)
+ }
+
+ fn is_singleton(&self, _: &AppContext) -> bool {
+ false
+ }
+
+ fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
+ self.editor.update(cx, |editor, _| {
+ editor.set_nav_history(Some(nav_history));
+ });
+ }
+
+ fn clone_on_split(
+ &self,
+ _workspace_id: workspace::WorkspaceId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<View<Self>>
+ where
+ Self: Sized,
+ {
+ Some(cx.new_view(|cx| {
+ ProjectDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
+ }))
+ }
+
fn is_dirty(&self, cx: &AppContext) -> bool {
self.excerpts.read(cx).is_dirty(cx)
}
@@ -676,209 +723,133 @@ impl Item for ProjectDiagnosticsEditor {
true
}
- fn save(
- &mut self,
- project: ModelHandle<Project>,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
+ fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
self.editor.save(project, cx)
}
- fn reload(
- &mut self,
- project: ModelHandle<Project>,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- self.editor.reload(project, cx)
- }
-
fn save_as(
&mut self,
- _: ModelHandle<Project>,
+ _: Model<Project>,
_: PathBuf,
_: &mut ViewContext<Self>,
) -> Task<Result<()>> {
unreachable!()
}
- fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
- Editor::to_item_events(event)
- }
-
- fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
- self.editor.update(cx, |editor, _| {
- editor.set_nav_history(Some(nav_history));
- });
- }
-
- fn clone_on_split(
- &self,
- _workspace_id: workspace::WorkspaceId,
- cx: &mut ViewContext<Self>,
- ) -> Option<Self>
- where
- Self: Sized,
- {
- Some(ProjectDiagnosticsEditor::new(
- self.project.clone(),
- self.workspace.clone(),
- cx,
- ))
+ fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+ self.editor.reload(project, cx)
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
- self_handle: &'a ViewHandle<Self>,
+ self_handle: &'a View<Self>,
_: &'a AppContext,
- ) -> Option<&AnyViewHandle> {
+ ) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
- Some(self_handle)
+ Some(self_handle.to_any())
} else if type_id == TypeId::of::<Editor>() {
- Some(&self.editor)
+ Some(self.editor.to_any())
} else {
None
}
}
- fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
- self.editor.update(cx, |editor, cx| editor.deactivated(cx));
- }
-
- fn serialized_item_kind() -> Option<&'static str> {
- Some("diagnostics")
+ fn breadcrumb_location(&self) -> ToolbarItemLocation {
+ ToolbarItemLocation::PrimaryLeft
}
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
self.editor.breadcrumbs(theme, cx)
}
- fn breadcrumb_location(&self) -> ToolbarItemLocation {
- ToolbarItemLocation::PrimaryLeft { flex: None }
+ fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
+ self.editor
+ .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+ }
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ Some("diagnostics")
}
fn deserialize(
- project: ModelHandle<Project>,
- workspace: WeakViewHandle<Workspace>,
+ project: Model<Project>,
+ workspace: WeakView<Workspace>,
_workspace_id: workspace::WorkspaceId,
_item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>,
- ) -> Task<Result<ViewHandle<Self>>> {
- Task::ready(Ok(cx.add_view(|cx| Self::new(project, workspace, cx))))
+ ) -> Task<Result<View<Self>>> {
+ Task::ready(Ok(cx.new_view(|cx| Self::new(project, workspace, cx))))
}
}
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
- let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message);
+ let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
+ let message: SharedString = message.into();
Arc::new(move |cx| {
- let settings = settings::get::<ThemeSettings>(cx);
- let theme = &settings.theme.editor;
- let style = theme.diagnostic_header.clone();
- let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
- let icon_width = cx.em_width * style.icon_width_factor;
- let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
- Svg::new("icons/error.svg").with_color(theme.error_diagnostic.message.text.color)
- } else {
- Svg::new("icons/warning.svg").with_color(theme.warning_diagnostic.message.text.color)
- };
-
- Flex::row()
- .with_child(
- icon.constrained()
- .with_width(icon_width)
- .aligned()
- .contained()
- .with_margin_right(cx.gutter_padding),
+ let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
+ h_stack()
+ .id("diagnostic header")
+ .py_2()
+ .pl_10()
+ .pr_5()
+ .w_full()
+ .justify_between()
+ .gap_2()
+ .child(
+ h_stack()
+ .gap_3()
+ .map(|stack| {
+ stack.child(
+ svg()
+ .size(cx.text_style().font_size)
+ .flex_none()
+ .map(|icon| {
+ if diagnostic.severity == DiagnosticSeverity::ERROR {
+ icon.path(Icon::XCircle.path())
+ .text_color(Color::Error.color(cx))
+ } else {
+ icon.path(Icon::ExclamationTriangle.path())
+ .text_color(Color::Warning.color(cx))
+ }
+ }),
+ )
+ })
+ .child(
+ h_stack()
+ .gap_1()
+ .child(
+ StyledText::new(message.clone()).with_highlights(
+ &cx.text_style(),
+ code_ranges
+ .iter()
+ .map(|range| (range.clone(), highlight_style)),
+ ),
+ )
+ .when_some(diagnostic.code.as_ref(), |stack, code| {
+ stack.child(
+ div()
+ .child(SharedString::from(format!("({code})")))
+ .text_color(cx.theme().colors().text_muted),
+ )
+ }),
+ ),
)
- .with_children(diagnostic.source.as_ref().map(|source| {
- Label::new(
- format!("{source}: "),
- style.source.label.clone().with_font_size(font_size),
- )
- .contained()
- .with_style(style.message.container)
- .aligned()
- }))
- .with_child(
- Label::new(
- message.clone(),
- style.message.label.clone().with_font_size(font_size),
- )
- .with_highlights(highlights.clone())
- .contained()
- .with_style(style.message.container)
- .aligned(),
+ .child(
+ h_stack()
+ .gap_1()
+ .when_some(diagnostic.source.as_ref(), |stack, source| {
+ stack.child(
+ div()
+ .child(SharedString::from(source.clone()))
+ .text_color(cx.theme().colors().text_muted),
+ )
+ }),
)
- .with_children(diagnostic.code.clone().map(|code| {
- Label::new(code, style.code.text.clone().with_font_size(font_size))
- .contained()
- .with_style(style.code.container)
- .aligned()
- }))
- .contained()
- .with_style(style.container)
- .with_padding_left(cx.gutter_padding)
- .with_padding_right(cx.gutter_padding)
- .expanded()
- .into_any_named("diagnostic header")
+ .into_any_element()
})
}
-pub(crate) fn render_summary<T: 'static>(
- summary: &DiagnosticSummary,
- text_style: &TextStyle,
- theme: &theme::ProjectDiagnostics,
-) -> AnyElement<T> {
- if summary.error_count == 0 && summary.warning_count == 0 {
- Label::new("No problems", text_style.clone()).into_any()
- } else {
- let icon_width = theme.tab_icon_width;
- let icon_spacing = theme.tab_icon_spacing;
- let summary_spacing = theme.tab_summary_spacing;
- Flex::row()
- .with_child(
- Svg::new("icons/error.svg")
- .with_color(text_style.color)
- .constrained()
- .with_width(icon_width)
- .aligned()
- .contained()
- .with_margin_right(icon_spacing),
- )
- .with_child(
- Label::new(
- summary.error_count.to_string(),
- LabelStyle {
- text: text_style.clone(),
- highlight_text: None,
- },
- )
- .aligned(),
- )
- .with_child(
- Svg::new("icons/warning.svg")
- .with_color(text_style.color)
- .constrained()
- .with_width(icon_width)
- .aligned()
- .contained()
- .with_margin_left(summary_spacing)
- .with_margin_right(icon_spacing),
- )
- .with_child(
- Label::new(
- summary.warning_count.to_string(),
- LabelStyle {
- text: text_style.clone(),
- highlight_text: None,
- },
- )
- .aligned(),
- )
- .into_any()
- }
-}
-
fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
lhs: &DiagnosticEntry<L>,
rhs: &DiagnosticEntry<R>,
@@ -904,7 +875,7 @@ mod tests {
display_map::{BlockContext, TransformBlock},
DisplayPoint,
};
- use gpui::{TestAppContext, WindowContext};
+ use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
use project::FakeFs;
use serde_json::json;
@@ -915,7 +886,7 @@ mod tests {
async fn test_diagnostics(cx: &mut TestAppContext) {
init_test(cx);
- let fs = FakeFs::new(cx.background());
+ let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/test",
json!({
@@ -945,7 +916,8 @@ mod tests {
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+ let workspace = window.root(cx).unwrap();
// Create some diagnostics
project.update(cx, |project, cx| {
@@ -1032,7 +1004,7 @@ mod tests {
});
// Open the project diagnostics view while there are already diagnostics.
- let view = window.add_view(cx, |cx| {
+ let view = window.build_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
@@ -1320,7 +1292,7 @@ mod tests {
async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
init_test(cx);
- let fs = FakeFs::new(cx.background());
+ let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/test",
json!({
@@ -1339,9 +1311,10 @@ mod tests {
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let workspace = window.root(cx);
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+ let workspace = window.root(cx).unwrap();
- let view = window.add_view(cx, |cx| {
+ let view = window.build_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
@@ -1376,7 +1349,7 @@ mod tests {
});
// Only the first language server's diagnostics are shown.
- cx.foreground().run_until_parked();
+ cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
@@ -1424,7 +1397,7 @@ mod tests {
});
// Both language server's diagnostics are shown.
- cx.foreground().run_until_parked();
+ cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
@@ -1492,7 +1465,7 @@ mod tests {
});
// Only the first language server's diagnostics are updated.
- cx.foreground().run_until_parked();
+ cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
@@ -1550,7 +1523,7 @@ mod tests {
});
// Both language servers' diagnostics are updated.
- cx.foreground().run_until_parked();
+ cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
@@ -1586,8 +1559,9 @@ mod tests {
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
- cx.set_global(SettingsStore::test(cx));
- theme::init((), cx);
+ let settings = SettingsStore::test(cx);
+ cx.set_global(settings);
+ theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx);
client::init_settings(cx);
workspace::init_settings(cx);
@@ -1596,7 +1570,7 @@ mod tests {
});
}
- fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
+ fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
snapshot
@@ -1607,23 +1581,25 @@ mod tests {
TransformBlock::Custom(block) => block
.render(&mut BlockContext {
view_context: cx,
- anchor_x: 0.,
- scroll_x: 0.,
- gutter_padding: 0.,
- gutter_width: 0.,
- line_height: 0.,
- em_width: 0.,
+ anchor_x: px(0.),
+ gutter_padding: px(0.),
+ gutter_width: px(0.),
+ line_height: px(0.),
+ em_width: px(0.),
block_id: ix,
+ editor_style: &editor::EditorStyle::default(),
})
- .name()?
- .to_string(),
+ .inner_id()?
+ .try_into()
+ .ok()?,
+
TransformBlock::ExcerptHeader {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
- "path header block".to_string()
+ "path header block".into()
} else {
- "collapsed context".to_string()
+ "collapsed context".into()
}
}
};
@@ -1,27 +1,105 @@
use collections::HashSet;
-use editor::{Editor, GoToDiagnostic};
+use editor::Editor;
use gpui::{
- elements::*,
- platform::{CursorStyle, MouseButton},
- serde_json, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
+ rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
+ ViewContext, WeakView,
};
use language::Diagnostic;
use lsp::LanguageServerId;
-use workspace::{item::ItemHandle, StatusItemView, Workspace};
+use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};
+use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
-use crate::ProjectDiagnosticsEditor;
+use crate::{Deploy, ProjectDiagnosticsEditor};
pub struct DiagnosticIndicator {
summary: project::DiagnosticSummary,
- active_editor: Option<WeakViewHandle<Editor>>,
- workspace: WeakViewHandle<Workspace>,
+ active_editor: Option<WeakView<Editor>>,
+ workspace: WeakView<Workspace>,
current_diagnostic: Option<Diagnostic>,
in_progress_checks: HashSet<LanguageServerId>,
_observe_active_editor: Option<Subscription>,
}
-pub fn init(cx: &mut AppContext) {
- cx.add_action(DiagnosticIndicator::go_to_next_diagnostic);
+impl Render for DiagnosticIndicator {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
+ (0, 0) => h_stack().child(
+ IconElement::new(Icon::Check)
+ .size(IconSize::Small)
+ .color(Color::Success),
+ ),
+ (0, warning_count) => h_stack()
+ .gap_1()
+ .child(
+ IconElement::new(Icon::ExclamationTriangle)
+ .size(IconSize::Small)
+ .color(Color::Warning),
+ )
+ .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
+ (error_count, 0) => h_stack()
+ .gap_1()
+ .child(
+ IconElement::new(Icon::XCircle)
+ .size(IconSize::Small)
+ .color(Color::Error),
+ )
+ .child(Label::new(error_count.to_string()).size(LabelSize::Small)),
+ (error_count, warning_count) => h_stack()
+ .gap_1()
+ .child(
+ IconElement::new(Icon::XCircle)
+ .size(IconSize::Small)
+ .color(Color::Error),
+ )
+ .child(Label::new(error_count.to_string()).size(LabelSize::Small))
+ .child(
+ IconElement::new(Icon::ExclamationTriangle)
+ .size(IconSize::Small)
+ .color(Color::Warning),
+ )
+ .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
+ };
+
+ let status = if !self.in_progress_checks.is_empty() {
+ Some(
+ Label::new("Checking…")
+ .size(LabelSize::Small)
+ .into_any_element(),
+ )
+ } else if let Some(diagnostic) = &self.current_diagnostic {
+ let message = diagnostic.message.split('\n').next().unwrap().to_string();
+ Some(
+ Button::new("diagnostic_message", message)
+ .label_size(LabelSize::Small)
+ .tooltip(|cx| {
+ Tooltip::for_action("Next Diagnostic", &editor::GoToDiagnostic, cx)
+ })
+ .on_click(cx.listener(|this, _, cx| {
+ this.go_to_next_diagnostic(cx);
+ }))
+ .into_any_element(),
+ )
+ } else {
+ None
+ };
+
+ h_stack()
+ .h(rems(1.375))
+ .gap_2()
+ .child(
+ ButtonLike::new("diagnostic-indicator")
+ .child(diagnostic_indicator)
+ .tooltip(|cx| Tooltip::for_action("Project Diagnostics", &Deploy, cx))
+ .on_click(cx.listener(|this, _, cx| {
+ if let Some(workspace) = this.workspace.upgrade() {
+ workspace.update(cx, |workspace, cx| {
+ ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
+ })
+ }
+ })),
+ )
+ .children(status)
+ }
}
impl DiagnosticIndicator {
@@ -32,19 +110,23 @@ impl DiagnosticIndicator {
this.in_progress_checks.insert(*language_server_id);
cx.notify();
}
+
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
+
project::Event::DiagnosticsUpdated { .. } => {
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
+
_ => {}
})
.detach();
+
Self {
summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
@@ -58,15 +140,15 @@ impl DiagnosticIndicator {
}
}
- fn go_to_next_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
- if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade(cx)) {
+ fn go_to_next_diagnostic(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
editor.update(cx, |editor, cx| {
editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
})
}
}
- fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
+ fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let buffer = editor.buffer().read(cx);
let cursor_position = editor.selections.newest::<usize>(cx).head();
@@ -83,146 +165,7 @@ impl DiagnosticIndicator {
}
}
-impl Entity for DiagnosticIndicator {
- type Event = ();
-}
-
-impl View for DiagnosticIndicator {
- fn ui_name() -> &'static str {
- "DiagnosticIndicator"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- enum Summary {}
- enum Message {}
-
- let tooltip_style = theme::current(cx).tooltip.clone();
- let in_progress = !self.in_progress_checks.is_empty();
- let mut element = Flex::row().with_child(
- MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
- let theme = theme::current(cx);
- let style = theme
- .workspace
- .status_bar
- .diagnostic_summary
- .style_for(state);
-
- let mut summary_row = Flex::row();
- if self.summary.error_count > 0 {
- summary_row.add_child(
- Svg::new("icons/error.svg")
- .with_color(style.icon_color_error)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .contained()
- .with_margin_right(style.icon_spacing),
- );
- summary_row.add_child(
- Label::new(self.summary.error_count.to_string(), style.text.clone())
- .aligned(),
- );
- }
-
- if self.summary.warning_count > 0 {
- summary_row.add_child(
- Svg::new("icons/warning.svg")
- .with_color(style.icon_color_warning)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .contained()
- .with_margin_right(style.icon_spacing)
- .with_margin_left(if self.summary.error_count > 0 {
- style.summary_spacing
- } else {
- 0.
- }),
- );
- summary_row.add_child(
- Label::new(self.summary.warning_count.to_string(), style.text.clone())
- .aligned(),
- );
- }
-
- if self.summary.error_count == 0 && self.summary.warning_count == 0 {
- summary_row.add_child(
- Svg::new("icons/check_circle.svg")
- .with_color(style.icon_color_ok)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .into_any_named("ok-icon"),
- );
- }
-
- summary_row
- .constrained()
- .with_height(style.height)
- .contained()
- .with_style(if self.summary.error_count > 0 {
- style.container_error
- } else if self.summary.warning_count > 0 {
- style.container_warning
- } else {
- style.container_ok
- })
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, this, cx| {
- if let Some(workspace) = this.workspace.upgrade(cx) {
- workspace.update(cx, |workspace, cx| {
- ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
- })
- }
- })
- .with_tooltip::<Summary>(
- 0,
- "Project Diagnostics",
- Some(Box::new(crate::Deploy)),
- tooltip_style,
- cx,
- )
- .aligned()
- .into_any(),
- );
-
- let style = &theme::current(cx).workspace.status_bar;
- let item_spacing = style.item_spacing;
-
- if in_progress {
- element.add_child(
- Label::new("Checking…", style.diagnostic_message.default.text.clone())
- .aligned()
- .contained()
- .with_margin_left(item_spacing),
- );
- } else if let Some(diagnostic) = &self.current_diagnostic {
- let message_style = style.diagnostic_message.clone();
- element.add_child(
- MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
- Label::new(
- diagnostic.message.split('\n').next().unwrap().to_string(),
- message_style.style_for(state).text.clone(),
- )
- .aligned()
- .contained()
- .with_margin_left(item_spacing)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, this, cx| {
- this.go_to_next_diagnostic(&Default::default(), cx)
- }),
- );
- }
-
- element.into_any_named("diagnostic indicator")
- }
-
- fn debug_json(&self, _: &gpui::AppContext) -> serde_json::Value {
- serde_json::json!({ "summary": self.summary })
- }
-}
+impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
impl StatusItemView for DiagnosticIndicator {
fn set_active_pane_item(
@@ -11,14 +11,14 @@ pub struct ProjectDiagnosticsSettingsContent {
include_warnings: Option<bool>,
}
-impl settings::Setting for ProjectDiagnosticsSettings {
+impl settings::Settings for ProjectDiagnosticsSettings {
const KEY: Option<&'static str> = Some("diagnostics");
type FileContent = ProjectDiagnosticsSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
- _cx: &gpui::AppContext,
+ _cx: &mut gpui::AppContext,
) -> anyhow::Result<Self>
where
Self: Sized,
@@ -1,55 +1,44 @@
-use crate::{ProjectDiagnosticsEditor, ToggleWarnings};
-use gpui::{
- elements::*,
- platform::{CursorStyle, MouseButton},
- Action, Entity, EventContext, View, ViewContext, WeakViewHandle,
-};
-use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
+use crate::ProjectDiagnosticsEditor;
+use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
+use ui::prelude::*;
+use ui::{Icon, IconButton, Tooltip};
+use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub struct ToolbarControls {
- editor: Option<WeakViewHandle<ProjectDiagnosticsEditor>>,
+ editor: Option<WeakView<ProjectDiagnosticsEditor>>,
}
-impl Entity for ToolbarControls {
- type Event = ();
-}
-
-impl View for ToolbarControls {
- fn ui_name() -> &'static str {
- "ToolbarControls"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+impl Render for ToolbarControls {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let include_warnings = self
.editor
.as_ref()
- .and_then(|editor| editor.upgrade(cx))
+ .and_then(|editor| editor.upgrade())
.map(|editor| editor.read(cx).include_warnings)
.unwrap_or(false);
+
let tooltip = if include_warnings {
- "Exclude Warnings".into()
+ "Exclude Warnings"
} else {
- "Include Warnings".into()
+ "Include Warnings"
};
- Flex::row()
- .with_child(render_toggle_button(
- 0,
- "icons/warning.svg",
- include_warnings,
- (tooltip, Some(Box::new(ToggleWarnings))),
- cx,
- move |this, cx| {
- if let Some(editor) = this.editor.and_then(|editor| editor.upgrade(cx)) {
+
+ div().child(
+ IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
+ .tooltip(move |cx| Tooltip::text(tooltip, cx))
+ .on_click(cx.listener(|this, _, cx| {
+ if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
editor.update(cx, |editor, cx| {
- editor.toggle_warnings(&Default::default(), cx)
+ editor.toggle_warnings(&Default::default(), cx);
});
}
- },
- ))
- .into_any()
+ })),
+ )
}
}
+impl EventEmitter<ToolbarItemEvent> for ToolbarControls {}
+
impl ToolbarItemView for ToolbarControls {
fn set_active_pane_item(
&mut self,
@@ -59,7 +48,7 @@ impl ToolbarItemView for ToolbarControls {
if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
self.editor = Some(editor.downgrade());
- ToolbarItemLocation::PrimaryRight { flex: None }
+ ToolbarItemLocation::PrimaryRight
} else {
ToolbarItemLocation::Hidden
}
@@ -74,42 +63,3 @@ impl ToolbarControls {
ToolbarControls { editor: None }
}
}
-
-fn render_toggle_button<
- F: 'static + Fn(&mut ToolbarControls, &mut EventContext<ToolbarControls>),
->(
- index: usize,
- icon: &'static str,
- toggled: bool,
- tooltip: (String, Option<Box<dyn Action>>),
- cx: &mut ViewContext<ToolbarControls>,
- on_click: F,
-) -> AnyElement<ToolbarControls> {
- enum Button {}
-
- let theme = theme::current(cx);
- let (tooltip_text, action) = tooltip;
-
- MouseEventHandler::new::<Button, _>(index, cx, |mouse_state, _| {
- let style = theme
- .workspace
- .toolbar
- .toggleable_tool
- .in_state(toggled)
- .style_for(mouse_state);
- Svg::new(icon)
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .constrained()
- .with_width(style.button_width)
- .with_height(style.button_width)
- .contained()
- .with_style(style.container)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, view, cx| on_click(view, cx))
- .with_tooltip::<Button>(index, tooltip_text, action, theme.tooltip.clone(), cx)
- .into_any_named("quick action bar button")
-}
@@ -1,43 +0,0 @@
-[package]
-name = "diagnostics2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/diagnostics.rs"
-doctest = false
-
-[dependencies]
-collections = { path = "../collections" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-ui = { package = "ui2", path = "../ui2" }
-language = { package = "language2", path = "../language2" }
-lsp = { package = "lsp2", path = "../lsp2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-theme = { package = "theme2", path = "../theme2" }
-util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
-
-log.workspace = true
-anyhow.workspace = true
-futures.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-smallvec.workspace = true
-postage.workspace = true
-
-[dev-dependencies]
-client = { package = "client2", path = "../client2", features = ["test-support"] }
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-
-serde_json.workspace = true
-unindent.workspace = true
@@ -1,1612 +0,0 @@
-pub mod items;
-mod project_diagnostics_settings;
-mod toolbar_controls;
-
-use anyhow::{Context as _, Result};
-use collections::{HashMap, HashSet};
-use editor::{
- diagnostic_block_renderer,
- display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
- highlight_diagnostic_message,
- scroll::autoscroll::Autoscroll,
- Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
-};
-use futures::future::try_join_all;
-use gpui::{
- actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
- FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
- SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
- WeakView, WindowContext,
-};
-use language::{
- Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
- SelectionGoal,
-};
-use lsp::LanguageServerId;
-use project::{DiagnosticSummary, Project, ProjectPath};
-use project_diagnostics_settings::ProjectDiagnosticsSettings;
-use settings::Settings;
-use std::{
- any::{Any, TypeId},
- cmp::Ordering,
- mem,
- ops::Range,
- path::PathBuf,
- sync::Arc,
-};
-use theme::ActiveTheme;
-pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, prelude::*, Icon, IconElement, Label};
-use util::TryFutureExt;
-use workspace::{
- item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
- ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
-};
-
-actions!(diagnostics, [Deploy, ToggleWarnings]);
-
-const CONTEXT_LINE_COUNT: u32 = 1;
-
-pub fn init(cx: &mut AppContext) {
- ProjectDiagnosticsSettings::register(cx);
- cx.observe_new_views(ProjectDiagnosticsEditor::register)
- .detach();
-}
-
-struct ProjectDiagnosticsEditor {
- project: Model<Project>,
- workspace: WeakView<Workspace>,
- focus_handle: FocusHandle,
- editor: View<Editor>,
- summary: DiagnosticSummary,
- excerpts: Model<MultiBuffer>,
- path_states: Vec<PathState>,
- paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
- current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
- include_warnings: bool,
- _subscriptions: Vec<Subscription>,
-}
-
-struct PathState {
- path: ProjectPath,
- diagnostic_groups: Vec<DiagnosticGroupState>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-struct Jump {
- path: ProjectPath,
- position: Point,
- anchor: Anchor,
-}
-
-struct DiagnosticGroupState {
- language_server_id: LanguageServerId,
- primary_diagnostic: DiagnosticEntry<language::Anchor>,
- primary_excerpt_ix: usize,
- excerpts: Vec<ExcerptId>,
- blocks: HashSet<BlockId>,
- block_count: usize,
-}
-
-impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
-
-impl Render for ProjectDiagnosticsEditor {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
- let child = if self.path_states.is_empty() {
- div()
- .bg(cx.theme().colors().editor_background)
- .flex()
- .items_center()
- .justify_center()
- .size_full()
- .child(Label::new("No problems in workspace"))
- } else {
- div().size_full().child(self.editor.clone())
- };
-
- div()
- .track_focus(&self.focus_handle)
- .size_full()
- .on_action(cx.listener(Self::toggle_warnings))
- .child(child)
- }
-}
-
-impl ProjectDiagnosticsEditor {
- fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(Self::deploy);
- }
-
- fn new(
- project_handle: Model<Project>,
- workspace: WeakView<Workspace>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
- let project_event_subscription =
- cx.subscribe(&project_handle, |this, _, event, cx| match event {
- project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
- log::debug!("Disk based diagnostics finished for server {language_server_id}");
- this.update_excerpts(Some(*language_server_id), cx);
- }
- project::Event::DiagnosticsUpdated {
- language_server_id,
- path,
- } => {
- log::debug!("Adding path {path:?} to update for server {language_server_id}");
- this.paths_to_update
- .entry(*language_server_id)
- .or_default()
- .insert(path.clone());
- if this.editor.read(cx).selections.all::<usize>(cx).is_empty()
- && !this.is_dirty(cx)
- {
- this.update_excerpts(Some(*language_server_id), cx);
- }
- }
- _ => {}
- });
-
- let focus_handle = cx.focus_handle();
-
- let focus_in_subscription =
- cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx));
-
- let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
- let editor = cx.new_view(|cx| {
- let mut editor =
- Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx);
- editor.set_vertical_scroll_margin(5, cx);
- editor
- });
- let editor_event_subscription =
- cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
- cx.emit(event.clone());
- if event == &EditorEvent::Focused && this.path_states.is_empty() {
- cx.focus(&this.focus_handle);
- }
- });
-
- let project = project_handle.read(cx);
- let summary = project.diagnostic_summary(false, cx);
- let mut this = Self {
- project: project_handle,
- summary,
- workspace,
- excerpts,
- focus_handle,
- editor,
- path_states: Default::default(),
- paths_to_update: HashMap::default(),
- include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
- current_diagnostics: HashMap::default(),
- _subscriptions: vec![
- project_event_subscription,
- editor_event_subscription,
- focus_in_subscription,
- ],
- };
- this.update_excerpts(None, cx);
- this
- }
-
- fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
- if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
- workspace.activate_item(&existing, cx);
- } else {
- let workspace_handle = cx.view().downgrade();
- let diagnostics = cx.new_view(|cx| {
- ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
- });
- workspace.add_item(Box::new(diagnostics), cx);
- }
- }
-
- fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
- self.include_warnings = !self.include_warnings;
- self.paths_to_update = self.current_diagnostics.clone();
- self.update_excerpts(None, cx);
- cx.notify();
- }
-
- fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
- if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
- self.editor.focus_handle(cx).focus(cx)
- }
- }
-
- fn update_excerpts(
- &mut self,
- language_server_id: Option<LanguageServerId>,
- cx: &mut ViewContext<Self>,
- ) {
- log::debug!("Updating excerpts for server {language_server_id:?}");
- let mut paths_to_recheck = HashSet::default();
- let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
- .project
- .read(cx)
- .diagnostic_summaries(false, cx)
- .fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
- summaries.entry(server_id).or_default().insert(path);
- summaries
- });
- let mut old_diagnostics = if let Some(language_server_id) = language_server_id {
- new_summaries.retain(|server_id, _| server_id == &language_server_id);
- self.paths_to_update.retain(|server_id, paths| {
- if server_id == &language_server_id {
- paths_to_recheck.extend(paths.drain());
- false
- } else {
- true
- }
- });
- let mut old_diagnostics = HashMap::default();
- if let Some(new_paths) = new_summaries.get(&language_server_id) {
- if let Some(old_paths) = self
- .current_diagnostics
- .insert(language_server_id, new_paths.clone())
- {
- old_diagnostics.insert(language_server_id, old_paths);
- }
- } else {
- if let Some(old_paths) = self.current_diagnostics.remove(&language_server_id) {
- old_diagnostics.insert(language_server_id, old_paths);
- }
- }
- old_diagnostics
- } else {
- paths_to_recheck.extend(self.paths_to_update.drain().flat_map(|(_, paths)| paths));
- mem::replace(&mut self.current_diagnostics, new_summaries.clone())
- };
- for (server_id, new_paths) in new_summaries {
- match old_diagnostics.remove(&server_id) {
- Some(mut old_paths) => {
- paths_to_recheck.extend(
- new_paths
- .into_iter()
- .filter(|new_path| !old_paths.remove(new_path)),
- );
- paths_to_recheck.extend(old_paths);
- }
- None => paths_to_recheck.extend(new_paths),
- }
- }
- paths_to_recheck.extend(old_diagnostics.into_iter().flat_map(|(_, paths)| paths));
-
- if paths_to_recheck.is_empty() {
- log::debug!("No paths to recheck for language server {language_server_id:?}");
- return;
- }
- log::debug!(
- "Rechecking {} paths for language server {:?}",
- paths_to_recheck.len(),
- language_server_id
- );
- let project = self.project.clone();
- cx.spawn(|this, mut cx| {
- async move {
- let _: Vec<()> = try_join_all(paths_to_recheck.into_iter().map(|path| {
- let mut cx = cx.clone();
- let project = project.clone();
- let this = this.clone();
- async move {
- let buffer = project
- .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
- .await
- .with_context(|| format!("opening buffer for path {path:?}"))?;
- this.update(&mut cx, |this, cx| {
- this.populate_excerpts(path, language_server_id, buffer, cx);
- })
- .context("missing project")?;
- anyhow::Ok(())
- }
- }))
- .await
- .context("rechecking diagnostics for paths")?;
-
- this.update(&mut cx, |this, cx| {
- this.summary = this.project.read(cx).diagnostic_summary(false, cx);
- cx.emit(EditorEvent::TitleChanged);
- })?;
- anyhow::Ok(())
- }
- .log_err()
- })
- .detach();
- }
-
- fn populate_excerpts(
- &mut self,
- path: ProjectPath,
- language_server_id: Option<LanguageServerId>,
- buffer: Model<Buffer>,
- cx: &mut ViewContext<Self>,
- ) {
- let was_empty = self.path_states.is_empty();
- let snapshot = buffer.read(cx).snapshot();
- let path_ix = match self.path_states.binary_search_by_key(&&path, |e| &e.path) {
- Ok(ix) => ix,
- Err(ix) => {
- self.path_states.insert(
- ix,
- PathState {
- path: path.clone(),
- diagnostic_groups: Default::default(),
- },
- );
- ix
- }
- };
-
- let mut prev_excerpt_id = if path_ix > 0 {
- let prev_path_last_group = &self.path_states[path_ix - 1]
- .diagnostic_groups
- .last()
- .unwrap();
- prev_path_last_group.excerpts.last().unwrap().clone()
- } else {
- ExcerptId::min()
- };
-
- let path_state = &mut self.path_states[path_ix];
- let mut groups_to_add = Vec::new();
- let mut group_ixs_to_remove = Vec::new();
- let mut blocks_to_add = Vec::new();
- let mut blocks_to_remove = HashSet::default();
- let mut first_excerpt_id = None;
- let max_severity = if self.include_warnings {
- DiagnosticSeverity::WARNING
- } else {
- DiagnosticSeverity::ERROR
- };
- let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
- let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
- let mut new_groups = snapshot
- .diagnostic_groups(language_server_id)
- .into_iter()
- .filter(|(_, group)| {
- group.entries[group.primary_ix].diagnostic.severity <= max_severity
- })
- .peekable();
- loop {
- let mut to_insert = None;
- let mut to_remove = None;
- let mut to_keep = None;
- match (old_groups.peek(), new_groups.peek()) {
- (None, None) => break,
- (None, Some(_)) => to_insert = new_groups.next(),
- (Some((_, old_group)), None) => {
- if language_server_id.map_or(true, |id| id == old_group.language_server_id)
- {
- to_remove = old_groups.next();
- } else {
- to_keep = old_groups.next();
- }
- }
- (Some((_, old_group)), Some((_, new_group))) => {
- let old_primary = &old_group.primary_diagnostic;
- let new_primary = &new_group.entries[new_group.primary_ix];
- match compare_diagnostics(old_primary, new_primary, &snapshot) {
- Ordering::Less => {
- if language_server_id
- .map_or(true, |id| id == old_group.language_server_id)
- {
- to_remove = old_groups.next();
- } else {
- to_keep = old_groups.next();
- }
- }
- Ordering::Equal => {
- to_keep = old_groups.next();
- new_groups.next();
- }
- Ordering::Greater => to_insert = new_groups.next(),
- }
- }
- }
-
- if let Some((language_server_id, group)) = to_insert {
- let mut group_state = DiagnosticGroupState {
- language_server_id,
- primary_diagnostic: group.entries[group.primary_ix].clone(),
- primary_excerpt_ix: 0,
- excerpts: Default::default(),
- blocks: Default::default(),
- block_count: 0,
- };
- let mut pending_range: Option<(Range<Point>, usize)> = None;
- let mut is_first_excerpt_for_group = true;
- for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
- let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
- if let Some((range, start_ix)) = &mut pending_range {
- if let Some(entry) = resolved_entry.as_ref() {
- if entry.range.start.row
- <= range.end.row + 1 + CONTEXT_LINE_COUNT * 2
- {
- range.end = range.end.max(entry.range.end);
- continue;
- }
- }
-
- let excerpt_start =
- Point::new(range.start.row.saturating_sub(CONTEXT_LINE_COUNT), 0);
- let excerpt_end = snapshot.clip_point(
- Point::new(range.end.row + CONTEXT_LINE_COUNT, u32::MAX),
- Bias::Left,
- );
- let excerpt_id = excerpts
- .insert_excerpts_after(
- prev_excerpt_id,
- buffer.clone(),
- [ExcerptRange {
- context: excerpt_start..excerpt_end,
- primary: Some(range.clone()),
- }],
- excerpts_cx,
- )
- .pop()
- .unwrap();
-
- prev_excerpt_id = excerpt_id.clone();
- first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
- group_state.excerpts.push(excerpt_id.clone());
- let header_position = (excerpt_id.clone(), language::Anchor::MIN);
-
- if is_first_excerpt_for_group {
- is_first_excerpt_for_group = false;
- let mut primary =
- group.entries[group.primary_ix].diagnostic.clone();
- primary.message =
- primary.message.split('\n').next().unwrap().to_string();
- group_state.block_count += 1;
- blocks_to_add.push(BlockProperties {
- position: header_position,
- height: 2,
- style: BlockStyle::Sticky,
- render: diagnostic_header_renderer(primary),
- disposition: BlockDisposition::Above,
- });
- }
-
- for entry in &group.entries[*start_ix..ix] {
- let mut diagnostic = entry.diagnostic.clone();
- if diagnostic.is_primary {
- group_state.primary_excerpt_ix = group_state.excerpts.len() - 1;
- diagnostic.message =
- entry.diagnostic.message.split('\n').skip(1).collect();
- }
-
- if !diagnostic.message.is_empty() {
- group_state.block_count += 1;
- blocks_to_add.push(BlockProperties {
- position: (excerpt_id.clone(), entry.range.start),
- height: diagnostic.message.matches('\n').count() as u8 + 1,
- style: BlockStyle::Fixed,
- render: diagnostic_block_renderer(diagnostic, true),
- disposition: BlockDisposition::Below,
- });
- }
- }
-
- pending_range.take();
- }
-
- if let Some(entry) = resolved_entry {
- pending_range = Some((entry.range.clone(), ix));
- }
- }
-
- groups_to_add.push(group_state);
- } else if let Some((group_ix, group_state)) = to_remove {
- excerpts.remove_excerpts(group_state.excerpts.iter().copied(), excerpts_cx);
- group_ixs_to_remove.push(group_ix);
- blocks_to_remove.extend(group_state.blocks.iter().copied());
- } else if let Some((_, group)) = to_keep {
- prev_excerpt_id = group.excerpts.last().unwrap().clone();
- first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
- }
- }
-
- excerpts.snapshot(excerpts_cx)
- });
-
- self.editor.update(cx, |editor, cx| {
- editor.remove_blocks(blocks_to_remove, None, cx);
- let block_ids = editor.insert_blocks(
- blocks_to_add.into_iter().map(|block| {
- let (excerpt_id, text_anchor) = block.position;
- BlockProperties {
- position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
- height: block.height,
- style: block.style,
- render: block.render,
- disposition: block.disposition,
- }
- }),
- Some(Autoscroll::fit()),
- cx,
- );
-
- let mut block_ids = block_ids.into_iter();
- for group_state in &mut groups_to_add {
- group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect();
- }
- });
-
- for ix in group_ixs_to_remove.into_iter().rev() {
- path_state.diagnostic_groups.remove(ix);
- }
- path_state.diagnostic_groups.extend(groups_to_add);
- path_state.diagnostic_groups.sort_unstable_by(|a, b| {
- let range_a = &a.primary_diagnostic.range;
- let range_b = &b.primary_diagnostic.range;
- range_a
- .start
- .cmp(&range_b.start, &snapshot)
- .then_with(|| range_a.end.cmp(&range_b.end, &snapshot))
- });
-
- if path_state.diagnostic_groups.is_empty() {
- self.path_states.remove(path_ix);
- }
-
- self.editor.update(cx, |editor, cx| {
- let groups;
- let mut selections;
- let new_excerpt_ids_by_selection_id;
- if was_empty {
- groups = self.path_states.first()?.diagnostic_groups.as_slice();
- new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect();
- selections = vec![Selection {
- id: 0,
- start: 0,
- end: 0,
- reversed: false,
- goal: SelectionGoal::None,
- }];
- } else {
- groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
- new_excerpt_ids_by_selection_id =
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.refresh());
- selections = editor.selections.all::<usize>(cx);
- }
-
- // If any selection has lost its position, move it to start of the next primary diagnostic.
- let snapshot = editor.snapshot(cx);
- for selection in &mut selections {
- if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
- let group_ix = match groups.binary_search_by(|probe| {
- probe
- .excerpts
- .last()
- .unwrap()
- .cmp(new_excerpt_id, &snapshot.buffer_snapshot)
- }) {
- Ok(ix) | Err(ix) => ix,
- };
- if let Some(group) = groups.get(group_ix) {
- let offset = excerpts_snapshot
- .anchor_in_excerpt(
- group.excerpts[group.primary_excerpt_ix].clone(),
- group.primary_diagnostic.range.start,
- )
- .to_offset(&excerpts_snapshot);
- selection.start = offset;
- selection.end = offset;
- }
- }
- }
- editor.change_selections(None, cx, |s| {
- s.select(selections);
- });
- Some(())
- });
-
- if self.path_states.is_empty() {
- if self.editor.focus_handle(cx).is_focused(cx) {
- cx.focus(&self.focus_handle);
- }
- } else if self.focus_handle.is_focused(cx) {
- let focus_handle = self.editor.focus_handle(cx);
- cx.focus(&focus_handle);
- }
- cx.notify();
- }
-}
-
-impl FocusableView for ProjectDiagnosticsEditor {
- fn focus_handle(&self, _: &AppContext) -> FocusHandle {
- self.focus_handle.clone()
- }
-}
-
-impl Item for ProjectDiagnosticsEditor {
- type Event = EditorEvent;
-
- fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
- Editor::to_item_events(event, f)
- }
-
- fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
- self.editor.update(cx, |editor, cx| editor.deactivated(cx));
- }
-
- fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
- self.editor
- .update(cx, |editor, cx| editor.navigate(data, cx))
- }
-
- fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
- Some("Project Diagnostics".into())
- }
-
- fn tab_content(&self, _detail: Option<usize>, selected: bool, _: &WindowContext) -> AnyElement {
- if self.summary.error_count == 0 && self.summary.warning_count == 0 {
- let label = Label::new("No problems");
- label.into_any_element()
- } else {
- h_stack()
- .gap_1()
- .when(self.summary.error_count > 0, |then| {
- then.child(
- h_stack()
- .gap_1()
- .child(IconElement::new(Icon::XCircle).color(Color::Error))
- .child(Label::new(self.summary.error_count.to_string()).color(
- if selected {
- Color::Default
- } else {
- Color::Muted
- },
- )),
- )
- })
- .when(self.summary.warning_count > 0, |then| {
- then.child(
- h_stack()
- .gap_1()
- .child(
- IconElement::new(Icon::ExclamationTriangle).color(Color::Warning),
- )
- .child(Label::new(self.summary.warning_count.to_string()).color(
- if selected {
- Color::Default
- } else {
- Color::Muted
- },
- )),
- )
- })
- .into_any_element()
- }
- }
-
- fn for_each_project_item(
- &self,
- cx: &AppContext,
- f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
- ) {
- self.editor.for_each_project_item(cx, f)
- }
-
- fn is_singleton(&self, _: &AppContext) -> bool {
- false
- }
-
- fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
- self.editor.update(cx, |editor, _| {
- editor.set_nav_history(Some(nav_history));
- });
- }
-
- fn clone_on_split(
- &self,
- _workspace_id: workspace::WorkspaceId,
- cx: &mut ViewContext<Self>,
- ) -> Option<View<Self>>
- where
- Self: Sized,
- {
- Some(cx.new_view(|cx| {
- ProjectDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
- }))
- }
-
- fn is_dirty(&self, cx: &AppContext) -> bool {
- self.excerpts.read(cx).is_dirty(cx)
- }
-
- fn has_conflict(&self, cx: &AppContext) -> bool {
- self.excerpts.read(cx).has_conflict(cx)
- }
-
- fn can_save(&self, _: &AppContext) -> bool {
- true
- }
-
- fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
- self.editor.save(project, cx)
- }
-
- fn save_as(
- &mut self,
- _: Model<Project>,
- _: PathBuf,
- _: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- unreachable!()
- }
-
- fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
- self.editor.reload(project, cx)
- }
-
- fn act_as_type<'a>(
- &'a self,
- type_id: TypeId,
- self_handle: &'a View<Self>,
- _: &'a AppContext,
- ) -> Option<AnyView> {
- if type_id == TypeId::of::<Self>() {
- Some(self_handle.to_any())
- } else if type_id == TypeId::of::<Editor>() {
- Some(self.editor.to_any())
- } else {
- None
- }
- }
-
- fn breadcrumb_location(&self) -> ToolbarItemLocation {
- ToolbarItemLocation::PrimaryLeft
- }
-
- fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
- self.editor.breadcrumbs(theme, cx)
- }
-
- fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
- self.editor
- .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
- }
-
- fn serialized_item_kind() -> Option<&'static str> {
- Some("diagnostics")
- }
-
- fn deserialize(
- project: Model<Project>,
- workspace: WeakView<Workspace>,
- _workspace_id: workspace::WorkspaceId,
- _item_id: workspace::ItemId,
- cx: &mut ViewContext<Pane>,
- ) -> Task<Result<View<Self>>> {
- Task::ready(Ok(cx.new_view(|cx| Self::new(project, workspace, cx))))
- }
-}
-
-fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
- let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
- let message: SharedString = message.into();
- Arc::new(move |cx| {
- let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
- h_stack()
- .id("diagnostic header")
- .py_2()
- .pl_10()
- .pr_5()
- .w_full()
- .justify_between()
- .gap_2()
- .child(
- h_stack()
- .gap_3()
- .map(|stack| {
- stack.child(
- svg()
- .size(cx.text_style().font_size)
- .flex_none()
- .map(|icon| {
- if diagnostic.severity == DiagnosticSeverity::ERROR {
- icon.path(Icon::XCircle.path())
- .text_color(Color::Error.color(cx))
- } else {
- icon.path(Icon::ExclamationTriangle.path())
- .text_color(Color::Warning.color(cx))
- }
- }),
- )
- })
- .child(
- h_stack()
- .gap_1()
- .child(
- StyledText::new(message.clone()).with_highlights(
- &cx.text_style(),
- code_ranges
- .iter()
- .map(|range| (range.clone(), highlight_style)),
- ),
- )
- .when_some(diagnostic.code.as_ref(), |stack, code| {
- stack.child(
- div()
- .child(SharedString::from(format!("({code})")))
- .text_color(cx.theme().colors().text_muted),
- )
- }),
- ),
- )
- .child(
- h_stack()
- .gap_1()
- .when_some(diagnostic.source.as_ref(), |stack, source| {
- stack.child(
- div()
- .child(SharedString::from(source.clone()))
- .text_color(cx.theme().colors().text_muted),
- )
- }),
- )
- .into_any_element()
- })
-}
-
-fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
- lhs: &DiagnosticEntry<L>,
- rhs: &DiagnosticEntry<R>,
- snapshot: &language::BufferSnapshot,
-) -> Ordering {
- lhs.range
- .start
- .to_offset(snapshot)
- .cmp(&rhs.range.start.to_offset(snapshot))
- .then_with(|| {
- lhs.range
- .end
- .to_offset(snapshot)
- .cmp(&rhs.range.end.to_offset(snapshot))
- })
- .then_with(|| lhs.diagnostic.message.cmp(&rhs.diagnostic.message))
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use editor::{
- display_map::{BlockContext, TransformBlock},
- DisplayPoint,
- };
- use gpui::{px, TestAppContext, VisualTestContext, WindowContext};
- use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
- use project::FakeFs;
- use serde_json::json;
- use settings::SettingsStore;
- use unindent::Unindent as _;
-
- #[gpui::test]
- async fn test_diagnostics(cx: &mut TestAppContext) {
- init_test(cx);
-
- let fs = FakeFs::new(cx.executor());
- fs.insert_tree(
- "/test",
- json!({
- "consts.rs": "
- const a: i32 = 'a';
- const b: i32 = c;
- "
- .unindent(),
-
- "main.rs": "
- fn main() {
- let x = vec![];
- let y = vec![];
- a(x);
- b(y);
- // comment 1
- // comment 2
- c(y);
- d(x);
- }
- "
- .unindent(),
- }),
- )
- .await;
-
- let language_server_id = LanguageServerId(0);
- let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let cx = &mut VisualTestContext::from_window(*window, cx);
- let workspace = window.root(cx).unwrap();
-
- // Create some diagnostics
- project.update(cx, |project, cx| {
- project
- .update_diagnostic_entries(
- language_server_id,
- PathBuf::from("/test/main.rs"),
- None,
- vec![
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(1, 8))..Unclipped(PointUtf16::new(1, 9)),
- diagnostic: Diagnostic {
- message:
- "move occurs because `x` has type `Vec<char>`, which does not implement the `Copy` trait"
- .to_string(),
- severity: DiagnosticSeverity::INFORMATION,
- is_primary: false,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- },
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(2, 8))..Unclipped(PointUtf16::new(2, 9)),
- diagnostic: Diagnostic {
- message:
- "move occurs because `y` has type `Vec<char>`, which does not implement the `Copy` trait"
- .to_string(),
- severity: DiagnosticSeverity::INFORMATION,
- is_primary: false,
- is_disk_based: true,
- group_id: 0,
- ..Default::default()
- },
- },
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(3, 6))..Unclipped(PointUtf16::new(3, 7)),
- diagnostic: Diagnostic {
- message: "value moved here".to_string(),
- severity: DiagnosticSeverity::INFORMATION,
- is_primary: false,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- },
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(4, 6))..Unclipped(PointUtf16::new(4, 7)),
- diagnostic: Diagnostic {
- message: "value moved here".to_string(),
- severity: DiagnosticSeverity::INFORMATION,
- is_primary: false,
- is_disk_based: true,
- group_id: 0,
- ..Default::default()
- },
- },
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(7, 6))..Unclipped(PointUtf16::new(7, 7)),
- diagnostic: Diagnostic {
- message: "use of moved value\nvalue used here after move".to_string(),
- severity: DiagnosticSeverity::ERROR,
- is_primary: true,
- is_disk_based: true,
- group_id: 0,
- ..Default::default()
- },
- },
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(8, 6))..Unclipped(PointUtf16::new(8, 7)),
- diagnostic: Diagnostic {
- message: "use of moved value\nvalue used here after move".to_string(),
- severity: DiagnosticSeverity::ERROR,
- is_primary: true,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- },
- ],
- cx,
- )
- .unwrap();
- });
-
- // Open the project diagnostics view while there are already diagnostics.
- let view = window.build_view(cx, |cx| {
- ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
- });
-
- view.next_notification(cx).await;
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- (15, "collapsed context".into()),
- (16, "diagnostic header".into()),
- (25, "collapsed context".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- //
- // main.rs
- //
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- " let x = vec![];\n",
- " let y = vec![];\n",
- "\n", // supporting diagnostic
- " a(x);\n",
- " b(y);\n",
- "\n", // supporting diagnostic
- " // comment 1\n",
- " // comment 2\n",
- " c(y);\n",
- "\n", // supporting diagnostic
- " d(x);\n",
- "\n", // context ellipsis
- // diagnostic group 2
- "\n", // primary message
- "\n", // padding
- "fn main() {\n",
- " let x = vec![];\n",
- "\n", // supporting diagnostic
- " let y = vec![];\n",
- " a(x);\n",
- "\n", // supporting diagnostic
- " b(y);\n",
- "\n", // context ellipsis
- " c(y);\n",
- " d(x);\n",
- "\n", // supporting diagnostic
- "}"
- )
- );
-
- // Cursor is at the first diagnostic
- view.editor.update(cx, |editor, cx| {
- assert_eq!(
- editor.selections.display_ranges(cx),
- [DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
- );
- });
- });
-
- // Diagnostics are added for another earlier path.
- project.update(cx, |project, cx| {
- project.disk_based_diagnostics_started(language_server_id, cx);
- project
- .update_diagnostic_entries(
- language_server_id,
- PathBuf::from("/test/consts.rs"),
- None,
- vec![DiagnosticEntry {
- range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)),
- diagnostic: Diagnostic {
- message: "mismatched types\nexpected `usize`, found `char`".to_string(),
- severity: DiagnosticSeverity::ERROR,
- is_primary: true,
- is_disk_based: true,
- group_id: 0,
- ..Default::default()
- },
- }],
- cx,
- )
- .unwrap();
- project.disk_based_diagnostics_finished(language_server_id, cx);
- });
-
- view.next_notification(cx).await;
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- (7, "path header block".into()),
- (9, "diagnostic header".into()),
- (22, "collapsed context".into()),
- (23, "diagnostic header".into()),
- (32, "collapsed context".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- //
- // consts.rs
- //
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- "const a: i32 = 'a';\n",
- "\n", // supporting diagnostic
- "const b: i32 = c;\n",
- //
- // main.rs
- //
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- " let x = vec![];\n",
- " let y = vec![];\n",
- "\n", // supporting diagnostic
- " a(x);\n",
- " b(y);\n",
- "\n", // supporting diagnostic
- " // comment 1\n",
- " // comment 2\n",
- " c(y);\n",
- "\n", // supporting diagnostic
- " d(x);\n",
- "\n", // collapsed context
- // diagnostic group 2
- "\n", // primary message
- "\n", // filename
- "fn main() {\n",
- " let x = vec![];\n",
- "\n", // supporting diagnostic
- " let y = vec![];\n",
- " a(x);\n",
- "\n", // supporting diagnostic
- " b(y);\n",
- "\n", // context ellipsis
- " c(y);\n",
- " d(x);\n",
- "\n", // supporting diagnostic
- "}"
- )
- );
-
- // Cursor keeps its position.
- view.editor.update(cx, |editor, cx| {
- assert_eq!(
- editor.selections.display_ranges(cx),
- [DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
- );
- });
- });
-
- // Diagnostics are added to the first path
- project.update(cx, |project, cx| {
- project.disk_based_diagnostics_started(language_server_id, cx);
- project
- .update_diagnostic_entries(
- language_server_id,
- PathBuf::from("/test/consts.rs"),
- None,
- vec![
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(0, 15))
- ..Unclipped(PointUtf16::new(0, 15)),
- diagnostic: Diagnostic {
- message: "mismatched types\nexpected `usize`, found `char`"
- .to_string(),
- severity: DiagnosticSeverity::ERROR,
- is_primary: true,
- is_disk_based: true,
- group_id: 0,
- ..Default::default()
- },
- },
- DiagnosticEntry {
- range: Unclipped(PointUtf16::new(1, 15))
- ..Unclipped(PointUtf16::new(1, 15)),
- diagnostic: Diagnostic {
- message: "unresolved name `c`".to_string(),
- severity: DiagnosticSeverity::ERROR,
- is_primary: true,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- },
- ],
- cx,
- )
- .unwrap();
- project.disk_based_diagnostics_finished(language_server_id, cx);
- });
-
- view.next_notification(cx).await;
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- (7, "collapsed context".into()),
- (8, "diagnostic header".into()),
- (13, "path header block".into()),
- (15, "diagnostic header".into()),
- (28, "collapsed context".into()),
- (29, "diagnostic header".into()),
- (38, "collapsed context".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- //
- // consts.rs
- //
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- "const a: i32 = 'a';\n",
- "\n", // supporting diagnostic
- "const b: i32 = c;\n",
- "\n", // context ellipsis
- // diagnostic group 2
- "\n", // primary message
- "\n", // padding
- "const a: i32 = 'a';\n",
- "const b: i32 = c;\n",
- "\n", // supporting diagnostic
- //
- // main.rs
- //
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- " let x = vec![];\n",
- " let y = vec![];\n",
- "\n", // supporting diagnostic
- " a(x);\n",
- " b(y);\n",
- "\n", // supporting diagnostic
- " // comment 1\n",
- " // comment 2\n",
- " c(y);\n",
- "\n", // supporting diagnostic
- " d(x);\n",
- "\n", // context ellipsis
- // diagnostic group 2
- "\n", // primary message
- "\n", // filename
- "fn main() {\n",
- " let x = vec![];\n",
- "\n", // supporting diagnostic
- " let y = vec![];\n",
- " a(x);\n",
- "\n", // supporting diagnostic
- " b(y);\n",
- "\n", // context ellipsis
- " c(y);\n",
- " d(x);\n",
- "\n", // supporting diagnostic
- "}"
- )
- );
- });
- }
-
- #[gpui::test]
- async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
- init_test(cx);
-
- let fs = FakeFs::new(cx.executor());
- fs.insert_tree(
- "/test",
- json!({
- "main.js": "
- a();
- b();
- c();
- d();
- e();
- ".unindent()
- }),
- )
- .await;
-
- let server_id_1 = LanguageServerId(100);
- let server_id_2 = LanguageServerId(101);
- let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
- let cx = &mut VisualTestContext::from_window(*window, cx);
- let workspace = window.root(cx).unwrap();
-
- let view = window.build_view(cx, |cx| {
- ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
- });
-
- // Two language servers start updating diagnostics
- project.update(cx, |project, cx| {
- project.disk_based_diagnostics_started(server_id_1, cx);
- project.disk_based_diagnostics_started(server_id_2, cx);
- project
- .update_diagnostic_entries(
- server_id_1,
- PathBuf::from("/test/main.js"),
- None,
- vec![DiagnosticEntry {
- range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)),
- diagnostic: Diagnostic {
- message: "error 1".to_string(),
- severity: DiagnosticSeverity::WARNING,
- is_primary: true,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- }],
- cx,
- )
- .unwrap();
- });
-
- // The first language server finishes
- project.update(cx, |project, cx| {
- project.disk_based_diagnostics_finished(server_id_1, cx);
- });
-
- // Only the first language server's diagnostics are shown.
- cx.executor().run_until_parked();
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- "a();\n", //
- "b();",
- )
- );
- });
-
- // The second language server finishes
- project.update(cx, |project, cx| {
- project
- .update_diagnostic_entries(
- server_id_2,
- PathBuf::from("/test/main.js"),
- None,
- vec![DiagnosticEntry {
- range: Unclipped(PointUtf16::new(1, 0))..Unclipped(PointUtf16::new(1, 1)),
- diagnostic: Diagnostic {
- message: "warning 1".to_string(),
- severity: DiagnosticSeverity::ERROR,
- is_primary: true,
- is_disk_based: true,
- group_id: 2,
- ..Default::default()
- },
- }],
- cx,
- )
- .unwrap();
- project.disk_based_diagnostics_finished(server_id_2, cx);
- });
-
- // Both language server's diagnostics are shown.
- cx.executor().run_until_parked();
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- (6, "collapsed context".into()),
- (7, "diagnostic header".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- "a();\n", // location
- "b();\n", //
- "\n", // collapsed context
- // diagnostic group 2
- "\n", // primary message
- "\n", // padding
- "a();\n", // context
- "b();\n", //
- "c();", // context
- )
- );
- });
-
- // Both language servers start updating diagnostics, and the first server finishes.
- project.update(cx, |project, cx| {
- project.disk_based_diagnostics_started(server_id_1, cx);
- project.disk_based_diagnostics_started(server_id_2, cx);
- project
- .update_diagnostic_entries(
- server_id_1,
- PathBuf::from("/test/main.js"),
- None,
- vec![DiagnosticEntry {
- range: Unclipped(PointUtf16::new(2, 0))..Unclipped(PointUtf16::new(2, 1)),
- diagnostic: Diagnostic {
- message: "warning 2".to_string(),
- severity: DiagnosticSeverity::WARNING,
- is_primary: true,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- }],
- cx,
- )
- .unwrap();
- project
- .update_diagnostic_entries(
- server_id_2,
- PathBuf::from("/test/main.rs"),
- None,
- vec![],
- cx,
- )
- .unwrap();
- project.disk_based_diagnostics_finished(server_id_1, cx);
- });
-
- // Only the first language server's diagnostics are updated.
- cx.executor().run_until_parked();
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- (7, "collapsed context".into()),
- (8, "diagnostic header".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- "a();\n", // location
- "b();\n", //
- "c();\n", // context
- "\n", // collapsed context
- // diagnostic group 2
- "\n", // primary message
- "\n", // padding
- "b();\n", // context
- "c();\n", //
- "d();", // context
- )
- );
- });
-
- // The second language server finishes.
- project.update(cx, |project, cx| {
- project
- .update_diagnostic_entries(
- server_id_2,
- PathBuf::from("/test/main.js"),
- None,
- vec![DiagnosticEntry {
- range: Unclipped(PointUtf16::new(3, 0))..Unclipped(PointUtf16::new(3, 1)),
- diagnostic: Diagnostic {
- message: "warning 2".to_string(),
- severity: DiagnosticSeverity::WARNING,
- is_primary: true,
- is_disk_based: true,
- group_id: 1,
- ..Default::default()
- },
- }],
- cx,
- )
- .unwrap();
- project.disk_based_diagnostics_finished(server_id_2, cx);
- });
-
- // Both language servers' diagnostics are updated.
- cx.executor().run_until_parked();
- view.update(cx, |view, cx| {
- assert_eq!(
- editor_blocks(&view.editor, cx),
- [
- (0, "path header block".into()),
- (2, "diagnostic header".into()),
- (7, "collapsed context".into()),
- (8, "diagnostic header".into()),
- ]
- );
- assert_eq!(
- view.editor.update(cx, |editor, cx| editor.display_text(cx)),
- concat!(
- "\n", // filename
- "\n", // padding
- // diagnostic group 1
- "\n", // primary message
- "\n", // padding
- "b();\n", // location
- "c();\n", //
- "d();\n", // context
- "\n", // collapsed context
- // diagnostic group 2
- "\n", // primary message
- "\n", // padding
- "c();\n", // context
- "d();\n", //
- "e();", // context
- )
- );
- });
- }
-
- fn init_test(cx: &mut TestAppContext) {
- cx.update(|cx| {
- let settings = SettingsStore::test(cx);
- cx.set_global(settings);
- theme::init(theme::LoadThemes::JustBase, cx);
- language::init(cx);
- client::init_settings(cx);
- workspace::init_settings(cx);
- Project::init_settings(cx);
- crate::init(cx);
- });
- }
-
- fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
- editor.update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- snapshot
- .blocks_in_range(0..snapshot.max_point().row())
- .enumerate()
- .filter_map(|(ix, (row, block))| {
- let name = match block {
- TransformBlock::Custom(block) => block
- .render(&mut BlockContext {
- view_context: cx,
- anchor_x: px(0.),
- gutter_padding: px(0.),
- gutter_width: px(0.),
- line_height: px(0.),
- em_width: px(0.),
- block_id: ix,
- editor_style: &editor::EditorStyle::default(),
- })
- .inner_id()?
- .try_into()
- .ok()?,
-
- TransformBlock::ExcerptHeader {
- starts_new_buffer, ..
- } => {
- if *starts_new_buffer {
- "path header block".into()
- } else {
- "collapsed context".into()
- }
- }
- };
-
- Some((row, name))
- })
- .collect()
- })
- }
-}
@@ -1,187 +0,0 @@
-use collections::HashSet;
-use editor::Editor;
-use gpui::{
- rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
- ViewContext, WeakView,
-};
-use language::Diagnostic;
-use lsp::LanguageServerId;
-use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip};
-use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
-
-use crate::{Deploy, ProjectDiagnosticsEditor};
-
-pub struct DiagnosticIndicator {
- summary: project::DiagnosticSummary,
- active_editor: Option<WeakView<Editor>>,
- workspace: WeakView<Workspace>,
- current_diagnostic: Option<Diagnostic>,
- in_progress_checks: HashSet<LanguageServerId>,
- _observe_active_editor: Option<Subscription>,
-}
-
-impl Render for DiagnosticIndicator {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
- (0, 0) => h_stack().child(
- IconElement::new(Icon::Check)
- .size(IconSize::Small)
- .color(Color::Success),
- ),
- (0, warning_count) => h_stack()
- .gap_1()
- .child(
- IconElement::new(Icon::ExclamationTriangle)
- .size(IconSize::Small)
- .color(Color::Warning),
- )
- .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
- (error_count, 0) => h_stack()
- .gap_1()
- .child(
- IconElement::new(Icon::XCircle)
- .size(IconSize::Small)
- .color(Color::Error),
- )
- .child(Label::new(error_count.to_string()).size(LabelSize::Small)),
- (error_count, warning_count) => h_stack()
- .gap_1()
- .child(
- IconElement::new(Icon::XCircle)
- .size(IconSize::Small)
- .color(Color::Error),
- )
- .child(Label::new(error_count.to_string()).size(LabelSize::Small))
- .child(
- IconElement::new(Icon::ExclamationTriangle)
- .size(IconSize::Small)
- .color(Color::Warning),
- )
- .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
- };
-
- let status = if !self.in_progress_checks.is_empty() {
- Some(
- Label::new("Checking…")
- .size(LabelSize::Small)
- .into_any_element(),
- )
- } else if let Some(diagnostic) = &self.current_diagnostic {
- let message = diagnostic.message.split('\n').next().unwrap().to_string();
- Some(
- Button::new("diagnostic_message", message)
- .label_size(LabelSize::Small)
- .tooltip(|cx| {
- Tooltip::for_action("Next Diagnostic", &editor::GoToDiagnostic, cx)
- })
- .on_click(cx.listener(|this, _, cx| {
- this.go_to_next_diagnostic(cx);
- }))
- .into_any_element(),
- )
- } else {
- None
- };
-
- h_stack()
- .h(rems(1.375))
- .gap_2()
- .child(
- ButtonLike::new("diagnostic-indicator")
- .child(diagnostic_indicator)
- .tooltip(|cx| Tooltip::for_action("Project Diagnostics", &Deploy, cx))
- .on_click(cx.listener(|this, _, cx| {
- if let Some(workspace) = this.workspace.upgrade() {
- workspace.update(cx, |workspace, cx| {
- ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
- })
- }
- })),
- )
- .children(status)
- }
-}
-
-impl DiagnosticIndicator {
- pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
- let project = workspace.project();
- cx.subscribe(project, |this, project, event, cx| match event {
- project::Event::DiskBasedDiagnosticsStarted { language_server_id } => {
- this.in_progress_checks.insert(*language_server_id);
- cx.notify();
- }
-
- project::Event::DiskBasedDiagnosticsFinished { language_server_id }
- | project::Event::LanguageServerRemoved(language_server_id) => {
- this.summary = project.read(cx).diagnostic_summary(false, cx);
- this.in_progress_checks.remove(language_server_id);
- cx.notify();
- }
-
- project::Event::DiagnosticsUpdated { .. } => {
- this.summary = project.read(cx).diagnostic_summary(false, cx);
- cx.notify();
- }
-
- _ => {}
- })
- .detach();
-
- Self {
- summary: project.read(cx).diagnostic_summary(false, cx),
- in_progress_checks: project
- .read(cx)
- .language_servers_running_disk_based_diagnostics()
- .collect(),
- active_editor: None,
- workspace: workspace.weak_handle(),
- current_diagnostic: None,
- _observe_active_editor: None,
- }
- }
-
- fn go_to_next_diagnostic(&mut self, cx: &mut ViewContext<Self>) {
- if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
- editor.update(cx, |editor, cx| {
- editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
- })
- }
- }
-
- fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
- let editor = editor.read(cx);
- let buffer = editor.buffer().read(cx);
- let cursor_position = editor.selections.newest::<usize>(cx).head();
- let new_diagnostic = buffer
- .snapshot(cx)
- .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
- .filter(|entry| !entry.range.is_empty())
- .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
- .map(|entry| entry.diagnostic);
- if new_diagnostic != self.current_diagnostic {
- self.current_diagnostic = new_diagnostic;
- cx.notify();
- }
- }
-}
-
-impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
-
-impl StatusItemView for DiagnosticIndicator {
- fn set_active_pane_item(
- &mut self,
- active_pane_item: Option<&dyn ItemHandle>,
- cx: &mut ViewContext<Self>,
- ) {
- if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
- self.active_editor = Some(editor.downgrade());
- self._observe_active_editor = Some(cx.observe(&editor, Self::update));
- self.update(editor, cx);
- } else {
- self.active_editor = None;
- self.current_diagnostic = None;
- self._observe_active_editor = None;
- }
- cx.notify();
- }
-}
@@ -1,28 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-#[derive(Deserialize, Debug)]
-pub struct ProjectDiagnosticsSettings {
- pub include_warnings: bool,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ProjectDiagnosticsSettingsContent {
- include_warnings: Option<bool>,
-}
-
-impl settings::Settings for ProjectDiagnosticsSettings {
- const KEY: Option<&'static str> = Some("diagnostics");
- type FileContent = ProjectDiagnosticsSettingsContent;
-
- fn load(
- default_value: &Self::FileContent,
- user_values: &[&Self::FileContent],
- _cx: &mut gpui::AppContext,
- ) -> anyhow::Result<Self>
- where
- Self: Sized,
- {
- Self::load_via_json_merge(default_value, user_values)
- }
-}
@@ -1,65 +0,0 @@
-use crate::ProjectDiagnosticsEditor;
-use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
-use ui::prelude::*;
-use ui::{Icon, IconButton, Tooltip};
-use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
-
-pub struct ToolbarControls {
- editor: Option<WeakView<ProjectDiagnosticsEditor>>,
-}
-
-impl Render for ToolbarControls {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- let include_warnings = self
- .editor
- .as_ref()
- .and_then(|editor| editor.upgrade())
- .map(|editor| editor.read(cx).include_warnings)
- .unwrap_or(false);
-
- let tooltip = if include_warnings {
- "Exclude Warnings"
- } else {
- "Include Warnings"
- };
-
- div().child(
- IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
- .tooltip(move |cx| Tooltip::text(tooltip, cx))
- .on_click(cx.listener(|this, _, cx| {
- if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
- editor.update(cx, |editor, cx| {
- editor.toggle_warnings(&Default::default(), cx);
- });
- }
- })),
- )
- }
-}
-
-impl EventEmitter<ToolbarItemEvent> for ToolbarControls {}
-
-impl ToolbarItemView for ToolbarControls {
- fn set_active_pane_item(
- &mut self,
- active_pane_item: Option<&dyn ItemHandle>,
- _: &mut ViewContext<Self>,
- ) -> ToolbarItemLocation {
- if let Some(pane_item) = active_pane_item.as_ref() {
- if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
- self.editor = Some(editor.downgrade());
- ToolbarItemLocation::PrimaryRight
- } else {
- ToolbarItemLocation::Hidden
- }
- } else {
- ToolbarItemLocation::Hidden
- }
- }
-}
-
-impl ToolbarControls {
- pub fn new() -> Self {
- ToolbarControls { editor: None }
- }
-}
@@ -9,26 +9,28 @@ path = "src/file_finder.rs"
doctest = false
[dependencies]
-editor = { path = "../editor" }
+editor = { package = "editor2", path = "../editor2" }
collections = { path = "../collections" }
-fuzzy = { path = "../fuzzy" }
-gpui = { path = "../gpui" }
-menu = { path = "../menu" }
-picker = { path = "../picker" }
-project = { path = "../project" }
-settings = { path = "../settings" }
-text = { path = "../text" }
+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+menu = { package = "menu2", path = "../menu2" }
+picker = { package = "picker2", path = "../picker2" }
+project = { package = "project2", path = "../project2" }
+settings = { package = "settings2", path = "../settings2" }
+text = { package = "text2", path = "../text2" }
util = { path = "../util" }
-theme = { path = "../theme" }
-workspace = { path = "../workspace" }
+theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2" }
+workspace = { package = "workspace2", path = "../workspace2" }
postage.workspace = true
+serde.workspace = true
[dev-dependencies]
-editor = { path = "../editor", features = ["test-support"] }
-gpui = { path = "../gpui", features = ["test-support"] }
-language = { path = "../language", features = ["test-support"] }
-workspace = { path = "../workspace", features = ["test-support"] }
-theme = { path = "../theme", features = ["test-support"] }
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+language = { package = "language2", path = "../language2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
serde_json.workspace = true
ctor.workspace = true
@@ -2,7 +2,8 @@ use collections::HashMap;
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
- actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
+ actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
+ ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -14,14 +15,118 @@ use std::{
},
};
use text::Point;
+use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
-use workspace::Workspace;
+use workspace::{ModalView, Workspace};
-pub type FileFinder = Picker<FileFinderDelegate>;
+actions!(file_finder, [Toggle]);
+
+impl ModalView for FileFinder {}
+
+pub struct FileFinder {
+ picker: View<Picker<FileFinderDelegate>>,
+}
+
+pub fn init(cx: &mut AppContext) {
+ cx.observe_new_views(FileFinder::register).detach();
+}
+
+impl FileFinder {
+ fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|workspace, _: &Toggle, cx| {
+ let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
+ Self::open(workspace, cx);
+ return;
+ };
+
+ file_finder.update(cx, |file_finder, cx| {
+ file_finder
+ .picker
+ .update(cx, |picker, cx| picker.cycle_selection(cx))
+ });
+ });
+ }
+
+ fn open(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+ let project = workspace.project().read(cx);
+
+ let currently_opened_path = workspace
+ .active_item(cx)
+ .and_then(|item| item.project_path(cx))
+ .map(|project_path| {
+ let abs_path = project
+ .worktree_for_id(project_path.worktree_id, cx)
+ .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
+ FoundPath::new(project_path, abs_path)
+ });
+
+ // if exists, bubble the currently opened path to the top
+ let history_items = currently_opened_path
+ .clone()
+ .into_iter()
+ .chain(
+ workspace
+ .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
+ .into_iter()
+ .filter(|(history_path, _)| {
+ Some(history_path)
+ != currently_opened_path
+ .as_ref()
+ .map(|found_path| &found_path.project)
+ })
+ .filter(|(_, history_abs_path)| {
+ history_abs_path.as_ref()
+ != currently_opened_path
+ .as_ref()
+ .and_then(|found_path| found_path.absolute.as_ref())
+ })
+ .filter(|(_, history_abs_path)| match history_abs_path {
+ Some(abs_path) => history_file_exists(abs_path),
+ None => true,
+ })
+ .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
+ )
+ .collect();
+
+ let project = workspace.project().clone();
+ let weak_workspace = cx.view().downgrade();
+ workspace.toggle_modal(cx, |cx| {
+ let delegate = FileFinderDelegate::new(
+ cx.view().downgrade(),
+ weak_workspace,
+ project,
+ currently_opened_path,
+ history_items,
+ cx,
+ );
+
+ FileFinder::new(delegate, cx)
+ });
+ }
+
+ fn new(delegate: FileFinderDelegate, cx: &mut ViewContext<Self>) -> Self {
+ Self {
+ picker: cx.new_view(|cx| Picker::new(delegate, cx)),
+ }
+ }
+}
+
+impl EventEmitter<DismissEvent> for FileFinder {}
+impl FocusableView for FileFinder {
+ fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+ self.picker.focus_handle(cx)
+ }
+}
+impl Render for FileFinder {
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+ v_stack().w(rems(34.)).child(self.picker.clone())
+ }
+}
pub struct FileFinderDelegate {
- workspace: WeakViewHandle<Workspace>,
- project: ModelHandle<Project>,
+ file_finder: WeakView<FileFinder>,
+ workspace: WeakView<Workspace>,
+ project: Model<Project>,
search_count: usize,
latest_search_id: usize,
latest_search_did_cancel: bool,
@@ -165,91 +270,8 @@ impl FoundPath {
}
}
-actions!(file_finder, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(toggle_or_cycle_file_finder);
- FileFinder::init(cx);
-}
-
const MAX_RECENT_SELECTIONS: usize = 20;
-fn toggle_or_cycle_file_finder(
- workspace: &mut Workspace,
- _: &Toggle,
- cx: &mut ViewContext<Workspace>,
-) {
- match workspace.modal::<FileFinder>() {
- Some(file_finder) => file_finder.update(cx, |file_finder, cx| {
- let current_index = file_finder.delegate().selected_index();
- file_finder.select_next(&menu::SelectNext, cx);
- let new_index = file_finder.delegate().selected_index();
- if current_index == new_index {
- file_finder.select_first(&menu::SelectFirst, cx);
- }
- }),
- None => {
- workspace.toggle_modal(cx, |workspace, cx| {
- let project = workspace.project().read(cx);
-
- let currently_opened_path = workspace
- .active_item(cx)
- .and_then(|item| item.project_path(cx))
- .map(|project_path| {
- let abs_path = project
- .worktree_for_id(project_path.worktree_id, cx)
- .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
- FoundPath::new(project_path, abs_path)
- });
-
- // if exists, bubble the currently opened path to the top
- let history_items = currently_opened_path
- .clone()
- .into_iter()
- .chain(
- workspace
- .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
- .into_iter()
- .filter(|(history_path, _)| {
- Some(history_path)
- != currently_opened_path
- .as_ref()
- .map(|found_path| &found_path.project)
- })
- .filter(|(_, history_abs_path)| {
- history_abs_path.as_ref()
- != currently_opened_path
- .as_ref()
- .and_then(|found_path| found_path.absolute.as_ref())
- })
- .filter(|(_, history_abs_path)| match history_abs_path {
- Some(abs_path) => history_file_exists(abs_path),
- None => true,
- })
- .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
- )
- .collect();
-
- let project = workspace.project().clone();
- let workspace = cx.handle().downgrade();
- let finder = cx.add_view(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace,
- project,
- currently_opened_path,
- history_items,
- cx,
- ),
- cx,
- )
- });
- finder
- });
- }
- }
-}
-
#[cfg(not(test))]
fn history_file_exists(abs_path: &PathBuf) -> bool {
abs_path.exists()
@@ -282,17 +304,23 @@ impl FileSearchQuery {
impl FileFinderDelegate {
fn new(
- workspace: WeakViewHandle<Workspace>,
- project: ModelHandle<Project>,
+ file_finder: WeakView<FileFinder>,
+ workspace: WeakView<Workspace>,
+ project: Model<Project>,
currently_opened_path: Option<FoundPath>,
history_items: Vec<FoundPath>,
cx: &mut ViewContext<FileFinder>,
) -> Self {
- cx.observe(&project, |picker, _, cx| {
- picker.update_matches(picker.query(cx), cx);
+ cx.observe(&project, |file_finder, _, cx| {
+ //todo!() We should probably not re-render on every project anything
+ file_finder
+ .picker
+ .update(cx, |picker, cx| picker.refresh(cx))
})
.detach();
+
Self {
+ file_finder,
workspace,
project,
search_count: 0,
@@ -310,7 +338,7 @@ impl FileFinderDelegate {
fn spawn_search(
&mut self,
query: PathLikeWithPosition<FileSearchQuery>,
- cx: &mut ViewContext<FileFinder>,
+ cx: &mut ViewContext<Picker<Self>>,
) -> Task<()> {
let relative_to = self
.currently_opened_path
@@ -343,14 +371,14 @@ impl FileFinderDelegate {
false,
100,
&cancel_flag,
- cx.background(),
+ cx.background_executor().clone(),
)
.await;
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
picker
.update(&mut cx, |picker, cx| {
picker
- .delegate_mut()
+ .delegate
.set_search_matches(search_id, did_cancel, query, matches, cx)
})
.log_err();
@@ -363,7 +391,7 @@ impl FileFinderDelegate {
did_cancel: bool,
query: PathLikeWithPosition<FileSearchQuery>,
matches: Vec<PathMatch>,
- cx: &mut ViewContext<FileFinder>,
+ cx: &mut ViewContext<Picker<Self>>,
) {
if search_id >= self.latest_search_id {
self.latest_search_id = search_id;
@@ -495,6 +523,8 @@ impl FileFinderDelegate {
}
impl PickerDelegate for FileFinderDelegate {
+ type ListItem = ListItem;
+
fn placeholder_text(&self) -> Arc<str> {
"Search project files...".into()
}
@@ -507,12 +537,25 @@ impl PickerDelegate for FileFinderDelegate {
self.selected_index.unwrap_or(0)
}
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<FileFinder>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = Some(ix);
cx.notify();
}
- fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
+ fn separators_after_indices(&self) -> Vec<usize> {
+ let history_items = self.matches.history.len();
+ if history_items == 0 || self.matches.search.is_empty() {
+ Vec::new()
+ } else {
+ vec![history_items - 1]
+ }
+ }
+
+ fn update_matches(
+ &mut self,
+ raw_query: String,
+ cx: &mut ViewContext<Picker<Self>>,
+ ) -> Task<()> {
let raw_query = raw_query.trim();
if raw_query.is_empty() {
let project = self.project.read(cx);
@@ -550,9 +593,9 @@ impl PickerDelegate for FileFinderDelegate {
}
}
- fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<FileFinder>) {
+ fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
if let Some(m) = self.matches.get(self.selected_index()) {
- if let Some(workspace) = self.workspace.upgrade(cx) {
+ if let Some(workspace) = self.workspace.upgrade() {
let open_task = workspace.update(cx, move |workspace, cx| {
let split_or_open = |workspace: &mut Workspace, project_path, cx| {
if secondary {
@@ -628,6 +671,8 @@ impl PickerDelegate for FileFinderDelegate {
.and_then(|query| query.column)
.unwrap_or(0)
.saturating_sub(1);
+ let finder = self.file_finder.clone();
+
cx.spawn(|_, mut cx| async move {
let item = open_task.await.log_err()?;
if let Some(row) = row {
@@ -646,10 +691,7 @@ impl PickerDelegate for FileFinderDelegate {
.log_err();
}
}
- workspace
- .downgrade()
- .update(&mut cx, |workspace, cx| workspace.dismiss_modal(cx))
- .log_err();
+ finder.update(&mut cx, |_, cx| cx.emit(DismissEvent)).ok()?;
Some(())
})
@@ -658,44 +700,47 @@ impl PickerDelegate for FileFinderDelegate {
}
}
- fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
+ self.file_finder
+ .update(cx, |_, cx| cx.emit(DismissEvent))
+ .log_err();
+ }
fn render_match(
&self,
ix: usize,
- mouse_state: &mut MouseState,
selected: bool,
- cx: &AppContext,
- ) -> AnyElement<Picker<Self>> {
+ cx: &mut ViewContext<Picker<Self>>,
+ ) -> Option<Self::ListItem> {
let path_match = self
.matches
.get(ix)
.expect("Invalid matches state: no element for index {ix}");
- let theme = theme::current(cx);
- let style = theme.picker.item.in_state(selected).style_for(mouse_state);
+
let (file_name, file_name_positions, full_path, full_path_positions) =
self.labels_for_match(path_match, cx, ix);
- Flex::column()
- .with_child(
- Label::new(file_name, style.label.clone()).with_highlights(file_name_positions),
- )
- .with_child(
- Label::new(full_path, style.label.clone()).with_highlights(full_path_positions),
- )
- .flex(1., false)
- .contained()
- .with_style(style.container)
- .into_any_named("match")
+
+ Some(
+ ListItem::new(ix)
+ .spacing(ListItemSpacing::Sparse)
+ .inset(true)
+ .selected(selected)
+ .child(
+ v_stack()
+ .child(HighlightedLabel::new(file_name, file_name_positions))
+ .child(HighlightedLabel::new(full_path, full_path_positions)),
+ ),
+ )
}
}
#[cfg(test)]
mod tests {
- use std::{assert_eq, collections::HashMap, path::Path, time::Duration};
+ use std::{assert_eq, path::Path, time::Duration};
use super::*;
use editor::Editor;
- use gpui::{TestAppContext, ViewHandle};
+ use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext};
use serde_json::json;
use workspace::{AppState, Workspace};
@@ -725,37 +770,18 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project, cx));
- let workspace = window.root(cx);
- cx.dispatch_action(window.into(), Toggle);
- let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+ let (picker, workspace, cx) = build_find_picker(project, cx);
- finder
- .update(cx, |finder, cx| {
- finder.delegate_mut().update_matches("bna".to_string(), cx)
- })
- .await;
- finder.read_with(cx, |finder, _| {
- assert_eq!(finder.delegate().matches.len(), 2);
+ cx.simulate_input("bna");
+ picker.update(cx, |picker, _| {
+ assert_eq!(picker.delegate.matches.len(), 2);
});
- let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window.into(), SelectNext);
- cx.dispatch_action(window.into(), Confirm);
- active_pane
- .condition(cx, |pane, _| pane.active_item().is_some())
- .await;
+ cx.dispatch_action(SelectNext);
+ cx.dispatch_action(Confirm);
cx.read(|cx| {
- let active_item = active_pane.read(cx).active_item().unwrap();
- assert_eq!(
- active_item
- .as_any()
- .downcast_ref::<Editor>()
- .unwrap()
- .read(cx)
- .title(cx),
- "bandana"
- );
+ let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+ assert_eq!(active_editor.read(cx).title(cx), "bandana");
});
for bandana_query in [
@@ -766,35 +792,26 @@ mod tests {
" ndan ",
" band ",
] {
- finder
- .update(cx, |finder, cx| {
- finder
- .delegate_mut()
+ picker
+ .update(cx, |picker, cx| {
+ picker
+ .delegate
.update_matches(bandana_query.to_string(), cx)
})
.await;
- finder.read_with(cx, |finder, _| {
+ picker.update(cx, |picker, _| {
assert_eq!(
- finder.delegate().matches.len(),
+ picker.delegate.matches.len(),
1,
"Wrong number of matches for bandana query '{bandana_query}'"
);
});
- let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window.into(), SelectNext);
- cx.dispatch_action(window.into(), Confirm);
- active_pane
- .condition(cx, |pane, _| pane.active_item().is_some())
- .await;
+ cx.dispatch_action(SelectNext);
+ cx.dispatch_action(Confirm);
cx.read(|cx| {
- let active_item = active_pane.read(cx).active_item().unwrap();
+ let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
assert_eq!(
- active_item
- .as_any()
- .downcast_ref::<Editor>()
- .unwrap()
- .read(cx)
- .title(cx),
+ active_editor.read(cx).title(cx),
"bandana",
"Wrong match for bandana query '{bandana_query}'"
);
@@ -823,25 +840,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project, cx));
- let workspace = window.root(cx);
- cx.dispatch_action(window.into(), Toggle);
- let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
+ let (picker, workspace, cx) = build_find_picker(project, cx);
let file_query = &first_file_name[..3];
let file_row = 1;
let file_column = 3;
assert!(file_column <= first_file_contents.len());
let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
- finder
+ picker
.update(cx, |finder, cx| {
finder
- .delegate_mut()
+ .delegate
.update_matches(query_inside_file.to_string(), cx)
})
.await;
- finder.read_with(cx, |finder, _| {
- let finder = finder.delegate();
+ picker.update(cx, |finder, _| {
+ let finder = &finder.delegate;
assert_eq!(finder.matches.len(), 1);
let latest_search_query = finder
.latest_search_query
@@ -856,34 +871,27 @@ mod tests {
assert_eq!(latest_search_query.column, Some(file_column as u32));
});
- let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window.into(), SelectNext);
- cx.dispatch_action(window.into(), Confirm);
- active_pane
- .condition(cx, |pane, _| pane.active_item().is_some())
- .await;
- let editor = cx.update(|cx| {
- let active_item = active_pane.read(cx).active_item().unwrap();
- active_item.downcast::<Editor>().unwrap()
- });
- cx.foreground().advance_clock(Duration::from_secs(2));
- cx.foreground().start_waiting();
- cx.foreground().finish_waiting();
+ cx.dispatch_action(SelectNext);
+ cx.dispatch_action(Confirm);
+
+ let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
+ cx.executor().advance_clock(Duration::from_secs(2));
+
editor.update(cx, |editor, cx| {
- let all_selections = editor.selections.all_adjusted(cx);
- assert_eq!(
- all_selections.len(),
- 1,
- "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
- );
- let caret_selection = all_selections.into_iter().next().unwrap();
- assert_eq!(caret_selection.start, caret_selection.end,
- "Caret selection should have its start and end at the same position");
- assert_eq!(file_row, caret_selection.start.row + 1,
- "Query inside file should get caret with the same focus row");
- assert_eq!(file_column, caret_selection.start.column as usize + 1,
- "Query inside file should get caret with the same focus column");
- });
+ let all_selections = editor.selections.all_adjusted(cx);
+ assert_eq!(
+ all_selections.len(),
+ 1,
+ "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
+ );
+ let caret_selection = all_selections.into_iter().next().unwrap();
+ assert_eq!(caret_selection.start, caret_selection.end,
+ "Caret selection should have its start and end at the same position");
+ assert_eq!(file_row, caret_selection.start.row + 1,
+ "Query inside file should get caret with the same focus row");
+ assert_eq!(file_column, caret_selection.start.column as usize + 1,
+ "Query inside file should get caret with the same focus column");
+ });
}
#[gpui::test]
@@ -907,27 +915,25 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project, cx));
- let workspace = window.root(cx);
- cx.dispatch_action(window.into(), Toggle);
- let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
+ let (picker, workspace, cx) = build_find_picker(project, cx);
let file_query = &first_file_name[..3];
let file_row = 200;
let file_column = 300;
assert!(file_column > first_file_contents.len());
let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
- finder
- .update(cx, |finder, cx| {
- finder
- .delegate_mut()
+ picker
+ .update(cx, |picker, cx| {
+ picker
+ .delegate
.update_matches(query_outside_file.to_string(), cx)
})
.await;
- finder.read_with(cx, |finder, _| {
- let finder = finder.delegate();
- assert_eq!(finder.matches.len(), 1);
- let latest_search_query = finder
+ picker.update(cx, |finder, _| {
+ let delegate = &finder.delegate;
+ assert_eq!(delegate.matches.len(), 1);
+ let latest_search_query = delegate
.latest_search_query
.as_ref()
.expect("Finder should have a query after the update_matches call");
@@ -940,34 +946,27 @@ mod tests {
assert_eq!(latest_search_query.column, Some(file_column as u32));
});
- let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window.into(), SelectNext);
- cx.dispatch_action(window.into(), Confirm);
- active_pane
- .condition(cx, |pane, _| pane.active_item().is_some())
- .await;
- let editor = cx.update(|cx| {
- let active_item = active_pane.read(cx).active_item().unwrap();
- active_item.downcast::<Editor>().unwrap()
- });
- cx.foreground().advance_clock(Duration::from_secs(2));
- cx.foreground().start_waiting();
- cx.foreground().finish_waiting();
+ cx.dispatch_action(SelectNext);
+ cx.dispatch_action(Confirm);
+
+ let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
+ cx.executor().advance_clock(Duration::from_secs(2));
+
editor.update(cx, |editor, cx| {
- let all_selections = editor.selections.all_adjusted(cx);
- assert_eq!(
- all_selections.len(),
- 1,
- "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
- );
- let caret_selection = all_selections.into_iter().next().unwrap();
- assert_eq!(caret_selection.start, caret_selection.end,
- "Caret selection should have its start and end at the same position");
- assert_eq!(0, caret_selection.start.row,
- "Excessive rows (as in query outside file borders) should get trimmed to last file row");
- assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
- "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
- });
+ let all_selections = editor.selections.all_adjusted(cx);
+ assert_eq!(
+ all_selections.len(),
+ 1,
+ "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
+ );
+ let caret_selection = all_selections.into_iter().next().unwrap();
+ assert_eq!(caret_selection.start, caret_selection.end,
+ "Caret selection should have its start and end at the same position");
+ assert_eq!(0, caret_selection.start.row,
+ "Excessive rows (as in query outside file borders) should get trimmed to last file row");
+ assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
+ "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
+ });
}
#[gpui::test]
@@ -991,32 +990,22 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
- let workspace = cx
- .add_window(|cx| Workspace::test_new(project, cx))
- .root(cx);
- let finder = cx
- .add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
- cx,
- ),
- cx,
- )
- })
- .root(cx);
+
+ let (picker, _, cx) = build_find_picker(project, cx);
let query = test_path_like("hi");
- finder
- .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
+ picker
+ .update(cx, |picker, cx| {
+ picker.delegate.spawn_search(query.clone(), cx)
+ })
.await;
- finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5));
- finder.update(cx, |finder, cx| {
- let delegate = finder.delegate_mut();
+ picker.update(cx, |picker, _cx| {
+ assert_eq!(picker.delegate.matches.len(), 5)
+ });
+
+ picker.update(cx, |picker, cx| {
+ let delegate = &mut picker.delegate;
assert!(
delegate.matches.history.is_empty(),
"Search matches expected"
@@ -1088,31 +1077,17 @@ mod tests {
cx,
)
.await;
- let workspace = cx
- .add_window(|cx| Workspace::test_new(project, cx))
- .root(cx);
- let finder = cx
- .add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
- cx,
- ),
- cx,
- )
- })
- .root(cx);
- finder
- .update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("hi"), cx)
+
+ let (picker, _, cx) = build_find_picker(project, cx);
+
+ picker
+ .update(cx, |picker, cx| {
+ picker.delegate.spawn_search(test_path_like("hi"), cx)
})
.await;
- finder.update(cx, |f, _| {
+ picker.update(cx, |picker, _| {
assert_eq!(
- collect_search_results(f),
+ collect_search_results(picker),
vec![
PathBuf::from("ignored-root/happiness"),
PathBuf::from("ignored-root/height"),
@@ -1157,20 +1132,13 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project, cx));
- let workspace = window.root(cx);
- cx.dispatch_action(window.into(), Toggle);
- let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+ let (picker, workspace, cx) = build_find_picker(project, cx);
- finder
- .update(cx, |finder, cx| {
- finder.delegate_mut().update_matches("env".to_string(), cx)
- })
- .await;
- finder.update(cx, |f, _| {
+ cx.simulate_input("env");
+ picker.update(cx, |picker, _| {
assert_eq!(
- collect_search_results(f),
+ collect_search_results(picker),
vec![
PathBuf::from(".env"),
PathBuf::from("a/banana_env"),
@@ -1190,15 +1158,11 @@ mod tests {
})
.await
.unwrap();
- cx.foreground().run_until_parked();
- finder
- .update(cx, |finder, cx| {
- finder.delegate_mut().update_matches("env".to_string(), cx)
- })
- .await;
- finder.update(cx, |f, _| {
+ cx.run_until_parked();
+ cx.simulate_input("env");
+ picker.update(cx, |picker, _| {
assert_eq!(
- collect_search_results(f),
+ collect_search_results(picker),
vec![
PathBuf::from(".env"),
PathBuf::from("a/banana_env"),
@@ -1226,34 +1190,19 @@ mod tests {
cx,
)
.await;
- let workspace = cx
- .add_window(|cx| Workspace::test_new(project, cx))
- .root(cx);
- let finder = cx
- .add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
- cx,
- ),
- cx,
- )
- })
- .root(cx);
+
+ let (picker, _, cx) = build_find_picker(project, cx);
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
- finder
- .update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("thf"), cx)
+ picker
+ .update(cx, |picker, cx| {
+ picker.delegate.spawn_search(test_path_like("thf"), cx)
})
.await;
cx.read(|cx| {
- let finder = finder.read(cx);
- let delegate = finder.delegate();
+ let picker = picker.read(cx);
+ let delegate = &picker.delegate;
assert!(
delegate.matches.history.is_empty(),
"Search matches expected"
@@ -1271,12 +1220,12 @@ mod tests {
// Since the worktree root is a file, searching for its name followed by a slash does
// not match anything.
- finder
+ picker
.update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("thf/"), cx)
+ f.delegate.spawn_search(test_path_like("thf/"), cx)
})
.await;
- finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
+ picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
}
#[gpui::test]
@@ -1298,45 +1247,36 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let workspace = cx
- .add_window(|cx| Workspace::test_new(project, cx))
- .root(cx);
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
- WorktreeId::from_usize(worktrees[0].id())
+ WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
});
// When workspace has an active item, sort items which are closer to that item
// first when they have the same name. In this case, b.txt is closer to dir2's a.txt
// so that one should be sorted earlier
- let b_path = Some(dummy_found_path(ProjectPath {
+ let b_path = ProjectPath {
worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")),
- }));
- let finder = cx
- .add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- b_path,
- Vec::new(),
- cx,
- ),
- cx,
- )
+ };
+ workspace
+ .update(cx, |workspace, cx| {
+ workspace.open_path(b_path, None, true, cx)
})
- .root(cx);
-
+ .await
+ .unwrap();
+ let finder = open_file_picker(&workspace, cx);
finder
.update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("a.txt"), cx)
+ f.delegate.spawn_search(test_path_like("a.txt"), cx)
})
.await;
- finder.read_with(cx, |f, _| {
- let delegate = f.delegate();
+ finder.update(cx, |f, _| {
+ let delegate = &f.delegate;
assert!(
delegate.matches.history.is_empty(),
"Search matches expected"
@@ -1365,39 +1305,21 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let workspace = cx
- .add_window(|cx| Workspace::test_new(project, cx))
- .root(cx);
- let finder = cx
- .add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
- cx,
- ),
- cx,
- )
- })
- .root(cx);
- finder
+ let (picker, _workspace, cx) = build_find_picker(project, cx);
+
+ picker
.update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("dir"), cx)
+ f.delegate.spawn_search(test_path_like("dir"), cx)
})
.await;
cx.read(|cx| {
- let finder = finder.read(cx);
- assert_eq!(finder.delegate().matches.len(), 0);
+ let finder = picker.read(cx);
+ assert_eq!(finder.delegate.matches.len(), 0);
});
}
#[gpui::test]
- async fn test_query_history(
- deterministic: Arc<gpui::executor::Deterministic>,
- cx: &mut gpui::TestAppContext,
- ) {
+ async fn test_query_history(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
app_state
@@ -1416,12 +1338,11 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let window = cx.add_window(|cx| Workspace::test_new(project, cx));
- let workspace = window.root(cx);
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
- WorktreeId::from_usize(worktrees[0].id())
+ WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
});
// Open and close panels, getting their history items afterwards.
@@ -1,37 +0,0 @@
-[package]
-name = "file_finder2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/file_finder.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-collections = { path = "../collections" }
-fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-menu = { package = "menu2", path = "../menu2" }
-picker = { package = "picker2", path = "../picker2" }
-project = { package = "project2", path = "../project2" }
-settings = { package = "settings2", path = "../settings2" }
-text = { package = "text2", path = "../text2" }
-util = { path = "../util" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-postage.workspace = true
-serde.workspace = true
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-
-serde_json.workspace = true
-ctor.workspace = true
-env_logger.workspace = true
@@ -1,1956 +0,0 @@
-use collections::HashMap;
-use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
-use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
-use gpui::{
- actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
- ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
-};
-use picker::{Picker, PickerDelegate};
-use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
-use std::{
- path::{Path, PathBuf},
- sync::{
- atomic::{self, AtomicBool},
- Arc,
- },
-};
-use text::Point;
-use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
-use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
-use workspace::{ModalView, Workspace};
-
-actions!(file_finder, [Toggle]);
-
-impl ModalView for FileFinder {}
-
-pub struct FileFinder {
- picker: View<Picker<FileFinderDelegate>>,
-}
-
-pub fn init(cx: &mut AppContext) {
- cx.observe_new_views(FileFinder::register).detach();
-}
-
-impl FileFinder {
- fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|workspace, _: &Toggle, cx| {
- let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
- Self::open(workspace, cx);
- return;
- };
-
- file_finder.update(cx, |file_finder, cx| {
- file_finder
- .picker
- .update(cx, |picker, cx| picker.cycle_selection(cx))
- });
- });
- }
-
- fn open(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- let project = workspace.project().read(cx);
-
- let currently_opened_path = workspace
- .active_item(cx)
- .and_then(|item| item.project_path(cx))
- .map(|project_path| {
- let abs_path = project
- .worktree_for_id(project_path.worktree_id, cx)
- .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
- FoundPath::new(project_path, abs_path)
- });
-
- // if exists, bubble the currently opened path to the top
- let history_items = currently_opened_path
- .clone()
- .into_iter()
- .chain(
- workspace
- .recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx)
- .into_iter()
- .filter(|(history_path, _)| {
- Some(history_path)
- != currently_opened_path
- .as_ref()
- .map(|found_path| &found_path.project)
- })
- .filter(|(_, history_abs_path)| {
- history_abs_path.as_ref()
- != currently_opened_path
- .as_ref()
- .and_then(|found_path| found_path.absolute.as_ref())
- })
- .filter(|(_, history_abs_path)| match history_abs_path {
- Some(abs_path) => history_file_exists(abs_path),
- None => true,
- })
- .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)),
- )
- .collect();
-
- let project = workspace.project().clone();
- let weak_workspace = cx.view().downgrade();
- workspace.toggle_modal(cx, |cx| {
- let delegate = FileFinderDelegate::new(
- cx.view().downgrade(),
- weak_workspace,
- project,
- currently_opened_path,
- history_items,
- cx,
- );
-
- FileFinder::new(delegate, cx)
- });
- }
-
- fn new(delegate: FileFinderDelegate, cx: &mut ViewContext<Self>) -> Self {
- Self {
- picker: cx.new_view(|cx| Picker::new(delegate, cx)),
- }
- }
-}
-
-impl EventEmitter<DismissEvent> for FileFinder {}
-impl FocusableView for FileFinder {
- fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
- self.picker.focus_handle(cx)
- }
-}
-impl Render for FileFinder {
- fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
- v_stack().w(rems(34.)).child(self.picker.clone())
- }
-}
-
-pub struct FileFinderDelegate {
- file_finder: WeakView<FileFinder>,
- workspace: WeakView<Workspace>,
- project: Model<Project>,
- search_count: usize,
- latest_search_id: usize,
- latest_search_did_cancel: bool,
- latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
- currently_opened_path: Option<FoundPath>,
- matches: Matches,
- selected_index: Option<usize>,
- cancel_flag: Arc<AtomicBool>,
- history_items: Vec<FoundPath>,
-}
-
-#[derive(Debug, Default)]
-struct Matches {
- history: Vec<(FoundPath, Option<PathMatch>)>,
- search: Vec<PathMatch>,
-}
-
-#[derive(Debug)]
-enum Match<'a> {
- History(&'a FoundPath, Option<&'a PathMatch>),
- Search(&'a PathMatch),
-}
-
-impl Matches {
- fn len(&self) -> usize {
- self.history.len() + self.search.len()
- }
-
- fn get(&self, index: usize) -> Option<Match<'_>> {
- if index < self.history.len() {
- self.history
- .get(index)
- .map(|(path, path_match)| Match::History(path, path_match.as_ref()))
- } else {
- self.search
- .get(index - self.history.len())
- .map(Match::Search)
- }
- }
-
- fn push_new_matches(
- &mut self,
- history_items: &Vec<FoundPath>,
- query: &PathLikeWithPosition<FileSearchQuery>,
- mut new_search_matches: Vec<PathMatch>,
- extend_old_matches: bool,
- ) {
- let matching_history_paths = matching_history_item_paths(history_items, query);
- new_search_matches
- .retain(|path_match| !matching_history_paths.contains_key(&path_match.path));
- let history_items_to_show = history_items
- .iter()
- .filter_map(|history_item| {
- Some((
- history_item.clone(),
- Some(
- matching_history_paths
- .get(&history_item.project.path)?
- .clone(),
- ),
- ))
- })
- .collect::<Vec<_>>();
- self.history = history_items_to_show;
- if extend_old_matches {
- self.search
- .retain(|path_match| !matching_history_paths.contains_key(&path_match.path));
- util::extend_sorted(
- &mut self.search,
- new_search_matches.into_iter(),
- 100,
- |a, b| b.cmp(a),
- )
- } else {
- self.search = new_search_matches;
- }
- }
-}
-
-fn matching_history_item_paths(
- history_items: &Vec<FoundPath>,
- query: &PathLikeWithPosition<FileSearchQuery>,
-) -> HashMap<Arc<Path>, PathMatch> {
- let history_items_by_worktrees = history_items
- .iter()
- .filter_map(|found_path| {
- let candidate = PathMatchCandidate {
- path: &found_path.project.path,
- // Only match history items names, otherwise their paths may match too many queries, producing false positives.
- // E.g. `foo` would match both `something/foo/bar.rs` and `something/foo/foo.rs` and if the former is a history item,
- // it would be shown first always, despite the latter being a better match.
- char_bag: CharBag::from_iter(
- found_path
- .project
- .path
- .file_name()?
- .to_string_lossy()
- .to_lowercase()
- .chars(),
- ),
- };
- Some((found_path.project.worktree_id, candidate))
- })
- .fold(
- HashMap::default(),
- |mut candidates, (worktree_id, new_candidate)| {
- candidates
- .entry(worktree_id)
- .or_insert_with(Vec::new)
- .push(new_candidate);
- candidates
- },
- );
- let mut matching_history_paths = HashMap::default();
- for (worktree, candidates) in history_items_by_worktrees {
- let max_results = candidates.len() + 1;
- matching_history_paths.extend(
- fuzzy::match_fixed_path_set(
- candidates,
- worktree.to_usize(),
- query.path_like.path_query(),
- false,
- max_results,
- )
- .into_iter()
- .map(|path_match| (Arc::clone(&path_match.path), path_match)),
- );
- }
- matching_history_paths
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-struct FoundPath {
- project: ProjectPath,
- absolute: Option<PathBuf>,
-}
-
-impl FoundPath {
- fn new(project: ProjectPath, absolute: Option<PathBuf>) -> Self {
- Self { project, absolute }
- }
-}
-
-const MAX_RECENT_SELECTIONS: usize = 20;
-
-#[cfg(not(test))]
-fn history_file_exists(abs_path: &PathBuf) -> bool {
- abs_path.exists()
-}
-
-#[cfg(test)]
-fn history_file_exists(abs_path: &PathBuf) -> bool {
- !abs_path.ends_with("nonexistent.rs")
-}
-
-pub enum Event {
- Selected(ProjectPath),
- Dismissed,
-}
-
-#[derive(Debug, Clone)]
-struct FileSearchQuery {
- raw_query: String,
- file_query_end: Option<usize>,
-}
-
-impl FileSearchQuery {
- fn path_query(&self) -> &str {
- match self.file_query_end {
- Some(file_path_end) => &self.raw_query[..file_path_end],
- None => &self.raw_query,
- }
- }
-}
-
-impl FileFinderDelegate {
- fn new(
- file_finder: WeakView<FileFinder>,
- workspace: WeakView<Workspace>,
- project: Model<Project>,
- currently_opened_path: Option<FoundPath>,
- history_items: Vec<FoundPath>,
- cx: &mut ViewContext<FileFinder>,
- ) -> Self {
- cx.observe(&project, |file_finder, _, cx| {
- //todo!() We should probably not re-render on every project anything
- file_finder
- .picker
- .update(cx, |picker, cx| picker.refresh(cx))
- })
- .detach();
-
- Self {
- file_finder,
- workspace,
- project,
- search_count: 0,
- latest_search_id: 0,
- latest_search_did_cancel: false,
- latest_search_query: None,
- currently_opened_path,
- matches: Matches::default(),
- selected_index: None,
- cancel_flag: Arc::new(AtomicBool::new(false)),
- history_items,
- }
- }
-
- fn spawn_search(
- &mut self,
- query: PathLikeWithPosition<FileSearchQuery>,
- cx: &mut ViewContext<Picker<Self>>,
- ) -> Task<()> {
- let relative_to = self
- .currently_opened_path
- .as_ref()
- .map(|found_path| Arc::clone(&found_path.project.path));
- let worktrees = self
- .project
- .read(cx)
- .visible_worktrees(cx)
- .collect::<Vec<_>>();
- let include_root_name = worktrees.len() > 1;
- let candidate_sets = worktrees
- .into_iter()
- .map(|worktree| PathMatchCandidateSet {
- snapshot: worktree.read(cx).snapshot(),
- include_ignored: true,
- include_root_name,
- })
- .collect::<Vec<_>>();
-
- let search_id = util::post_inc(&mut self.search_count);
- self.cancel_flag.store(true, atomic::Ordering::Relaxed);
- self.cancel_flag = Arc::new(AtomicBool::new(false));
- let cancel_flag = self.cancel_flag.clone();
- cx.spawn(|picker, mut cx| async move {
- let matches = fuzzy::match_path_sets(
- candidate_sets.as_slice(),
- query.path_like.path_query(),
- relative_to,
- false,
- 100,
- &cancel_flag,
- cx.background_executor().clone(),
- )
- .await;
- let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
- picker
- .update(&mut cx, |picker, cx| {
- picker
- .delegate
- .set_search_matches(search_id, did_cancel, query, matches, cx)
- })
- .log_err();
- })
- }
-
- fn set_search_matches(
- &mut self,
- search_id: usize,
- did_cancel: bool,
- query: PathLikeWithPosition<FileSearchQuery>,
- matches: Vec<PathMatch>,
- cx: &mut ViewContext<Picker<Self>>,
- ) {
- if search_id >= self.latest_search_id {
- self.latest_search_id = search_id;
- let extend_old_matches = self.latest_search_did_cancel
- && Some(query.path_like.path_query())
- == self
- .latest_search_query
- .as_ref()
- .map(|query| query.path_like.path_query());
- self.matches
- .push_new_matches(&self.history_items, &query, matches, extend_old_matches);
- self.latest_search_query = Some(query);
- self.latest_search_did_cancel = did_cancel;
- cx.notify();
- }
- }
-
- fn labels_for_match(
- &self,
- path_match: Match,
- cx: &AppContext,
- ix: usize,
- ) -> (String, Vec<usize>, String, Vec<usize>) {
- let (file_name, file_name_positions, full_path, full_path_positions) = match path_match {
- Match::History(found_path, found_path_match) => {
- let worktree_id = found_path.project.worktree_id;
- let project_relative_path = &found_path.project.path;
- let has_worktree = self
- .project
- .read(cx)
- .worktree_for_id(worktree_id, cx)
- .is_some();
-
- if !has_worktree {
- if let Some(absolute_path) = &found_path.absolute {
- return (
- absolute_path
- .file_name()
- .map_or_else(
- || project_relative_path.to_string_lossy(),
- |file_name| file_name.to_string_lossy(),
- )
- .to_string(),
- Vec::new(),
- absolute_path.to_string_lossy().to_string(),
- Vec::new(),
- );
- }
- }
-
- let mut path = Arc::clone(project_relative_path);
- if project_relative_path.as_ref() == Path::new("") {
- if let Some(absolute_path) = &found_path.absolute {
- path = Arc::from(absolute_path.as_path());
- }
- }
-
- let mut path_match = PathMatch {
- score: ix as f64,
- positions: Vec::new(),
- worktree_id: worktree_id.to_usize(),
- path,
- path_prefix: "".into(),
- distance_to_relative_ancestor: usize::MAX,
- };
- if let Some(found_path_match) = found_path_match {
- path_match
- .positions
- .extend(found_path_match.positions.iter())
- }
-
- self.labels_for_path_match(&path_match)
- }
- Match::Search(path_match) => self.labels_for_path_match(path_match),
- };
-
- if file_name_positions.is_empty() {
- if let Some(user_home_path) = std::env::var("HOME").ok() {
- let user_home_path = user_home_path.trim();
- if !user_home_path.is_empty() {
- if (&full_path).starts_with(user_home_path) {
- return (
- file_name,
- file_name_positions,
- full_path.replace(user_home_path, "~"),
- full_path_positions,
- );
- }
- }
- }
- }
-
- (
- file_name,
- file_name_positions,
- full_path,
- full_path_positions,
- )
- }
-
- fn labels_for_path_match(
- &self,
- path_match: &PathMatch,
- ) -> (String, Vec<usize>, String, Vec<usize>) {
- let path = &path_match.path;
- let path_string = path.to_string_lossy();
- let full_path = [path_match.path_prefix.as_ref(), path_string.as_ref()].join("");
- let path_positions = path_match.positions.clone();
-
- let file_name = path.file_name().map_or_else(
- || path_match.path_prefix.to_string(),
- |file_name| file_name.to_string_lossy().to_string(),
- );
- let file_name_start = path_match.path_prefix.chars().count() + path_string.chars().count()
- - file_name.chars().count();
- let file_name_positions = path_positions
- .iter()
- .filter_map(|pos| {
- if pos >= &file_name_start {
- Some(pos - file_name_start)
- } else {
- None
- }
- })
- .collect();
-
- (file_name, file_name_positions, full_path, path_positions)
- }
-}
-
-impl PickerDelegate for FileFinderDelegate {
- type ListItem = ListItem;
-
- fn placeholder_text(&self) -> Arc<str> {
- "Search project files...".into()
- }
-
- fn match_count(&self) -> usize {
- self.matches.len()
- }
-
- fn selected_index(&self) -> usize {
- self.selected_index.unwrap_or(0)
- }
-
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
- self.selected_index = Some(ix);
- cx.notify();
- }
-
- fn separators_after_indices(&self) -> Vec<usize> {
- let history_items = self.matches.history.len();
- if history_items == 0 || self.matches.search.is_empty() {
- Vec::new()
- } else {
- vec![history_items - 1]
- }
- }
-
- fn update_matches(
- &mut self,
- raw_query: String,
- cx: &mut ViewContext<Picker<Self>>,
- ) -> Task<()> {
- let raw_query = raw_query.trim();
- if raw_query.is_empty() {
- let project = self.project.read(cx);
- self.latest_search_id = post_inc(&mut self.search_count);
- self.matches = Matches {
- history: self
- .history_items
- .iter()
- .filter(|history_item| {
- project
- .worktree_for_id(history_item.project.worktree_id, cx)
- .is_some()
- || (project.is_local() && history_item.absolute.is_some())
- })
- .cloned()
- .map(|p| (p, None))
- .collect(),
- search: Vec::new(),
- };
- cx.notify();
- Task::ready(())
- } else {
- let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
- Ok::<_, std::convert::Infallible>(FileSearchQuery {
- raw_query: raw_query.to_owned(),
- file_query_end: if path_like_str == raw_query {
- None
- } else {
- Some(path_like_str.len())
- },
- })
- })
- .expect("infallible");
- self.spawn_search(query, cx)
- }
- }
-
- fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
- if let Some(m) = self.matches.get(self.selected_index()) {
- if let Some(workspace) = self.workspace.upgrade() {
- let open_task = workspace.update(cx, move |workspace, cx| {
- let split_or_open = |workspace: &mut Workspace, project_path, cx| {
- if secondary {
- workspace.split_path(project_path, cx)
- } else {
- workspace.open_path(project_path, None, true, cx)
- }
- };
- match m {
- Match::History(history_match, _) => {
- let worktree_id = history_match.project.worktree_id;
- if workspace
- .project()
- .read(cx)
- .worktree_for_id(worktree_id, cx)
- .is_some()
- {
- split_or_open(
- workspace,
- ProjectPath {
- worktree_id,
- path: Arc::clone(&history_match.project.path),
- },
- cx,
- )
- } else {
- match history_match.absolute.as_ref() {
- Some(abs_path) => {
- if secondary {
- workspace.split_abs_path(
- abs_path.to_path_buf(),
- false,
- cx,
- )
- } else {
- workspace.open_abs_path(
- abs_path.to_path_buf(),
- false,
- cx,
- )
- }
- }
- None => split_or_open(
- workspace,
- ProjectPath {
- worktree_id,
- path: Arc::clone(&history_match.project.path),
- },
- cx,
- ),
- }
- }
- }
- Match::Search(m) => split_or_open(
- workspace,
- ProjectPath {
- worktree_id: WorktreeId::from_usize(m.worktree_id),
- path: m.path.clone(),
- },
- cx,
- ),
- }
- });
-
- let row = self
- .latest_search_query
- .as_ref()
- .and_then(|query| query.row)
- .map(|row| row.saturating_sub(1));
- let col = self
- .latest_search_query
- .as_ref()
- .and_then(|query| query.column)
- .unwrap_or(0)
- .saturating_sub(1);
- let finder = self.file_finder.clone();
-
- cx.spawn(|_, mut cx| async move {
- let item = open_task.await.log_err()?;
- if let Some(row) = row {
- if let Some(active_editor) = item.downcast::<Editor>() {
- active_editor
- .downgrade()
- .update(&mut cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot
- .buffer_snapshot
- .clip_point(Point::new(row, col), Bias::Left);
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([point..point])
- });
- })
- .log_err();
- }
- }
- finder.update(&mut cx, |_, cx| cx.emit(DismissEvent)).ok()?;
-
- Some(())
- })
- .detach();
- }
- }
- }
-
- fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
- self.file_finder
- .update(cx, |_, cx| cx.emit(DismissEvent))
- .log_err();
- }
-
- fn render_match(
- &self,
- ix: usize,
- selected: bool,
- cx: &mut ViewContext<Picker<Self>>,
- ) -> Option<Self::ListItem> {
- let path_match = self
- .matches
- .get(ix)
- .expect("Invalid matches state: no element for index {ix}");
-
- let (file_name, file_name_positions, full_path, full_path_positions) =
- self.labels_for_match(path_match, cx, ix);
-
- Some(
- ListItem::new(ix)
- .spacing(ListItemSpacing::Sparse)
- .inset(true)
- .selected(selected)
- .child(
- v_stack()
- .child(HighlightedLabel::new(file_name, file_name_positions))
- .child(HighlightedLabel::new(full_path, full_path_positions)),
- ),
- )
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::{assert_eq, path::Path, time::Duration};
-
- use super::*;
- use editor::Editor;
- use gpui::{Entity, TestAppContext, VisualTestContext};
- use menu::{Confirm, SelectNext};
- use serde_json::json;
- use workspace::{AppState, Workspace};
-
- #[ctor::ctor]
- fn init_logger() {
- if std::env::var("RUST_LOG").is_ok() {
- env_logger::init();
- }
- }
-
- #[gpui::test]
- async fn test_matching_paths(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "a": {
- "banana": "",
- "bandana": "",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-
- let (picker, workspace, cx) = build_find_picker(project, cx);
-
- cx.simulate_input("bna");
- picker.update(cx, |picker, _| {
- assert_eq!(picker.delegate.matches.len(), 2);
- });
- cx.dispatch_action(SelectNext);
- cx.dispatch_action(Confirm);
- cx.read(|cx| {
- let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
- assert_eq!(active_editor.read(cx).title(cx), "bandana");
- });
-
- for bandana_query in [
- "bandana",
- " bandana",
- "bandana ",
- " bandana ",
- " ndan ",
- " band ",
- ] {
- picker
- .update(cx, |picker, cx| {
- picker
- .delegate
- .update_matches(bandana_query.to_string(), cx)
- })
- .await;
- picker.update(cx, |picker, _| {
- assert_eq!(
- picker.delegate.matches.len(),
- 1,
- "Wrong number of matches for bandana query '{bandana_query}'"
- );
- });
- cx.dispatch_action(SelectNext);
- cx.dispatch_action(Confirm);
- cx.read(|cx| {
- let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
- assert_eq!(
- active_editor.read(cx).title(cx),
- "bandana",
- "Wrong match for bandana query '{bandana_query}'"
- );
- });
- }
- }
-
- #[gpui::test]
- async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
-
- let first_file_name = "first.rs";
- let first_file_contents = "// First Rust file";
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- first_file_name: first_file_contents,
- "second.rs": "// Second Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-
- let (picker, workspace, cx) = build_find_picker(project, cx);
-
- let file_query = &first_file_name[..3];
- let file_row = 1;
- let file_column = 3;
- assert!(file_column <= first_file_contents.len());
- let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
- picker
- .update(cx, |finder, cx| {
- finder
- .delegate
- .update_matches(query_inside_file.to_string(), cx)
- })
- .await;
- picker.update(cx, |finder, _| {
- let finder = &finder.delegate;
- assert_eq!(finder.matches.len(), 1);
- let latest_search_query = finder
- .latest_search_query
- .as_ref()
- .expect("Finder should have a query after the update_matches call");
- assert_eq!(latest_search_query.path_like.raw_query, query_inside_file);
- assert_eq!(
- latest_search_query.path_like.file_query_end,
- Some(file_query.len())
- );
- assert_eq!(latest_search_query.row, Some(file_row));
- assert_eq!(latest_search_query.column, Some(file_column as u32));
- });
-
- cx.dispatch_action(SelectNext);
- cx.dispatch_action(Confirm);
-
- let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
- cx.executor().advance_clock(Duration::from_secs(2));
-
- editor.update(cx, |editor, cx| {
- let all_selections = editor.selections.all_adjusted(cx);
- assert_eq!(
- all_selections.len(),
- 1,
- "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
- );
- let caret_selection = all_selections.into_iter().next().unwrap();
- assert_eq!(caret_selection.start, caret_selection.end,
- "Caret selection should have its start and end at the same position");
- assert_eq!(file_row, caret_selection.start.row + 1,
- "Query inside file should get caret with the same focus row");
- assert_eq!(file_column, caret_selection.start.column as usize + 1,
- "Query inside file should get caret with the same focus column");
- });
- }
-
- #[gpui::test]
- async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
-
- let first_file_name = "first.rs";
- let first_file_contents = "// First Rust file";
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- first_file_name: first_file_contents,
- "second.rs": "// Second Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-
- let (picker, workspace, cx) = build_find_picker(project, cx);
-
- let file_query = &first_file_name[..3];
- let file_row = 200;
- let file_column = 300;
- assert!(file_column > first_file_contents.len());
- let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
- picker
- .update(cx, |picker, cx| {
- picker
- .delegate
- .update_matches(query_outside_file.to_string(), cx)
- })
- .await;
- picker.update(cx, |finder, _| {
- let delegate = &finder.delegate;
- assert_eq!(delegate.matches.len(), 1);
- let latest_search_query = delegate
- .latest_search_query
- .as_ref()
- .expect("Finder should have a query after the update_matches call");
- assert_eq!(latest_search_query.path_like.raw_query, query_outside_file);
- assert_eq!(
- latest_search_query.path_like.file_query_end,
- Some(file_query.len())
- );
- assert_eq!(latest_search_query.row, Some(file_row));
- assert_eq!(latest_search_query.column, Some(file_column as u32));
- });
-
- cx.dispatch_action(SelectNext);
- cx.dispatch_action(Confirm);
-
- let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
- cx.executor().advance_clock(Duration::from_secs(2));
-
- editor.update(cx, |editor, cx| {
- let all_selections = editor.selections.all_adjusted(cx);
- assert_eq!(
- all_selections.len(),
- 1,
- "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
- );
- let caret_selection = all_selections.into_iter().next().unwrap();
- assert_eq!(caret_selection.start, caret_selection.end,
- "Caret selection should have its start and end at the same position");
- assert_eq!(0, caret_selection.start.row,
- "Excessive rows (as in query outside file borders) should get trimmed to last file row");
- assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
- "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
- });
- }
-
- #[gpui::test]
- async fn test_matching_cancellation(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/dir",
- json!({
- "hello": "",
- "goodbye": "",
- "halogen-light": "",
- "happiness": "",
- "height": "",
- "hi": "",
- "hiccup": "",
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
-
- let (picker, _, cx) = build_find_picker(project, cx);
-
- let query = test_path_like("hi");
- picker
- .update(cx, |picker, cx| {
- picker.delegate.spawn_search(query.clone(), cx)
- })
- .await;
-
- picker.update(cx, |picker, _cx| {
- assert_eq!(picker.delegate.matches.len(), 5)
- });
-
- picker.update(cx, |picker, cx| {
- let delegate = &mut picker.delegate;
- assert!(
- delegate.matches.history.is_empty(),
- "Search matches expected"
- );
- let matches = delegate.matches.search.clone();
-
- // Simulate a search being cancelled after the time limit,
- // returning only a subset of the matches that would have been found.
- drop(delegate.spawn_search(query.clone(), cx));
- delegate.set_search_matches(
- delegate.latest_search_id,
- true, // did-cancel
- query.clone(),
- vec![matches[1].clone(), matches[3].clone()],
- cx,
- );
-
- // Simulate another cancellation.
- drop(delegate.spawn_search(query.clone(), cx));
- delegate.set_search_matches(
- delegate.latest_search_id,
- true, // did-cancel
- query.clone(),
- vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
- cx,
- );
-
- assert!(
- delegate.matches.history.is_empty(),
- "Search matches expected"
- );
- assert_eq!(delegate.matches.search.as_slice(), &matches[0..4]);
- });
- }
-
- #[gpui::test]
- async fn test_ignored_root(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/ancestor",
- json!({
- ".gitignore": "ignored-root",
- "ignored-root": {
- "happiness": "",
- "height": "",
- "hi": "",
- "hiccup": "",
- },
- "tracked-root": {
- ".gitignore": "height",
- "happiness": "",
- "height": "",
- "hi": "",
- "hiccup": "",
- },
- }),
- )
- .await;
-
- let project = Project::test(
- app_state.fs.clone(),
- [
- "/ancestor/tracked-root".as_ref(),
- "/ancestor/ignored-root".as_ref(),
- ],
- cx,
- )
- .await;
-
- let (picker, _, cx) = build_find_picker(project, cx);
-
- picker
- .update(cx, |picker, cx| {
- picker.delegate.spawn_search(test_path_like("hi"), cx)
- })
- .await;
- picker.update(cx, |picker, _| {
- assert_eq!(
- collect_search_results(picker),
- vec![
- PathBuf::from("ignored-root/happiness"),
- PathBuf::from("ignored-root/height"),
- PathBuf::from("ignored-root/hi"),
- PathBuf::from("ignored-root/hiccup"),
- PathBuf::from("tracked-root/happiness"),
- PathBuf::from("tracked-root/height"),
- PathBuf::from("tracked-root/hi"),
- PathBuf::from("tracked-root/hiccup"),
- ],
- "All files in all roots (including gitignored) should be searched"
- )
- });
- }
-
- #[gpui::test]
- async fn test_ignored_files(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- ".git": {},
- ".gitignore": "ignored_a\n.env\n",
- "a": {
- "banana_env": "11",
- "bandana_env": "12",
- },
- "ignored_a": {
- "ignored_banana_env": "21",
- "ignored_bandana_env": "22",
- "ignored_nested": {
- "ignored_nested_banana_env": "31",
- "ignored_nested_bandana_env": "32",
- },
- },
- ".env": "something",
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-
- let (picker, workspace, cx) = build_find_picker(project, cx);
-
- cx.simulate_input("env");
- picker.update(cx, |picker, _| {
- assert_eq!(
- collect_search_results(picker),
- vec![
- PathBuf::from(".env"),
- PathBuf::from("a/banana_env"),
- PathBuf::from("a/bandana_env"),
- ],
- "Root gitignored files and all non-gitignored files should be searched"
- )
- });
-
- let _ = workspace
- .update(cx, |workspace, cx| {
- workspace.open_abs_path(
- PathBuf::from("/root/ignored_a/ignored_banana_env"),
- true,
- cx,
- )
- })
- .await
- .unwrap();
- cx.run_until_parked();
- cx.simulate_input("env");
- picker.update(cx, |picker, _| {
- assert_eq!(
- collect_search_results(picker),
- vec![
- PathBuf::from(".env"),
- PathBuf::from("a/banana_env"),
- PathBuf::from("a/bandana_env"),
- PathBuf::from("ignored_a/ignored_banana_env"),
- PathBuf::from("ignored_a/ignored_bandana_env"),
- ],
- "Root gitignored dir got listed and its entries got into worktree, but all gitignored dirs below it were not listed. Old entries + new listed gitignored entries should be searched"
- )
- });
- }
-
- #[gpui::test]
- async fn test_single_file_worktrees(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
- .await;
-
- let project = Project::test(
- app_state.fs.clone(),
- ["/root/the-parent-dir/the-file".as_ref()],
- cx,
- )
- .await;
-
- let (picker, _, cx) = build_find_picker(project, cx);
-
- // Even though there is only one worktree, that worktree's filename
- // is included in the matching, because the worktree is a single file.
- picker
- .update(cx, |picker, cx| {
- picker.delegate.spawn_search(test_path_like("thf"), cx)
- })
- .await;
- cx.read(|cx| {
- let picker = picker.read(cx);
- let delegate = &picker.delegate;
- assert!(
- delegate.matches.history.is_empty(),
- "Search matches expected"
- );
- let matches = delegate.matches.search.clone();
- assert_eq!(matches.len(), 1);
-
- let (file_name, file_name_positions, full_path, full_path_positions) =
- delegate.labels_for_path_match(&matches[0]);
- assert_eq!(file_name, "the-file");
- assert_eq!(file_name_positions, &[0, 1, 4]);
- assert_eq!(full_path, "the-file");
- assert_eq!(full_path_positions, &[0, 1, 4]);
- });
-
- // Since the worktree root is a file, searching for its name followed by a slash does
- // not match anything.
- picker
- .update(cx, |f, cx| {
- f.delegate.spawn_search(test_path_like("thf/"), cx)
- })
- .await;
- picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
- }
-
- #[gpui::test]
- async fn test_path_distance_ordering(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "dir1": { "a.txt": "" },
- "dir2": {
- "a.txt": "",
- "b.txt": ""
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-
- let worktree_id = cx.read(|cx| {
- let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
- assert_eq!(worktrees.len(), 1);
- WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
- });
-
- // When workspace has an active item, sort items which are closer to that item
- // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
- // so that one should be sorted earlier
- let b_path = ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("/root/dir2/b.txt")),
- };
- workspace
- .update(cx, |workspace, cx| {
- workspace.open_path(b_path, None, true, cx)
- })
- .await
- .unwrap();
- let finder = open_file_picker(&workspace, cx);
- finder
- .update(cx, |f, cx| {
- f.delegate.spawn_search(test_path_like("a.txt"), cx)
- })
- .await;
-
- finder.update(cx, |f, _| {
- let delegate = &f.delegate;
- assert!(
- delegate.matches.history.is_empty(),
- "Search matches expected"
- );
- let matches = delegate.matches.search.clone();
- assert_eq!(matches[0].path.as_ref(), Path::new("dir2/a.txt"));
- assert_eq!(matches[1].path.as_ref(), Path::new("dir1/a.txt"));
- });
- }
-
- #[gpui::test]
- async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "dir1": {},
- "dir2": {
- "dir3": {}
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (picker, _workspace, cx) = build_find_picker(project, cx);
-
- picker
- .update(cx, |f, cx| {
- f.delegate.spawn_search(test_path_like("dir"), cx)
- })
- .await;
- cx.read(|cx| {
- let finder = picker.read(cx);
- assert_eq!(finder.delegate.matches.len(), 0);
- });
- }
-
- #[gpui::test]
- async fn test_query_history(cx: &mut gpui::TestAppContext) {
- let app_state = init_test(cx);
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- "first.rs": "// First Rust file",
- "second.rs": "// Second Rust file",
- "third.rs": "// Third Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
- let worktree_id = cx.read(|cx| {
- let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
- assert_eq!(worktrees.len(), 1);
- WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
- });
-
- // Open and close panels, getting their history items afterwards.
- // Ensure history items get populated with opened items, and items are kept in a certain order.
- // The history lags one opened buffer behind, since it's updated in the search panel only on its reopen.
- //
- // TODO: without closing, the opened items do not propagate their history changes for some reason
- // it does work in real app though, only tests do not propagate.
- workspace.update(cx, |_, cx| cx.focused());
-
- let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
- assert!(
- initial_history.is_empty(),
- "Should have no history before opening any files"
- );
-
- let history_after_first =
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
- assert_eq!(
- history_after_first,
- vec![FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/first.rs")),
- },
- Some(PathBuf::from("/src/test/first.rs"))
- )],
- "Should show 1st opened item in the history when opening the 2nd item"
- );
-
- let history_after_second =
- open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
- assert_eq!(
- history_after_second,
- vec![
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/second.rs")),
- },
- Some(PathBuf::from("/src/test/second.rs"))
- ),
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/first.rs")),
- },
- Some(PathBuf::from("/src/test/first.rs"))
- ),
- ],
- "Should show 1st and 2nd opened items in the history when opening the 3rd item. \
- 2nd item should be the first in the history, as the last opened."
- );
-
- let history_after_third =
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
- assert_eq!(
- history_after_third,
- vec![
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/third.rs")),
- },
- Some(PathBuf::from("/src/test/third.rs"))
- ),
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/second.rs")),
- },
- Some(PathBuf::from("/src/test/second.rs"))
- ),
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/first.rs")),
- },
- Some(PathBuf::from("/src/test/first.rs"))
- ),
- ],
- "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \
- 3rd item should be the first in the history, as the last opened."
- );
-
- let history_after_second_again =
- open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
- assert_eq!(
- history_after_second_again,
- vec![
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/second.rs")),
- },
- Some(PathBuf::from("/src/test/second.rs"))
- ),
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/third.rs")),
- },
- Some(PathBuf::from("/src/test/third.rs"))
- ),
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/first.rs")),
- },
- Some(PathBuf::from("/src/test/first.rs"))
- ),
- ],
- "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \
- 2nd item, as the last opened, 3rd item should go next as it was opened right before."
- );
- }
-
- #[gpui::test]
- async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
- let app_state = init_test(cx);
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- "first.rs": "// First Rust file",
- "second.rs": "// Second Rust file",
- }
- }),
- )
- .await;
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/external-src",
- json!({
- "test": {
- "third.rs": "// Third Rust file",
- "fourth.rs": "// Fourth Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- cx.update(|cx| {
- project.update(cx, |project, cx| {
- project.find_or_create_local_worktree("/external-src", false, cx)
- })
- })
- .detach();
- cx.background_executor.run_until_parked();
-
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
- let worktree_id = cx.read(|cx| {
- let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
- assert_eq!(worktrees.len(), 1,);
-
- WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
- });
- workspace
- .update(cx, |workspace, cx| {
- workspace.open_abs_path(PathBuf::from("/external-src/test/third.rs"), false, cx)
- })
- .detach();
- cx.background_executor.run_until_parked();
- let external_worktree_id = cx.read(|cx| {
- let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
- assert_eq!(
- worktrees.len(),
- 2,
- "External file should get opened in a new worktree"
- );
-
- WorktreeId::from_usize(
- worktrees
- .into_iter()
- .find(|worktree| {
- worktree.entity_id().as_u64() as usize != worktree_id.to_usize()
- })
- .expect("New worktree should have a different id")
- .entity_id()
- .as_u64() as usize,
- )
- });
- cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
-
- let initial_history_items =
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
- assert_eq!(
- initial_history_items,
- vec![FoundPath::new(
- ProjectPath {
- worktree_id: external_worktree_id,
- path: Arc::from(Path::new("")),
- },
- Some(PathBuf::from("/external-src/test/third.rs"))
- )],
- "Should show external file with its full path in the history after it was open"
- );
-
- let updated_history_items =
- open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
- assert_eq!(
- updated_history_items,
- vec![
- FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/second.rs")),
- },
- Some(PathBuf::from("/src/test/second.rs"))
- ),
- FoundPath::new(
- ProjectPath {
- worktree_id: external_worktree_id,
- path: Arc::from(Path::new("")),
- },
- Some(PathBuf::from("/external-src/test/third.rs"))
- ),
- ],
- "Should keep external file with history updates",
- );
- }
-
- #[gpui::test]
- async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
- let app_state = init_test(cx);
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- "first.rs": "// First Rust file",
- "second.rs": "// Second Rust file",
- "third.rs": "// Third Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-
- // generate some history to select from
- open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
- cx.executor().run_until_parked();
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
- open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
- let current_history =
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
- for expected_selected_index in 0..current_history.len() {
- cx.dispatch_action(Toggle);
- let picker = active_file_picker(&workspace, cx);
- let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
- assert_eq!(
- selected_index, expected_selected_index,
- "Should select the next item in the history"
- );
- }
-
- cx.dispatch_action(Toggle);
- let selected_index = workspace.update(cx, |workspace, cx| {
- workspace
- .active_modal::<FileFinder>(cx)
- .unwrap()
- .read(cx)
- .picker
- .read(cx)
- .delegate
- .selected_index()
- });
- assert_eq!(
- selected_index, 0,
- "Should wrap around the history and start all over"
- );
- }
-
- #[gpui::test]
- async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
- let app_state = init_test(cx);
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- "first.rs": "// First Rust file",
- "second.rs": "// Second Rust file",
- "third.rs": "// Third Rust file",
- "fourth.rs": "// Fourth Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
- let worktree_id = cx.read(|cx| {
- let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
- assert_eq!(worktrees.len(), 1,);
-
- WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
- });
-
- // generate some history to select from
- open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
- open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
- let finder = open_file_picker(&workspace, cx);
- let first_query = "f";
- finder
- .update(cx, |finder, cx| {
- finder.delegate.update_matches(first_query.to_string(), cx)
- })
- .await;
- finder.update(cx, |finder, _| {
- let delegate = &finder.delegate;
- assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
- let history_match = delegate.matches.history.first().unwrap();
- assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
- assert_eq!(history_match.0, FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/first.rs")),
- },
- Some(PathBuf::from("/src/test/first.rs"))
- ));
- assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
- assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
- });
-
- let second_query = "fsdasdsa";
- let finder = active_file_picker(&workspace, cx);
- finder
- .update(cx, |finder, cx| {
- finder.delegate.update_matches(second_query.to_string(), cx)
- })
- .await;
- finder.update(cx, |finder, _| {
- let delegate = &finder.delegate;
- assert!(
- delegate.matches.history.is_empty(),
- "No history entries should match {second_query}"
- );
- assert!(
- delegate.matches.search.is_empty(),
- "No search entries should match {second_query}"
- );
- });
-
- let first_query_again = first_query;
-
- let finder = active_file_picker(&workspace, cx);
- finder
- .update(cx, |finder, cx| {
- finder
- .delegate
- .update_matches(first_query_again.to_string(), cx)
- })
- .await;
- finder.update(cx, |finder, _| {
- let delegate = &finder.delegate;
- assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
- let history_match = delegate.matches.history.first().unwrap();
- assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
- assert_eq!(history_match.0, FoundPath::new(
- ProjectPath {
- worktree_id,
- path: Arc::from(Path::new("test/first.rs")),
- },
- Some(PathBuf::from("/src/test/first.rs"))
- ));
- assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
- assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
- });
- }
-
- #[gpui::test]
- async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
- let app_state = init_test(cx);
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "collab_ui": {
- "first.rs": "// First Rust file",
- "second.rs": "// Second Rust file",
- "third.rs": "// Third Rust file",
- "collab_ui.rs": "// Fourth Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
- // generate some history to select from
- open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
- open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
- open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
- let finder = open_file_picker(&workspace, cx);
- let query = "collab_ui";
- cx.simulate_input(query);
- finder.update(cx, |finder, _| {
- let delegate = &finder.delegate;
- assert!(
- delegate.matches.history.is_empty(),
- "History items should not math query {query}, they should be matched by name only"
- );
-
- let search_entries = delegate
- .matches
- .search
- .iter()
- .map(|path_match| path_match.path.to_path_buf())
- .collect::<Vec<_>>();
- assert_eq!(
- search_entries,
- vec![
- PathBuf::from("collab_ui/collab_ui.rs"),
- PathBuf::from("collab_ui/third.rs"),
- PathBuf::from("collab_ui/first.rs"),
- PathBuf::from("collab_ui/second.rs"),
- ],
- "Despite all search results having the same directory name, the most matching one should be on top"
- );
- });
- }
-
- #[gpui::test]
- async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext) {
- let app_state = init_test(cx);
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/src",
- json!({
- "test": {
- "first.rs": "// First Rust file",
- "nonexistent.rs": "// Second Rust file",
- "third.rs": "// Third Rust file",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); // generate some history to select from
- open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
- open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
- open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
- open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-
- let picker = open_file_picker(&workspace, cx);
- cx.simulate_input("rs");
-
- picker.update(cx, |finder, _| {
- let history_entries = finder.delegate
- .matches
- .history
- .iter()
- .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf())
- .collect::<Vec<_>>();
- assert_eq!(
- history_entries,
- vec![
- PathBuf::from("test/first.rs"),
- PathBuf::from("test/third.rs"),
- ],
- "Should have all opened files in the history, except the ones that do not exist on disk"
- );
- });
- }
-
- async fn open_close_queried_buffer(
- input: &str,
- expected_matches: usize,
- expected_editor_title: &str,
- workspace: &View<Workspace>,
- cx: &mut gpui::VisualTestContext<'_>,
- ) -> Vec<FoundPath> {
- let picker = open_file_picker(&workspace, cx);
- cx.simulate_input(input);
-
- let history_items = picker.update(cx, |finder, _| {
- assert_eq!(
- finder.delegate.matches.len(),
- expected_matches,
- "Unexpected number of matches found for query {input}"
- );
- finder.delegate.history_items.clone()
- });
-
- cx.dispatch_action(SelectNext);
- cx.dispatch_action(Confirm);
-
- cx.read(|cx| {
- let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
- let active_editor_title = active_editor.read(cx).title(cx);
- assert_eq!(
- expected_editor_title, active_editor_title,
- "Unexpected editor title for query {input}"
- );
- });
-
- cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
-
- history_items
- }
-
- fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
- cx.update(|cx| {
- let state = AppState::test(cx);
- theme::init(theme::LoadThemes::JustBase, cx);
- language::init(cx);
- super::init(cx);
- editor::init(cx);
- workspace::init_settings(cx);
- Project::init_settings(cx);
- state
- })
- }
-
- fn test_path_like(test_str: &str) -> PathLikeWithPosition<FileSearchQuery> {
- PathLikeWithPosition::parse_str(test_str, |path_like_str| {
- Ok::<_, std::convert::Infallible>(FileSearchQuery {
- raw_query: test_str.to_owned(),
- file_query_end: if path_like_str == test_str {
- None
- } else {
- Some(path_like_str.len())
- },
- })
- })
- .unwrap()
- }
-
- fn build_find_picker(
- project: Model<Project>,
- cx: &mut TestAppContext,
- ) -> (
- View<Picker<FileFinderDelegate>>,
- View<Workspace>,
- &mut VisualTestContext,
- ) {
- let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
- let picker = open_file_picker(&workspace, cx);
- (picker, workspace, cx)
- }
-
- #[track_caller]
- fn open_file_picker(
- workspace: &View<Workspace>,
- cx: &mut VisualTestContext,
- ) -> View<Picker<FileFinderDelegate>> {
- cx.dispatch_action(Toggle);
- active_file_picker(workspace, cx)
- }
-
- #[track_caller]
- fn active_file_picker(
- workspace: &View<Workspace>,
- cx: &mut VisualTestContext,
- ) -> View<Picker<FileFinderDelegate>> {
- workspace.update(cx, |workspace, cx| {
- workspace
- .active_modal::<FileFinder>(cx)
- .unwrap()
- .read(cx)
- .picker
- .clone()
- })
- }
-
- fn collect_search_results(picker: &Picker<FileFinderDelegate>) -> Vec<PathBuf> {
- let matches = &picker.delegate.matches;
- assert!(
- matches.history.is_empty(),
- "Should have no history matches, but got: {:?}",
- matches.history
- );
- let mut results = matches
- .search
- .iter()
- .map(|path_match| Path::new(path_match.path_prefix.as_ref()).join(&path_match.path))
- .collect::<Vec<_>>();
- results.sort();
- results
- }
-}
@@ -9,15 +9,17 @@ path = "src/go_to_line.rs"
doctest = false
[dependencies]
-editor = { path = "../editor" }
-gpui = { path = "../gpui" }
-menu = { path = "../menu" }
-settings = { path = "../settings" }
-text = { path = "../text" }
-workspace = { path = "../workspace" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+menu = { package = "menu2", path = "../menu2" }
+serde.workspace = true
+settings = { package = "settings2", path = "../settings2" }
+text = { package = "text2", path = "../text2" }
+workspace = { package = "workspace2", path = "../workspace2" }
postage.workspace = true
-theme = { path = "../theme" }
+theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
[dev-dependencies]
-editor = { path = "../editor", features = ["test-support"] }
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
@@ -1,117 +1,117 @@
-use std::sync::Arc;
-
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
use gpui::{
- actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity,
- View, ViewContext, ViewHandle,
+ actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
+ FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
};
-use menu::{Cancel, Confirm};
use text::{Bias, Point};
+use theme::ActiveTheme;
+use ui::{h_stack, prelude::*, v_stack, Label};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::{Modal, Workspace};
+use workspace::ModalView;
actions!(go_to_line, [Toggle]);
pub fn init(cx: &mut AppContext) {
- cx.add_action(GoToLine::toggle);
- cx.add_action(GoToLine::confirm);
- cx.add_action(GoToLine::cancel);
+ cx.observe_new_views(GoToLine::register).detach();
}
pub struct GoToLine {
- line_editor: ViewHandle<Editor>,
- active_editor: ViewHandle<Editor>,
- prev_scroll_position: Option<Vector2F>,
- cursor_point: Point,
- max_point: Point,
- has_focus: bool,
+ line_editor: View<Editor>,
+ active_editor: View<Editor>,
+ current_text: SharedString,
+ prev_scroll_position: Option<gpui::Point<f32>>,
+ _subscriptions: Vec<Subscription>,
}
-pub enum Event {
- Dismissed,
+impl ModalView for GoToLine {}
+
+impl FocusableView for GoToLine {
+ fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+ self.line_editor.focus_handle(cx)
+ }
}
+impl EventEmitter<DismissEvent> for GoToLine {}
impl GoToLine {
- pub fn new(active_editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) -> Self {
- let line_editor = cx.add_view(|cx| {
- Editor::single_line(
- Some(Arc::new(|theme| theme.picker.input_editor.clone())),
- cx,
- )
- });
- cx.subscribe(&line_editor, Self::on_line_editor_event)
- .detach();
-
- let (scroll_position, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
- let scroll_position = editor.scroll_position(cx);
- let buffer = editor.buffer().read(cx).snapshot(cx);
- (
- Some(scroll_position),
- editor.selections.newest(cx).head(),
- buffer.max_point(),
- )
+ fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+ let handle = cx.view().downgrade();
+ editor.register_action(move |_: &Toggle, cx| {
+ let Some(editor) = handle.upgrade() else {
+ return;
+ };
+ let Some(workspace) = editor.read(cx).workspace() else {
+ return;
+ };
+ workspace.update(cx, |workspace, cx| {
+ workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
+ })
});
+ }
+
+ pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
+ let line_editor = cx.new_view(|cx| Editor::single_line(cx));
+ let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
+
+ let editor = active_editor.read(cx);
+ let cursor = editor.selections.last::<Point>(cx).head();
+ let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
+ let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
+
+ let current_text = format!(
+ "line {} of {} (column {})",
+ cursor.row + 1,
+ last_line + 1,
+ cursor.column + 1,
+ );
Self {
line_editor,
active_editor,
- prev_scroll_position: scroll_position,
- cursor_point,
- max_point,
- has_focus: false,
+ current_text: current_text.into(),
+ prev_scroll_position: Some(scroll_position),
+ _subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
}
}
- fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
- if let Some(editor) = workspace
- .active_item(cx)
- .and_then(|active_item| active_item.downcast::<Editor>())
- {
- workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx)));
- }
+ fn release(&mut self, window: AnyWindowHandle, cx: &mut AppContext) {
+ window
+ .update(cx, |_, cx| {
+ let scroll_position = self.prev_scroll_position.take();
+ self.active_editor.update(cx, |editor, cx| {
+ editor.highlight_rows(None);
+ if let Some(scroll_position) = scroll_position {
+ editor.set_scroll_position(scroll_position, cx);
+ }
+ cx.notify();
+ })
+ })
+ .ok();
}
- fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
- cx.emit(Event::Dismissed);
+ fn on_line_editor_event(
+ &mut self,
+ _: View<Editor>,
+ event: &editor::EditorEvent,
+ cx: &mut ViewContext<Self>,
+ ) {
+ match event {
+ editor::EditorEvent::Blurred => cx.emit(DismissEvent),
+ editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
+ _ => {}
+ }
}
- fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
- self.prev_scroll_position.take();
+ fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
if let Some(point) = self.point_from_query(cx) {
self.active_editor.update(cx, |active_editor, cx| {
let snapshot = active_editor.snapshot(cx).display_snapshot;
let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([point..point])
- });
+ let display_point = point.to_display_point(&snapshot);
+ let row = display_point.row();
+ active_editor.highlight_rows(Some(row..row + 1));
+ active_editor.request_autoscroll(Autoscroll::center(), cx);
});
- }
-
- cx.emit(Event::Dismissed);
- }
-
- fn on_line_editor_event(
- &mut self,
- _: ViewHandle<Editor>,
- event: &editor::Event,
- cx: &mut ViewContext<Self>,
- ) {
- match event {
- editor::Event::Blurred => cx.emit(Event::Dismissed),
- editor::Event::BufferEdited { .. } => {
- if let Some(point) = self.point_from_query(cx) {
- self.active_editor.update(cx, |active_editor, cx| {
- let snapshot = active_editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- let display_point = point.to_display_point(&snapshot);
- let row = display_point.row();
- active_editor.highlight_rows(Some(row..row + 1));
- active_editor.request_autoscroll(Autoscroll::center(), cx);
- });
- cx.notify();
- }
- }
- _ => {}
+ cx.notify();
}
}
@@ -128,73 +128,61 @@ impl GoToLine {
column.unwrap_or(0).saturating_sub(1),
))
}
-}
-
-impl Entity for GoToLine {
- type Event = Event;
-
- fn release(&mut self, cx: &mut AppContext) {
- let scroll_position = self.prev_scroll_position.take();
- self.active_editor.window().update(cx, |cx| {
- self.active_editor.update(cx, |editor, cx| {
- editor.highlight_rows(None);
- if let Some(scroll_position) = scroll_position {
- editor.set_scroll_position(scroll_position, cx);
- }
- })
- });
- }
-}
-
-impl View for GoToLine {
- fn ui_name() -> &'static str {
- "GoToLine"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- let theme = &theme::current(cx).picker;
- let label = format!(
- "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",
- self.cursor_point.row + 1,
- self.cursor_point.column + 1,
- self.max_point.row + 1
- );
-
- Flex::new(Axis::Vertical)
- .with_child(
- ChildView::new(&self.line_editor, cx)
- .contained()
- .with_style(theme.input_editor.container),
- )
- .with_child(
- Label::new(label, theme.no_matches.label.clone())
- .contained()
- .with_style(theme.no_matches.container),
- )
- .contained()
- .with_style(theme.container)
- .constrained()
- .with_max_width(500.0)
- .into_any_named("go to line")
+ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+ cx.emit(DismissEvent);
}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- self.has_focus = true;
- cx.focus(&self.line_editor);
- }
+ fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+ if let Some(point) = self.point_from_query(cx) {
+ self.active_editor.update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx).display_snapshot;
+ let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
+ editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_ranges([point..point])
+ });
+ editor.focus(cx);
+ cx.notify();
+ });
+ self.prev_scroll_position.take();
+ }
- fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
- self.has_focus = false;
+ cx.emit(DismissEvent);
}
}
-impl Modal for GoToLine {
- fn has_focus(&self) -> bool {
- self.has_focus
- }
-
- fn dismiss_on_event(event: &Self::Event) -> bool {
- matches!(event, Event::Dismissed)
+impl Render for GoToLine {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ div()
+ .elevation_2(cx)
+ .key_context("GoToLine")
+ .on_action(cx.listener(Self::cancel))
+ .on_action(cx.listener(Self::confirm))
+ .w_96()
+ .child(
+ v_stack()
+ .px_1()
+ .pt_0p5()
+ .gap_px()
+ .child(
+ v_stack()
+ .py_0p5()
+ .px_1()
+ .child(div().px_1().py_0p5().child(self.line_editor.clone())),
+ )
+ .child(
+ div()
+ .h_px()
+ .w_full()
+ .bg(cx.theme().colors().element_background),
+ )
+ .child(
+ h_stack()
+ .justify_between()
+ .px_2()
+ .py_1()
+ .child(Label::new(self.current_text.clone()).color(Color::Muted)),
+ ),
+ )
}
}
@@ -1,25 +0,0 @@
-[package]
-name = "go_to_line2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/go_to_line.rs"
-doctest = false
-
-[dependencies]
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-menu = { package = "menu2", path = "../menu2" }
-serde.workspace = true
-settings = { package = "settings2", path = "../settings2" }
-text = { package = "text2", path = "../text2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-postage.workspace = true
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2" }
-util = { path = "../util" }
-
-[dev-dependencies]
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
@@ -1,188 +0,0 @@
-use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
-use gpui::{
- actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
- FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
-};
-use text::{Bias, Point};
-use theme::ActiveTheme;
-use ui::{h_stack, prelude::*, v_stack, Label};
-use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::ModalView;
-
-actions!(go_to_line, [Toggle]);
-
-pub fn init(cx: &mut AppContext) {
- cx.observe_new_views(GoToLine::register).detach();
-}
-
-pub struct GoToLine {
- line_editor: View<Editor>,
- active_editor: View<Editor>,
- current_text: SharedString,
- prev_scroll_position: Option<gpui::Point<f32>>,
- _subscriptions: Vec<Subscription>,
-}
-
-impl ModalView for GoToLine {}
-
-impl FocusableView for GoToLine {
- fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
- self.line_editor.focus_handle(cx)
- }
-}
-impl EventEmitter<DismissEvent> for GoToLine {}
-
-impl GoToLine {
- fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
- let handle = cx.view().downgrade();
- editor.register_action(move |_: &Toggle, cx| {
- let Some(editor) = handle.upgrade() else {
- return;
- };
- let Some(workspace) = editor.read(cx).workspace() else {
- return;
- };
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
- })
- });
- }
-
- pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
- let line_editor = cx.new_view(|cx| Editor::single_line(cx));
- let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
-
- let editor = active_editor.read(cx);
- let cursor = editor.selections.last::<Point>(cx).head();
- let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
- let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
-
- let current_text = format!(
- "line {} of {} (column {})",
- cursor.row + 1,
- last_line + 1,
- cursor.column + 1,
- );
-
- Self {
- line_editor,
- active_editor,
- current_text: current_text.into(),
- prev_scroll_position: Some(scroll_position),
- _subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
- }
- }
-
- fn release(&mut self, window: AnyWindowHandle, cx: &mut AppContext) {
- window
- .update(cx, |_, cx| {
- let scroll_position = self.prev_scroll_position.take();
- self.active_editor.update(cx, |editor, cx| {
- editor.highlight_rows(None);
- if let Some(scroll_position) = scroll_position {
- editor.set_scroll_position(scroll_position, cx);
- }
- cx.notify();
- })
- })
- .ok();
- }
-
- fn on_line_editor_event(
- &mut self,
- _: View<Editor>,
- event: &editor::EditorEvent,
- cx: &mut ViewContext<Self>,
- ) {
- match event {
- editor::EditorEvent::Blurred => cx.emit(DismissEvent),
- editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
- _ => {}
- }
- }
-
- fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
- if let Some(point) = self.point_from_query(cx) {
- self.active_editor.update(cx, |active_editor, cx| {
- let snapshot = active_editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- let display_point = point.to_display_point(&snapshot);
- let row = display_point.row();
- active_editor.highlight_rows(Some(row..row + 1));
- active_editor.request_autoscroll(Autoscroll::center(), cx);
- });
- cx.notify();
- }
- }
-
- fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
- let line_editor = self.line_editor.read(cx).text(cx);
- let mut components = line_editor
- .splitn(2, FILE_ROW_COLUMN_DELIMITER)
- .map(str::trim)
- .fuse();
- let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
- let column = components.next().and_then(|col| col.parse::<u32>().ok());
- Some(Point::new(
- row.saturating_sub(1),
- column.unwrap_or(0).saturating_sub(1),
- ))
- }
-
- fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
- cx.emit(DismissEvent);
- }
-
- fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
- if let Some(point) = self.point_from_query(cx) {
- self.active_editor.update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([point..point])
- });
- editor.focus(cx);
- cx.notify();
- });
- self.prev_scroll_position.take();
- }
-
- cx.emit(DismissEvent);
- }
-}
-
-impl Render for GoToLine {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- div()
- .elevation_2(cx)
- .key_context("GoToLine")
- .on_action(cx.listener(Self::cancel))
- .on_action(cx.listener(Self::confirm))
- .w_96()
- .child(
- v_stack()
- .px_1()
- .pt_0p5()
- .gap_px()
- .child(
- v_stack()
- .py_0p5()
- .px_1()
- .child(div().px_1().py_0p5().child(self.line_editor.clone())),
- )
- .child(
- div()
- .h_px()
- .w_full()
- .bg(cx.theme().colors().element_background),
- )
- .child(
- h_stack()
- .justify_between()
- .px_2()
- .py_1()
- .child(Label::new(self.current_text.clone()).color(Color::Muted)),
- ),
- )
- }
-}
@@ -26,28 +26,28 @@ serde_json.workspace = true
collections = { path = "../collections" }
command_palette = { path = "../command_palette" }
-editor = { path = "../editor" }
-gpui = { path = "../gpui" }
-language = { path = "../language" }
-search = { path = "../search" }
-settings = { path = "../settings" }
-workspace = { path = "../workspace" }
-theme = { path = "../theme" }
-language_selector = { path = "../language_selector"}
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+language = { package = "language2", path = "../language2" }
+search = { package = "search2", path = "../search2" }
+settings = { package = "settings2", path = "../settings2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+theme = { package = "theme2", path = "../theme2" }
+ui = { package = "ui2", path = "../ui2"}
diagnostics = { path = "../diagnostics" }
-zed-actions = { path = "../zed-actions" }
+zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
[dev-dependencies]
indoc.workspace = true
parking_lot.workspace = true
futures.workspace = true
-editor = { path = "../editor", features = ["test-support"] }
-gpui = { path = "../gpui", features = ["test-support"] }
-language = { path = "../language", features = ["test-support"] }
-project = { path = "../project", features = ["test-support"] }
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+language = { package = "language2", path = "../language2", features = ["test-support"] }
+project = { package = "project2", path = "../project2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
-settings = { path = "../settings" }
-workspace = { path = "../workspace", features = ["test-support"] }
-theme = { path = "../theme", features = ["test-support"] }
-lsp = { path = "../lsp", features = ["test-support"] }
+settings = { package = "settings2", path = "../settings2" }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
+lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
@@ -1,6 +1,6 @@
use command_palette::CommandInterceptResult;
use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
-use gpui::{impl_actions, Action, AppContext};
+use gpui::{impl_actions, Action, AppContext, ViewContext};
use serde_derive::Deserialize;
use workspace::{SaveIntent, Workspace};
@@ -22,8 +22,8 @@ pub struct GoToLine {
impl_actions!(vim, [GoToLine]);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(|_: &mut Workspace, action: &GoToLine, cx| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, action: &GoToLine, cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(Mode::Normal, false, cx);
move_cursor(vim, Motion::StartOfDocument, Some(action.line as usize), cx);
@@ -293,14 +293,11 @@ mod test {
use std::path::Path;
use crate::test::{NeovimBackedTestContext, VimTestContext};
- use gpui::{executor::Foreground, TestAppContext};
+ use gpui::TestAppContext;
use indoc::indoc;
#[gpui::test]
async fn test_command_basics(cx: &mut TestAppContext) {
- if let Foreground::Deterministic { cx_id: _, executor } = cx.foreground().as_ref() {
- executor.run_until_parked();
- }
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
@@ -410,15 +407,14 @@ mod test {
// conflict!
cx.simulate_keystrokes(["i", "@", "escape"]);
cx.simulate_keystrokes([":", "w", "enter"]);
- let window = cx.window;
- assert!(window.has_pending_prompt(cx.cx));
+ assert!(cx.has_pending_prompt());
// "Cancel"
- window.simulate_prompt_answer(0, cx.cx);
+ cx.simulate_prompt_answer(0);
assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
- assert!(!window.has_pending_prompt(cx.cx));
+ assert!(!cx.has_pending_prompt());
// force overwrite
cx.simulate_keystrokes([":", "w", "!", "enter"]);
- assert!(!window.has_pending_prompt(cx.cx));
+ assert!(!cx.has_pending_prompt());
assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
}
@@ -1,65 +1,67 @@
-use crate::{Vim, VimEvent};
-use editor::{EditorBlurred, EditorFocused, EditorReleased};
-use gpui::AppContext;
+use crate::Vim;
+use editor::{Editor, EditorEvent};
+use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
pub fn init(cx: &mut AppContext) {
- cx.subscribe_global(focused).detach();
- cx.subscribe_global(blurred).detach();
- cx.subscribe_global(released).detach();
+ cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
+ let editor = cx.view().clone();
+ cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
+ EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
+ EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
+ _ => {}
+ })
+ .detach();
+
+ let id = cx.view().entity_id();
+ cx.on_release(move |_, _, cx| released(id, cx)).detach();
+ })
+ .detach();
}
-fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
- if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() {
- previously_active_editor.window().update(cx, |cx| {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |previously_active_editor, cx| {
- vim.unhook_vim_settings(previously_active_editor, cx)
- });
+fn focused(editor: View<Editor>, cx: &mut WindowContext) {
+ if Vim::read(cx).active_editor.clone().is_some() {
+ Vim::update(cx, |vim, cx| {
+ vim.update_active_editor(cx, |previously_active_editor, cx| {
+ vim.unhook_vim_settings(previously_active_editor, cx)
});
});
}
- editor.window().update(cx, |cx| {
- Vim::update(cx, |vim, cx| {
- vim.set_active_editor(editor.clone(), cx);
- if vim.enabled {
- cx.emit_global(VimEvent::ModeChanged {
- mode: vim.state().mode,
- });
- }
- });
+ Vim::update(cx, |vim, cx| {
+ vim.set_active_editor(editor.clone(), cx);
});
}
-fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
- editor.window().update(cx, |cx| {
- Vim::update(cx, |vim, cx| {
- vim.workspace_state.recording = false;
- vim.workspace_state.recorded_actions.clear();
- if let Some(previous_editor) = vim.active_editor.clone() {
- if previous_editor == editor.clone() {
- vim.clear_operator(cx);
- vim.active_editor = None;
- vim.editor_subscription = None;
- }
+fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
+ Vim::update(cx, |vim, cx| {
+ vim.workspace_state.recording = false;
+ vim.workspace_state.recorded_actions.clear();
+ if let Some(previous_editor) = vim.active_editor.clone() {
+ if previous_editor
+ .upgrade()
+ .is_some_and(|previous| previous == editor.clone())
+ {
+ vim.clear_operator(cx);
+ vim.active_editor = None;
+ vim.editor_subscription = None;
}
+ }
- editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
- });
+ editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
});
}
-fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
- editor.window().update(cx, |cx| {
- Vim::update(cx, |vim, _| {
- if let Some(previous_editor) = vim.active_editor.clone() {
- if previous_editor == editor.clone() {
- vim.active_editor = None;
- vim.editor_subscription = None;
- }
- }
- vim.editor_states.remove(&editor.id())
- });
+fn released(entity_id: EntityId, cx: &mut AppContext) {
+ cx.update_global(|vim: &mut Vim, _| {
+ if vim
+ .active_editor
+ .as_ref()
+ .is_some_and(|previous| previous.entity_id() == entity_id)
+ {
+ vim.active_editor = None;
+ vim.editor_subscription = None;
+ }
+ vim.editor_states.remove(&entity_id)
});
}
@@ -67,7 +69,7 @@ fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
mod test {
use crate::{test::VimTestContext, Vim};
use editor::Editor;
- use gpui::View;
+ use gpui::{Context, Entity};
use language::Buffer;
// regression test for blur called with a different active editor
@@ -75,18 +77,28 @@ mod test {
async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
- let buffer = cx.add_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
+ let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
- let editor2 = cx.read(|cx| window2.root(cx)).unwrap();
+ let editor2 = cx
+ .update(|cx| {
+ window2.update(cx, |_, cx| {
+ cx.focus_self();
+ cx.view().clone()
+ })
+ })
+ .unwrap();
cx.update(|cx| {
let vim = Vim::read(cx);
- assert_eq!(vim.active_editor.unwrap().id(), editor2.id())
+ assert_eq!(
+ vim.active_editor.as_ref().unwrap().entity_id(),
+ editor2.entity_id(),
+ )
});
// no panic when blurring an editor in a different window.
cx.update_editor(|editor1, cx| {
- editor1.focus_out(cx.handle().into_any(), cx);
+ editor1.handle_blur(cx);
});
}
}
@@ -1,13 +1,13 @@
use crate::{normal::repeat, state::Mode, Vim};
use editor::{scroll::autoscroll::Autoscroll, Bias};
-use gpui::{actions, Action, AppContext, ViewContext};
+use gpui::{actions, Action, ViewContext};
use language::SelectionGoal;
use workspace::Workspace;
actions!(vim, [NormalBefore]);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(normal_before);
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(normal_before);
}
fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
@@ -38,10 +38,6 @@ fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<
#[cfg(test)]
mod test {
- use std::sync::Arc;
-
- use gpui::executor::Deterministic;
-
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
@@ -60,76 +56,70 @@ mod test {
}
#[gpui::test]
- async fn test_insert_with_counts(
- deterministic: Arc<Deterministic>,
- cx: &mut gpui::TestAppContext,
- ) {
+ async fn test_insert_with_counts(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("----ˇ-hello\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("h----ˇ-ello\n").await;
cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("---ˇ-h-----ello\n").await;
cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("----h-----ello--ˇ-\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
}
#[gpui::test]
- async fn test_insert_with_repeat(
- deterministic: Arc<Deterministic>,
- cx: &mut gpui::TestAppContext,
- ) {
+ async fn test_insert_with_repeat(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("--ˇ-hello\n").await;
cx.simulate_shared_keystrokes(["."]).await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("----ˇ--hello\n").await;
cx.simulate_shared_keystrokes(["2", "."]).await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("-----ˇ---hello\n").await;
cx.set_shared_state("ˇhello\n").await;
cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
.await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("hello\nkk\nkˇk\n").await;
cx.simulate_shared_keystrokes(["."]).await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
cx.simulate_shared_keystrokes(["1", "."]).await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
}
}
@@ -1,58 +1,40 @@
-use gpui::{
- elements::{Empty, Label},
- AnyElement, Element, Entity, Subscription, View, ViewContext,
-};
+use gpui::{div, Element, Render, Subscription, ViewContext};
use settings::SettingsStore;
-use workspace::{item::ItemHandle, StatusItemView};
+use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
-use crate::{state::Mode, Vim, VimEvent, VimModeSetting};
+use crate::{state::Mode, Vim};
pub struct ModeIndicator {
pub mode: Option<Mode>,
- _subscription: Subscription,
+ _subscriptions: Vec<Subscription>,
}
impl ModeIndicator {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.handle().downgrade();
+ let _subscriptions = vec![
+ cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
+ cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
+ ];
- let _subscription = cx.subscribe_global::<VimEvent, _>(move |&event, cx| {
- if let Some(mode_indicator) = handle.upgrade(cx) {
- match event {
- VimEvent::ModeChanged { mode } => {
- mode_indicator.window().update(cx, |cx| {
- mode_indicator.update(cx, move |mode_indicator, cx| {
- mode_indicator.set_mode(mode, cx);
- })
- });
- }
- }
- }
- });
-
- cx.observe_global::<SettingsStore, _>(move |mode_indicator, cx| {
- if settings::get::<VimModeSetting>(cx).0 {
- mode_indicator.mode = cx
- .has_global::<Vim>()
- .then(|| cx.global::<Vim>().state().mode);
- } else {
- mode_indicator.mode.take();
- }
- })
- .detach();
+ let mut this = Self {
+ mode: None,
+ _subscriptions,
+ };
+ this.update_mode(cx);
+ this
+ }
+ fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
// Vim doesn't exist in some tests
- let mode = cx
- .has_global::<Vim>()
- .then(|| {
- let vim = cx.global::<Vim>();
- vim.enabled.then(|| vim.state().mode)
- })
- .flatten();
+ if !cx.has_global::<Vim>() {
+ return;
+ }
- Self {
- mode,
- _subscription,
+ let vim = Vim::read(cx);
+ if vim.enabled {
+ self.mode = Some(vim.state().mode);
+ } else {
+ self.mode = None;
}
}
@@ -64,22 +46,12 @@ impl ModeIndicator {
}
}
-impl Entity for ModeIndicator {
- type Event = ();
-}
-
-impl View for ModeIndicator {
- fn ui_name() -> &'static str {
- "ModeIndicatorView"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+impl Render for ModeIndicator {
+ fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
let Some(mode) = self.mode.as_ref() else {
- return Empty::new().into_any();
+ return div().into_any();
};
- let theme = &theme::current(cx).workspace.status_bar;
-
let text = match mode {
Mode::Normal => "-- NORMAL --",
Mode::Insert => "-- INSERT --",
@@ -87,10 +59,7 @@ impl View for ModeIndicator {
Mode::VisualLine => "-- VISUAL LINE --",
Mode::VisualBlock => "-- VISUAL BLOCK --",
};
- Label::new(text, theme.vim_mode_indicator.text.clone())
- .contained()
- .with_style(theme.vim_mode_indicator.container)
- .into_any()
+ Label::new(text).size(LabelSize::Small).into_any_element()
}
}
@@ -4,7 +4,7 @@ use editor::{
movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
Bias, CharKind, DisplayPoint, ToOffset,
};
-use gpui::{actions, impl_actions, AppContext, WindowContext};
+use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
use language::{Point, Selection, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@@ -105,6 +105,21 @@ struct RepeatFind {
backwards: bool,
}
+impl_actions!(
+ vim,
+ [
+ RepeatFind,
+ StartOfLine,
+ EndOfLine,
+ FirstNonWhitespace,
+ Down,
+ Up,
+ PreviousWordStart,
+ NextWordEnd,
+ NextWordStart
+ ]
+);
+
actions!(
vim,
[
@@ -123,25 +138,12 @@ actions!(
GoToColumn,
]
);
-impl_actions!(
- vim,
- [
- NextWordStart,
- NextWordEnd,
- PreviousWordStart,
- RepeatFind,
- Up,
- Down,
- FirstNonWhitespace,
- EndOfLine,
- StartOfLine,
- ]
-);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
- cx.add_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
- cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
+ workspace
+ .register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
+ workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
motion(
Motion::Down {
display_lines: action.display_lines,
@@ -149,7 +151,7 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
- cx.add_action(|_: &mut Workspace, action: &Up, cx: _| {
+ workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
motion(
Motion::Up {
display_lines: action.display_lines,
@@ -157,8 +159,8 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
- cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
- cx.add_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
+ workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
+ workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
motion(
Motion::FirstNonWhitespace {
display_lines: action.display_lines,
@@ -166,7 +168,7 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
- cx.add_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
+ workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
motion(
Motion::StartOfLine {
display_lines: action.display_lines,
@@ -174,7 +176,7 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
- cx.add_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
+ workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
motion(
Motion::EndOfLine {
display_lines: action.display_lines,
@@ -182,45 +184,53 @@ pub fn init(cx: &mut AppContext) {
cx,
)
});
- cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));
- cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
+ workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
+ motion(Motion::CurrentLine, cx)
+ });
+ workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
motion(Motion::StartOfParagraph, cx)
});
- cx.add_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
+ workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
motion(Motion::EndOfParagraph, cx)
});
- cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
+ workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
motion(Motion::StartOfDocument, cx)
});
- cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));
- cx.add_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
+ workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
+ motion(Motion::EndOfDocument, cx)
+ });
+ workspace
+ .register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
- cx.add_action(
+ workspace.register_action(
|_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
motion(Motion::NextWordStart { ignore_punctuation }, cx)
},
);
- cx.add_action(
+ workspace.register_action(
|_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
motion(Motion::NextWordEnd { ignore_punctuation }, cx)
},
);
- cx.add_action(
+ workspace.register_action(
|_: &mut Workspace,
&PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
);
- cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx));
- cx.add_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
+ workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
+ motion(Motion::NextLineStart, cx)
+ });
+ workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
motion(Motion::StartOfLineDownward, cx)
});
- cx.add_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
+ workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
motion(Motion::EndOfLineDownward, cx)
});
- cx.add_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
- cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
+ workspace
+ .register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
+ workspace.register_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
repeat_motion(action.backwards, cx)
- })
+ });
}
pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
@@ -578,9 +588,9 @@ fn up_down_buffer_rows(
SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end),
SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x),
_ => {
- let x = map.x_for_point(point, text_layout_details);
- goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x));
- (select_nth_wrapped_row, x)
+ let x = map.x_for_display_point(point, text_layout_details);
+ goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x.0));
+ (select_nth_wrapped_row, x.0)
}
};
@@ -608,7 +618,7 @@ fn up_down_buffer_rows(
}
let new_col = if i == goal_wrap {
- map.column_for_x(begin_folded_line.row(), goal_x, text_layout_details)
+ map.display_column_for_x(begin_folded_line.row(), px(goal_x), text_layout_details)
} else {
map.line_len(begin_folded_line.row())
};
@@ -943,7 +953,6 @@ pub(crate) fn next_line_end(
}
#[cfg(test)]
-
mod test {
use crate::test::NeovimBackedTestContext;
@@ -20,7 +20,7 @@ use crate::{
use collections::HashSet;
use editor::scroll::autoscroll::Autoscroll;
use editor::{Bias, DisplayPoint};
-use gpui::{actions, AppContext, ViewContext, WindowContext};
+use gpui::{actions, ViewContext, WindowContext};
use language::SelectionGoal;
use log::error;
use workspace::Workspace;
@@ -52,38 +52,31 @@ actions!(
]
);
-pub fn init(cx: &mut AppContext) {
- paste::init(cx);
- repeat::init(cx);
- scroll::init(cx);
- search::init(cx);
- substitute::init(cx);
- increment::init(cx);
-
- cx.add_action(insert_after);
- cx.add_action(insert_before);
- cx.add_action(insert_first_non_whitespace);
- cx.add_action(insert_end_of_line);
- cx.add_action(insert_line_above);
- cx.add_action(insert_line_below);
- cx.add_action(change_case);
- cx.add_action(yank_line);
-
- cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
+pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+ workspace.register_action(insert_after);
+ workspace.register_action(insert_before);
+ workspace.register_action(insert_first_non_whitespace);
+ workspace.register_action(insert_end_of_line);
+ workspace.register_action(insert_line_above);
+ workspace.register_action(insert_line_below);
+ workspace.register_action(change_case);
+ workspace.register_action(yank_line);
+
+ workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let times = vim.take_count(cx);
delete_motion(vim, Motion::Left, times, cx);
})
});
- cx.add_action(|_: &mut Workspace, _: &DeleteRight, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let times = vim.take_count(cx);
delete_motion(vim, Motion::Right, times, cx);
})
});
- cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
Vim::update(cx, |vim, cx| {
vim.start_recording(cx);
let times = vim.take_count(cx);
@@ -97,7 +90,7 @@ pub fn init(cx: &mut AppContext) {
);
})
});
- cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let times = vim.take_count(cx);
@@ -111,7 +104,7 @@ pub fn init(cx: &mut AppContext) {
);
})
});
- cx.add_action(|_: &mut Workspace, _: &JoinLines, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let mut times = vim.take_count(cx).unwrap_or(1);
@@ -129,8 +122,15 @@ pub fn init(cx: &mut AppContext) {
}
})
})
- })
- })
+ });
+ });
+
+ paste::register(workspace, cx);
+ repeat::register(workspace, cx);
+ scroll::register(workspace, cx);
+ search::register(workspace, cx);
+ substitute::register(workspace, cx);
+ increment::register(workspace, cx);
}
pub fn normal_motion(
@@ -1,7 +1,7 @@
use std::ops::Range;
use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
-use gpui::{impl_actions, AppContext, WindowContext};
+use gpui::{impl_actions, ViewContext, WindowContext};
use language::{Bias, Point};
use serde::Deserialize;
use workspace::Workspace;
@@ -24,8 +24,8 @@ struct Decrement {
impl_actions!(vim, [Increment, Decrement]);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(|_: &mut Workspace, action: &Increment, cx| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1);
@@ -33,7 +33,7 @@ pub fn init(cx: &mut AppContext) {
increment(vim, count as i32, step, cx)
})
});
- cx.add_action(|_: &mut Workspace, action: &Decrement, cx| {
+ workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1);
@@ -4,7 +4,7 @@ use editor::{
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
DisplayPoint,
};
-use gpui::{impl_actions, AppContext, ViewContext};
+use gpui::{impl_actions, ViewContext};
use language::{Bias, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@@ -22,8 +22,8 @@ struct Paste {
impl_actions!(vim, [Paste]);
-pub(crate) fn init(cx: &mut AppContext) {
- cx.add_action(paste);
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(paste);
}
fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
@@ -5,14 +5,14 @@ use crate::{
visual::visual_motion,
Vim,
};
-use gpui::{actions, Action, AppContext, WindowContext};
+use gpui::{actions, Action, ViewContext, WindowContext};
use workspace::Workspace;
-actions!(vim, [Repeat, EndRepeat,]);
+actions!(vim, [Repeat, EndRepeat]);
fn should_replay(action: &Box<dyn Action>) -> bool {
// skip so that we don't leave the character palette open
- if editor::ShowCharacterPalette.id() == action.id() {
+ if editor::ShowCharacterPalette.partial_eq(&**action) {
return false;
}
true
@@ -21,14 +21,14 @@ fn should_replay(action: &Box<dyn Action>) -> bool {
fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
match action {
ReplayableAction::Action(action) => {
- if super::InsertBefore.id() == action.id()
- || super::InsertAfter.id() == action.id()
- || super::InsertFirstNonWhitespace.id() == action.id()
- || super::InsertEndOfLine.id() == action.id()
+ if super::InsertBefore.partial_eq(&**action)
+ || super::InsertAfter.partial_eq(&**action)
+ || super::InsertFirstNonWhitespace.partial_eq(&**action)
+ || super::InsertEndOfLine.partial_eq(&**action)
{
Some(super::InsertBefore.boxed_clone())
- } else if super::InsertLineAbove.id() == action.id()
- || super::InsertLineBelow.id() == action.id()
+ } else if super::InsertLineAbove.partial_eq(&**action)
+ || super::InsertLineBelow.partial_eq(&**action)
{
Some(super::InsertLineBelow.boxed_clone())
} else {
@@ -39,15 +39,15 @@ fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
}
}
-pub(crate) fn init(cx: &mut AppContext) {
- cx.add_action(|_: &mut Workspace, _: &EndRepeat, cx| {
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
Vim::update(cx, |vim, cx| {
vim.workspace_state.replaying = false;
vim.switch_mode(Mode::Normal, false, cx)
});
});
- cx.add_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
+ workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
}
pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
@@ -142,7 +142,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
// 3 times, instead it inserts the content thrice at the insert position.
if let Some(to_repeat) = repeatable_insert(&actions[0]) {
if let Some(ReplayableAction::Action(action)) = actions.last() {
- if action.id() == NormalBefore.id() {
+ if NormalBefore.partial_eq(&**action) {
actions.pop();
}
}
@@ -166,50 +166,43 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
}
Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
- let window = cx.window();
- cx.app_context()
- .spawn(move |mut cx| async move {
- editor.update(&mut cx, |editor, _| {
- editor.show_local_selections = false;
- })?;
- for action in actions {
- match action {
- ReplayableAction::Action(action) => {
- if should_replay(&action) {
- window
- .dispatch_action(editor.id(), action.as_ref(), &mut cx)
- .ok_or_else(|| anyhow::anyhow!("window was closed"))
- } else {
- Ok(())
- }
+ let window = cx.window_handle();
+ cx.spawn(move |mut cx| async move {
+ editor.update(&mut cx, |editor, _| {
+ editor.show_local_selections = false;
+ })?;
+ for action in actions {
+ match action {
+ ReplayableAction::Action(action) => {
+ if should_replay(&action) {
+ window.update(&mut cx, |_, cx| cx.dispatch_action(action))
+ } else {
+ Ok(())
}
- ReplayableAction::Insertion {
- text,
- utf16_range_to_replace,
- } => editor.update(&mut cx, |editor, cx| {
- editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
- }),
- }?
- }
- editor.update(&mut cx, |editor, _| {
- editor.show_local_selections = true;
- })?;
- window
- .dispatch_action(editor.id(), &EndRepeat, &mut cx)
- .ok_or_else(|| anyhow::anyhow!("window was closed"))
- })
- .detach_and_log_err(cx);
+ }
+ ReplayableAction::Insertion {
+ text,
+ utf16_range_to_replace,
+ } => editor.update(&mut cx, |editor, cx| {
+ editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
+ }),
+ }?
+ }
+ editor.update(&mut cx, |editor, _| {
+ editor.show_local_selections = true;
+ })?;
+ window.update(&mut cx, |_, cx| cx.dispatch_action(EndRepeat.boxed_clone()))
+ })
+ .detach_and_log_err(cx);
}
#[cfg(test)]
mod test {
- use std::sync::Arc;
-
use editor::test::editor_lsp_test_context::EditorLspTestContext;
use futures::StreamExt;
use indoc::indoc;
- use gpui::{executor::Deterministic, View};
+ use gpui::InputHandler;
use crate::{
state::Mode,
@@ -217,7 +210,7 @@ mod test {
};
#[gpui::test]
- async fn test_dot_repeat(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+ async fn test_dot_repeat(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// "o"
@@ -226,38 +219,32 @@ mod test {
.await;
cx.assert_shared_state("hello\nworlˇd").await;
cx.simulate_shared_keystrokes(["."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state("hello\nworld\nworlˇd").await;
// "d"
cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;
cx.simulate_shared_keystrokes(["g", "g", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state("ˇ\nworld\nrld").await;
// "p" (note that it pastes the current clipboard)
cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;
cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])
.await;
- deterministic.run_until_parked();
cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;
// "~" (note that counts apply to the action taken, not . itself)
cx.set_shared_state("ˇthe quick brown fox").await;
cx.simulate_shared_keystrokes(["2", "~", "."]).await;
- deterministic.run_until_parked();
cx.set_shared_state("THE ˇquick brown fox").await;
cx.simulate_shared_keystrokes(["3", "."]).await;
- deterministic.run_until_parked();
cx.set_shared_state("THE QUIˇck brown fox").await;
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.simulate_shared_keystrokes(["."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state("THE QUICK ˇbrown fox").await;
}
#[gpui::test]
- async fn test_repeat_ime(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+ async fn test_repeat_ime(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("hˇllo", Mode::Normal);
@@ -271,15 +258,12 @@ mod test {
cx.simulate_keystrokes(["escape"]);
cx.assert_state("hˇällo", Mode::Normal);
cx.simulate_keystrokes(["."]);
- deterministic.run_until_parked();
cx.assert_state("hˇäällo", Mode::Normal);
}
#[gpui::test]
- async fn test_repeat_completion(
- deterministic: Arc<Deterministic>,
- cx: &mut gpui::TestAppContext,
- ) {
+ async fn test_repeat_completion(cx: &mut gpui::TestAppContext) {
+ VimTestContext::init(cx);
let cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
@@ -340,7 +324,6 @@ mod test {
Mode::Normal,
);
cx.simulate_keystrokes(["j", "."]);
- deterministic.run_until_parked();
cx.assert_state(
indoc! {"
one.second!
@@ -352,7 +335,7 @@ mod test {
}
#[gpui::test]
- async fn test_repeat_visual(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+ async fn test_repeat_visual(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
// single-line (3 columns)
@@ -371,7 +354,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "w", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"o quick brown
fox ˇops over
@@ -379,7 +361,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["f", "r", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"o quick brown
fox ops oveˇothe lazy dog"
@@ -404,7 +385,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"the ˇumps over
fox jumps over
@@ -412,14 +392,12 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["w", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"the umps ˇumps over
the lazy dog"
})
.await;
cx.simulate_shared_keystrokes(["j", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"the umps umps over
the ˇog"
@@ -442,7 +420,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"othe quick brown
ofoxˇo jumps over
@@ -466,7 +443,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
"o
ˇo
@@ -476,10 +452,7 @@ mod test {
}
#[gpui::test]
- async fn test_repeat_motion_counts(
- deterministic: Arc<Deterministic>,
- cx: &mut gpui::TestAppContext,
- ) {
+ async fn test_repeat_motion_counts(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
@@ -496,7 +469,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
" brown
ˇ over
@@ -504,7 +476,6 @@ mod test {
})
.await;
cx.simulate_shared_keystrokes(["j", "2", "."]).await;
- deterministic.run_until_parked();
cx.assert_shared_state(indoc! {
" brown
over
@@ -514,15 +485,12 @@ mod test {
}
#[gpui::test]
- async fn test_record_interrupted(
- deterministic: Arc<Deterministic>,
- cx: &mut gpui::TestAppContext,
- ) {
+ async fn test_record_interrupted(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("ˇhello\n", Mode::Normal);
- cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape", "escape"]);
- deterministic.run_until_parked();
+ cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape"]);
+ cx.simulate_keystrokes(["escape"]);
cx.assert_state("ˇjhello\n", Mode::Normal);
}
}
@@ -4,29 +4,29 @@ use editor::{
scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
DisplayPoint, Editor,
};
-use gpui::{actions, AppContext, ViewContext};
+use gpui::{actions, ViewContext};
use language::Bias;
use workspace::Workspace;
actions!(
vim,
- [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown,]
+ [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(|_: &mut Workspace, _: &LineDown, cx| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
});
- cx.add_action(|_: &mut Workspace, _: &LineUp, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
});
- cx.add_action(|_: &mut Workspace, _: &PageDown, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
});
- cx.add_action(|_: &mut Workspace, _: &PageUp, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
});
- cx.add_action(|_: &mut Workspace, _: &ScrollDown, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
scroll(cx, true, |c| {
if let Some(c) = c {
ScrollAmount::Line(c)
@@ -35,7 +35,7 @@ pub fn init(cx: &mut AppContext) {
}
})
});
- cx.add_action(|_: &mut Workspace, _: &ScrollUp, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
scroll(cx, true, |c| {
if let Some(c) = c {
ScrollAmount::Line(-c)
@@ -114,7 +114,7 @@ mod test {
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
- use gpui::geometry::vector::vec2f;
+ use gpui::{point, px, size, Context};
use indoc::indoc;
use language::Point;
@@ -122,10 +122,27 @@ mod test {
async fn test_scroll(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
+ let (line_height, visible_line_count) = cx.editor(|editor, cx| {
+ (
+ editor
+ .style()
+ .unwrap()
+ .text
+ .line_height_in_pixels(cx.rem_size()),
+ editor.visible_line_count().unwrap(),
+ )
+ });
+
let window = cx.window;
- let line_height =
- cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
- window.simulate_resize(vec2f(1000., 8.0 * line_height - 1.0), &mut cx);
+ let margin = cx
+ .update_window(window, |_, cx| {
+ cx.viewport_size().height - line_height * visible_line_count
+ })
+ .unwrap();
+ cx.simulate_window_resize(
+ cx.window,
+ size(px(1000.), margin + 8. * line_height - px(1.0)),
+ );
cx.set_state(
indoc!(
@@ -147,29 +164,29 @@ mod test {
);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
});
cx.simulate_keystrokes(["ctrl-e"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.))
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 1.))
});
cx.simulate_keystrokes(["2", "ctrl-e"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.))
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.))
});
cx.simulate_keystrokes(["ctrl-y"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.))
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 2.))
});
// does not select in normal mode
cx.simulate_keystrokes(["g", "g"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
});
cx.simulate_keystrokes(["ctrl-d"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
assert_eq!(
editor.selections.newest(cx).range(),
Point::new(6, 0)..Point::new(6, 0)
@@ -179,11 +196,11 @@ mod test {
// does select in visual mode
cx.simulate_keystrokes(["g", "g"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
});
cx.simulate_keystrokes(["v", "ctrl-d"]);
cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.0));
+ assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
assert_eq!(
editor.selections.newest(cx).range(),
Point::new(0, 0)..Point::new(6, 1)
@@ -1,7 +1,7 @@
-use gpui::{actions, impl_actions, AppContext, ViewContext};
+use gpui::{actions, impl_actions, ViewContext};
use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
use serde_derive::Deserialize;
-use workspace::{searchable::Direction, Pane, Workspace};
+use workspace::{searchable::Direction, Workspace};
use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
@@ -44,21 +44,21 @@ struct Replacement {
is_case_sensitive: bool,
}
+actions!(vim, [SearchSubmit]);
impl_actions!(
vim,
- [MoveToNext, MoveToPrev, Search, FindCommand, ReplaceCommand]
+ [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
);
-actions!(vim, [SearchSubmit]);
-pub(crate) fn init(cx: &mut AppContext) {
- cx.add_action(move_to_next);
- cx.add_action(move_to_prev);
- cx.add_action(search);
- cx.add_action(search_submit);
- cx.add_action(search_deploy);
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(move_to_next);
+ workspace.register_action(move_to_prev);
+ workspace.register_action(search);
+ workspace.register_action(search_submit);
+ workspace.register_action(search_deploy);
- cx.add_action(find_command);
- cx.add_action(replace_command);
+ workspace.register_action(find_command);
+ workspace.register_action(replace_command);
}
fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
@@ -106,9 +106,9 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
}
// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
-fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext<Pane>) {
+fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
- cx.propagate_action();
+ cx.propagate();
}
fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
@@ -347,58 +347,50 @@ fn parse_replace_all(query: &str) -> Replacement {
#[cfg(test)]
mod test {
- use std::sync::Arc;
-
use editor::DisplayPoint;
use search::BufferSearchBar;
use crate::{state::Mode, test::VimTestContext};
#[gpui::test]
- async fn test_move_to_next(
- cx: &mut gpui::TestAppContext,
- deterministic: Arc<gpui::executor::Deterministic>,
- ) {
+ async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["*"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["*"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["#"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["#"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["2", "*"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["g", "*"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes(["n"]);
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes(["g", "#"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
}
#[gpui::test]
- async fn test_search(
- cx: &mut gpui::TestAppContext,
- deterministic: Arc<gpui::executor::Deterministic>,
- ) {
+ async fn test_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
@@ -414,11 +406,11 @@ mod test {
.expect("Buffer search bar should be deployed")
});
- search_bar.read_with(cx.cx, |bar, cx| {
+ cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "cc");
});
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.update_editor(|editor, cx| {
let highlights = editor.all_text_background_highlights(cx);
@@ -440,51 +432,41 @@ mod test {
// ?<enter> to go to previous
cx.simulate_keystrokes(["?", "enter"]);
- deterministic.run_until_parked();
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
cx.simulate_keystrokes(["?", "enter"]);
- deterministic.run_until_parked();
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
// /<enter> to go to next
cx.simulate_keystrokes(["/", "enter"]);
- deterministic.run_until_parked();
cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
// ?{search}<enter> to search backwards
cx.simulate_keystrokes(["?", "b", "enter"]);
- deterministic.run_until_parked();
cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
// works with counts
cx.simulate_keystrokes(["4", "/", "c"]);
- deterministic.run_until_parked();
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
// check that searching resumes from cursor, not previous match
cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
cx.simulate_keystrokes(["/", "d"]);
- deterministic.run_until_parked();
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
cx.simulate_keystrokes(["/", "b"]);
- deterministic.run_until_parked();
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
}
#[gpui::test]
- async fn test_non_vim_search(
- cx: &mut gpui::TestAppContext,
- deterministic: Arc<gpui::executor::Deterministic>,
- ) {
+ async fn test_non_vim_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, false).await;
cx.set_state("ˇone one one one", Mode::Normal);
cx.simulate_keystrokes(["cmd-f"]);
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.assert_editor_state("«oneˇ» one one one");
cx.simulate_keystrokes(["enter"]);
@@ -1,5 +1,5 @@
use editor::movement;
-use gpui::{actions, AppContext, WindowContext};
+use gpui::{actions, ViewContext, WindowContext};
use language::Point;
use workspace::Workspace;
@@ -7,8 +7,8 @@ use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
actions!(vim, [Substitute, SubstituteLine]);
-pub(crate) fn init(cx: &mut AppContext) {
- cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
+pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
Vim::update(cx, |vim, cx| {
vim.start_recording(cx);
let count = vim.take_count(cx);
@@ -16,7 +16,7 @@ pub(crate) fn init(cx: &mut AppContext) {
})
});
- cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
Vim::update(cx, |vim, cx| {
vim.start_recording(cx);
if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
@@ -6,7 +6,7 @@ use editor::{
movement::{self, FindRange},
Bias, CharKind, DisplayPoint,
};
-use gpui::{actions, impl_actions, AppContext, WindowContext};
+use gpui::{actions, impl_actions, ViewContext, WindowContext};
use language::Selection;
use serde::Deserialize;
use workspace::Workspace;
@@ -34,6 +34,8 @@ struct Word {
ignore_punctuation: bool,
}
+impl_actions!(vim, [Word]);
+
actions!(
vim,
[
@@ -48,25 +50,36 @@ actions!(
AngleBrackets
]
);
-impl_actions!(vim, [Word]);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(
|_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
object(Object::Word { ignore_punctuation }, cx)
},
);
- 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: _| {
+ workspace
+ .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
+ workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
+ workspace
+ .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
+ workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
+ object(Object::DoubleQuotes, cx)
+ });
+ workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
+ object(Object::Parentheses, cx)
+ });
+ workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
object(Object::SquareBrackets, cx)
});
- cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx));
- cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
- cx.add_action(|_: &mut Workspace, _: &VerticalBars, cx: _| object(Object::VerticalBars, cx));
+ workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
+ object(Object::CurlyBrackets, cx)
+ });
+ workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
+ object(Object::AngleBrackets, cx)
+ });
+ workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
+ object(Object::VerticalBars, cx)
+ });
}
fn object(object: Object, cx: &mut WindowContext) {
@@ -1,6 +1,6 @@
use std::{ops::Range, sync::Arc};
-use gpui::{keymap_matcher::KeymapContext, Action};
+use gpui::{Action, KeyContext};
use language::CursorShape;
use serde::{Deserialize, Serialize};
use workspace::searchable::Direction;
@@ -167,10 +167,10 @@ impl EditorState {
self.operator_stack.last().copied()
}
- pub fn keymap_context_layer(&self) -> KeymapContext {
- let mut context = KeymapContext::default();
- context.add_identifier("VimEnabled");
- context.add_key(
+ pub fn keymap_context_layer(&self) -> KeyContext {
+ let mut context = KeyContext::default();
+ context.add("VimEnabled");
+ context.set(
"vim_mode",
match self.mode {
Mode::Normal => "normal",
@@ -180,24 +180,24 @@ impl EditorState {
);
if self.vim_controlled() {
- context.add_identifier("VimControl");
+ context.add("VimControl");
}
if self.active_operator().is_none() && self.pre_count.is_some()
|| self.active_operator().is_some() && self.post_count.is_some()
{
- context.add_identifier("VimCount");
+ context.add("VimCount");
}
let active_operator = self.active_operator();
if let Some(active_operator) = active_operator {
for context_flag in active_operator.context_flags().into_iter() {
- context.add_identifier(*context_flag);
+ context.add(*context_flag);
}
}
- context.add_key(
+ context.set(
"vim_operator",
active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
);
@@ -3,8 +3,6 @@ mod neovim_backed_test_context;
mod neovim_connection;
mod vim_test_context;
-use std::sync::Arc;
-
use command_palette::CommandPalette;
use editor::DisplayPoint;
pub use neovim_backed_binding_test_context::*;
@@ -96,7 +94,7 @@ async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
.expect("Buffer search bar should be deployed")
});
- search_bar.read_with(cx.cx, |bar, cx| {
+ cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "");
})
}
@@ -149,9 +147,10 @@ async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
cx.set_state("aˇbc\n", Mode::Normal);
cx.simulate_keystrokes(["i", "cmd-shift-p"]);
- assert!(cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
+ assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
cx.simulate_keystroke("escape");
- assert!(!cx.workspace(|workspace, _| workspace.modal::<CommandPalette>().is_some()));
+ cx.run_until_parked();
+ assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
cx.assert_state("aˇbc\n", Mode::Insert);
}
@@ -182,7 +181,7 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
.expect("Buffer search bar should be deployed")
});
- search_bar.read_with(cx.cx, |bar, cx| {
+ cx.update_view(search_bar, |bar, cx| {
assert_eq!(bar.query(cx), "cc");
});
@@ -204,12 +203,8 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_status_indicator(
- cx: &mut gpui::TestAppContext,
- deterministic: Arc<gpui::executor::Deterministic>,
-) {
+async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
- deterministic.run_until_parked();
let mode_indicator = cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
@@ -225,7 +220,6 @@ async fn test_status_indicator(
// shows the correct mode
cx.simulate_keystrokes(["i"]);
- deterministic.run_until_parked();
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Insert)
@@ -233,7 +227,6 @@ async fn test_status_indicator(
// shows even in search
cx.simulate_keystrokes(["escape", "v", "/"]);
- deterministic.run_until_parked();
assert_eq!(
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
Some(Mode::Visual)
@@ -241,7 +234,7 @@ async fn test_status_indicator(
// hides if vim mode is disabled
cx.disable_vim();
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
@@ -249,7 +242,7 @@ async fn test_status_indicator(
});
cx.enable_vim();
- deterministic.run_until_parked();
+ cx.run_until_parked();
cx.workspace(|workspace, cx| {
let status_bar = workspace.status_bar().read(cx);
let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
@@ -1,4 +1,5 @@
-use editor::scroll::VERTICAL_SCROLL_MARGIN;
+use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle};
+use gpui::{px, size, Context};
use indoc::indoc;
use settings::SettingsStore;
use std::{
@@ -7,7 +8,6 @@ use std::{
};
use collections::{HashMap, HashSet};
-use gpui::{geometry::vector::vec2f, ContextHandle};
use language::language_settings::{AllLanguageSettings, SoftWrap};
use util::test::marked_text_offsets;
@@ -158,11 +158,28 @@ impl<'a> NeovimBackedTestContext<'a> {
.await;
// +2 to account for the vim command UI at the bottom.
self.neovim.set_option(&format!("lines={}", rows + 2)).await;
+ let (line_height, visible_line_count) = self.editor(|editor, cx| {
+ (
+ editor
+ .style()
+ .unwrap()
+ .text
+ .line_height_in_pixels(cx.rem_size()),
+ editor.visible_line_count().unwrap(),
+ )
+ });
+
let window = self.window;
- let line_height =
- self.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
+ let margin = self
+ .update_window(window, |_, cx| {
+ cx.viewport_size().height - line_height * visible_line_count
+ })
+ .unwrap();
- window.simulate_resize(vec2f(1000., (rows as f32) * line_height), &mut self.cx);
+ self.simulate_window_resize(
+ self.window,
+ size(px(1000.), margin + (rows as f32) * line_height),
+ );
}
pub async fn set_neovim_option(&mut self, option: &str) {
@@ -211,12 +228,7 @@ impl<'a> NeovimBackedTestContext<'a> {
pub async fn assert_shared_clipboard(&mut self, text: &str) {
let neovim = self.neovim.read_register('"').await;
- let editor = self
- .platform()
- .read_from_clipboard()
- .unwrap()
- .text()
- .clone();
+ let editor = self.read_from_clipboard().unwrap().text().clone();
if text == neovim && text == editor {
return;
@@ -10,7 +10,7 @@ use async_compat::Compat;
#[cfg(feature = "neovim")]
use async_trait::async_trait;
#[cfg(feature = "neovim")]
-use gpui::keymap_matcher::Keystroke;
+use gpui::Keystroke;
#[cfg(feature = "neovim")]
use language::Point;
@@ -116,16 +116,24 @@ impl NeovimConnection {
keystroke.key = "lt".to_string()
}
- let special = keystroke.shift
- || keystroke.ctrl
- || keystroke.alt
- || keystroke.cmd
+ let special = keystroke.modifiers.shift
+ || keystroke.modifiers.control
+ || keystroke.modifiers.alt
+ || keystroke.modifiers.command
|| keystroke.key.len() > 1;
let start = if special { "<" } else { "" };
- let shift = if keystroke.shift { "S-" } else { "" };
- let ctrl = if keystroke.ctrl { "C-" } else { "" };
- let alt = if keystroke.alt { "M-" } else { "" };
- let cmd = if keystroke.cmd { "D-" } else { "" };
+ let shift = if keystroke.modifiers.shift { "S-" } else { "" };
+ let ctrl = if keystroke.modifiers.control {
+ "C-"
+ } else {
+ ""
+ };
+ let alt = if keystroke.modifiers.alt { "M-" } else { "" };
+ let cmd = if keystroke.modifiers.command {
+ "D-"
+ } else {
+ ""
+ };
let end = if special { ">" } else { "" };
let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);
@@ -4,9 +4,9 @@ use editor::test::{
editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
};
use futures::Future;
-use gpui::ContextHandle;
+use gpui::{Context, View, VisualContext};
use lsp::request;
-use search::{BufferSearchBar, ProjectSearchBar};
+use search::BufferSearchBar;
use crate::{state::Operator, *};
@@ -15,12 +15,28 @@ pub struct VimTestContext<'a> {
}
impl<'a> VimTestContext<'a> {
+ pub fn init(cx: &mut gpui::TestAppContext) {
+ if cx.has_global::<Vim>() {
+ dbg!("OOPS");
+ return;
+ }
+ cx.update(|cx| {
+ search::init(cx);
+ let settings = SettingsStore::test(cx);
+ cx.set_global(settings);
+ command_palette::init(cx);
+ crate::init(cx);
+ });
+ }
+
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
+ Self::init(cx);
let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
Self::new_with_lsp(lsp, enabled)
}
pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
+ Self::init(cx);
Self::new_with_lsp(
EditorLspTestContext::new_typescript(Default::default(), cx).await,
true,
@@ -28,12 +44,6 @@ impl<'a> VimTestContext<'a> {
}
pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
- cx.update(|cx| {
- search::init(cx);
- crate::init(cx);
- command_palette::init(cx);
- });
-
cx.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
@@ -47,14 +57,15 @@ impl<'a> VimTestContext<'a> {
observe_keystrokes(cx);
workspace.active_pane().update(cx, |pane, cx| {
pane.toolbar().update(cx, |toolbar, cx| {
- let buffer_search_bar = cx.add_view(BufferSearchBar::new);
+ let buffer_search_bar = cx.new_view(BufferSearchBar::new);
toolbar.add_item(buffer_search_bar, cx);
- let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
- toolbar.add_item(project_search_bar, cx);
+ // todo!();
+ // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+ // toolbar.add_item(project_search_bar, cx);
})
});
workspace.status_bar().update(cx, |status_bar, cx| {
- let vim_mode_indicator = cx.add_view(ModeIndicator::new);
+ let vim_mode_indicator = cx.new_view(ModeIndicator::new);
status_bar.add_right_item(vim_mode_indicator, cx);
});
});
@@ -62,11 +73,21 @@ impl<'a> VimTestContext<'a> {
Self { cx }
}
- pub fn workspace<F, T>(&mut self, read: F) -> T
+ pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
where
- F: FnOnce(&Workspace, &ViewContext<Workspace>) -> T,
+ T: 'static,
+ F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
{
- self.cx.workspace.read_with(self.cx.cx.cx, read)
+ let window = self.window.clone();
+ self.update_window(window, move |_, cx| view.update(cx, update))
+ .unwrap()
+ }
+
+ pub fn workspace<F, T>(&mut self, update: F) -> T
+ where
+ F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+ {
+ self.cx.update_workspace(update)
}
pub fn enable_vim(&mut self) {
@@ -94,16 +115,16 @@ impl<'a> VimTestContext<'a> {
.read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
}
- pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
+ pub fn set_state(&mut self, text: &str, mode: Mode) {
let window = self.window;
- let context_handle = self.cx.set_state(text);
- window.update(self.cx.cx.cx, |cx| {
+ self.cx.set_state(text);
+ self.update_window(window, |_, cx| {
Vim::update(cx, |vim, cx| {
vim.switch_mode(mode, true, cx);
})
- });
- self.cx.foreground().run_until_parked();
- context_handle
+ })
+ .unwrap();
+ self.cx.cx.cx.run_until_parked();
}
#[track_caller]
@@ -1,5 +1,3 @@
-#![allow(unused)]
-
#[cfg(test)]
mod test;
@@ -17,17 +15,17 @@ mod visual;
use anyhow::Result;
use collections::{CommandPaletteFilter, HashMap};
use command_palette::CommandPaletteInterceptor;
-use editor::{movement, Editor, EditorMode, Event};
+use editor::{movement, Editor, EditorEvent, EditorMode};
use gpui::{
- actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, Action,
- AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+ actions, impl_actions, Action, AppContext, EntityId, KeyContext, Subscription, View,
+ ViewContext, WeakView, WindowContext,
};
use language::{CursorShape, Point, Selection, SelectionGoal};
pub use mode_indicator::ModeIndicator;
use motion::Motion;
use normal::normal_replace;
use serde::Deserialize;
-use settings::{update_settings_file, Setting, SettingsStore};
+use settings::{update_settings_file, Settings, SettingsStore};
use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
use std::{ops::Range, sync::Arc};
use visual::{visual_block_motion, visual_replace};
@@ -50,83 +48,86 @@ actions!(
vim,
[Tab, Enter, Object, InnerObject, FindForward, FindBackward]
);
+// in the workspace namespace so it's not filtered out when vim is disabled.
actions!(workspace, [ToggleVimMode]);
-impl_actions!(vim, [Number, SwitchMode, PushOperator]);
-#[derive(Copy, Clone, Debug)]
-enum VimEvent {
- ModeChanged { mode: Mode },
-}
+impl_actions!(vim, [SwitchMode, PushOperator, Number]);
pub fn init(cx: &mut AppContext) {
cx.set_global(Vim::default());
- settings::register::<VimModeSetting>(cx);
+ VimModeSetting::register(cx);
editor_events::init(cx);
- normal::init(cx);
- visual::init(cx);
- insert::init(cx);
- object::init(cx);
- motion::init(cx);
- command::init(cx);
-
- // Vim Actions
- cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
+
+ cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
+ .detach();
+
+ // Any time settings change, update vim mode to match. The Vim struct
+ // will be initialized as disabled by default, so we filter its commands
+ // out when starting up.
+ cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
+ filter.hidden_namespaces.insert("vim");
+ });
+ cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
+ vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+ });
+ cx.observe_global::<SettingsStore>(|cx| {
+ cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
+ vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
+ });
+ })
+ .detach();
+}
+
+fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
});
- cx.add_action(
+ workspace.register_action(
|_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
},
);
- cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
+ workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
});
- cx.add_action(|_: &mut Workspace, _: &Tab, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
Vim::active_editor_input_ignored(" ".into(), cx)
});
- cx.add_action(|_: &mut Workspace, _: &Enter, cx| {
+ workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
Vim::active_editor_input_ignored("\n".into(), cx)
});
- cx.add_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
+ workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
let fs = workspace.app_state().fs.clone();
- let currently_enabled = settings::get::<VimModeSetting>(cx).0;
+ let currently_enabled = VimModeSetting::get_global(cx).0;
update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
*setting = Some(!currently_enabled)
})
});
- // Any time settings change, update vim mode to match. The Vim struct
- // will be initialized as disabled by default, so we filter its commands
- // out when starting up.
- cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
- filter.hidden_namespaces.insert("vim");
- });
- cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
- vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
- });
- cx.observe_global::<SettingsStore, _>(|cx| {
- cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
- vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
- });
- })
- .detach();
+ normal::register(workspace, cx);
+ insert::register(workspace, cx);
+ motion::register(workspace, cx);
+ command::register(workspace, cx);
+ object::register(workspace, cx);
+ visual::register(workspace, cx);
}
pub fn observe_keystrokes(cx: &mut WindowContext) {
- cx.observe_keystrokes(|_keystroke, result, handled_by, cx| {
- if result == &MatchResult::Pending {
- return true;
- }
- if let Some(handled_by) = handled_by {
+ cx.observe_keystrokes(|keystroke_event, cx| {
+ if let Some(action) = keystroke_event
+ .action
+ .as_ref()
+ .map(|action| action.boxed_clone())
+ {
Vim::update(cx, |vim, _| {
if vim.workspace_state.recording {
vim.workspace_state
.recorded_actions
- .push(ReplayableAction::Action(handled_by.boxed_clone()));
+ .push(ReplayableAction::Action(action.boxed_clone()));
if vim.workspace_state.stop_recording_after_next_action {
vim.workspace_state.recording = false;
@@ -136,9 +137,11 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
});
// Keystroke is handled by the vim system, so continue forward
- if handled_by.namespace() == "vim" {
- return true;
+ if action.name().starts_with("vim::") {
+ return;
}
+ } else if cx.has_pending_keystrokes() {
+ return;
}
Vim::update(cx, |vim, cx| match vim.active_operator() {
@@ -150,24 +153,23 @@ pub fn observe_keystrokes(cx: &mut WindowContext) {
}
_ => {}
});
- true
})
.detach()
}
#[derive(Default)]
pub struct Vim {
- active_editor: Option<WeakViewHandle<Editor>>,
+ active_editor: Option<WeakView<Editor>>,
editor_subscription: Option<Subscription>,
enabled: bool,
- editor_states: HashMap<usize, EditorState>,
+ editor_states: HashMap<EntityId, EditorState>,
workspace_state: WorkspaceState,
default_state: EditorState,
}
impl Vim {
fn read(cx: &mut AppContext) -> &Self {
- cx.default_global()
+ cx.global::<Self>()
}
fn update<F, S>(cx: &mut WindowContext, update: F) -> S
@@ -177,21 +179,21 @@ impl Vim {
cx.update_global(update)
}
- fn set_active_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut WindowContext) {
+ fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
self.active_editor = Some(editor.clone().downgrade());
self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
- Event::SelectionsChanged { local: true } => {
+ EditorEvent::SelectionsChanged { local: true } => {
let editor = editor.read(cx);
if editor.leader_peer_id().is_none() {
let newest = editor.selections.newest::<usize>(cx);
local_selections_changed(newest, cx);
}
}
- Event::InputIgnored { text } => {
+ EditorEvent::InputIgnored { text } => {
Vim::active_editor_input_ignored(text.clone(), cx);
Vim::record_insertion(text, None, cx)
}
- Event::InputHandled {
+ EditorEvent::InputHandled {
text,
utf16_range_to_replace: range_to_replace,
} => Vim::record_insertion(text, range_to_replace.clone(), cx),
@@ -242,7 +244,7 @@ impl Vim {
cx: &mut WindowContext,
update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
) -> Option<S> {
- let editor = self.active_editor.clone()?.upgrade(cx)?;
+ let editor = self.active_editor.clone()?.upgrade()?;
Some(editor.update(cx, update))
}
@@ -254,7 +256,8 @@ impl Vim {
let selections = self
.active_editor
- .and_then(|editor| editor.upgrade(cx))
+ .as_ref()
+ .and_then(|editor| editor.upgrade())
.map(|editor| {
let editor = editor.read(cx);
(
@@ -323,8 +326,6 @@ impl Vim {
self.take_count(cx);
}
- cx.emit_global(VimEvent::ModeChanged { mode });
-
// Sync editor settings like clip mode
self.sync_vim_settings(cx);
@@ -477,7 +478,7 @@ impl Vim {
if self.enabled != enabled {
self.enabled = enabled;
- cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
+ cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
if self.enabled {
filter.hidden_namespaces.remove("vim");
} else {
@@ -491,26 +492,30 @@ impl Vim {
let _ = cx.remove_global::<CommandPaletteInterceptor>();
}
- cx.update_active_window(|cx| {
- if self.enabled {
- let active_editor = cx
- .root_view()
- .downcast_ref::<Workspace>()
- .and_then(|workspace| workspace.read(cx).active_item(cx))
- .and_then(|item| item.downcast::<Editor>());
- if let Some(active_editor) = active_editor {
- self.set_active_editor(active_editor, cx);
- }
- self.switch_mode(Mode::Normal, false, cx);
- }
- self.sync_vim_settings(cx);
- });
+ if let Some(active_window) = cx.active_window() {
+ active_window
+ .update(cx, |root_view, cx| {
+ if self.enabled {
+ let active_editor = root_view
+ .downcast::<Workspace>()
+ .ok()
+ .and_then(|workspace| workspace.read(cx).active_item(cx))
+ .and_then(|item| item.downcast::<Editor>());
+ if let Some(active_editor) = active_editor {
+ self.set_active_editor(active_editor, cx);
+ }
+ self.switch_mode(Mode::Normal, false, cx);
+ }
+ self.sync_vim_settings(cx);
+ })
+ .ok();
+ }
}
}
pub fn state(&self) -> &EditorState {
if let Some(active_editor) = self.active_editor.as_ref() {
- if let Some(state) = self.editor_states.get(&active_editor.id()) {
+ if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
return state;
}
}
@@ -523,7 +528,7 @@ impl Vim {
let ret = func(&mut state);
if let Some(active_editor) = self.active_editor.as_ref() {
- self.editor_states.insert(active_editor.id(), state);
+ self.editor_states.insert(active_editor.entity_id(), state);
}
ret
@@ -564,8 +569,8 @@ impl Vim {
// This is a bit of a hack, but currently the search crate does not depend on vim,
// and it seems nice to keep it that way.
if self.enabled {
- let mut context = KeymapContext::default();
- context.add_identifier("VimEnabled");
+ let mut context = KeyContext::default();
+ context.add("VimEnabled");
editor.set_keymap_context_layer::<Self>(context, cx)
} else {
editor.remove_keymap_context_layer::<Self>(cx);
@@ -573,7 +578,7 @@ impl Vim {
}
}
-impl Setting for VimModeSetting {
+impl Settings for VimModeSetting {
const KEY: Option<&'static str> = Some("vim_mode");
type FileContent = Option<bool>;
@@ -581,7 +586,7 @@ impl Setting for VimModeSetting {
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
- _: &AppContext,
+ _: &mut AppContext,
) -> Result<Self> {
Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
default_value.ok_or_else(Self::missing_default)?,
@@ -8,7 +8,7 @@ use editor::{
scroll::autoscroll::Autoscroll,
Bias, DisplayPoint, Editor,
};
-use gpui::{actions, AppContext, ViewContext, WindowContext};
+use gpui::{actions, ViewContext, WindowContext};
use language::{Selection, SelectionGoal};
use workspace::Workspace;
@@ -34,24 +34,28 @@ actions!(
]
);
-pub fn init(cx: &mut AppContext) {
- cx.add_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
+pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+ workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
toggle_mode(Mode::Visual, cx)
});
- cx.add_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
+ workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
toggle_mode(Mode::VisualLine, cx)
});
- cx.add_action(
+ workspace.register_action(
|_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
toggle_mode(Mode::VisualBlock, cx)
},
);
- cx.add_action(other_end);
- cx.add_action(delete);
- cx.add_action(yank);
+ workspace.register_action(other_end);
+ workspace.register_action(delete);
+ workspace.register_action(yank);
- cx.add_action(select_next);
- cx.add_action(select_previous);
+ workspace.register_action(|workspace, action, cx| {
+ select_next(workspace, action, cx).ok();
+ });
+ workspace.register_action(|workspace, action, cx| {
+ select_previous(workspace, action, cx).ok();
+ });
}
pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
@@ -146,13 +150,13 @@ pub fn visual_block_motion(
let mut head = s.newest_anchor().head().to_display_point(map);
let mut tail = s.oldest_anchor().tail().to_display_point(map);
- let mut head_x = map.x_for_point(head, &text_layout_details);
- let mut tail_x = map.x_for_point(tail, &text_layout_details);
+ let mut head_x = map.x_for_display_point(head, &text_layout_details);
+ let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
let (start, end) = match s.newest_anchor().goal {
SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
- _ => (tail_x, head_x),
+ _ => (tail_x.0, head_x.0),
};
let mut goal = SelectionGoal::HorizontalRange { start, end };
@@ -165,19 +169,19 @@ pub fn visual_block_motion(
return;
};
head = new_head;
- head_x = map.x_for_point(head, &text_layout_details);
+ head_x = map.x_for_display_point(head, &text_layout_details);
let is_reversed = tail_x > head_x;
if was_reversed && !is_reversed {
tail = movement::saturating_left(map, tail);
- tail_x = map.x_for_point(tail, &text_layout_details);
+ tail_x = map.x_for_display_point(tail, &text_layout_details);
} else if !was_reversed && is_reversed {
tail = movement::saturating_right(map, tail);
- tail_x = map.x_for_point(tail, &text_layout_details);
+ tail_x = map.x_for_display_point(tail, &text_layout_details);
}
if !is_reversed && !preserve_goal {
head = movement::saturating_right(map, head);
- head_x = map.x_for_point(head, &text_layout_details);
+ head_x = map.x_for_display_point(head, &text_layout_details);
}
let positions = if is_reversed {
@@ -188,8 +192,8 @@ pub fn visual_block_motion(
if !preserve_goal {
goal = SelectionGoal::HorizontalRange {
- start: positions.start,
- end: positions.end,
+ start: positions.start.0,
+ end: positions.end.0,
};
}
@@ -197,7 +201,7 @@ pub fn visual_block_motion(
let mut row = tail.row();
loop {
- let layed_out_line = map.lay_out_line_for_row(row, &text_layout_details);
+ let layed_out_line = map.layout_row(row, &text_layout_details);
let start = DisplayPoint::new(
row,
layed_out_line.closest_index_for_x(positions.start) as u32,
@@ -214,7 +218,7 @@ pub fn visual_block_motion(
}
}
- if positions.start <= layed_out_line.width() {
+ if positions.start <= layed_out_line.width {
let selection = Selection {
id: s.new_selection_id(),
start: start.to_point(map),
@@ -749,7 +753,12 @@ mod test {
fox jumps over
the lazy dog"})
.await;
- cx.assert_clipboard_content(Some("The q"));
+ assert_eq!(
+ cx.read_from_clipboard()
+ .map(|item| item.text().clone())
+ .unwrap(),
+ "The q"
+ );
cx.set_shared_state(indoc! {"
The quick brown
@@ -1,53 +0,0 @@
-[package]
-name = "vim2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/vim.rs"
-doctest = false
-
-[features]
-neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
-
-[dependencies]
-anyhow.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-itertools = "0.10"
-log.workspace = true
-
-async-compat = { version = "0.2.1", "optional" = true }
-async-trait = { workspace = true, "optional" = true }
-nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
-tokio = { version = "1.15", "optional" = true }
-serde_json.workspace = true
-
-collections = { path = "../collections" }
-command_palette = { package = "command_palette2", path = "../command_palette2" }
-editor = { package = "editor2", path = "../editor2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-language = { package = "language2", path = "../language2" }
-search = { package = "search2", path = "../search2" }
-settings = { package = "settings2", path = "../settings2" }
-workspace = { package = "workspace2", path = "../workspace2" }
-theme = { package = "theme2", path = "../theme2" }
-ui = { package = "ui2", path = "../ui2"}
-diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
-zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
-
-[dev-dependencies]
-indoc.workspace = true
-parking_lot.workspace = true
-futures.workspace = true
-
-editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-language = { package = "language2", path = "../language2", features = ["test-support"] }
-project = { package = "project2", path = "../project2", features = ["test-support"] }
-util = { path = "../util", features = ["test-support"] }
-settings = { package = "settings2", path = "../settings2" }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
-theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
@@ -1,434 +0,0 @@
-use command_palette::CommandInterceptResult;
-use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
-use gpui::{impl_actions, Action, AppContext, ViewContext};
-use serde_derive::Deserialize;
-use workspace::{SaveIntent, Workspace};
-
-use crate::{
- motion::{EndOfDocument, Motion},
- normal::{
- move_cursor,
- search::{FindCommand, ReplaceCommand},
- JoinLines,
- },
- state::Mode,
- Vim,
-};
-
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct GoToLine {
- pub line: u32,
-}
-
-impl_actions!(vim, [GoToLine]);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, action: &GoToLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.switch_mode(Mode::Normal, false, cx);
- move_cursor(vim, Motion::StartOfDocument, Some(action.line as usize), cx);
- });
- });
-}
-
-pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option<CommandInterceptResult> {
- // Note: this is a very poor simulation of vim's command palette.
- // In the future we should adjust it to handle parsing range syntax,
- // and then calling the appropriate commands with/without ranges.
- //
- // We also need to support passing arguments to commands like :w
- // (ideally with filename autocompletion).
- //
- // For now, you can only do a replace on the % range, and you can
- // only use a specific line number range to "go to line"
- while query.starts_with(":") {
- query = &query[1..];
- }
-
- let (name, action) = match query {
- // save and quit
- "w" | "wr" | "wri" | "writ" | "write" => (
- "write",
- workspace::Save {
- save_intent: Some(SaveIntent::Save),
- }
- .boxed_clone(),
- ),
- "w!" | "wr!" | "wri!" | "writ!" | "write!" => (
- "write!",
- workspace::Save {
- save_intent: Some(SaveIntent::Overwrite),
- }
- .boxed_clone(),
- ),
- "q" | "qu" | "qui" | "quit" => (
- "quit",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::Close),
- }
- .boxed_clone(),
- ),
- "q!" | "qu!" | "qui!" | "quit!" => (
- "quit!",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::Skip),
- }
- .boxed_clone(),
- ),
- "wq" => (
- "wq",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::Save),
- }
- .boxed_clone(),
- ),
- "wq!" => (
- "wq!",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::Overwrite),
- }
- .boxed_clone(),
- ),
- "x" | "xi" | "xit" | "exi" | "exit" => (
- "exit",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::SaveAll),
- }
- .boxed_clone(),
- ),
- "x!" | "xi!" | "xit!" | "exi!" | "exit!" => (
- "exit!",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::Overwrite),
- }
- .boxed_clone(),
- ),
- "up" | "upd" | "upda" | "updat" | "update" => (
- "update",
- workspace::Save {
- save_intent: Some(SaveIntent::SaveAll),
- }
- .boxed_clone(),
- ),
- "wa" | "wal" | "wall" => (
- "wall",
- workspace::SaveAll {
- save_intent: Some(SaveIntent::SaveAll),
- }
- .boxed_clone(),
- ),
- "wa!" | "wal!" | "wall!" => (
- "wall!",
- workspace::SaveAll {
- save_intent: Some(SaveIntent::Overwrite),
- }
- .boxed_clone(),
- ),
- "qa" | "qal" | "qall" | "quita" | "quital" | "quitall" => (
- "quitall",
- workspace::CloseAllItemsAndPanes {
- save_intent: Some(SaveIntent::Close),
- }
- .boxed_clone(),
- ),
- "qa!" | "qal!" | "qall!" | "quita!" | "quital!" | "quitall!" => (
- "quitall!",
- workspace::CloseAllItemsAndPanes {
- save_intent: Some(SaveIntent::Skip),
- }
- .boxed_clone(),
- ),
- "xa" | "xal" | "xall" => (
- "xall",
- workspace::CloseAllItemsAndPanes {
- save_intent: Some(SaveIntent::SaveAll),
- }
- .boxed_clone(),
- ),
- "xa!" | "xal!" | "xall!" => (
- "xall!",
- workspace::CloseAllItemsAndPanes {
- save_intent: Some(SaveIntent::Overwrite),
- }
- .boxed_clone(),
- ),
- "wqa" | "wqal" | "wqall" => (
- "wqall",
- workspace::CloseAllItemsAndPanes {
- save_intent: Some(SaveIntent::SaveAll),
- }
- .boxed_clone(),
- ),
- "wqa!" | "wqal!" | "wqall!" => (
- "wqall!",
- workspace::CloseAllItemsAndPanes {
- save_intent: Some(SaveIntent::Overwrite),
- }
- .boxed_clone(),
- ),
- "cq" | "cqu" | "cqui" | "cquit" | "cq!" | "cqu!" | "cqui!" | "cquit!" => {
- ("cquit!", zed_actions::Quit.boxed_clone())
- }
-
- // pane management
- "sp" | "spl" | "spli" | "split" => ("split", workspace::SplitUp.boxed_clone()),
- "vs" | "vsp" | "vspl" | "vspli" | "vsplit" => {
- ("vsplit", workspace::SplitLeft.boxed_clone())
- }
- "new" => (
- "new",
- workspace::NewFileInDirection(workspace::SplitDirection::Up).boxed_clone(),
- ),
- "vne" | "vnew" => (
- "vnew",
- workspace::NewFileInDirection(workspace::SplitDirection::Left).boxed_clone(),
- ),
- "tabe" | "tabed" | "tabedi" | "tabedit" => ("tabedit", workspace::NewFile.boxed_clone()),
- "tabnew" => ("tabnew", workspace::NewFile.boxed_clone()),
-
- "tabn" | "tabne" | "tabnex" | "tabnext" => {
- ("tabnext", workspace::ActivateNextItem.boxed_clone())
- }
- "tabp" | "tabpr" | "tabpre" | "tabprev" | "tabprevi" | "tabprevio" | "tabpreviou"
- | "tabprevious" => ("tabprevious", workspace::ActivatePrevItem.boxed_clone()),
- "tabN" | "tabNe" | "tabNex" | "tabNext" => {
- ("tabNext", workspace::ActivatePrevItem.boxed_clone())
- }
- "tabc" | "tabcl" | "tabclo" | "tabclos" | "tabclose" => (
- "tabclose",
- workspace::CloseActiveItem {
- save_intent: Some(SaveIntent::Close),
- }
- .boxed_clone(),
- ),
-
- // quickfix / loclist (merged together for now)
- "cl" | "cli" | "clis" | "clist" => ("clist", diagnostics::Deploy.boxed_clone()),
- "cc" => ("cc", editor::Hover.boxed_clone()),
- "ll" => ("ll", editor::Hover.boxed_clone()),
- "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
- "lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
-
- "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => {
- ("cprevious", editor::GoToPrevDiagnostic.boxed_clone())
- }
- "cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()),
- "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => {
- ("lprevious", editor::GoToPrevDiagnostic.boxed_clone())
- }
- "lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()),
-
- // modify the buffer (should accept [range])
- "j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()),
- "d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl"
- | "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => {
- ("delete", editor::DeleteLine.boxed_clone())
- }
- "sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()),
- "sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()),
-
- // goto (other ranges handled under _ => )
- "$" => ("$", EndOfDocument.boxed_clone()),
-
- _ => {
- if query.starts_with("/") || query.starts_with("?") {
- (
- query,
- FindCommand {
- query: query[1..].to_string(),
- backwards: query.starts_with("?"),
- }
- .boxed_clone(),
- )
- } else if query.starts_with("%") {
- (
- query,
- ReplaceCommand {
- query: query.to_string(),
- }
- .boxed_clone(),
- )
- } else if let Ok(line) = query.parse::<u32>() {
- (query, GoToLine { line }.boxed_clone())
- } else {
- return None;
- }
- }
- };
-
- let string = ":".to_owned() + name;
- let positions = generate_positions(&string, query);
-
- Some(CommandInterceptResult {
- action,
- string,
- positions,
- })
-}
-
-fn generate_positions(string: &str, query: &str) -> Vec<usize> {
- let mut positions = Vec::new();
- let mut chars = query.chars().into_iter();
-
- let Some(mut current) = chars.next() else {
- return positions;
- };
-
- for (i, c) in string.chars().enumerate() {
- if c == current {
- positions.push(i);
- if let Some(c) = chars.next() {
- current = c;
- } else {
- break;
- }
- }
- }
-
- positions
-}
-
-#[cfg(test)]
-mod test {
- use std::path::Path;
-
- use crate::test::{NeovimBackedTestContext, VimTestContext};
- use gpui::TestAppContext;
- use indoc::indoc;
-
- #[gpui::test]
- async fn test_command_basics(cx: &mut TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ˇa
- b
- c"})
- .await;
-
- cx.simulate_shared_keystrokes([":", "j", "enter"]).await;
-
- // hack: our cursor positionining after a join command is wrong
- cx.simulate_shared_keystrokes(["^"]).await;
- cx.assert_shared_state(indoc! {
- "ˇa b
- c"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_command_goto(cx: &mut TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ˇa
- b
- c"})
- .await;
- cx.simulate_shared_keystrokes([":", "3", "enter"]).await;
- cx.assert_shared_state(indoc! {"
- a
- b
- ˇc"})
- .await;
- }
-
- #[gpui::test]
- async fn test_command_replace(cx: &mut TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ˇa
- b
- c"})
- .await;
- cx.simulate_shared_keystrokes([":", "%", "s", "/", "b", "/", "d", "enter"])
- .await;
- cx.assert_shared_state(indoc! {"
- a
- ˇd
- c"})
- .await;
- cx.simulate_shared_keystrokes([
- ":", "%", "s", ":", ".", ":", "\\", "0", "\\", "0", "enter",
- ])
- .await;
- cx.assert_shared_state(indoc! {"
- aa
- dd
- ˇcc"})
- .await;
- }
-
- #[gpui::test]
- async fn test_command_search(cx: &mut TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ˇa
- b
- a
- c"})
- .await;
- cx.simulate_shared_keystrokes([":", "/", "b", "enter"])
- .await;
- cx.assert_shared_state(indoc! {"
- a
- ˇb
- a
- c"})
- .await;
- cx.simulate_shared_keystrokes([":", "?", "a", "enter"])
- .await;
- cx.assert_shared_state(indoc! {"
- ˇa
- b
- a
- c"})
- .await;
- }
-
- #[gpui::test]
- async fn test_command_write(cx: &mut TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
- let path = Path::new("/root/dir/file.rs");
- let fs = cx.workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
-
- cx.simulate_keystrokes(["i", "@", "escape"]);
- cx.simulate_keystrokes([":", "w", "enter"]);
-
- assert_eq!(fs.load(&path).await.unwrap(), "@\n");
-
- fs.as_fake()
- .write_file_internal(path, "oops\n".to_string())
- .unwrap();
-
- // conflict!
- cx.simulate_keystrokes(["i", "@", "escape"]);
- cx.simulate_keystrokes([":", "w", "enter"]);
- assert!(cx.has_pending_prompt());
- // "Cancel"
- cx.simulate_prompt_answer(0);
- assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
- assert!(!cx.has_pending_prompt());
- // force overwrite
- cx.simulate_keystrokes([":", "w", "!", "enter"]);
- assert!(!cx.has_pending_prompt());
- assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
- }
-
- #[gpui::test]
- async fn test_command_quit(cx: &mut TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
- cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
- cx.simulate_keystrokes([":", "q", "enter"]);
- cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
- cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
- cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
- cx.simulate_keystrokes([":", "q", "a", "enter"]);
- cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 0));
- }
-}
@@ -1,104 +0,0 @@
-use crate::Vim;
-use editor::{Editor, EditorEvent};
-use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext};
-
-pub fn init(cx: &mut AppContext) {
- cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
- let editor = cx.view().clone();
- cx.subscribe(&editor, |_, editor, event: &EditorEvent, cx| match event {
- EditorEvent::Focused => cx.window_context().defer(|cx| focused(editor, cx)),
- EditorEvent::Blurred => cx.window_context().defer(|cx| blurred(editor, cx)),
- _ => {}
- })
- .detach();
-
- let id = cx.view().entity_id();
- cx.on_release(move |_, _, cx| released(id, cx)).detach();
- })
- .detach();
-}
-
-fn focused(editor: View<Editor>, cx: &mut WindowContext) {
- if Vim::read(cx).active_editor.clone().is_some() {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |previously_active_editor, cx| {
- vim.unhook_vim_settings(previously_active_editor, cx)
- });
- });
- }
-
- Vim::update(cx, |vim, cx| {
- vim.set_active_editor(editor.clone(), cx);
- });
-}
-
-fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.workspace_state.recording = false;
- vim.workspace_state.recorded_actions.clear();
- if let Some(previous_editor) = vim.active_editor.clone() {
- if previous_editor
- .upgrade()
- .is_some_and(|previous| previous == editor.clone())
- {
- vim.clear_operator(cx);
- vim.active_editor = None;
- vim.editor_subscription = None;
- }
- }
-
- editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
- });
-}
-
-fn released(entity_id: EntityId, cx: &mut AppContext) {
- cx.update_global(|vim: &mut Vim, _| {
- if vim
- .active_editor
- .as_ref()
- .is_some_and(|previous| previous.entity_id() == entity_id)
- {
- vim.active_editor = None;
- vim.editor_subscription = None;
- }
- vim.editor_states.remove(&entity_id)
- });
-}
-
-#[cfg(test)]
-mod test {
- use crate::{test::VimTestContext, Vim};
- use editor::Editor;
- use gpui::{Context, Entity};
- use language::Buffer;
-
- // regression test for blur called with a different active editor
- #[gpui::test]
- async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
- let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
- let editor2 = cx
- .update(|cx| {
- window2.update(cx, |_, cx| {
- cx.focus_self();
- cx.view().clone()
- })
- })
- .unwrap();
-
- cx.update(|cx| {
- let vim = Vim::read(cx);
- assert_eq!(
- vim.active_editor.as_ref().unwrap().entity_id(),
- editor2.entity_id(),
- )
- });
-
- // no panic when blurring an editor in a different window.
- cx.update_editor(|editor1, cx| {
- editor1.handle_blur(cx);
- });
- }
-}
@@ -1,125 +0,0 @@
-use crate::{normal::repeat, state::Mode, Vim};
-use editor::{scroll::autoscroll::Autoscroll, Bias};
-use gpui::{actions, Action, ViewContext};
-use language::SelectionGoal;
-use workspace::Workspace;
-
-actions!(vim, [NormalBefore]);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(normal_before);
-}
-
-fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext<Workspace>) {
- let should_repeat = Vim::update(cx, |vim, cx| {
- let count = vim.take_count(cx).unwrap_or(1);
- vim.stop_recording_immediately(action.boxed_clone());
- if count <= 1 || vim.workspace_state.replaying {
- vim.update_active_editor(cx, |editor, cx| {
- editor.cancel(&Default::default(), cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, mut cursor, _| {
- *cursor.column_mut() = cursor.column().saturating_sub(1);
- (map.clip_point(cursor, Bias::Left), SelectionGoal::None)
- });
- });
- });
- vim.switch_mode(Mode::Normal, false, cx);
- false
- } else {
- true
- }
- });
-
- if should_repeat {
- repeat::repeat(cx, true)
- }
-}
-
-#[cfg(test)]
-mod test {
- use crate::{
- state::Mode,
- test::{NeovimBackedTestContext, VimTestContext},
- };
-
- #[gpui::test]
- async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
- cx.simulate_keystroke("i");
- assert_eq!(cx.mode(), Mode::Insert);
- cx.simulate_keystrokes(["T", "e", "s", "t"]);
- cx.assert_editor_state("Testˇ");
- cx.simulate_keystroke("escape");
- assert_eq!(cx.mode(), Mode::Normal);
- cx.assert_editor_state("Tesˇt");
- }
-
- #[gpui::test]
- async fn test_insert_with_counts(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state("ˇhello\n").await;
- cx.simulate_shared_keystrokes(["5", "i", "-", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("----ˇ-hello\n").await;
-
- cx.set_shared_state("ˇhello\n").await;
- cx.simulate_shared_keystrokes(["5", "a", "-", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("h----ˇ-ello\n").await;
-
- cx.simulate_shared_keystrokes(["4", "shift-i", "-", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("---ˇ-h-----ello\n").await;
-
- cx.simulate_shared_keystrokes(["3", "shift-a", "-", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("----h-----ello--ˇ-\n").await;
-
- cx.set_shared_state("ˇhello\n").await;
- cx.simulate_shared_keystrokes(["3", "o", "o", "i", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("hello\noi\noi\noˇi\n").await;
-
- cx.set_shared_state("ˇhello\n").await;
- cx.simulate_shared_keystrokes(["3", "shift-o", "o", "i", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("oi\noi\noˇi\nhello\n").await;
- }
-
- #[gpui::test]
- async fn test_insert_with_repeat(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state("ˇhello\n").await;
- cx.simulate_shared_keystrokes(["3", "i", "-", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("--ˇ-hello\n").await;
- cx.simulate_shared_keystrokes(["."]).await;
- cx.run_until_parked();
- cx.assert_shared_state("----ˇ--hello\n").await;
- cx.simulate_shared_keystrokes(["2", "."]).await;
- cx.run_until_parked();
- cx.assert_shared_state("-----ˇ---hello\n").await;
-
- cx.set_shared_state("ˇhello\n").await;
- cx.simulate_shared_keystrokes(["2", "o", "k", "k", "escape"])
- .await;
- cx.run_until_parked();
- cx.assert_shared_state("hello\nkk\nkˇk\n").await;
- cx.simulate_shared_keystrokes(["."]).await;
- cx.run_until_parked();
- cx.assert_shared_state("hello\nkk\nkk\nkk\nkˇk\n").await;
- cx.simulate_shared_keystrokes(["1", "."]).await;
- cx.run_until_parked();
- cx.assert_shared_state("hello\nkk\nkk\nkk\nkk\nkˇk\n").await;
- }
-}
@@ -1,74 +0,0 @@
-use gpui::{div, Element, Render, Subscription, ViewContext};
-use settings::SettingsStore;
-use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
-
-use crate::{state::Mode, Vim};
-
-pub struct ModeIndicator {
- pub mode: Option<Mode>,
- _subscriptions: Vec<Subscription>,
-}
-
-impl ModeIndicator {
- pub fn new(cx: &mut ViewContext<Self>) -> Self {
- let _subscriptions = vec![
- cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
- cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
- ];
-
- let mut this = Self {
- mode: None,
- _subscriptions,
- };
- this.update_mode(cx);
- this
- }
-
- fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
- // Vim doesn't exist in some tests
- if !cx.has_global::<Vim>() {
- return;
- }
-
- let vim = Vim::read(cx);
- if vim.enabled {
- self.mode = Some(vim.state().mode);
- } else {
- self.mode = None;
- }
- }
-
- pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
- if self.mode != Some(mode) {
- self.mode = Some(mode);
- cx.notify();
- }
- }
-}
-
-impl Render for ModeIndicator {
- fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
- let Some(mode) = self.mode.as_ref() else {
- return div().into_any();
- };
-
- let text = match mode {
- Mode::Normal => "-- NORMAL --",
- Mode::Insert => "-- INSERT --",
- Mode::Visual => "-- VISUAL --",
- Mode::VisualLine => "-- VISUAL LINE --",
- Mode::VisualBlock => "-- VISUAL BLOCK --",
- };
- Label::new(text).size(LabelSize::Small).into_any_element()
- }
-}
-
-impl StatusItemView for ModeIndicator {
- fn set_active_pane_item(
- &mut self,
- _active_pane_item: Option<&dyn ItemHandle>,
- _cx: &mut ViewContext<Self>,
- ) {
- // nothing to do.
- }
-}
@@ -1,1107 +0,0 @@
-use editor::{
- char_kind,
- display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
- movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
- Bias, CharKind, DisplayPoint, ToOffset,
-};
-use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
-use language::{Point, Selection, SelectionGoal};
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{
- normal::normal_motion,
- state::{Mode, Operator},
- visual::visual_motion,
- Vim,
-};
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Motion {
- Left,
- Backspace,
- Down { display_lines: bool },
- Up { display_lines: bool },
- Right,
- NextWordStart { ignore_punctuation: bool },
- NextWordEnd { ignore_punctuation: bool },
- PreviousWordStart { ignore_punctuation: bool },
- FirstNonWhitespace { display_lines: bool },
- CurrentLine,
- StartOfLine { display_lines: bool },
- EndOfLine { display_lines: bool },
- StartOfParagraph,
- EndOfParagraph,
- StartOfDocument,
- EndOfDocument,
- Matching,
- FindForward { before: bool, char: char },
- FindBackward { after: bool, char: char },
- NextLineStart,
- StartOfLineDownward,
- EndOfLineDownward,
- GoToColumn,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct NextWordStart {
- #[serde(default)]
- ignore_punctuation: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct NextWordEnd {
- #[serde(default)]
- ignore_punctuation: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct PreviousWordStart {
- #[serde(default)]
- ignore_punctuation: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub(crate) struct Up {
- #[serde(default)]
- pub(crate) display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Down {
- #[serde(default)]
- display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct FirstNonWhitespace {
- #[serde(default)]
- display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct EndOfLine {
- #[serde(default)]
- display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct StartOfLine {
- #[serde(default)]
- pub(crate) display_lines: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-struct RepeatFind {
- #[serde(default)]
- backwards: bool,
-}
-
-impl_actions!(
- vim,
- [
- RepeatFind,
- StartOfLine,
- EndOfLine,
- FirstNonWhitespace,
- Down,
- Up,
- PreviousWordStart,
- NextWordEnd,
- NextWordStart
- ]
-);
-
-actions!(
- vim,
- [
- Left,
- Backspace,
- Right,
- CurrentLine,
- StartOfParagraph,
- EndOfParagraph,
- StartOfDocument,
- EndOfDocument,
- Matching,
- NextLineStart,
- StartOfLineDownward,
- EndOfLineDownward,
- GoToColumn,
- ]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
- workspace
- .register_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
- workspace.register_action(|_: &mut Workspace, action: &Down, cx: _| {
- motion(
- Motion::Down {
- display_lines: action.display_lines,
- },
- cx,
- )
- });
- workspace.register_action(|_: &mut Workspace, action: &Up, cx: _| {
- motion(
- Motion::Up {
- display_lines: action.display_lines,
- },
- cx,
- )
- });
- workspace.register_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
- workspace.register_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
- motion(
- Motion::FirstNonWhitespace {
- display_lines: action.display_lines,
- },
- cx,
- )
- });
- workspace.register_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
- motion(
- Motion::StartOfLine {
- display_lines: action.display_lines,
- },
- cx,
- )
- });
- workspace.register_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
- motion(
- Motion::EndOfLine {
- display_lines: action.display_lines,
- },
- cx,
- )
- });
- workspace.register_action(|_: &mut Workspace, _: &CurrentLine, cx: _| {
- motion(Motion::CurrentLine, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
- motion(Motion::StartOfParagraph, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
- motion(Motion::EndOfParagraph, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
- motion(Motion::StartOfDocument, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| {
- motion(Motion::EndOfDocument, cx)
- });
- workspace
- .register_action(|_: &mut Workspace, _: &Matching, cx: _| motion(Motion::Matching, cx));
-
- workspace.register_action(
- |_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
- motion(Motion::NextWordStart { ignore_punctuation }, cx)
- },
- );
- workspace.register_action(
- |_: &mut Workspace, &NextWordEnd { ignore_punctuation }: &NextWordEnd, cx: _| {
- motion(Motion::NextWordEnd { ignore_punctuation }, cx)
- },
- );
- workspace.register_action(
- |_: &mut Workspace,
- &PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
- cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
- );
- workspace.register_action(|_: &mut Workspace, &NextLineStart, cx: _| {
- motion(Motion::NextLineStart, cx)
- });
- workspace.register_action(|_: &mut Workspace, &StartOfLineDownward, cx: _| {
- motion(Motion::StartOfLineDownward, cx)
- });
- workspace.register_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
- motion(Motion::EndOfLineDownward, cx)
- });
- workspace
- .register_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
- workspace.register_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
- repeat_motion(action.backwards, cx)
- });
-}
-
-pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
- if let Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) =
- Vim::read(cx).active_operator()
- {
- Vim::update(cx, |vim, cx| vim.pop_operator(cx));
- }
-
- let count = Vim::update(cx, |vim, cx| vim.take_count(cx));
- let operator = Vim::read(cx).active_operator();
- match Vim::read(cx).state().mode {
- Mode::Normal => normal_motion(motion, operator, count, cx),
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_motion(motion, count, cx),
- Mode::Insert => {
- // Shouldn't execute a motion in insert mode. Ignoring
- }
- }
- Vim::update(cx, |vim, cx| vim.clear_operator(cx));
-}
-
-fn repeat_motion(backwards: bool, cx: &mut WindowContext) {
- let find = match Vim::read(cx).workspace_state.last_find.clone() {
- Some(Motion::FindForward { before, char }) => {
- if backwards {
- Motion::FindBackward {
- after: before,
- char,
- }
- } else {
- Motion::FindForward { before, char }
- }
- }
-
- Some(Motion::FindBackward { after, char }) => {
- if backwards {
- Motion::FindForward {
- before: after,
- char,
- }
- } else {
- Motion::FindBackward { after, char }
- }
- }
- _ => return,
- };
-
- motion(find, cx)
-}
-
-// Motion handling is specified here:
-// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
-impl Motion {
- pub fn linewise(&self) -> bool {
- use Motion::*;
- match self {
- Down { .. }
- | Up { .. }
- | StartOfDocument
- | EndOfDocument
- | CurrentLine
- | NextLineStart
- | StartOfLineDownward
- | StartOfParagraph
- | EndOfParagraph => true,
- EndOfLine { .. }
- | NextWordEnd { .. }
- | Matching
- | FindForward { .. }
- | Left
- | Backspace
- | Right
- | StartOfLine { .. }
- | EndOfLineDownward
- | GoToColumn
- | NextWordStart { .. }
- | PreviousWordStart { .. }
- | FirstNonWhitespace { .. }
- | FindBackward { .. } => false,
- }
- }
-
- pub fn infallible(&self) -> bool {
- use Motion::*;
- match self {
- StartOfDocument | EndOfDocument | CurrentLine => true,
- Down { .. }
- | Up { .. }
- | EndOfLine { .. }
- | NextWordEnd { .. }
- | Matching
- | FindForward { .. }
- | Left
- | Backspace
- | Right
- | StartOfLine { .. }
- | StartOfParagraph
- | EndOfParagraph
- | StartOfLineDownward
- | EndOfLineDownward
- | GoToColumn
- | NextWordStart { .. }
- | PreviousWordStart { .. }
- | FirstNonWhitespace { .. }
- | FindBackward { .. }
- | NextLineStart => false,
- }
- }
-
- pub fn inclusive(&self) -> bool {
- use Motion::*;
- match self {
- Down { .. }
- | Up { .. }
- | StartOfDocument
- | EndOfDocument
- | CurrentLine
- | EndOfLine { .. }
- | EndOfLineDownward
- | NextWordEnd { .. }
- | Matching
- | FindForward { .. }
- | NextLineStart => true,
- Left
- | Backspace
- | Right
- | StartOfLine { .. }
- | StartOfLineDownward
- | StartOfParagraph
- | EndOfParagraph
- | GoToColumn
- | NextWordStart { .. }
- | PreviousWordStart { .. }
- | FirstNonWhitespace { .. }
- | FindBackward { .. } => false,
- }
- }
-
- pub fn move_point(
- &self,
- map: &DisplaySnapshot,
- point: DisplayPoint,
- goal: SelectionGoal,
- maybe_times: Option<usize>,
- text_layout_details: &TextLayoutDetails,
- ) -> Option<(DisplayPoint, SelectionGoal)> {
- let times = maybe_times.unwrap_or(1);
- use Motion::*;
- let infallible = self.infallible();
- let (new_point, goal) = match self {
- Left => (left(map, point, times), SelectionGoal::None),
- Backspace => (backspace(map, point, times), SelectionGoal::None),
- Down {
- display_lines: false,
- } => up_down_buffer_rows(map, point, goal, times as isize, &text_layout_details),
- Down {
- display_lines: true,
- } => down_display(map, point, goal, times, &text_layout_details),
- Up {
- display_lines: false,
- } => up_down_buffer_rows(map, point, goal, 0 - times as isize, &text_layout_details),
- Up {
- display_lines: true,
- } => up_display(map, point, goal, times, &text_layout_details),
- Right => (right(map, point, times), SelectionGoal::None),
- NextWordStart { ignore_punctuation } => (
- next_word_start(map, point, *ignore_punctuation, times),
- SelectionGoal::None,
- ),
- NextWordEnd { ignore_punctuation } => (
- next_word_end(map, point, *ignore_punctuation, times),
- SelectionGoal::None,
- ),
- PreviousWordStart { ignore_punctuation } => (
- previous_word_start(map, point, *ignore_punctuation, times),
- SelectionGoal::None,
- ),
- FirstNonWhitespace { display_lines } => (
- first_non_whitespace(map, *display_lines, point),
- SelectionGoal::None,
- ),
- StartOfLine { display_lines } => (
- start_of_line(map, *display_lines, point),
- SelectionGoal::None,
- ),
- EndOfLine { display_lines } => {
- (end_of_line(map, *display_lines, point), SelectionGoal::None)
- }
- StartOfParagraph => (
- movement::start_of_paragraph(map, point, times),
- SelectionGoal::None,
- ),
- EndOfParagraph => (
- map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
- SelectionGoal::None,
- ),
- CurrentLine => (next_line_end(map, point, times), SelectionGoal::None),
- StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
- EndOfDocument => (
- end_of_document(map, point, maybe_times),
- SelectionGoal::None,
- ),
- Matching => (matching(map, point), SelectionGoal::None),
- FindForward { before, char } => (
- find_forward(map, point, *before, *char, times),
- SelectionGoal::None,
- ),
- FindBackward { after, char } => (
- find_backward(map, point, *after, *char, times),
- SelectionGoal::None,
- ),
- NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
- StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None),
- EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None),
- GoToColumn => (go_to_column(map, point, times), SelectionGoal::None),
- };
-
- (new_point != point || infallible).then_some((new_point, goal))
- }
-
- // Expands a selection using self motion for an operator
- pub fn expand_selection(
- &self,
- map: &DisplaySnapshot,
- selection: &mut Selection<DisplayPoint>,
- times: Option<usize>,
- expand_to_surrounding_newline: bool,
- text_layout_details: &TextLayoutDetails,
- ) -> bool {
- if let Some((new_head, goal)) = self.move_point(
- map,
- selection.head(),
- selection.goal,
- times,
- &text_layout_details,
- ) {
- selection.set_head(new_head, goal);
-
- if self.linewise() {
- selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
-
- if expand_to_surrounding_newline {
- if selection.end.row() < map.max_point().row() {
- *selection.end.row_mut() += 1;
- *selection.end.column_mut() = 0;
- selection.end = map.clip_point(selection.end, Bias::Right);
- // Don't reset the end here
- return true;
- } else if selection.start.row() > 0 {
- *selection.start.row_mut() -= 1;
- *selection.start.column_mut() = map.line_len(selection.start.row());
- selection.start = map.clip_point(selection.start, Bias::Left);
- }
- }
-
- (_, selection.end) = map.next_line_boundary(selection.end.to_point(map));
- } else {
- // Another special case: When using the "w" motion in combination with an
- // operator and the last word moved over is at the end of a line, the end of
- // that word becomes the end of the operated text, not the first word in the
- // next line.
- if let Motion::NextWordStart {
- ignore_punctuation: _,
- } = self
- {
- let start_row = selection.start.to_point(&map).row;
- if selection.end.to_point(&map).row > start_row {
- selection.end =
- Point::new(start_row, map.buffer_snapshot.line_len(start_row))
- .to_display_point(&map)
- }
- }
-
- // If the motion is exclusive and the end of the motion is in column 1, the
- // end of the motion is moved to the end of the previous line and the motion
- // becomes inclusive. Example: "}" moves to the first line after a paragraph,
- // but "d}" will not include that line.
- let mut inclusive = self.inclusive();
- if !inclusive
- && self != &Motion::Backspace
- && selection.end.row() > selection.start.row()
- && selection.end.column() == 0
- {
- inclusive = true;
- *selection.end.row_mut() -= 1;
- *selection.end.column_mut() = 0;
- selection.end = map.clip_point(
- map.next_line_boundary(selection.end.to_point(map)).1,
- Bias::Left,
- );
- }
-
- if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
- *selection.end.column_mut() += 1;
- }
- }
- true
- } else {
- false
- }
- }
-}
-
-fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
- for _ in 0..times {
- point = movement::saturating_left(map, point);
- if point.column() == 0 {
- break;
- }
- }
- point
-}
-
-fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
- for _ in 0..times {
- point = movement::left(map, point);
- }
- point
-}
-
-pub(crate) fn start_of_relative_buffer_row(
- map: &DisplaySnapshot,
- point: DisplayPoint,
- times: isize,
-) -> DisplayPoint {
- let start = map.display_point_to_fold_point(point, Bias::Left);
- let target = start.row() as isize + times;
- let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row());
-
- map.clip_point(
- map.fold_point_to_display_point(
- map.fold_snapshot
- .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
- ),
- Bias::Right,
- )
-}
-
-fn up_down_buffer_rows(
- map: &DisplaySnapshot,
- point: DisplayPoint,
- mut goal: SelectionGoal,
- times: isize,
- text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
- let start = map.display_point_to_fold_point(point, Bias::Left);
- let begin_folded_line = map.fold_point_to_display_point(
- map.fold_snapshot
- .clip_point(FoldPoint::new(start.row(), 0), Bias::Left),
- );
- let select_nth_wrapped_row = point.row() - begin_folded_line.row();
-
- let (goal_wrap, goal_x) = match goal {
- SelectionGoal::WrappedHorizontalPosition((row, x)) => (row, x),
- SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end),
- SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x),
- _ => {
- let x = map.x_for_display_point(point, text_layout_details);
- goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x.0));
- (select_nth_wrapped_row, x.0)
- }
- };
-
- let target = start.row() as isize + times;
- let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row());
-
- let mut begin_folded_line = map.fold_point_to_display_point(
- map.fold_snapshot
- .clip_point(FoldPoint::new(new_row, 0), Bias::Left),
- );
-
- let mut i = 0;
- while i < goal_wrap && begin_folded_line.row() < map.max_point().row() {
- let next_folded_line = DisplayPoint::new(begin_folded_line.row() + 1, 0);
- if map
- .display_point_to_fold_point(next_folded_line, Bias::Right)
- .row()
- == new_row
- {
- i += 1;
- begin_folded_line = next_folded_line;
- } else {
- break;
- }
- }
-
- let new_col = if i == goal_wrap {
- map.display_column_for_x(begin_folded_line.row(), px(goal_x), text_layout_details)
- } else {
- map.line_len(begin_folded_line.row())
- };
-
- (
- map.clip_point(
- DisplayPoint::new(begin_folded_line.row(), new_col),
- Bias::Left,
- ),
- goal,
- )
-}
-
-fn down_display(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- mut goal: SelectionGoal,
- times: usize,
- text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
- for _ in 0..times {
- (point, goal) = movement::down(map, point, goal, true, text_layout_details);
- }
-
- (point, goal)
-}
-
-fn up_display(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- mut goal: SelectionGoal,
- times: usize,
- text_layout_details: &TextLayoutDetails,
-) -> (DisplayPoint, SelectionGoal) {
- for _ in 0..times {
- (point, goal) = movement::up(map, point, goal, true, &text_layout_details);
- }
-
- (point, goal)
-}
-
-pub(crate) fn right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
- for _ in 0..times {
- let new_point = movement::saturating_right(map, point);
- if point == new_point {
- break;
- }
- point = new_point;
- }
- point
-}
-
-pub(crate) fn next_word_start(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- ignore_punctuation: bool,
- times: usize,
-) -> DisplayPoint {
- let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
- for _ in 0..times {
- let mut crossed_newline = false;
- point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
- let at_newline = right == '\n';
-
- let found = (left_kind != right_kind && right_kind != CharKind::Whitespace)
- || at_newline && crossed_newline
- || at_newline && left == '\n'; // Prevents skipping repeated empty lines
-
- crossed_newline |= at_newline;
- found
- })
- }
- point
-}
-
-fn next_word_end(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- ignore_punctuation: bool,
- times: usize,
-) -> DisplayPoint {
- let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
- for _ in 0..times {
- if point.column() < map.line_len(point.row()) {
- *point.column_mut() += 1;
- } else if point.row() < map.max_buffer_row() {
- *point.row_mut() += 1;
- *point.column_mut() = 0;
- }
- point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
- left_kind != right_kind && left_kind != CharKind::Whitespace
- });
-
- // find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know
- // we have backtracked already
- if !map
- .chars_at(point)
- .nth(1)
- .map(|(c, _)| c == '\n')
- .unwrap_or(true)
- {
- *point.column_mut() = point.column().saturating_sub(1);
- }
- point = map.clip_point(point, Bias::Left);
- }
- point
-}
-
-fn previous_word_start(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- ignore_punctuation: bool,
- times: usize,
-) -> DisplayPoint {
- let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
- for _ in 0..times {
- // This works even though find_preceding_boundary is called for every character in the line containing
- // cursor because the newline is checked only once.
- point =
- movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
- (left_kind != right_kind && !right.is_whitespace()) || left == '\n'
- });
- }
- point
-}
-
-pub(crate) fn first_non_whitespace(
- map: &DisplaySnapshot,
- display_lines: bool,
- from: DisplayPoint,
-) -> DisplayPoint {
- let mut last_point = start_of_line(map, display_lines, from);
- let scope = map.buffer_snapshot.language_scope_at(from.to_point(map));
- for (ch, point) in map.chars_at(last_point) {
- if ch == '\n' {
- return from;
- }
-
- last_point = point;
-
- if char_kind(&scope, ch) != CharKind::Whitespace {
- break;
- }
- }
-
- map.clip_point(last_point, Bias::Left)
-}
-
-pub(crate) fn start_of_line(
- map: &DisplaySnapshot,
- display_lines: bool,
- point: DisplayPoint,
-) -> DisplayPoint {
- if display_lines {
- map.clip_point(DisplayPoint::new(point.row(), 0), Bias::Right)
- } else {
- map.prev_line_boundary(point.to_point(map)).1
- }
-}
-
-pub(crate) fn end_of_line(
- map: &DisplaySnapshot,
- display_lines: bool,
- point: DisplayPoint,
-) -> DisplayPoint {
- if display_lines {
- map.clip_point(
- DisplayPoint::new(point.row(), map.line_len(point.row())),
- Bias::Left,
- )
- } else {
- map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
- }
-}
-
-fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
- let mut new_point = Point::new((line - 1) as u32, 0).to_display_point(map);
- *new_point.column_mut() = point.column();
- map.clip_point(new_point, Bias::Left)
-}
-
-fn end_of_document(
- map: &DisplaySnapshot,
- point: DisplayPoint,
- line: Option<usize>,
-) -> DisplayPoint {
- let new_row = if let Some(line) = line {
- (line - 1) as u32
- } else {
- map.max_buffer_row()
- };
-
- let new_point = Point::new(new_row, point.column());
- map.clip_point(new_point.to_display_point(map), Bias::Left)
-}
-
-fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
- // https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200
- let point = display_point.to_point(map);
- let offset = point.to_offset(&map.buffer_snapshot);
-
- // Ensure the range is contained by the current line.
- let mut line_end = map.next_line_boundary(point).0;
- if line_end == point {
- line_end = map.max_point().to_point(map);
- }
-
- let line_range = map.prev_line_boundary(point).0..line_end;
- let visible_line_range =
- line_range.start..Point::new(line_range.end.row, line_range.end.column.saturating_sub(1));
- let ranges = map
- .buffer_snapshot
- .bracket_ranges(visible_line_range.clone());
- if let Some(ranges) = ranges {
- let line_range = line_range.start.to_offset(&map.buffer_snapshot)
- ..line_range.end.to_offset(&map.buffer_snapshot);
- let mut closest_pair_destination = None;
- let mut closest_distance = usize::MAX;
-
- for (open_range, close_range) in ranges {
- if open_range.start >= offset && line_range.contains(&open_range.start) {
- let distance = open_range.start - offset;
- if distance < closest_distance {
- closest_pair_destination = Some(close_range.start);
- closest_distance = distance;
- continue;
- }
- }
-
- if close_range.start >= offset && line_range.contains(&close_range.start) {
- let distance = close_range.start - offset;
- if distance < closest_distance {
- closest_pair_destination = Some(open_range.start);
- closest_distance = distance;
- continue;
- }
- }
-
- continue;
- }
-
- closest_pair_destination
- .map(|destination| destination.to_display_point(map))
- .unwrap_or(display_point)
- } else {
- display_point
- }
-}
-
-fn find_forward(
- map: &DisplaySnapshot,
- from: DisplayPoint,
- before: bool,
- target: char,
- times: usize,
-) -> DisplayPoint {
- let mut to = from;
- let mut found = false;
-
- for _ in 0..times {
- found = false;
- to = find_boundary(map, to, FindRange::SingleLine, |_, right| {
- found = right == target;
- found
- });
- }
-
- if found {
- if before && to.column() > 0 {
- *to.column_mut() -= 1;
- map.clip_point(to, Bias::Left)
- } else {
- to
- }
- } else {
- from
- }
-}
-
-fn find_backward(
- map: &DisplaySnapshot,
- from: DisplayPoint,
- after: bool,
- target: char,
- times: usize,
-) -> DisplayPoint {
- let mut to = from;
-
- for _ in 0..times {
- to = find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
- }
-
- if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) {
- if after {
- *to.column_mut() += 1;
- map.clip_point(to, Bias::Right)
- } else {
- to
- }
- } else {
- from
- }
-}
-
-fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
- let correct_line = start_of_relative_buffer_row(map, point, times as isize);
- first_non_whitespace(map, false, correct_line)
-}
-
-fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
- let correct_line = start_of_relative_buffer_row(map, point, 0);
- right(map, correct_line, times.saturating_sub(1))
-}
-
-pub(crate) fn next_line_end(
- map: &DisplaySnapshot,
- mut point: DisplayPoint,
- times: usize,
-) -> DisplayPoint {
- if times > 1 {
- point = start_of_relative_buffer_row(map, point, times as isize - 1);
- }
- end_of_line(map, false, point)
-}
-
-#[cfg(test)]
-mod test {
-
- use crate::test::NeovimBackedTestContext;
- use indoc::indoc;
-
- #[gpui::test]
- async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- let initial_state = indoc! {r"ˇabc
- def
-
- paragraph
- the second
-
-
-
- third and
- final"};
-
- // goes down once
- cx.set_shared_state(initial_state).await;
- cx.simulate_shared_keystrokes(["}"]).await;
- cx.assert_shared_state(indoc! {r"abc
- def
- ˇ
- paragraph
- the second
-
-
-
- third and
- final"})
- .await;
-
- // goes up once
- cx.simulate_shared_keystrokes(["{"]).await;
- cx.assert_shared_state(initial_state).await;
-
- // goes down twice
- cx.simulate_shared_keystrokes(["2", "}"]).await;
- cx.assert_shared_state(indoc! {r"abc
- def
-
- paragraph
- the second
- ˇ
-
-
- third and
- final"})
- .await;
-
- // goes down over multiple blanks
- cx.simulate_shared_keystrokes(["}"]).await;
- cx.assert_shared_state(indoc! {r"abc
- def
-
- paragraph
- the second
-
-
-
- third and
- finaˇl"})
- .await;
-
- // goes up twice
- cx.simulate_shared_keystrokes(["2", "{"]).await;
- cx.assert_shared_state(indoc! {r"abc
- def
- ˇ
- paragraph
- the second
-
-
-
- third and
- final"})
- .await
- }
-
- #[gpui::test]
- async fn test_matching(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {r"func ˇ(a string) {
- do(something(with<Types>.and_arrays[0, 2]))
- }"})
- .await;
- cx.simulate_shared_keystrokes(["%"]).await;
- cx.assert_shared_state(indoc! {r"func (a stringˇ) {
- do(something(with<Types>.and_arrays[0, 2]))
- }"})
- .await;
-
- // test it works on the last character of the line
- cx.set_shared_state(indoc! {r"func (a string) ˇ{
- do(something(with<Types>.and_arrays[0, 2]))
- }"})
- .await;
- cx.simulate_shared_keystrokes(["%"]).await;
- cx.assert_shared_state(indoc! {r"func (a string) {
- do(something(with<Types>.and_arrays[0, 2]))
- ˇ}"})
- .await;
-
- // test it works on immediate nesting
- cx.set_shared_state("ˇ{()}").await;
- cx.simulate_shared_keystrokes(["%"]).await;
- cx.assert_shared_state("{()ˇ}").await;
- cx.simulate_shared_keystrokes(["%"]).await;
- cx.assert_shared_state("ˇ{()}").await;
-
- // test it works on immediate nesting inside braces
- cx.set_shared_state("{\n ˇ{()}\n}").await;
- cx.simulate_shared_keystrokes(["%"]).await;
- cx.assert_shared_state("{\n {()ˇ}\n}").await;
-
- // test it jumps to the next paren on a line
- cx.set_shared_state("func ˇboop() {\n}").await;
- cx.simulate_shared_keystrokes(["%"]).await;
- cx.assert_shared_state("func boop(ˇ) {\n}").await;
- }
-
- #[gpui::test]
- async fn test_comma_semicolon(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state("ˇone two three four").await;
- cx.simulate_shared_keystrokes(["f", "o"]).await;
- cx.assert_shared_state("one twˇo three four").await;
- cx.simulate_shared_keystrokes([","]).await;
- cx.assert_shared_state("ˇone two three four").await;
- cx.simulate_shared_keystrokes(["2", ";"]).await;
- cx.assert_shared_state("one two three fˇour").await;
- cx.simulate_shared_keystrokes(["shift-t", "e"]).await;
- cx.assert_shared_state("one two threeˇ four").await;
- cx.simulate_shared_keystrokes(["3", ";"]).await;
- cx.assert_shared_state("oneˇ two three four").await;
- cx.simulate_shared_keystrokes([","]).await;
- cx.assert_shared_state("one two thˇree four").await;
- }
-
- #[gpui::test]
- async fn test_next_line_start(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.set_shared_state("ˇone\n two\nthree").await;
- cx.simulate_shared_keystrokes(["enter"]).await;
- cx.assert_shared_state("one\n ˇtwo\nthree").await;
- }
-}
@@ -1,910 +0,0 @@
-mod case;
-mod change;
-mod delete;
-mod increment;
-mod paste;
-pub(crate) mod repeat;
-mod scroll;
-pub(crate) mod search;
-pub mod substitute;
-mod yank;
-
-use std::sync::Arc;
-
-use crate::{
- motion::{self, first_non_whitespace, next_line_end, right, Motion},
- object::Object,
- state::{Mode, Operator},
- Vim,
-};
-use collections::HashSet;
-use editor::scroll::autoscroll::Autoscroll;
-use editor::{Bias, DisplayPoint};
-use gpui::{actions, ViewContext, WindowContext};
-use language::SelectionGoal;
-use log::error;
-use workspace::Workspace;
-
-use self::{
- case::change_case,
- change::{change_motion, change_object},
- delete::{delete_motion, delete_object},
- yank::{yank_motion, yank_object},
-};
-
-actions!(
- vim,
- [
- InsertAfter,
- InsertBefore,
- InsertFirstNonWhitespace,
- InsertEndOfLine,
- InsertLineAbove,
- InsertLineBelow,
- DeleteLeft,
- DeleteRight,
- ChangeToEndOfLine,
- DeleteToEndOfLine,
- Yank,
- YankLine,
- ChangeCase,
- JoinLines,
- ]
-);
-
-pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- workspace.register_action(insert_after);
- workspace.register_action(insert_before);
- workspace.register_action(insert_first_non_whitespace);
- workspace.register_action(insert_end_of_line);
- workspace.register_action(insert_line_above);
- workspace.register_action(insert_line_below);
- workspace.register_action(change_case);
- workspace.register_action(yank_line);
-
- workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let times = vim.take_count(cx);
- delete_motion(vim, Motion::Left, times, cx);
- })
- });
- workspace.register_action(|_: &mut Workspace, _: &DeleteRight, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let times = vim.take_count(cx);
- delete_motion(vim, Motion::Right, times, cx);
- })
- });
- workspace.register_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- let times = vim.take_count(cx);
- change_motion(
- vim,
- Motion::EndOfLine {
- display_lines: false,
- },
- times,
- cx,
- );
- })
- });
- workspace.register_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let times = vim.take_count(cx);
- delete_motion(
- vim,
- Motion::EndOfLine {
- display_lines: false,
- },
- times,
- cx,
- );
- })
- });
- workspace.register_action(|_: &mut Workspace, _: &JoinLines, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let mut times = vim.take_count(cx).unwrap_or(1);
- if vim.state().mode.is_visual() {
- times = 1;
- } else if times > 1 {
- // 2J joins two lines together (same as J or 1J)
- times -= 1;
- }
-
- vim.update_active_editor(cx, |editor, cx| {
- editor.transact(cx, |editor, cx| {
- for _ in 0..times {
- editor.join_lines(&Default::default(), cx)
- }
- })
- })
- });
- });
-
- paste::register(workspace, cx);
- repeat::register(workspace, cx);
- scroll::register(workspace, cx);
- search::register(workspace, cx);
- substitute::register(workspace, cx);
- increment::register(workspace, cx);
-}
-
-pub fn normal_motion(
- motion: Motion,
- operator: Option<Operator>,
- times: Option<usize>,
- cx: &mut WindowContext,
-) {
- Vim::update(cx, |vim, cx| {
- match operator {
- None => move_cursor(vim, motion, times, cx),
- Some(Operator::Change) => change_motion(vim, motion, times, cx),
- Some(Operator::Delete) => delete_motion(vim, motion, times, cx),
- Some(Operator::Yank) => yank_motion(vim, motion, times, cx),
- Some(operator) => {
- // Can't do anything for text objects, Ignoring
- error!("Unexpected normal mode motion operator: {:?}", operator)
- }
- }
- });
-}
-
-pub fn normal_object(object: Object, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- match vim.maybe_pop_operator() {
- Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
- Some(Operator::Change) => change_object(vim, object, around, cx),
- Some(Operator::Delete) => delete_object(vim, object, around, cx),
- Some(Operator::Yank) => yank_object(vim, object, around, cx),
- _ => {
- // Can't do anything for namespace operators. Ignoring
- }
- },
- _ => {
- // Can't do anything with change/delete/yank and text objects. Ignoring
- }
- }
- vim.clear_operator(cx);
- })
-}
-
-pub(crate) fn move_cursor(
- vim: &mut Vim,
- motion: Motion,
- times: Option<usize>,
- cx: &mut WindowContext,
-) {
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, cursor, goal| {
- motion
- .move_point(map, cursor, goal, times, &text_layout_details)
- .unwrap_or((cursor, goal))
- })
- })
- });
-}
-
-fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None));
- });
- });
- });
-}
-
-fn insert_before(_: &mut Workspace, _: &InsertBefore, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- });
-}
-
-fn insert_first_non_whitespace(
- _: &mut Workspace,
- _: &InsertFirstNonWhitespace,
- cx: &mut ViewContext<Workspace>,
-) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, cursor, _| {
- (
- first_non_whitespace(map, false, cursor),
- SelectionGoal::None,
- )
- });
- });
- });
- });
-}
-
-fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, cursor, _| {
- (next_line_end(map, cursor, 1), SelectionGoal::None)
- });
- });
- });
- });
-}
-
-fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |editor, cx| {
- editor.transact(cx, |editor, cx| {
- let (map, old_selections) = editor.selections.all_display(cx);
- let selection_start_rows: HashSet<u32> = old_selections
- .into_iter()
- .map(|selection| selection.start.row())
- .collect();
- let edits = selection_start_rows.into_iter().map(|row| {
- let (indent, _) = map.line_indent(row);
- let start_of_line =
- motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
- .to_point(&map);
- let mut new_text = " ".repeat(indent as usize);
- new_text.push('\n');
- (start_of_line..start_of_line, new_text)
- });
- editor.edit_with_autoindent(edits, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_cursors_with(|map, cursor, _| {
- let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1);
- let insert_point = motion::end_of_line(map, false, previous_line);
- (insert_point, SelectionGoal::None)
- });
- });
- });
- });
- });
-}
-
-fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- vim.switch_mode(Mode::Insert, false, cx);
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- let (map, old_selections) = editor.selections.all_display(cx);
-
- let selection_end_rows: HashSet<u32> = old_selections
- .into_iter()
- .map(|selection| selection.end.row())
- .collect();
- let edits = selection_end_rows.into_iter().map(|row| {
- let (indent, _) = map.line_indent(row);
- let end_of_line =
- motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
-
- let mut new_text = "\n".to_string();
- new_text.push_str(&" ".repeat(indent as usize));
- (end_of_line..end_of_line, new_text)
- });
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.maybe_move_cursors_with(|map, cursor, goal| {
- Motion::CurrentLine.move_point(
- map,
- cursor,
- goal,
- None,
- &text_layout_details,
- )
- });
- });
- editor.edit_with_autoindent(edits, cx);
- });
- });
- });
-}
-
-fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- let count = vim.take_count(cx);
- yank_motion(vim, motion::Motion::CurrentLine, count, cx)
- })
-}
-
-pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.stop_recording();
- vim.update_active_editor(cx, |editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let (map, display_selections) = editor.selections.all_display(cx);
- // Selections are biased right at the start. So we need to store
- // anchors that are biased left so that we can restore the selections
- // after the change
- let stable_anchors = editor
- .selections
- .disjoint_anchors()
- .into_iter()
- .map(|selection| {
- let start = selection.start.bias_left(&map.buffer_snapshot);
- start..start
- })
- .collect::<Vec<_>>();
-
- let edits = display_selections
- .into_iter()
- .map(|selection| {
- let mut range = selection.range();
- *range.end.column_mut() += 1;
- range.end = map.clip_point(range.end, Bias::Right);
-
- (
- range.start.to_offset(&map, Bias::Left)
- ..range.end.to_offset(&map, Bias::Left),
- text.clone(),
- )
- })
- .collect::<Vec<_>>();
-
- editor.buffer().update(cx, |buffer, cx| {
- buffer.edit(edits, None, cx);
- });
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(None, cx, |s| {
- s.select_anchor_ranges(stable_anchors);
- });
- });
- });
- vim.pop_operator(cx)
- });
-}
-
-#[cfg(test)]
-mod test {
- use gpui::TestAppContext;
- use indoc::indoc;
-
- use crate::{
- state::Mode::{self},
- test::NeovimBackedTestContext,
- };
-
- #[gpui::test]
- async fn test_h(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
- cx.assert_all(indoc! {"
- ˇThe qˇuick
- ˇbrown"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_backspace(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["backspace"]);
- cx.assert_all(indoc! {"
- ˇThe qˇuick
- ˇbrown"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_j(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- aaˇaa
- 😃😃"
- })
- .await;
- cx.simulate_shared_keystrokes(["j"]).await;
- cx.assert_shared_state(indoc! {"
- aaaa
- 😃ˇ😃"
- })
- .await;
-
- for marked_position in cx.each_marked_position(indoc! {"
- ˇThe qˇuick broˇwn
- ˇfox jumps"
- }) {
- cx.assert_neovim_compatible(&marked_position, ["j"]).await;
- }
- }
-
- #[gpui::test]
- async fn test_enter(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
- cx.assert_all(indoc! {"
- ˇThe qˇuick broˇwn
- ˇfox jumps"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_k(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["k"]);
- cx.assert_all(indoc! {"
- ˇThe qˇuick
- ˇbrown fˇox jumˇps"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_l(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["l"]);
- cx.assert_all(indoc! {"
- ˇThe qˇuicˇk
- ˇbrowˇn"})
- .await;
- }
-
- #[gpui::test]
- async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_binding_matches_all(
- ["$"],
- indoc! {"
- ˇThe qˇuicˇk
- ˇbrowˇn"},
- )
- .await;
- cx.assert_binding_matches_all(
- ["0"],
- indoc! {"
- ˇThe qˇuicˇk
- ˇbrowˇn"},
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_jump_to_end(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-g"]);
-
- cx.assert_all(indoc! {"
- The ˇquick
-
- brown fox jumps
- overˇ the lazy doˇg"})
- .await;
- cx.assert(indoc! {"
- The quiˇck
-
- brown"})
- .await;
- cx.assert(indoc! {"
- The quiˇck
-
- "})
- .await;
- }
-
- #[gpui::test]
- async fn test_w(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["w"]);
- cx.assert_all(indoc! {"
- The ˇquickˇ-ˇbrown
- ˇ
- ˇ
- ˇfox_jumps ˇover
- ˇthˇe"})
- .await;
- let mut cx = cx.binding(["shift-w"]);
- cx.assert_all(indoc! {"
- The ˇquickˇ-ˇbrown
- ˇ
- ˇ
- ˇfox_jumps ˇover
- ˇthˇe"})
- .await;
- }
-
- #[gpui::test]
- async fn test_end_of_word(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
- cx.assert_all(indoc! {"
- Thˇe quicˇkˇ-browˇn
-
-
- fox_jumpˇs oveˇr
- thˇe"})
- .await;
- let mut cx = cx.binding(["shift-e"]);
- cx.assert_all(indoc! {"
- Thˇe quicˇkˇ-browˇn
-
-
- fox_jumpˇs oveˇr
- thˇe"})
- .await;
- }
-
- #[gpui::test]
- async fn test_b(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["b"]);
- cx.assert_all(indoc! {"
- ˇThe ˇquickˇ-ˇbrown
- ˇ
- ˇ
- ˇfox_jumps ˇover
- ˇthe"})
- .await;
- let mut cx = cx.binding(["shift-b"]);
- cx.assert_all(indoc! {"
- ˇThe ˇquickˇ-ˇbrown
- ˇ
- ˇ
- ˇfox_jumps ˇover
- ˇthe"})
- .await;
- }
-
- #[gpui::test]
- async fn test_gg(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_binding_matches_all(
- ["g", "g"],
- indoc! {"
- The qˇuick
-
- brown fox jumps
- over ˇthe laˇzy dog"},
- )
- .await;
- cx.assert_binding_matches(
- ["g", "g"],
- indoc! {"
-
-
- brown fox jumps
- over the laˇzy dog"},
- )
- .await;
- cx.assert_binding_matches(
- ["2", "g", "g"],
- indoc! {"
- ˇ
-
- brown fox jumps
- over the lazydog"},
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_end_of_document(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_binding_matches_all(
- ["shift-g"],
- indoc! {"
- The qˇuick
-
- brown fox jumps
- over ˇthe laˇzy dog"},
- )
- .await;
- cx.assert_binding_matches(
- ["shift-g"],
- indoc! {"
-
-
- brown fox jumps
- over the laˇzy dog"},
- )
- .await;
- cx.assert_binding_matches(
- ["2", "shift-g"],
- indoc! {"
- ˇ
-
- brown fox jumps
- over the lazydog"},
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_a(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["a"]);
- cx.assert_all("The qˇuicˇk").await;
- }
-
- #[gpui::test]
- async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-a"]);
- cx.assert_all(indoc! {"
- ˇ
- The qˇuick
- brown ˇfox "})
- .await;
- }
-
- #[gpui::test]
- async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["^"]);
- cx.assert("The qˇuick").await;
- cx.assert(" The qˇuick").await;
- cx.assert("ˇ").await;
- cx.assert(indoc! {"
- The qˇuick
- brown fox"})
- .await;
- cx.assert(indoc! {"
- ˇ
- The quick"})
- .await;
- // Indoc disallows trailing whitespace.
- cx.assert(" ˇ \nThe quick").await;
- }
-
- #[gpui::test]
- async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-i"]);
- cx.assert("The qˇuick").await;
- cx.assert(" The qˇuick").await;
- cx.assert("ˇ").await;
- cx.assert(indoc! {"
- The qˇuick
- brown fox"})
- .await;
- cx.assert(indoc! {"
- ˇ
- The quick"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-d"]);
- cx.assert(indoc! {"
- The qˇuick
- brown fox"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
- }
-
- #[gpui::test]
- async fn test_x(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["x"]);
- cx.assert_all("ˇTeˇsˇt").await;
- cx.assert(indoc! {"
- Tesˇt
- test"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_left(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["shift-x"]);
- cx.assert_all("ˇTˇeˇsˇt").await;
- cx.assert(indoc! {"
- Test
- ˇtest"})
- .await;
- }
-
- #[gpui::test]
- async fn test_o(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["o"]);
- cx.assert("ˇ").await;
- cx.assert("The ˇquick").await;
- cx.assert_all(indoc! {"
- The qˇuick
- brown ˇfox
- jumps ˇover"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
-
- cx.assert_manual(
- indoc! {"
- fn test() {
- println!(ˇ);
- }"},
- Mode::Normal,
- indoc! {"
- fn test() {
- println!();
- ˇ
- }"},
- Mode::Insert,
- );
-
- cx.assert_manual(
- indoc! {"
- fn test(ˇ) {
- println!();
- }"},
- Mode::Normal,
- indoc! {"
- fn test() {
- ˇ
- println!();
- }"},
- Mode::Insert,
- );
- }
-
- #[gpui::test]
- async fn test_insert_line_above(cx: &mut gpui::TestAppContext) {
- let cx = NeovimBackedTestContext::new(cx).await;
- let mut cx = cx.binding(["shift-o"]);
- cx.assert("ˇ").await;
- cx.assert("The ˇquick").await;
- cx.assert_all(indoc! {"
- The qˇuick
- brown ˇfox
- jumps ˇover"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
-
- // Our indentation is smarter than vims. So we don't match here
- cx.assert_manual(
- indoc! {"
- fn test() {
- println!(ˇ);
- }"},
- Mode::Normal,
- indoc! {"
- fn test() {
- ˇ
- println!();
- }"},
- Mode::Insert,
- );
- cx.assert_manual(
- indoc! {"
- fn test(ˇ) {
- println!();
- }"},
- Mode::Normal,
- indoc! {"
- ˇ
- fn test() {
- println!();
- }"},
- Mode::Insert,
- );
- }
-
- #[gpui::test]
- async fn test_dd(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_neovim_compatible("ˇ", ["d", "d"]).await;
- cx.assert_neovim_compatible("The ˇquick", ["d", "d"]).await;
- for marked_text in cx.each_marked_position(indoc! {"
- The qˇuick
- brown ˇfox
- jumps ˇover"})
- {
- cx.assert_neovim_compatible(&marked_text, ["d", "d"]).await;
- }
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- ˇ
- brown fox"},
- ["d", "d"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_cc(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "c"]);
- cx.assert("ˇ").await;
- cx.assert("The ˇquick").await;
- cx.assert_all(indoc! {"
- The quˇick
- brown ˇfox
- jumps ˇover"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
- }
-
- #[gpui::test]
- async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for count in 1..=5 {
- cx.assert_binding_matches_all(
- [&count.to_string(), "w"],
- indoc! {"
- ˇThe quˇickˇ browˇn
- ˇ
- ˇfox ˇjumpsˇ-ˇoˇver
- ˇthe lazy dog
- "},
- )
- .await;
- }
- }
-
- #[gpui::test]
- async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
- cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
- }
-
- #[gpui::test]
- async fn test_f_and_t(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for count in 1..=3 {
- let test_case = indoc! {"
- ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
- ˇ ˇbˇaaˇa ˇbˇbˇb
- ˇ
- ˇb
- "};
-
- cx.assert_binding_matches_all([&count.to_string(), "f", "b"], test_case)
- .await;
-
- cx.assert_binding_matches_all([&count.to_string(), "t", "b"], test_case)
- .await;
- }
- }
-
- #[gpui::test]
- async fn test_capital_f_and_capital_t(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- let test_case = indoc! {"
- ˇaaaˇbˇ ˇbˇ ˇbˇbˇ aˇaaˇbaaa
- ˇ ˇbˇaaˇa ˇbˇbˇb
- ˇ•••
- ˇb
- "
- };
-
- for count in 1..=3 {
- cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)
- .await;
-
- cx.assert_binding_matches_all([&count.to_string(), "shift-t", "b"], test_case)
- .await;
- }
- }
-
- #[gpui::test]
- async fn test_percent(cx: &mut TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
- cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
- cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")
- .await;
- cx.assert_all("let result = curried_funˇ(ˇ)ˇ(ˇ)ˇ;").await;
- }
-}
@@ -1,116 +0,0 @@
-use editor::scroll::autoscroll::Autoscroll;
-use gpui::ViewContext;
-use language::{Bias, Point};
-use workspace::Workspace;
-
-use crate::{normal::ChangeCase, state::Mode, Vim};
-
-pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1) as u32;
- vim.update_active_editor(cx, |editor, cx| {
- let mut ranges = Vec::new();
- let mut cursor_positions = Vec::new();
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- for selection in editor.selections.all::<Point>(cx) {
- match vim.state().mode {
- Mode::VisualLine => {
- let start = Point::new(selection.start.row, 0);
- let end =
- Point::new(selection.end.row, snapshot.line_len(selection.end.row));
- ranges.push(start..end);
- cursor_positions.push(start..start);
- }
- Mode::Visual => {
- ranges.push(selection.start..selection.end);
- cursor_positions.push(selection.start..selection.start);
- }
- Mode::VisualBlock => {
- ranges.push(selection.start..selection.end);
- if cursor_positions.len() == 0 {
- cursor_positions.push(selection.start..selection.start);
- }
- }
- Mode::Insert | Mode::Normal => {
- let start = selection.start;
- let mut end = start;
- for _ in 0..count {
- end = snapshot.clip_point(end + Point::new(0, 1), Bias::Right);
- }
- ranges.push(start..end);
-
- if end.column == snapshot.line_len(end.row) {
- end = snapshot.clip_point(end - Point::new(0, 1), Bias::Left);
- }
- cursor_positions.push(end..end)
- }
- }
- }
- editor.transact(cx, |editor, cx| {
- for range in ranges.into_iter().rev() {
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- editor.buffer().update(cx, |buffer, cx| {
- let text = snapshot
- .text_for_range(range.start..range.end)
- .flat_map(|s| s.chars())
- .flat_map(|c| {
- if c.is_lowercase() {
- c.to_uppercase().collect::<Vec<char>>()
- } else {
- c.to_lowercase().collect::<Vec<char>>()
- }
- })
- .collect::<String>();
-
- buffer.edit([(range, text)], None, cx)
- })
- }
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.select_ranges(cursor_positions)
- })
- });
- });
- vim.switch_mode(Mode::Normal, true, cx)
- })
-}
-#[cfg(test)]
-mod test {
- use crate::{state::Mode, test::NeovimBackedTestContext};
-
- #[gpui::test]
- async fn test_change_case(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.set_shared_state("ˇabC\n").await;
- cx.simulate_shared_keystrokes(["~"]).await;
- cx.assert_shared_state("AˇbC\n").await;
- cx.simulate_shared_keystrokes(["2", "~"]).await;
- cx.assert_shared_state("ABˇc\n").await;
-
- // works in visual mode
- cx.set_shared_state("a😀C«dÉ1*fˇ»\n").await;
- cx.simulate_shared_keystrokes(["~"]).await;
- cx.assert_shared_state("a😀CˇDé1*F\n").await;
-
- // works with multibyte characters
- cx.simulate_shared_keystrokes(["~"]).await;
- cx.set_shared_state("aˇC😀é1*F\n").await;
- cx.simulate_shared_keystrokes(["4", "~"]).await;
- cx.assert_shared_state("ac😀É1ˇ*F\n").await;
-
- // works with line selections
- cx.set_shared_state("abˇC\n").await;
- cx.simulate_shared_keystrokes(["shift-v", "~"]).await;
- cx.assert_shared_state("ˇABc\n").await;
-
- // works in visual block mode
- cx.set_shared_state("ˇaa\nbb\ncc").await;
- cx.simulate_shared_keystrokes(["ctrl-v", "j", "~"]).await;
- cx.assert_shared_state("ˇAa\nBb\ncc").await;
-
- // works with multiple cursors (zed only)
- cx.set_state("aˇßcdˇe\n", Mode::Normal);
- cx.simulate_keystroke("~");
- cx.assert_state("aSSˇcdˇE\n", Mode::Normal);
- }
-}
@@ -1,502 +0,0 @@
-use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
-use editor::{
- char_kind,
- display_map::DisplaySnapshot,
- movement::{self, FindRange, TextLayoutDetails},
- scroll::autoscroll::Autoscroll,
- CharKind, DisplayPoint,
-};
-use gpui::WindowContext;
-use language::Selection;
-
-pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- // Some motions ignore failure when switching to normal mode
- let mut motion_succeeded = matches!(
- motion,
- Motion::Left
- | Motion::Right
- | Motion::EndOfLine { .. }
- | Motion::Backspace
- | Motion::StartOfLine { .. }
- );
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- // We are swapping to insert mode anyway. Just set the line end clipping behavior now
- editor.set_clip_at_line_ends(false, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion
- {
- expand_changed_word_selection(
- map,
- selection,
- times,
- ignore_punctuation,
- &text_layout_details,
- )
- } else {
- motion.expand_selection(map, selection, times, false, &text_layout_details)
- };
- });
- });
- copy_selections_content(editor, motion.linewise(), cx);
- editor.insert("", cx);
- });
- });
-
- if motion_succeeded {
- vim.switch_mode(Mode::Insert, false, cx)
- } else {
- vim.switch_mode(Mode::Normal, false, cx)
- }
-}
-
-pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- let mut objects_found = false;
- vim.update_active_editor(cx, |editor, cx| {
- // We are swapping to insert mode anyway. Just set the line end clipping behavior now
- editor.set_clip_at_line_ends(false, cx);
- editor.transact(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- objects_found |= object.expand_selection(map, selection, around);
- });
- });
- if objects_found {
- copy_selections_content(editor, false, cx);
- editor.insert("", cx);
- }
- });
- });
-
- if objects_found {
- vim.switch_mode(Mode::Insert, false, cx);
- } else {
- vim.switch_mode(Mode::Normal, false, cx);
- }
-}
-
-// From the docs https://vimdoc.sourceforge.net/htmldoc/motion.html
-// Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is
-// on a non-blank. This is because "cw" is interpreted as change-word, and a
-// word does not include the following white space. {Vi: "cw" when on a blank
-// followed by other blanks changes only the first blank; this is probably a
-// bug, because "dw" deletes all the blanks}
-fn expand_changed_word_selection(
- map: &DisplaySnapshot,
- selection: &mut Selection<DisplayPoint>,
- times: Option<usize>,
- ignore_punctuation: bool,
- text_layout_details: &TextLayoutDetails,
-) -> bool {
- if times.is_none() || times.unwrap() == 1 {
- let scope = map
- .buffer_snapshot
- .language_scope_at(selection.start.to_point(map));
- let in_word = map
- .chars_at(selection.head())
- .next()
- .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
- .unwrap_or_default();
-
- if in_word {
- selection.end =
- movement::find_boundary(map, selection.end, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind =
- char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
- left_kind != right_kind && left_kind != CharKind::Whitespace
- });
- true
- } else {
- Motion::NextWordStart { ignore_punctuation }.expand_selection(
- map,
- selection,
- None,
- false,
- &text_layout_details,
- )
- }
- } else {
- Motion::NextWordStart { ignore_punctuation }.expand_selection(
- map,
- selection,
- times,
- false,
- &text_layout_details,
- )
- }
-}
-
-#[cfg(test)]
-mod test {
- use indoc::indoc;
-
- use crate::test::NeovimBackedTestContext;
-
- #[gpui::test]
- async fn test_change_h(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "h"]);
- cx.assert("Teˇst").await;
- cx.assert("Tˇest").await;
- cx.assert("ˇTest").await;
- cx.assert(indoc! {"
- Test
- ˇtest"})
- .await;
- }
-
- #[gpui::test]
- async fn test_change_backspace(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["c", "backspace"]);
- cx.assert("Teˇst").await;
- cx.assert("Tˇest").await;
- cx.assert("ˇTest").await;
- cx.assert(indoc! {"
- Test
- ˇtest"})
- .await;
- }
-
- #[gpui::test]
- async fn test_change_l(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "l"]);
- cx.assert("Teˇst").await;
- cx.assert("Tesˇt").await;
- }
-
- #[gpui::test]
- async fn test_change_w(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "w"]);
- cx.assert("Teˇst").await;
- cx.assert("Tˇest test").await;
- cx.assert("Testˇ test").await;
- cx.assert(indoc! {"
- Test teˇst
- test"})
- .await;
- cx.assert(indoc! {"
- Test tesˇt
- test"})
- .await;
- cx.assert(indoc! {"
- Test test
- ˇ
- test"})
- .await;
-
- let mut cx = cx.binding(["c", "shift-w"]);
- cx.assert("Test teˇst-test test").await;
- }
-
- #[gpui::test]
- async fn test_change_e(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "e"]);
- cx.assert("Teˇst Test").await;
- cx.assert("Tˇest test").await;
- cx.assert(indoc! {"
- Test teˇst
- test"})
- .await;
- cx.assert(indoc! {"
- Test tesˇt
- test"})
- .await;
- cx.assert(indoc! {"
- Test test
- ˇ
- test"})
- .await;
-
- let mut cx = cx.binding(["c", "shift-e"]);
- cx.assert("Test teˇst-test test").await;
- }
-
- #[gpui::test]
- async fn test_change_b(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "b"]);
- cx.assert("Teˇst Test").await;
- cx.assert("Test ˇtest").await;
- cx.assert("Test1 test2 ˇtest3").await;
- cx.assert(indoc! {"
- Test test
- ˇtest"})
- .await;
- cx.assert(indoc! {"
- Test test
- ˇ
- test"})
- .await;
-
- let mut cx = cx.binding(["c", "shift-b"]);
- cx.assert("Test test-test ˇtest").await;
- }
-
- #[gpui::test]
- async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["c", "$"]);
- cx.assert(indoc! {"
- The qˇuick
- brown fox"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
- }
-
- #[gpui::test]
- async fn test_change_0(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.assert_neovim_compatible(
- indoc! {"
- The qˇuick
- brown fox"},
- ["c", "0"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- ˇ
- brown fox"},
- ["c", "0"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_change_k(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown ˇfox
- jumps over"},
- ["c", "k"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps ˇover"},
- ["c", "k"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The qˇuick
- brown fox
- jumps over"},
- ["c", "k"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- ˇ
- brown fox
- jumps over"},
- ["c", "k"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_change_j(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown ˇfox
- jumps over"},
- ["c", "j"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps ˇover"},
- ["c", "j"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The qˇuick
- brown fox
- jumps over"},
- ["c", "j"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- ˇ"},
- ["c", "j"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brownˇ fox
- jumps over
- the lazy"},
- ["c", "shift-g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brownˇ fox
- jumps over
- the lazy"},
- ["c", "shift-g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps over
- the lˇazy"},
- ["c", "shift-g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps over
- ˇ"},
- ["c", "shift-g"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_change_gg(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brownˇ fox
- jumps over
- the lazy"},
- ["c", "g", "g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps over
- the lˇazy"},
- ["c", "g", "g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The qˇuick
- brown fox
- jumps over
- the lazy"},
- ["c", "g", "g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- ˇ
- brown fox
- jumps over
- the lazy"},
- ["c", "g", "g"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_repeated_cj(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for count in 1..=5 {
- cx.assert_binding_matches_all(
- ["c", &count.to_string(), "j"],
- indoc! {"
- ˇThe quˇickˇ browˇn
- ˇ
- ˇfox ˇjumpsˇ-ˇoˇver
- ˇthe lazy dog
- "},
- )
- .await;
- }
- }
-
- #[gpui::test]
- async fn test_repeated_cl(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for count in 1..=5 {
- cx.assert_binding_matches_all(
- ["c", &count.to_string(), "l"],
- indoc! {"
- ˇThe quˇickˇ browˇn
- ˇ
- ˇfox ˇjumpsˇ-ˇoˇver
- ˇthe lazy dog
- "},
- )
- .await;
- }
- }
-
- #[gpui::test]
- async fn test_repeated_cb(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for count in 1..=5 {
- for marked_text in cx.each_marked_position(indoc! {"
- ˇThe quˇickˇ browˇn
- ˇ
- ˇfox ˇjumpsˇ-ˇoˇver
- ˇthe lazy dog
- "})
- {
- cx.assert_neovim_compatible(&marked_text, ["c", &count.to_string(), "b"])
- .await;
- }
- }
- }
-
- #[gpui::test]
- async fn test_repeated_ce(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for count in 1..=5 {
- cx.assert_binding_matches_all(
- ["c", &count.to_string(), "e"],
- indoc! {"
- ˇThe quˇickˇ browˇn
- ˇ
- ˇfox ˇjumpsˇ-ˇoˇver
- ˇthe lazy dog
- "},
- )
- .await;
- }
- }
-}
@@ -1,475 +0,0 @@
-use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
-use collections::{HashMap, HashSet};
-use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
-use gpui::WindowContext;
-use language::Point;
-
-pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- vim.stop_recording();
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let mut original_columns: HashMap<_, _> = Default::default();
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let original_head = selection.head();
- original_columns.insert(selection.id, original_head.column());
- motion.expand_selection(map, selection, times, true, &text_layout_details);
-
- // Motion::NextWordStart on an empty line should delete it.
- if let Motion::NextWordStart {
- ignore_punctuation: _,
- } = motion
- {
- if selection.is_empty()
- && map
- .buffer_snapshot
- .line_len(selection.start.to_point(&map).row)
- == 0
- {
- selection.end = map
- .buffer_snapshot
- .clip_point(
- Point::new(selection.start.to_point(&map).row + 1, 0),
- Bias::Left,
- )
- .to_display_point(map)
- }
- }
- });
- });
- copy_selections_content(editor, motion.linewise(), cx);
- editor.insert("", cx);
-
- // Fixup cursor position after the deletion
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut cursor = selection.head();
- if motion.linewise() {
- if let Some(column) = original_columns.get(&selection.id) {
- *cursor.column_mut() = *column
- }
- }
- cursor = map.clip_point(cursor, Bias::Left);
- selection.collapse_to(cursor, selection.goal)
- });
- });
- });
- });
-}
-
-pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- vim.stop_recording();
- vim.update_active_editor(cx, |editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- // Emulates behavior in vim where if we expanded backwards to include a newline
- // the cursor gets set back to the start of the line
- let mut should_move_to_start: HashSet<_> = Default::default();
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- object.expand_selection(map, selection, around);
- let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range();
- let contains_only_newlines = map
- .chars_at(selection.start)
- .take_while(|(_, p)| p < &selection.end)
- .all(|(char, _)| char == '\n')
- && !offset_range.is_empty();
- let end_at_newline = map
- .chars_at(selection.end)
- .next()
- .map(|(c, _)| c == '\n')
- .unwrap_or(false);
-
- // If expanded range contains only newlines and
- // the object is around or sentence, expand to include a newline
- // at the end or start
- if (around || object == Object::Sentence) && contains_only_newlines {
- if end_at_newline {
- selection.end =
- (offset_range.end + '\n'.len_utf8()).to_display_point(map);
- } else if selection.start.row() > 0 {
- should_move_to_start.insert(selection.id);
- selection.start =
- (offset_range.start - '\n'.len_utf8()).to_display_point(map);
- }
- }
- });
- });
- copy_selections_content(editor, false, cx);
- editor.insert("", cx);
-
- // Fixup cursor position after the deletion
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut cursor = selection.head();
- if should_move_to_start.contains(&selection.id) {
- *cursor.column_mut() = 0;
- }
- cursor = map.clip_point(cursor, Bias::Left);
- selection.collapse_to(cursor, selection.goal)
- });
- });
- });
- });
-}
-
-#[cfg(test)]
-mod test {
- use indoc::indoc;
-
- use crate::{
- state::Mode,
- test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
- };
-
- #[gpui::test]
- async fn test_delete_h(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "h"]);
- cx.assert("Teˇst").await;
- cx.assert("Tˇest").await;
- cx.assert("ˇTest").await;
- cx.assert(indoc! {"
- Test
- ˇtest"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_l(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "l"]);
- cx.assert("ˇTest").await;
- cx.assert("Teˇst").await;
- cx.assert("Tesˇt").await;
- cx.assert(indoc! {"
- Tesˇt
- test"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_w(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_neovim_compatible(
- indoc! {"
- Test tesˇt
- test"},
- ["d", "w"],
- )
- .await;
-
- cx.assert_neovim_compatible("Teˇst", ["d", "w"]).await;
- cx.assert_neovim_compatible("Tˇest test", ["d", "w"]).await;
- cx.assert_neovim_compatible(
- indoc! {"
- Test teˇst
- test"},
- ["d", "w"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- Test tesˇt
- test"},
- ["d", "w"],
- )
- .await;
-
- cx.assert_neovim_compatible(
- indoc! {"
- Test test
- ˇ
- test"},
- ["d", "w"],
- )
- .await;
-
- let mut cx = cx.binding(["d", "shift-w"]);
- cx.assert_neovim_compatible("Test teˇst-test test", ["d", "shift-w"])
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_next_word_end(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "e"]);
- // cx.assert("Teˇst Test").await;
- // cx.assert("Tˇest test").await;
- cx.assert(indoc! {"
- Test teˇst
- test"})
- .await;
- cx.assert(indoc! {"
- Test tesˇt
- test"})
- .await;
- cx.assert_exempted(
- indoc! {"
- Test test
- ˇ
- test"},
- ExemptionFeatures::OperatorLastNewlineRemains,
- )
- .await;
-
- let mut cx = cx.binding(["d", "shift-e"]);
- cx.assert("Test teˇst-test test").await;
- }
-
- #[gpui::test]
- async fn test_delete_b(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "b"]);
- cx.assert("Teˇst Test").await;
- cx.assert("Test ˇtest").await;
- cx.assert("Test1 test2 ˇtest3").await;
- cx.assert(indoc! {"
- Test test
- ˇtest"})
- .await;
- cx.assert(indoc! {"
- Test test
- ˇ
- test"})
- .await;
-
- let mut cx = cx.binding(["d", "shift-b"]);
- cx.assert("Test test-test ˇtest").await;
- }
-
- #[gpui::test]
- async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "$"]);
- cx.assert(indoc! {"
- The qˇuick
- brown fox"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_0(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "0"]);
- cx.assert(indoc! {"
- The qˇuick
- brown fox"})
- .await;
- cx.assert(indoc! {"
- The quick
- ˇ
- brown fox"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_k(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "k"]);
- cx.assert(indoc! {"
- The quick
- brown ˇfox
- jumps over"})
- .await;
- cx.assert(indoc! {"
- The quick
- brown fox
- jumps ˇover"})
- .await;
- cx.assert(indoc! {"
- The qˇuick
- brown fox
- jumps over"})
- .await;
- cx.assert(indoc! {"
- ˇbrown fox
- jumps over"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_j(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "j"]);
- cx.assert(indoc! {"
- The quick
- brown ˇfox
- jumps over"})
- .await;
- cx.assert(indoc! {"
- The quick
- brown fox
- jumps ˇover"})
- .await;
- cx.assert(indoc! {"
- The qˇuick
- brown fox
- jumps over"})
- .await;
- cx.assert(indoc! {"
- The quick
- brown fox
- ˇ"})
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brownˇ fox
- jumps over
- the lazy"},
- ["d", "shift-g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brownˇ fox
- jumps over
- the lazy"},
- ["d", "shift-g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps over
- the lˇazy"},
- ["d", "shift-g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps over
- ˇ"},
- ["d", "shift-g"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_gg(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["d", "g", "g"]);
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brownˇ fox
- jumps over
- the lazy"},
- ["d", "g", "g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The quick
- brown fox
- jumps over
- the lˇazy"},
- ["d", "g", "g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- The qˇuick
- brown fox
- jumps over
- the lazy"},
- ["d", "g", "g"],
- )
- .await;
- cx.assert_neovim_compatible(
- indoc! {"
- ˇ
- brown fox
- jumps over
- the lazy"},
- ["d", "g", "g"],
- )
- .await;
- }
-
- #[gpui::test]
- async fn test_cancel_delete_operator(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
- cx.set_state(
- indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"},
- Mode::Normal,
- );
-
- // Canceling operator twice reverts to normal mode with no active operator
- cx.simulate_keystrokes(["d", "escape", "k"]);
- assert_eq!(cx.active_operator(), None);
- assert_eq!(cx.mode(), Mode::Normal);
- cx.assert_editor_state(indoc! {"
- The quˇick brown
- fox jumps over
- the lazy dog"});
- }
-
- #[gpui::test]
- async fn test_unbound_command_cancels_pending_operator(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
- cx.set_state(
- indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"},
- Mode::Normal,
- );
-
- // Canceling operator twice reverts to normal mode with no active operator
- cx.simulate_keystrokes(["d", "y"]);
- assert_eq!(cx.active_operator(), None);
- assert_eq!(cx.mode(), Mode::Normal);
- }
-
- #[gpui::test]
- async fn test_delete_with_counts(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["d", "2", "d"]).await;
- cx.assert_shared_state(indoc! {"
- the ˇlazy dog"})
- .await;
-
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["2", "d", "d"]).await;
- cx.assert_shared_state(indoc! {"
- the ˇlazy dog"})
- .await;
-
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the moon,
- a star, and
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["2", "d", "2", "d"]).await;
- cx.assert_shared_state(indoc! {"
- the ˇlazy dog"})
- .await;
- }
-}
@@ -1,278 +0,0 @@
-use std::ops::Range;
-
-use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
-use gpui::{impl_actions, ViewContext, WindowContext};
-use language::{Bias, Point};
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{state::Mode, Vim};
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Increment {
- #[serde(default)]
- step: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Decrement {
- #[serde(default)]
- step: bool,
-}
-
-impl_actions!(vim, [Increment, Decrement]);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, action: &Increment, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1);
- let step = if action.step { 1 } else { 0 };
- increment(vim, count as i32, step, cx)
- })
- });
- workspace.register_action(|_: &mut Workspace, action: &Decrement, cx| {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- let count = vim.take_count(cx).unwrap_or(1);
- let step = if action.step { -1 } else { 0 };
- increment(vim, count as i32 * -1, step, cx)
- })
- });
-}
-
-fn increment(vim: &mut Vim, mut delta: i32, step: i32, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |editor, cx| {
- let mut edits = Vec::new();
- let mut new_anchors = Vec::new();
-
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- for selection in editor.selections.all_adjusted(cx) {
- if !selection.is_empty() {
- if vim.state().mode != Mode::VisualBlock || new_anchors.is_empty() {
- new_anchors.push((true, snapshot.anchor_before(selection.start)))
- }
- }
- for row in selection.start.row..=selection.end.row {
- let start = if row == selection.start.row {
- selection.start
- } else {
- Point::new(row, 0)
- };
-
- if let Some((range, num, radix)) = find_number(&snapshot, start) {
- if let Ok(val) = i32::from_str_radix(&num, radix) {
- let result = val + delta;
- delta += step;
- let replace = match radix {
- 10 => format!("{}", result),
- 16 => {
- if num.to_ascii_lowercase() == num {
- format!("{:x}", result)
- } else {
- format!("{:X}", result)
- }
- }
- 2 => format!("{:b}", result),
- _ => unreachable!(),
- };
- edits.push((range.clone(), replace));
- }
- if selection.is_empty() {
- new_anchors.push((false, snapshot.anchor_after(range.end)))
- }
- } else {
- if selection.is_empty() {
- new_anchors.push((true, snapshot.anchor_after(start)))
- }
- }
- }
- }
- editor.transact(cx, |editor, cx| {
- editor.edit(edits, cx);
-
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let mut new_ranges = Vec::new();
- for (visual, anchor) in new_anchors.iter() {
- let mut point = anchor.to_point(&snapshot);
- if !*visual && point.column > 0 {
- point.column -= 1;
- point = snapshot.clip_point(point, Bias::Left)
- }
- new_ranges.push(point..point);
- }
- s.select_ranges(new_ranges)
- })
- });
- });
- vim.switch_mode(Mode::Normal, true, cx)
-}
-
-fn find_number(
- snapshot: &MultiBufferSnapshot,
- start: Point,
-) -> Option<(Range<Point>, String, u32)> {
- let mut offset = start.to_offset(snapshot);
-
- // go backwards to the start of any number the selection is within
- for ch in snapshot.reversed_chars_at(offset) {
- if ch.is_ascii_digit() || ch == '-' || ch == 'b' || ch == 'x' {
- offset -= ch.len_utf8();
- continue;
- }
- break;
- }
-
- let mut begin = None;
- let mut end = None;
- let mut num = String::new();
- let mut radix = 10;
-
- let mut chars = snapshot.chars_at(offset).peekable();
- // find the next number on the line (may start after the original cursor position)
- while let Some(ch) = chars.next() {
- if num == "0" && ch == 'b' && chars.peek().is_some() && chars.peek().unwrap().is_digit(2) {
- radix = 2;
- begin = None;
- num = String::new();
- }
- if num == "0" && ch == 'x' && chars.peek().is_some() && chars.peek().unwrap().is_digit(16) {
- radix = 16;
- begin = None;
- num = String::new();
- }
-
- if ch.is_digit(radix)
- || (begin.is_none()
- && ch == '-'
- && chars.peek().is_some()
- && chars.peek().unwrap().is_digit(radix))
- {
- if begin.is_none() {
- begin = Some(offset);
- }
- num.push(ch);
- } else {
- if begin.is_some() {
- end = Some(offset);
- break;
- } else if ch == '\n' {
- break;
- }
- }
- offset += ch.len_utf8();
- }
- if let Some(begin) = begin {
- let end = end.unwrap_or(offset);
- Some((begin.to_point(snapshot)..end.to_point(snapshot), num, radix))
- } else {
- None
- }
-}
-
-#[cfg(test)]
-mod test {
- use indoc::indoc;
-
- use crate::test::NeovimBackedTestContext;
-
- #[gpui::test]
- async fn test_increment(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- 1ˇ2
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["ctrl-a"]).await;
- cx.assert_shared_state(indoc! {"
- 1ˇ3
- "})
- .await;
- cx.simulate_shared_keystrokes(["ctrl-x"]).await;
- cx.assert_shared_state(indoc! {"
- 1ˇ2
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["9", "9", "ctrl-a"]).await;
- cx.assert_shared_state(indoc! {"
- 11ˇ1
- "})
- .await;
- cx.simulate_shared_keystrokes(["1", "1", "1", "ctrl-x"])
- .await;
- cx.assert_shared_state(indoc! {"
- ˇ0
- "})
- .await;
- cx.simulate_shared_keystrokes(["."]).await;
- cx.assert_shared_state(indoc! {"
- -11ˇ1
- "})
- .await;
- }
-
- #[gpui::test]
- async fn test_increment_radix(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.assert_matches_neovim("ˇ total: 0xff", ["ctrl-a"], " total: 0x10ˇ0")
- .await;
- cx.assert_matches_neovim("ˇ total: 0xff", ["ctrl-x"], " total: 0xfˇe")
- .await;
- cx.assert_matches_neovim("ˇ total: 0xFF", ["ctrl-x"], " total: 0xFˇE")
- .await;
- cx.assert_matches_neovim("(ˇ0b10f)", ["ctrl-a"], "(0b1ˇ1f)")
- .await;
- cx.assert_matches_neovim("ˇ-1", ["ctrl-a"], "ˇ0").await;
- cx.assert_matches_neovim("banˇana", ["ctrl-a"], "banˇana")
- .await;
- }
-
- #[gpui::test]
- async fn test_increment_steps(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ˇ1
- 1
- 1 2
- 1
- 1"})
- .await;
-
- cx.simulate_shared_keystrokes(["j", "v", "shift-g", "g", "ctrl-a"])
- .await;
- cx.assert_shared_state(indoc! {"
- 1
- ˇ2
- 3 2
- 4
- 5"})
- .await;
-
- cx.simulate_shared_keystrokes(["shift-g", "ctrl-v", "g", "g"])
- .await;
- cx.assert_shared_state(indoc! {"
- «1ˇ»
- «2ˇ»
- «3ˇ» 2
- «4ˇ»
- «5ˇ»"})
- .await;
-
- cx.simulate_shared_keystrokes(["g", "ctrl-x"]).await;
- cx.assert_shared_state(indoc! {"
- ˇ0
- 0
- 0 2
- 0
- 0"})
- .await;
- }
-}
@@ -1,476 +0,0 @@
-use std::{borrow::Cow, cmp};
-
-use editor::{
- display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
- DisplayPoint,
-};
-use gpui::{impl_actions, ViewContext};
-use language::{Bias, SelectionGoal};
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{state::Mode, utils::copy_selections_content, Vim};
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Paste {
- #[serde(default)]
- before: bool,
- #[serde(default)]
- preserve_clipboard: bool,
-}
-
-impl_actions!(vim, [Paste]);
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(paste);
-}
-
-fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
-
- let Some(item) = cx.read_from_clipboard() else {
- return;
- };
- let clipboard_text = Cow::Borrowed(item.text());
- if clipboard_text.is_empty() {
- return;
- }
-
- if !action.preserve_clipboard && vim.state().mode.is_visual() {
- copy_selections_content(editor, vim.state().mode == Mode::VisualLine, cx);
- }
-
- // if we are copying from multi-cursor (of visual block mode), we want
- // to
- let clipboard_selections =
- item.metadata::<Vec<ClipboardSelection>>()
- .filter(|clipboard_selections| {
- clipboard_selections.len() > 1 && vim.state().mode != Mode::VisualLine
- });
-
- let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
-
- // unlike zed, if you have a multi-cursor selection from vim block mode,
- // pasting it will paste it on subsequent lines, even if you don't yet
- // have a cursor there.
- let mut selections_to_process = Vec::new();
- let mut i = 0;
- while i < current_selections.len() {
- selections_to_process
- .push((current_selections[i].start..current_selections[i].end, true));
- i += 1;
- }
- if let Some(clipboard_selections) = clipboard_selections.as_ref() {
- let left = current_selections
- .iter()
- .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
- .min()
- .unwrap();
- let mut row = current_selections.last().unwrap().end.row() + 1;
- while i < clipboard_selections.len() {
- let cursor =
- display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
- selections_to_process.push((cursor..cursor, false));
- i += 1;
- row += 1;
- }
- }
-
- let first_selection_indent_column =
- clipboard_selections.as_ref().and_then(|zed_selections| {
- zed_selections
- .first()
- .map(|selection| selection.first_line_indent)
- });
- let before = action.before || vim.state().mode == Mode::VisualLine;
-
- let mut edits = Vec::new();
- let mut new_selections = Vec::new();
- let mut original_indent_columns = Vec::new();
- let mut start_offset = 0;
-
- for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
- let (mut to_insert, original_indent_column) =
- if let Some(clipboard_selections) = &clipboard_selections {
- if let Some(clipboard_selection) = clipboard_selections.get(ix) {
- let end_offset = start_offset + clipboard_selection.len;
- let text = clipboard_text[start_offset..end_offset].to_string();
- start_offset = end_offset + 1;
- (text, Some(clipboard_selection.first_line_indent))
- } else {
- ("".to_string(), first_selection_indent_column)
- }
- } else {
- (clipboard_text.to_string(), first_selection_indent_column)
- };
- let line_mode = to_insert.ends_with("\n");
- let is_multiline = to_insert.contains("\n");
-
- if line_mode && !before {
- if selection.is_empty() {
- to_insert =
- "\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
- } else {
- to_insert = "\n".to_owned() + &to_insert;
- }
- } else if !line_mode && vim.state().mode == Mode::VisualLine {
- to_insert = to_insert + "\n";
- }
-
- let display_range = if !selection.is_empty() {
- selection.start..selection.end
- } else if line_mode {
- let point = if before {
- movement::line_beginning(&display_map, selection.start, false)
- } else {
- movement::line_end(&display_map, selection.start, false)
- };
- point..point
- } else {
- let point = if before {
- selection.start
- } else {
- movement::saturating_right(&display_map, selection.start)
- };
- point..point
- };
-
- let point_range = display_range.start.to_point(&display_map)
- ..display_range.end.to_point(&display_map);
- let anchor = if is_multiline || vim.state().mode == Mode::VisualLine {
- display_map.buffer_snapshot.anchor_before(point_range.start)
- } else {
- display_map.buffer_snapshot.anchor_after(point_range.end)
- };
-
- if *preserve {
- new_selections.push((anchor, line_mode, is_multiline));
- }
- edits.push((point_range, to_insert));
- original_indent_columns.extend(original_indent_column);
- }
-
- editor.edit_with_block_indent(edits, original_indent_columns, cx);
-
- // in line_mode vim will insert the new text on the next (or previous if before) line
- // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
- // otherwise vim will insert the next text at (or before) the current cursor position,
- // the cursor will go to the last (or first, if is_multiline) inserted character.
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.replace_cursors_with(|map| {
- let mut cursors = Vec::new();
- for (anchor, line_mode, is_multiline) in &new_selections {
- let mut cursor = anchor.to_display_point(map);
- if *line_mode {
- if !before {
- cursor = movement::down(
- map,
- cursor,
- SelectionGoal::None,
- false,
- &text_layout_details,
- )
- .0;
- }
- cursor = movement::indented_line_beginning(map, cursor, true);
- } else if !is_multiline {
- cursor = movement::saturating_left(map, cursor)
- }
- cursors.push(cursor);
- if vim.state().mode == Mode::VisualBlock {
- break;
- }
- }
-
- cursors
- });
- })
- });
- });
- vim.switch_mode(Mode::Normal, true, cx);
- });
-}
-
-#[cfg(test)]
-mod test {
- use crate::{
- state::Mode,
- test::{NeovimBackedTestContext, VimTestContext},
- };
- use indoc::indoc;
-
- #[gpui::test]
- async fn test_paste(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- // single line
- cx.set_shared_state(indoc! {"
- The quick brown
- fox ˇjumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
- cx.assert_shared_clipboard("jumps o").await;
- cx.set_shared_state(indoc! {"
- The quick brown
- fox jumps oveˇr
- the lazy dog"})
- .await;
- cx.simulate_shared_keystroke("p").await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox jumps overjumps ˇo
- the lazy dog"})
- .await;
-
- cx.set_shared_state(indoc! {"
- The quick brown
- fox jumps oveˇr
- the lazy dog"})
- .await;
- cx.simulate_shared_keystroke("shift-p").await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox jumps ovejumps ˇor
- the lazy dog"})
- .await;
-
- // line mode
- cx.set_shared_state(indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["d", "d"]).await;
- cx.assert_shared_clipboard("fox jumps over\n").await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- the laˇzy dog"})
- .await;
- cx.simulate_shared_keystroke("p").await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- the lazy dog
- ˇfox jumps over"})
- .await;
- cx.simulate_shared_keystrokes(["k", "shift-p"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- ˇfox jumps over
- the lazy dog
- fox jumps over"})
- .await;
-
- // multiline, cursor to first character of pasted text.
- cx.set_shared_state(indoc! {"
- The quick brown
- fox jumps ˇover
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "j", "y"]).await;
- cx.assert_shared_clipboard("over\nthe lazy do").await;
-
- cx.simulate_shared_keystroke("p").await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox jumps oˇover
- the lazy dover
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["u", "shift-p"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox jumps ˇover
- the lazy doover
- the lazy dog"})
- .await;
- }
-
- #[gpui::test]
- async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- // copy in visual mode
- cx.set_shared_state(indoc! {"
- The quick brown
- fox jˇumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "w", "y"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox ˇjumps over
- the lazy dog"})
- .await;
- // paste in visual mode
- cx.simulate_shared_keystrokes(["w", "v", "i", "w", "p"])
- .await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox jumps jumpˇs
- the lazy dog"})
- .await;
- cx.assert_shared_clipboard("over").await;
- // paste in visual line mode
- cx.simulate_shared_keystrokes(["up", "shift-v", "shift-p"])
- .await;
- cx.assert_shared_state(indoc! {"
- ˇover
- fox jumps jumps
- the lazy dog"})
- .await;
- cx.assert_shared_clipboard("over").await;
- // paste in visual block mode
- cx.simulate_shared_keystrokes(["ctrl-v", "down", "down", "p"])
- .await;
- cx.assert_shared_state(indoc! {"
- oveˇrver
- overox jumps jumps
- overhe lazy dog"})
- .await;
-
- // copy in visual line mode
- cx.set_shared_state(indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- the laˇzy dog"})
- .await;
- // paste in visual mode
- cx.simulate_shared_keystrokes(["v", "i", "w", "p"]).await;
- cx.assert_shared_state(
- &indoc! {"
- The quick brown
- the_
- ˇfox jumps over
- _dog"}
- .replace("_", " "), // Hack for trailing whitespace
- )
- .await;
- cx.assert_shared_clipboard("lazy").await;
- cx.set_shared_state(indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- the laˇzy dog"})
- .await;
- // paste in visual line mode
- cx.simulate_shared_keystrokes(["k", "shift-v", "p"]).await;
- cx.assert_shared_state(indoc! {"
- ˇfox jumps over
- the lazy dog"})
- .await;
- cx.assert_shared_clipboard("The quick brown\n").await;
- }
-
- #[gpui::test]
- async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- // copy in visual block mode
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "2", "j", "y"])
- .await;
- cx.assert_shared_clipboard("q\nj\nl").await;
- cx.simulate_shared_keystrokes(["p"]).await;
- cx.assert_shared_state(indoc! {"
- The qˇquick brown
- fox jjumps over
- the llazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
- .await;
- cx.assert_shared_state(indoc! {"
- The ˇq brown
- fox jjjumps over
- the lllazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
- .await;
-
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "j", "y"]).await;
- cx.assert_shared_clipboard("q\nj").await;
- cx.simulate_shared_keystrokes(["l", "ctrl-v", "2", "j", "shift-p"])
- .await;
- cx.assert_shared_state(indoc! {"
- The qˇqick brown
- fox jjmps over
- the lzy dog"})
- .await;
-
- cx.simulate_shared_keystrokes(["shift-v", "p"]).await;
- cx.assert_shared_state(indoc! {"
- ˇq
- j
- fox jjmps over
- the lzy dog"})
- .await;
- }
-
- #[gpui::test]
- async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new_typescript(cx).await;
-
- cx.set_state(
- indoc! {"
- class A {ˇ
- }
- "},
- Mode::Normal,
- );
- cx.simulate_keystrokes(["o", "a", "(", ")", "{", "escape"]);
- cx.assert_state(
- indoc! {"
- class A {
- a()ˇ{}
- }
- "},
- Mode::Normal,
- );
- // cursor goes to the first non-blank character in the line;
- cx.simulate_keystrokes(["y", "y", "p"]);
- cx.assert_state(
- indoc! {"
- class A {
- a(){}
- ˇa(){}
- }
- "},
- Mode::Normal,
- );
- // indentation is preserved when pasting
- cx.simulate_keystrokes(["u", "shift-v", "up", "y", "shift-p"]);
- cx.assert_state(
- indoc! {"
- ˇclass A {
- a(){}
- class A {
- a(){}
- }
- "},
- Mode::Normal,
- );
- }
-}
@@ -1,496 +0,0 @@
-use crate::{
- insert::NormalBefore,
- motion::Motion,
- state::{Mode, RecordedSelection, ReplayableAction},
- visual::visual_motion,
- Vim,
-};
-use gpui::{actions, Action, ViewContext, WindowContext};
-use workspace::Workspace;
-
-actions!(vim, [Repeat, EndRepeat]);
-
-fn should_replay(action: &Box<dyn Action>) -> bool {
- // skip so that we don't leave the character palette open
- if editor::ShowCharacterPalette.partial_eq(&**action) {
- return false;
- }
- true
-}
-
-fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {
- match action {
- ReplayableAction::Action(action) => {
- if super::InsertBefore.partial_eq(&**action)
- || super::InsertAfter.partial_eq(&**action)
- || super::InsertFirstNonWhitespace.partial_eq(&**action)
- || super::InsertEndOfLine.partial_eq(&**action)
- {
- Some(super::InsertBefore.boxed_clone())
- } else if super::InsertLineAbove.partial_eq(&**action)
- || super::InsertLineBelow.partial_eq(&**action)
- {
- Some(super::InsertLineBelow.boxed_clone())
- } else {
- None
- }
- }
- ReplayableAction::Insertion { .. } => None,
- }
-}
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &EndRepeat, cx| {
- Vim::update(cx, |vim, cx| {
- vim.workspace_state.replaying = false;
- vim.switch_mode(Mode::Normal, false, cx)
- });
- });
-
- workspace.register_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));
-}
-
-pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
- let Some((mut actions, editor, selection)) = Vim::update(cx, |vim, cx| {
- let actions = vim.workspace_state.recorded_actions.clone();
- if actions.is_empty() {
- return None;
- }
-
- let Some(editor) = vim.active_editor.clone() else {
- return None;
- };
- let count = vim.take_count(cx);
-
- let selection = vim.workspace_state.recorded_selection.clone();
- match selection {
- RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {
- vim.workspace_state.recorded_count = None;
- vim.switch_mode(Mode::Visual, false, cx)
- }
- RecordedSelection::VisualLine { .. } => {
- vim.workspace_state.recorded_count = None;
- vim.switch_mode(Mode::VisualLine, false, cx)
- }
- RecordedSelection::VisualBlock { .. } => {
- vim.workspace_state.recorded_count = None;
- vim.switch_mode(Mode::VisualBlock, false, cx)
- }
- RecordedSelection::None => {
- if let Some(count) = count {
- vim.workspace_state.recorded_count = Some(count);
- }
- }
- }
-
- Some((actions, editor, selection))
- }) else {
- return;
- };
-
- match selection {
- RecordedSelection::SingleLine { cols } => {
- if cols > 1 {
- visual_motion(Motion::Right, Some(cols as usize - 1), cx)
- }
- }
- RecordedSelection::Visual { rows, cols } => {
- visual_motion(
- Motion::Down {
- display_lines: false,
- },
- Some(rows as usize),
- cx,
- );
- visual_motion(
- Motion::StartOfLine {
- display_lines: false,
- },
- None,
- cx,
- );
- if cols > 1 {
- visual_motion(Motion::Right, Some(cols as usize - 1), cx)
- }
- }
- RecordedSelection::VisualBlock { rows, cols } => {
- visual_motion(
- Motion::Down {
- display_lines: false,
- },
- Some(rows as usize),
- cx,
- );
- if cols > 1 {
- visual_motion(Motion::Right, Some(cols as usize - 1), cx);
- }
- }
- RecordedSelection::VisualLine { rows } => {
- visual_motion(
- Motion::Down {
- display_lines: false,
- },
- Some(rows as usize),
- cx,
- );
- }
- RecordedSelection::None => {}
- }
-
- // insert internally uses repeat to handle counts
- // vim doesn't treat 3a1 as though you literally repeated a1
- // 3 times, instead it inserts the content thrice at the insert position.
- if let Some(to_repeat) = repeatable_insert(&actions[0]) {
- if let Some(ReplayableAction::Action(action)) = actions.last() {
- if NormalBefore.partial_eq(&**action) {
- actions.pop();
- }
- }
-
- let mut new_actions = actions.clone();
- actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());
-
- let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
-
- // if we came from insert mode we're just doing repititions 2 onwards.
- if from_insert_mode {
- count -= 1;
- new_actions[0] = actions[0].clone();
- }
-
- for _ in 1..count {
- new_actions.append(actions.clone().as_mut());
- }
- new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));
- actions = new_actions;
- }
-
- Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);
- let window = cx.window_handle();
- cx.spawn(move |mut cx| async move {
- editor.update(&mut cx, |editor, _| {
- editor.show_local_selections = false;
- })?;
- for action in actions {
- match action {
- ReplayableAction::Action(action) => {
- if should_replay(&action) {
- window.update(&mut cx, |_, cx| cx.dispatch_action(action))
- } else {
- Ok(())
- }
- }
- ReplayableAction::Insertion {
- text,
- utf16_range_to_replace,
- } => editor.update(&mut cx, |editor, cx| {
- editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)
- }),
- }?
- }
- editor.update(&mut cx, |editor, _| {
- editor.show_local_selections = true;
- })?;
- window.update(&mut cx, |_, cx| cx.dispatch_action(EndRepeat.boxed_clone()))
- })
- .detach_and_log_err(cx);
-}
-
-#[cfg(test)]
-mod test {
- use editor::test::editor_lsp_test_context::EditorLspTestContext;
- use futures::StreamExt;
- use indoc::indoc;
-
- use gpui::InputHandler;
-
- use crate::{
- state::Mode,
- test::{NeovimBackedTestContext, VimTestContext},
- };
-
- #[gpui::test]
- async fn test_dot_repeat(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- // "o"
- cx.set_shared_state("ˇhello").await;
- cx.simulate_shared_keystrokes(["o", "w", "o", "r", "l", "d", "escape"])
- .await;
- cx.assert_shared_state("hello\nworlˇd").await;
- cx.simulate_shared_keystrokes(["."]).await;
- cx.assert_shared_state("hello\nworld\nworlˇd").await;
-
- // "d"
- cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;
- cx.simulate_shared_keystrokes(["g", "g", "."]).await;
- cx.assert_shared_state("ˇ\nworld\nrld").await;
-
- // "p" (note that it pastes the current clipboard)
- cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;
- cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])
- .await;
- cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;
-
- // "~" (note that counts apply to the action taken, not . itself)
- cx.set_shared_state("ˇthe quick brown fox").await;
- cx.simulate_shared_keystrokes(["2", "~", "."]).await;
- cx.set_shared_state("THE ˇquick brown fox").await;
- cx.simulate_shared_keystrokes(["3", "."]).await;
- cx.set_shared_state("THE QUIˇck brown fox").await;
- cx.run_until_parked();
- cx.simulate_shared_keystrokes(["."]).await;
- cx.assert_shared_state("THE QUICK ˇbrown fox").await;
- }
-
- #[gpui::test]
- async fn test_repeat_ime(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state("hˇllo", Mode::Normal);
- cx.simulate_keystrokes(["i"]);
-
- // simulate brazilian input for ä.
- cx.update_editor(|editor, cx| {
- editor.replace_and_mark_text_in_range(None, "\"", Some(1..1), cx);
- editor.replace_text_in_range(None, "ä", cx);
- });
- cx.simulate_keystrokes(["escape"]);
- cx.assert_state("hˇällo", Mode::Normal);
- cx.simulate_keystrokes(["."]);
- cx.assert_state("hˇäällo", Mode::Normal);
- }
-
- #[gpui::test]
- async fn test_repeat_completion(cx: &mut gpui::TestAppContext) {
- VimTestContext::init(cx);
- let cx = EditorLspTestContext::new_rust(
- lsp::ServerCapabilities {
- completion_provider: Some(lsp::CompletionOptions {
- trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
- resolve_provider: Some(true),
- ..Default::default()
- }),
- ..Default::default()
- },
- cx,
- )
- .await;
- let mut cx = VimTestContext::new_with_lsp(cx, true);
-
- cx.set_state(
- indoc! {"
- onˇe
- two
- three
- "},
- Mode::Normal,
- );
-
- let mut request =
- cx.handle_request::<lsp::request::Completion, _, _>(move |_, params, _| async move {
- let position = params.text_document_position.position;
- Ok(Some(lsp::CompletionResponse::Array(vec![
- lsp::CompletionItem {
- label: "first".to_string(),
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: lsp::Range::new(position.clone(), position.clone()),
- new_text: "first".to_string(),
- })),
- ..Default::default()
- },
- lsp::CompletionItem {
- label: "second".to_string(),
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: lsp::Range::new(position.clone(), position.clone()),
- new_text: "second".to_string(),
- })),
- ..Default::default()
- },
- ])))
- });
- cx.simulate_keystrokes(["a", "."]);
- request.next().await;
- cx.condition(|editor, _| editor.context_menu_visible())
- .await;
- cx.simulate_keystrokes(["down", "enter", "!", "escape"]);
-
- cx.assert_state(
- indoc! {"
- one.secondˇ!
- two
- three
- "},
- Mode::Normal,
- );
- cx.simulate_keystrokes(["j", "."]);
- cx.assert_state(
- indoc! {"
- one.second!
- two.secondˇ!
- three
- "},
- Mode::Normal,
- );
- }
-
- #[gpui::test]
- async fn test_repeat_visual(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- // single-line (3 columns)
- cx.set_shared_state(indoc! {
- "ˇthe quick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "w", "s", "o", "escape"])
- .await;
- cx.assert_shared_state(indoc! {
- "ˇo quick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "w", "."]).await;
- cx.assert_shared_state(indoc! {
- "o quick brown
- fox ˇops over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["f", "r", "."]).await;
- cx.assert_shared_state(indoc! {
- "o quick brown
- fox ops oveˇothe lazy dog"
- })
- .await;
-
- // visual
- cx.set_shared_state(indoc! {
- "the ˇquick brown
- fox jumps over
- fox jumps over
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "j", "x"]).await;
- cx.assert_shared_state(indoc! {
- "the ˇumps over
- fox jumps over
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["."]).await;
- cx.assert_shared_state(indoc! {
- "the ˇumps over
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["w", "."]).await;
- cx.assert_shared_state(indoc! {
- "the umps ˇumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "."]).await;
- cx.assert_shared_state(indoc! {
- "the umps umps over
- the ˇog"
- })
- .await;
-
- // block mode (3 rows)
- cx.set_shared_state(indoc! {
- "ˇthe quick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "j", "j", "shift-i", "o", "escape"])
- .await;
- cx.assert_shared_state(indoc! {
- "ˇothe quick brown
- ofox jumps over
- othe lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;
- cx.assert_shared_state(indoc! {
- "othe quick brown
- ofoxˇo jumps over
- otheo lazy dog"
- })
- .await;
-
- // line mode
- cx.set_shared_state(indoc! {
- "ˇthe quick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "shift-r", "o", "escape"])
- .await;
- cx.assert_shared_state(indoc! {
- "ˇo
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "."]).await;
- cx.assert_shared_state(indoc! {
- "o
- ˇo
- the lazy dog"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_repeat_motion_counts(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "ˇthe quick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["3", "d", "3", "l"]).await;
- cx.assert_shared_state(indoc! {
- "ˇ brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "."]).await;
- cx.assert_shared_state(indoc! {
- " brown
- ˇ over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "2", "."]).await;
- cx.assert_shared_state(indoc! {
- " brown
- over
- ˇe lazy dog"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_record_interrupted(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state("ˇhello\n", Mode::Normal);
- cx.simulate_keystrokes(["4", "i", "j", "cmd-shift-p", "escape"]);
- cx.simulate_keystrokes(["escape"]);
- cx.assert_state("ˇjhello\n", Mode::Normal);
- }
-}
@@ -1,247 +0,0 @@
-use crate::Vim;
-use editor::{
- display_map::ToDisplayPoint,
- scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
- DisplayPoint, Editor,
-};
-use gpui::{actions, ViewContext};
-use language::Bias;
-use workspace::Workspace;
-
-actions!(
- vim,
- [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &LineDown, cx| {
- scroll(cx, false, |c| ScrollAmount::Line(c.unwrap_or(1.)))
- });
- workspace.register_action(|_: &mut Workspace, _: &LineUp, cx| {
- scroll(cx, false, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
- });
- workspace.register_action(|_: &mut Workspace, _: &PageDown, cx| {
- scroll(cx, false, |c| ScrollAmount::Page(c.unwrap_or(1.)))
- });
- workspace.register_action(|_: &mut Workspace, _: &PageUp, cx| {
- scroll(cx, false, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
- });
- workspace.register_action(|_: &mut Workspace, _: &ScrollDown, cx| {
- scroll(cx, true, |c| {
- if let Some(c) = c {
- ScrollAmount::Line(c)
- } else {
- ScrollAmount::Page(0.5)
- }
- })
- });
- workspace.register_action(|_: &mut Workspace, _: &ScrollUp, cx| {
- scroll(cx, true, |c| {
- if let Some(c) = c {
- ScrollAmount::Line(-c)
- } else {
- ScrollAmount::Page(-0.5)
- }
- })
- });
-}
-
-fn scroll(
- cx: &mut ViewContext<Workspace>,
- move_cursor: bool,
- by: fn(c: Option<f32>) -> ScrollAmount,
-) {
- Vim::update(cx, |vim, cx| {
- let amount = by(vim.take_count(cx).map(|c| c as f32));
- vim.update_active_editor(cx, |editor, cx| {
- scroll_editor(editor, move_cursor, &amount, cx)
- });
- })
-}
-
-fn scroll_editor(
- editor: &mut Editor,
- preserve_cursor_position: bool,
- amount: &ScrollAmount,
- cx: &mut ViewContext<Editor>,
-) {
- let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
- let old_top_anchor = editor.scroll_manager.anchor().anchor;
-
- editor.scroll_screen(amount, cx);
- if should_move_cursor {
- let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
- visible_rows as u32
- } else {
- return;
- };
-
- let top_anchor = editor.scroll_manager.anchor().anchor;
-
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let mut head = selection.head();
- let top = top_anchor.to_display_point(map);
-
- if preserve_cursor_position {
- let old_top = old_top_anchor.to_display_point(map);
- let new_row = top.row() + selection.head().row() - old_top.row();
- head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left)
- }
- let min_row = top.row() + VERTICAL_SCROLL_MARGIN as u32;
- let max_row = top.row() + visible_rows - VERTICAL_SCROLL_MARGIN as u32 - 1;
-
- let new_head = if head.row() < min_row {
- map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left)
- } else if head.row() > max_row {
- map.clip_point(DisplayPoint::new(max_row, head.column()), Bias::Left)
- } else {
- head
- };
- if selection.is_empty() {
- selection.collapse_to(new_head, selection.goal)
- } else {
- selection.set_head(new_head, selection.goal)
- };
- })
- });
- }
-}
-
-#[cfg(test)]
-mod test {
- use crate::{
- state::Mode,
- test::{NeovimBackedTestContext, VimTestContext},
- };
- use gpui::{point, px, size, Context};
- use indoc::indoc;
- use language::Point;
-
- #[gpui::test]
- async fn test_scroll(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- let (line_height, visible_line_count) = cx.editor(|editor, cx| {
- (
- editor
- .style()
- .unwrap()
- .text
- .line_height_in_pixels(cx.rem_size()),
- editor.visible_line_count().unwrap(),
- )
- });
-
- let window = cx.window;
- let margin = cx
- .update_window(window, |_, cx| {
- cx.viewport_size().height - line_height * visible_line_count
- })
- .unwrap();
- cx.simulate_window_resize(
- cx.window,
- size(px(1000.), margin + 8. * line_height - px(1.0)),
- );
-
- cx.set_state(
- indoc!(
- "ˇone
- two
- three
- four
- five
- six
- seven
- eight
- nine
- ten
- eleven
- twelve
- "
- ),
- Mode::Normal,
- );
-
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
- });
- cx.simulate_keystrokes(["ctrl-e"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 1.))
- });
- cx.simulate_keystrokes(["2", "ctrl-e"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.))
- });
- cx.simulate_keystrokes(["ctrl-y"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 2.))
- });
-
- // does not select in normal mode
- cx.simulate_keystrokes(["g", "g"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
- });
- cx.simulate_keystrokes(["ctrl-d"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
- assert_eq!(
- editor.selections.newest(cx).range(),
- Point::new(6, 0)..Point::new(6, 0)
- )
- });
-
- // does select in visual mode
- cx.simulate_keystrokes(["g", "g"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 0.))
- });
- cx.simulate_keystrokes(["v", "ctrl-d"]);
- cx.update_editor(|editor, cx| {
- assert_eq!(editor.snapshot(cx).scroll_position(), point(0., 3.0));
- assert_eq!(
- editor.selections.newest(cx).range(),
- Point::new(0, 0)..Point::new(6, 1)
- )
- });
- }
- #[gpui::test]
- async fn test_ctrl_d_u(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_scroll_height(10).await;
-
- pub fn sample_text(rows: usize, cols: usize, start_char: char) -> String {
- let mut text = String::new();
- for row in 0..rows {
- let c: char = (start_char as u32 + row as u32) as u8 as char;
- let mut line = c.to_string().repeat(cols);
- if row < rows - 1 {
- line.push('\n');
- }
- text += &line;
- }
- text
- }
- let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
- cx.set_shared_state(&content).await;
-
- // skip over the scrolloff at the top
- // test ctrl-d
- cx.simulate_shared_keystrokes(["4", "j", "ctrl-d"]).await;
- cx.assert_state_matches().await;
- cx.simulate_shared_keystrokes(["ctrl-d"]).await;
- cx.assert_state_matches().await;
- cx.simulate_shared_keystrokes(["g", "g", "ctrl-d"]).await;
- cx.assert_state_matches().await;
-
- // test ctrl-u
- cx.simulate_shared_keystrokes(["ctrl-u"]).await;
- cx.assert_state_matches().await;
- cx.simulate_shared_keystrokes(["ctrl-d", "ctrl-d", "4", "j", "ctrl-u", "ctrl-u"])
- .await;
- cx.assert_state_matches().await;
- }
-}
@@ -1,477 +0,0 @@
-use gpui::{actions, impl_actions, ViewContext};
-use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
-use serde_derive::Deserialize;
-use workspace::{searchable::Direction, Workspace};
-
-use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub(crate) struct MoveToNext {
- #[serde(default)]
- partial_word: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub(crate) struct MoveToPrev {
- #[serde(default)]
- partial_word: bool,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub(crate) struct Search {
- #[serde(default)]
- backwards: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct FindCommand {
- pub query: String,
- pub backwards: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-pub struct ReplaceCommand {
- pub query: String,
-}
-
-#[derive(Debug, Default)]
-struct Replacement {
- search: String,
- replacement: String,
- should_replace_all: bool,
- is_case_sensitive: bool,
-}
-
-actions!(vim, [SearchSubmit]);
-impl_actions!(
- vim,
- [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
-);
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(move_to_next);
- workspace.register_action(move_to_prev);
- workspace.register_action(search);
- workspace.register_action(search_submit);
- workspace.register_action(search_deploy);
-
- workspace.register_action(find_command);
- workspace.register_action(replace_command);
-}
-
-fn move_to_next(workspace: &mut Workspace, action: &MoveToNext, cx: &mut ViewContext<Workspace>) {
- move_to_internal(workspace, Direction::Next, !action.partial_word, cx)
-}
-
-fn move_to_prev(workspace: &mut Workspace, action: &MoveToPrev, cx: &mut ViewContext<Workspace>) {
- move_to_internal(workspace, Direction::Prev, !action.partial_word, cx)
-}
-
-fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Workspace>) {
- let pane = workspace.active_pane().clone();
- let direction = if action.backwards {
- Direction::Prev
- } else {
- Direction::Next
- };
- Vim::update(cx, |vim, cx| {
- let count = vim.take_count(cx).unwrap_or(1);
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, |search_bar, cx| {
- if !search_bar.show(cx) {
- return;
- }
- let query = search_bar.query(cx);
-
- search_bar.select_query(cx);
- cx.focus_self();
-
- if query.is_empty() {
- search_bar.set_replacement(None, cx);
- search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);
- search_bar.activate_search_mode(SearchMode::Regex, cx);
- }
- vim.workspace_state.search = SearchState {
- direction,
- count,
- initial_query: query.clone(),
- };
- });
- }
- })
- })
-}
-
-// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
-fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
- cx.propagate();
-}
-
-fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- let pane = workspace.active_pane().clone();
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, |search_bar, cx| {
- let state = &mut vim.workspace_state.search;
- let mut count = state.count;
- let direction = state.direction;
-
- // in the case that the query has changed, the search bar
- // will have selected the next match already.
- if (search_bar.query(cx) != state.initial_query)
- && state.direction == Direction::Next
- {
- count = count.saturating_sub(1)
- }
- state.count = 1;
- search_bar.select_match(direction, count, cx);
- search_bar.focus_editor(&Default::default(), cx);
- });
- }
- });
- })
-}
-
-pub fn move_to_internal(
- workspace: &mut Workspace,
- direction: Direction,
- whole_word: bool,
- cx: &mut ViewContext<Workspace>,
-) {
- Vim::update(cx, |vim, cx| {
- let pane = workspace.active_pane().clone();
- let count = vim.take_count(cx).unwrap_or(1);
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- let search = search_bar.update(cx, |search_bar, cx| {
- let mut options = SearchOptions::CASE_SENSITIVE;
- options.set(SearchOptions::WHOLE_WORD, whole_word);
- if search_bar.show(cx) {
- search_bar
- .query_suggestion(cx)
- .map(|query| search_bar.search(&query, Some(options), cx))
- } else {
- None
- }
- });
-
- if let Some(search) = search {
- let search_bar = search_bar.downgrade();
- cx.spawn(|_, mut cx| async move {
- search.await?;
- search_bar.update(&mut cx, |search_bar, cx| {
- search_bar.select_match(direction, count, cx)
- })?;
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
- }
- }
- });
- vim.clear_operator(cx);
- });
-}
-
-fn find_command(workspace: &mut Workspace, action: &FindCommand, cx: &mut ViewContext<Workspace>) {
- let pane = workspace.active_pane().clone();
- pane.update(cx, |pane, cx| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
- let search = search_bar.update(cx, |search_bar, cx| {
- if !search_bar.show(cx) {
- return None;
- }
- let mut query = action.query.clone();
- if query == "" {
- query = search_bar.query(cx);
- };
-
- search_bar.activate_search_mode(SearchMode::Regex, cx);
- Some(search_bar.search(&query, Some(SearchOptions::CASE_SENSITIVE), cx))
- });
- let Some(search) = search else { return };
- let search_bar = search_bar.downgrade();
- let direction = if action.backwards {
- Direction::Prev
- } else {
- Direction::Next
- };
- cx.spawn(|_, mut cx| async move {
- search.await?;
- search_bar.update(&mut cx, |search_bar, cx| {
- search_bar.select_match(direction, 1, cx)
- })?;
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
- }
- })
-}
-
-fn replace_command(
- workspace: &mut Workspace,
- action: &ReplaceCommand,
- cx: &mut ViewContext<Workspace>,
-) {
- let replacement = parse_replace_all(&action.query);
- let pane = workspace.active_pane().clone();
- pane.update(cx, |pane, cx| {
- let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() else {
- return;
- };
- let search = search_bar.update(cx, |search_bar, cx| {
- if !search_bar.show(cx) {
- return None;
- }
-
- let mut options = SearchOptions::default();
- if replacement.is_case_sensitive {
- options.set(SearchOptions::CASE_SENSITIVE, true)
- }
- let search = if replacement.search == "" {
- search_bar.query(cx)
- } else {
- replacement.search
- };
-
- search_bar.set_replacement(Some(&replacement.replacement), cx);
- search_bar.activate_search_mode(SearchMode::Regex, cx);
- Some(search_bar.search(&search, Some(options), cx))
- });
- let Some(search) = search else { return };
- let search_bar = search_bar.downgrade();
- cx.spawn(|_, mut cx| async move {
- search.await?;
- search_bar.update(&mut cx, |search_bar, cx| {
- if replacement.should_replace_all {
- search_bar.select_last_match(cx);
- search_bar.replace_all(&Default::default(), cx);
- Vim::update(cx, |vim, cx| {
- move_cursor(
- vim,
- Motion::StartOfLine {
- display_lines: false,
- },
- None,
- cx,
- )
- })
- }
- })?;
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
- })
-}
-
-// convert a vim query into something more usable by zed.
-// we don't attempt to fully convert between the two regex syntaxes,
-// but we do flip \( and \) to ( and ) (and vice-versa) in the pattern,
-// and convert \0..\9 to $0..$9 in the replacement so that common idioms work.
-fn parse_replace_all(query: &str) -> Replacement {
- let mut chars = query.chars();
- if Some('%') != chars.next() || Some('s') != chars.next() {
- return Replacement::default();
- }
-
- let Some(delimeter) = chars.next() else {
- return Replacement::default();
- };
-
- let mut search = String::new();
- let mut replacement = String::new();
- let mut flags = String::new();
-
- let mut buffer = &mut search;
-
- let mut escaped = false;
- // 0 - parsing search
- // 1 - parsing replacement
- // 2 - parsing flags
- let mut phase = 0;
-
- for c in chars {
- if escaped {
- escaped = false;
- if phase == 1 && c.is_digit(10) {
- buffer.push('$')
- // unescape escaped parens
- } else if phase == 0 && c == '(' || c == ')' {
- } else if c != delimeter {
- buffer.push('\\')
- }
- buffer.push(c)
- } else if c == '\\' {
- escaped = true;
- } else if c == delimeter {
- if phase == 0 {
- buffer = &mut replacement;
- phase = 1;
- } else if phase == 1 {
- buffer = &mut flags;
- phase = 2;
- } else {
- break;
- }
- } else {
- // escape unescaped parens
- if phase == 0 && c == '(' || c == ')' {
- buffer.push('\\')
- }
- buffer.push(c)
- }
- }
-
- let mut replacement = Replacement {
- search,
- replacement,
- should_replace_all: true,
- is_case_sensitive: true,
- };
-
- for c in flags.chars() {
- match c {
- 'g' | 'I' => {}
- 'c' | 'n' => replacement.should_replace_all = false,
- 'i' => replacement.is_case_sensitive = false,
- _ => {}
- }
- }
-
- replacement
-}
-
-#[cfg(test)]
-mod test {
- use editor::DisplayPoint;
- use search::BufferSearchBar;
-
- use crate::{state::Mode, test::VimTestContext};
-
- #[gpui::test]
- async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
- cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["*"]);
- cx.run_until_parked();
- cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["*"]);
- cx.run_until_parked();
- cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["#"]);
- cx.run_until_parked();
- cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["#"]);
- cx.run_until_parked();
- cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["2", "*"]);
- cx.run_until_parked();
- cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["g", "*"]);
- cx.run_until_parked();
- cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["n"]);
- cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
-
- cx.simulate_keystrokes(["g", "#"]);
- cx.run_until_parked();
- cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
- }
-
- #[gpui::test]
- async fn test_search(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
- cx.simulate_keystrokes(["/", "c", "c"]);
-
- let search_bar = cx.workspace(|workspace, cx| {
- workspace
- .active_pane()
- .read(cx)
- .toolbar()
- .read(cx)
- .item_of_type::<BufferSearchBar>()
- .expect("Buffer search bar should be deployed")
- });
-
- cx.update_view(search_bar, |bar, cx| {
- assert_eq!(bar.query(cx), "cc");
- });
-
- cx.run_until_parked();
-
- cx.update_editor(|editor, cx| {
- let highlights = editor.all_text_background_highlights(cx);
- assert_eq!(3, highlights.len());
- assert_eq!(
- DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
- highlights[0].0
- )
- });
-
- cx.simulate_keystrokes(["enter"]);
- cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
-
- // n to go to next/N to go to previous
- cx.simulate_keystrokes(["n"]);
- cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
- cx.simulate_keystrokes(["shift-n"]);
- cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
-
- // ?<enter> to go to previous
- cx.simulate_keystrokes(["?", "enter"]);
- cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
- cx.simulate_keystrokes(["?", "enter"]);
- cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
-
- // /<enter> to go to next
- cx.simulate_keystrokes(["/", "enter"]);
- cx.assert_state("aa\nbb\ncc\ncc\nˇcc\n", Mode::Normal);
-
- // ?{search}<enter> to search backwards
- cx.simulate_keystrokes(["?", "b", "enter"]);
- cx.assert_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
-
- // works with counts
- cx.simulate_keystrokes(["4", "/", "c"]);
- cx.simulate_keystrokes(["enter"]);
- cx.assert_state("aa\nbb\ncc\ncˇc\ncc\n", Mode::Normal);
-
- // check that searching resumes from cursor, not previous match
- cx.set_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
- cx.simulate_keystrokes(["/", "d"]);
- cx.simulate_keystrokes(["enter"]);
- cx.assert_state("aa\nbb\nˇdd\ncc\nbb\n", Mode::Normal);
- cx.update_editor(|editor, cx| editor.move_to_beginning(&Default::default(), cx));
- cx.assert_state("ˇaa\nbb\ndd\ncc\nbb\n", Mode::Normal);
- cx.simulate_keystrokes(["/", "b"]);
- cx.simulate_keystrokes(["enter"]);
- cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal);
- }
-
- #[gpui::test]
- async fn test_non_vim_search(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, false).await;
- cx.set_state("ˇone one one one", Mode::Normal);
- cx.simulate_keystrokes(["cmd-f"]);
- cx.run_until_parked();
-
- cx.assert_editor_state("«oneˇ» one one one");
- cx.simulate_keystrokes(["enter"]);
- cx.assert_editor_state("one «oneˇ» one one");
- cx.simulate_keystrokes(["shift-enter"]);
- cx.assert_editor_state("«oneˇ» one one one");
- }
-}
@@ -1,276 +0,0 @@
-use editor::movement;
-use gpui::{actions, ViewContext, WindowContext};
-use language::Point;
-use workspace::Workspace;
-
-use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
-
-actions!(vim, [Substitute, SubstituteLine]);
-
-pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, _: &Substitute, cx| {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- let count = vim.take_count(cx);
- substitute(vim, count, vim.state().mode == Mode::VisualLine, cx);
- })
- });
-
- workspace.register_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
- Vim::update(cx, |vim, cx| {
- vim.start_recording(cx);
- if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
- vim.switch_mode(Mode::VisualLine, false, cx)
- }
- let count = vim.take_count(cx);
- substitute(vim, count, true, cx)
- })
- });
-}
-
-pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- editor.transact(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- if selection.start == selection.end {
- Motion::Right.expand_selection(
- map,
- selection,
- count,
- true,
- &text_layout_details,
- );
- }
- if line_mode {
- // in Visual mode when the selection contains the newline at the end
- // of the line, we should exclude it.
- if !selection.is_empty() && selection.end.column() == 0 {
- selection.end = movement::left(map, selection.end);
- }
- Motion::CurrentLine.expand_selection(
- map,
- selection,
- None,
- false,
- &text_layout_details,
- );
- if let Some((point, _)) = (Motion::FirstNonWhitespace {
- display_lines: false,
- })
- .move_point(
- map,
- selection.start,
- selection.goal,
- None,
- &text_layout_details,
- ) {
- selection.start = point;
- }
- }
- })
- });
- copy_selections_content(editor, line_mode, cx);
- let selections = editor.selections.all::<Point>(cx).into_iter();
- let edits = selections.map(|selection| (selection.start..selection.end, ""));
- editor.edit(edits, cx);
- });
- });
- vim.switch_mode(Mode::Insert, true, cx);
-}
-
-#[cfg(test)]
-mod test {
- use crate::{
- state::Mode,
- test::{NeovimBackedTestContext, VimTestContext},
- };
- use indoc::indoc;
-
- #[gpui::test]
- async fn test_substitute(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- // supports a single cursor
- cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
- cx.simulate_keystrokes(["s", "x"]);
- cx.assert_editor_state("xˇbc\n");
-
- // supports a selection
- cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
- cx.assert_editor_state("a«bcˇ»\n");
- cx.simulate_keystrokes(["s", "x"]);
- cx.assert_editor_state("axˇ\n");
-
- // supports counts
- cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
- cx.simulate_keystrokes(["2", "s", "x"]);
- cx.assert_editor_state("xˇc\n");
-
- // supports multiple cursors
- cx.set_state(indoc! {"a«bcˇ»deˇffg\n"}, Mode::Normal);
- cx.simulate_keystrokes(["2", "s", "x"]);
- cx.assert_editor_state("axˇdexˇg\n");
-
- // does not read beyond end of line
- cx.set_state(indoc! {"ˇabc\n"}, Mode::Normal);
- cx.simulate_keystrokes(["5", "s", "x"]);
- cx.assert_editor_state("xˇ\n");
-
- // it handles multibyte characters
- cx.set_state(indoc! {"ˇcàfé\n"}, Mode::Normal);
- cx.simulate_keystrokes(["4", "s"]);
- cx.assert_editor_state("ˇ\n");
-
- // should transactionally undo selection changes
- cx.simulate_keystrokes(["escape", "u"]);
- cx.assert_editor_state("ˇcàfé\n");
-
- // it handles visual line mode
- cx.set_state(
- indoc! {"
- alpha
- beˇta
- gamma"},
- Mode::Normal,
- );
- cx.simulate_keystrokes(["shift-v", "s"]);
- cx.assert_editor_state(indoc! {"
- alpha
- ˇ
- gamma"});
- }
-
- #[gpui::test]
- async fn test_visual_change(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state("The quick ˇbrown").await;
- cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
- cx.assert_shared_state("The quick ˇ").await;
-
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "w", "j", "c"]).await;
- cx.assert_shared_state(indoc! {"
- The ˇver
- the lazy dog"})
- .await;
-
- let cases = cx.each_marked_position(indoc! {"
- The ˇquick brown
- fox jumps ˇover
- the ˇlazy dog"});
- for initial_state in cases {
- cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
- .await;
- cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
- .await;
- }
- }
-
- #[gpui::test]
- async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["shift-v", "c"]);
- cx.assert(indoc! {"
- The quˇick brown
- fox jumps over
- the lazy dog"})
- .await;
- // Test pasting code copied on change
- cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
- cx.assert_state_matches().await;
-
- cx.assert_all(indoc! {"
- The quick brown
- fox juˇmps over
- the laˇzy dog"})
- .await;
- let mut cx = cx.binding(["shift-v", "j", "c"]);
- cx.assert(indoc! {"
- The quˇick brown
- fox jumps over
- the lazy dog"})
- .await;
- // Test pasting code copied on delete
- cx.simulate_shared_keystrokes(["escape", "j", "p"]).await;
- cx.assert_state_matches().await;
-
- cx.assert_all(indoc! {"
- The quick brown
- fox juˇmps over
- the laˇzy dog"})
- .await;
- }
-
- #[gpui::test]
- async fn test_substitute_line(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- let initial_state = indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog
- "};
-
- // normal mode
- cx.set_shared_state(initial_state).await;
- cx.simulate_shared_keystrokes(["shift-s", "o"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- oˇ
- the lazy dog
- "})
- .await;
-
- // visual mode
- cx.set_shared_state(initial_state).await;
- cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"])
- .await;
- cx.assert_shared_state(indoc! {"
- oˇ
- the lazy dog
- "})
- .await;
-
- // visual block mode
- cx.set_shared_state(initial_state).await;
- cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"])
- .await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- oˇ
- "})
- .await;
-
- // visual mode including newline
- cx.set_shared_state(initial_state).await;
- cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"])
- .await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- oˇ
- the lazy dog
- "})
- .await;
-
- // indentation
- cx.set_neovim_option("shiftwidth=4").await;
- cx.set_shared_state(initial_state).await;
- cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"])
- .await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- oˇ
- the lazy dog
- "})
- .await;
- }
-}
@@ -1,50 +0,0 @@
-use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
-use collections::HashMap;
-use gpui::WindowContext;
-
-pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let original_position = (selection.head(), selection.goal);
- original_positions.insert(selection.id, original_position);
- motion.expand_selection(map, selection, times, true, &text_layout_details);
- });
- });
- copy_selections_content(editor, motion.linewise(), cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|_, selection| {
- let (head, goal) = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(head, goal);
- });
- });
- });
- });
-}
-
-pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
- vim.update_active_editor(cx, |editor, cx| {
- editor.transact(cx, |editor, cx| {
- editor.set_clip_at_line_ends(false, cx);
- let mut original_positions: HashMap<_, _> = Default::default();
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- let original_position = (selection.head(), selection.goal);
- object.expand_selection(map, selection, around);
- original_positions.insert(selection.id, original_position);
- });
- });
- copy_selections_content(editor, false, cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|_, selection| {
- let (head, goal) = original_positions.remove(&selection.id).unwrap();
- selection.collapse_to(head, goal);
- });
- });
- });
- });
-}
@@ -1,1025 +0,0 @@
-use std::ops::Range;
-
-use editor::{
- char_kind,
- display_map::{DisplaySnapshot, ToDisplayPoint},
- movement::{self, FindRange},
- Bias, CharKind, DisplayPoint,
-};
-use gpui::{actions, impl_actions, ViewContext, WindowContext};
-use language::Selection;
-use serde::Deserialize;
-use workspace::Workspace;
-
-use crate::{motion::right, normal::normal_object, state::Mode, visual::visual_object, Vim};
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum Object {
- Word { ignore_punctuation: bool },
- Sentence,
- Quotes,
- BackQuotes,
- DoubleQuotes,
- VerticalBars,
- Parentheses,
- SquareBrackets,
- CurlyBrackets,
- AngleBrackets,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-struct Word {
- #[serde(default)]
- ignore_punctuation: bool,
-}
-
-impl_actions!(vim, [Word]);
-
-actions!(
- vim,
- [
- Sentence,
- Quotes,
- BackQuotes,
- DoubleQuotes,
- VerticalBars,
- Parentheses,
- SquareBrackets,
- CurlyBrackets,
- AngleBrackets
- ]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(
- |_: &mut Workspace, &Word { ignore_punctuation }: &Word, cx: _| {
- object(Object::Word { ignore_punctuation }, cx)
- },
- );
- workspace
- .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
- workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
- workspace
- .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx));
- workspace.register_action(|_: &mut Workspace, _: &DoubleQuotes, cx: _| {
- object(Object::DoubleQuotes, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &Parentheses, cx: _| {
- object(Object::Parentheses, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &SquareBrackets, cx: _| {
- object(Object::SquareBrackets, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| {
- object(Object::CurlyBrackets, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| {
- object(Object::AngleBrackets, cx)
- });
- workspace.register_action(|_: &mut Workspace, _: &VerticalBars, cx: _| {
- object(Object::VerticalBars, cx)
- });
-}
-
-fn object(object: Object, cx: &mut WindowContext) {
- match Vim::read(cx).state().mode {
- Mode::Normal => normal_object(object, cx),
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_object(object, cx),
- Mode::Insert => {
- // Shouldn't execute a text object in insert mode. Ignoring
- }
- }
-}
-
-impl Object {
- pub fn is_multiline(self) -> bool {
- match self {
- Object::Word { .. }
- | Object::Quotes
- | Object::BackQuotes
- | Object::VerticalBars
- | Object::DoubleQuotes => false,
- Object::Sentence
- | Object::Parentheses
- | Object::AngleBrackets
- | Object::CurlyBrackets
- | Object::SquareBrackets => true,
- }
- }
-
- pub fn always_expands_both_ways(self) -> bool {
- match self {
- Object::Word { .. } | Object::Sentence => false,
- Object::Quotes
- | Object::BackQuotes
- | Object::DoubleQuotes
- | Object::VerticalBars
- | Object::Parentheses
- | Object::SquareBrackets
- | Object::CurlyBrackets
- | Object::AngleBrackets => true,
- }
- }
-
- pub fn target_visual_mode(self, current_mode: Mode) -> Mode {
- match self {
- Object::Word { .. } if current_mode == Mode::VisualLine => Mode::Visual,
- Object::Word { .. } => current_mode,
- Object::Sentence
- | Object::Quotes
- | Object::BackQuotes
- | Object::DoubleQuotes
- | Object::VerticalBars
- | Object::Parentheses
- | Object::SquareBrackets
- | Object::CurlyBrackets
- | Object::AngleBrackets => Mode::Visual,
- }
- }
-
- pub fn range(
- self,
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- around: bool,
- ) -> Option<Range<DisplayPoint>> {
- match self {
- Object::Word { ignore_punctuation } => {
- if around {
- around_word(map, relative_to, ignore_punctuation)
- } else {
- in_word(map, relative_to, ignore_punctuation)
- }
- }
- Object::Sentence => sentence(map, relative_to, around),
- Object::Quotes => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'')
- }
- Object::BackQuotes => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')
- }
- Object::DoubleQuotes => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
- }
- Object::VerticalBars => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '|', '|')
- }
- Object::Parentheses => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
- }
- Object::SquareBrackets => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')
- }
- Object::CurlyBrackets => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '{', '}')
- }
- Object::AngleBrackets => {
- surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
- }
- }
- }
-
- pub fn expand_selection(
- self,
- map: &DisplaySnapshot,
- selection: &mut Selection<DisplayPoint>,
- around: bool,
- ) -> bool {
- if let Some(range) = self.range(map, selection.head(), around) {
- selection.start = range.start;
- selection.end = range.end;
- true
- } else {
- false
- }
- }
-}
-
-/// Return a range that surrounds the word relative_to is in
-/// If relative_to is at the start of a word, return the word.
-/// If relative_to is between words, return the space between
-fn in_word(
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
- // Use motion::right so that we consider the character under the cursor when looking for the start
- let scope = map
- .buffer_snapshot
- .language_scope_at(relative_to.to_point(map));
- let start = movement::find_preceding_boundary(
- map,
- right(map, relative_to, 1),
- movement::FindRange::SingleLine,
- |left, right| {
- char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
- != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
- },
- );
-
- let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
- char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
- != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
- });
-
- Some(start..end)
-}
-
-/// Return a range that surrounds the word and following whitespace
-/// relative_to is in.
-/// If relative_to is at the start of a word, return the word and following whitespace.
-/// If relative_to is between words, return the whitespace back and the following word
-
-/// if in word
-/// delete that word
-/// if there is whitespace following the word, delete that as well
-/// otherwise, delete any preceding whitespace
-/// otherwise
-/// delete whitespace around cursor
-/// delete word following the cursor
-fn around_word(
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
- let scope = map
- .buffer_snapshot
- .language_scope_at(relative_to.to_point(map));
- let in_word = map
- .chars_at(relative_to)
- .next()
- .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace)
- .unwrap_or(false);
-
- if in_word {
- around_containing_word(map, relative_to, ignore_punctuation)
- } else {
- around_next_word(map, relative_to, ignore_punctuation)
- }
-}
-
-fn around_containing_word(
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
- in_word(map, relative_to, ignore_punctuation)
- .map(|range| expand_to_include_whitespace(map, range, true))
-}
-
-fn around_next_word(
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- ignore_punctuation: bool,
-) -> Option<Range<DisplayPoint>> {
- let scope = map
- .buffer_snapshot
- .language_scope_at(relative_to.to_point(map));
- // Get the start of the word
- let start = movement::find_preceding_boundary(
- map,
- right(map, relative_to, 1),
- FindRange::SingleLine,
- |left, right| {
- char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
- != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
- },
- );
-
- let mut word_found = false;
- let end = movement::find_boundary(map, relative_to, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
-
- let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
-
- if right_kind != CharKind::Whitespace {
- word_found = true;
- }
-
- found
- });
-
- Some(start..end)
-}
-
-fn sentence(
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- around: bool,
-) -> Option<Range<DisplayPoint>> {
- let mut start = None;
- let mut previous_end = relative_to;
-
- let mut chars = map.chars_at(relative_to).peekable();
-
- // Search backwards for the previous sentence end or current sentence start. Include the character under relative_to
- for (char, point) in chars
- .peek()
- .cloned()
- .into_iter()
- .chain(map.reverse_chars_at(relative_to))
- {
- if is_sentence_end(map, point) {
- break;
- }
-
- if is_possible_sentence_start(char) {
- start = Some(point);
- }
-
- previous_end = point;
- }
-
- // Search forward for the end of the current sentence or if we are between sentences, the start of the next one
- let mut end = relative_to;
- for (char, point) in chars {
- if start.is_none() && is_possible_sentence_start(char) {
- if around {
- start = Some(point);
- continue;
- } else {
- end = point;
- break;
- }
- }
-
- end = point;
- *end.column_mut() += char.len_utf8() as u32;
- end = map.clip_point(end, Bias::Left);
-
- if is_sentence_end(map, end) {
- break;
- }
- }
-
- let mut range = start.unwrap_or(previous_end)..end;
- if around {
- range = expand_to_include_whitespace(map, range, false);
- }
-
- Some(range)
-}
-
-fn is_possible_sentence_start(character: char) -> bool {
- !character.is_whitespace() && character != '.'
-}
-
-const SENTENCE_END_PUNCTUATION: &[char] = &['.', '!', '?'];
-const SENTENCE_END_FILLERS: &[char] = &[')', ']', '"', '\''];
-const SENTENCE_END_WHITESPACE: &[char] = &[' ', '\t', '\n'];
-fn is_sentence_end(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
- let mut next_chars = map.chars_at(point).peekable();
- if let Some((char, _)) = next_chars.next() {
- // We are at a double newline. This position is a sentence end.
- if char == '\n' && next_chars.peek().map(|(c, _)| c == &'\n').unwrap_or(false) {
- return true;
- }
-
- // The next text is not a valid whitespace. This is not a sentence end
- if !SENTENCE_END_WHITESPACE.contains(&char) {
- return false;
- }
- }
-
- for (char, _) in map.reverse_chars_at(point) {
- if SENTENCE_END_PUNCTUATION.contains(&char) {
- return true;
- }
-
- if !SENTENCE_END_FILLERS.contains(&char) {
- return false;
- }
- }
-
- return false;
-}
-
-/// Expands the passed range to include whitespace on one side or the other in a line. Attempts to add the
-/// whitespace to the end first and falls back to the start if there was none.
-fn expand_to_include_whitespace(
- map: &DisplaySnapshot,
- mut range: Range<DisplayPoint>,
- stop_at_newline: bool,
-) -> Range<DisplayPoint> {
- let mut whitespace_included = false;
-
- let mut chars = map.chars_at(range.end).peekable();
- while let Some((char, point)) = chars.next() {
- if char == '\n' && stop_at_newline {
- break;
- }
-
- if char.is_whitespace() {
- // Set end to the next display_point or the character position after the current display_point
- range.end = chars.peek().map(|(_, point)| *point).unwrap_or_else(|| {
- let mut end = point;
- *end.column_mut() += char.len_utf8() as u32;
- map.clip_point(end, Bias::Left)
- });
-
- if char != '\n' {
- whitespace_included = true;
- }
- } else {
- // Found non whitespace. Quit out.
- break;
- }
- }
-
- if !whitespace_included {
- for (char, point) in map.reverse_chars_at(range.start) {
- if char == '\n' && stop_at_newline {
- break;
- }
-
- if !char.is_whitespace() {
- break;
- }
-
- range.start = point;
- }
- }
-
- range
-}
-
-fn surrounding_markers(
- map: &DisplaySnapshot,
- relative_to: DisplayPoint,
- around: bool,
- search_across_lines: bool,
- open_marker: char,
- close_marker: char,
-) -> Option<Range<DisplayPoint>> {
- let point = relative_to.to_offset(map, Bias::Left);
-
- let mut matched_closes = 0;
- let mut opening = None;
-
- if let Some((ch, range)) = movement::chars_after(map, point).next() {
- if ch == open_marker {
- if open_marker == close_marker {
- let mut total = 0;
- for (ch, _) in movement::chars_before(map, point) {
- if ch == '\n' {
- break;
- }
- if ch == open_marker {
- total += 1;
- }
- }
- if total % 2 == 0 {
- opening = Some(range)
- }
- } else {
- opening = Some(range)
- }
- }
- }
-
- if opening.is_none() {
- for (ch, range) in movement::chars_before(map, point) {
- if ch == '\n' && !search_across_lines {
- break;
- }
-
- if ch == open_marker {
- if matched_closes == 0 {
- opening = Some(range);
- break;
- }
- matched_closes -= 1;
- } else if ch == close_marker {
- matched_closes += 1
- }
- }
- }
-
- if opening.is_none() {
- for (ch, range) in movement::chars_after(map, point) {
- if ch == open_marker {
- opening = Some(range);
- break;
- } else if ch == close_marker {
- break;
- }
- }
- }
-
- let Some(mut opening) = opening else {
- return None;
- };
-
- let mut matched_opens = 0;
- let mut closing = None;
-
- for (ch, range) in movement::chars_after(map, opening.end) {
- if ch == '\n' && !search_across_lines {
- break;
- }
-
- if ch == close_marker {
- if matched_opens == 0 {
- closing = Some(range);
- break;
- }
- matched_opens -= 1;
- } else if ch == open_marker {
- matched_opens += 1;
- }
- }
-
- let Some(mut closing) = closing else {
- return None;
- };
-
- if around && !search_across_lines {
- let mut found = false;
-
- for (ch, range) in movement::chars_after(map, closing.end) {
- if ch.is_whitespace() && ch != '\n' {
- found = true;
- closing.end = range.end;
- } else {
- break;
- }
- }
-
- if !found {
- for (ch, range) in movement::chars_before(map, opening.start) {
- if ch.is_whitespace() && ch != '\n' {
- opening.start = range.start
- } else {
- break;
- }
- }
- }
- }
-
- if !around && search_across_lines {
- if let Some((ch, range)) = movement::chars_after(map, opening.end).next() {
- if ch == '\n' {
- opening.end = range.end
- }
- }
-
- for (ch, range) in movement::chars_before(map, closing.start) {
- if !ch.is_whitespace() {
- break;
- }
- if ch != '\n' {
- closing.start = range.start
- }
- }
- }
-
- let result = if around {
- opening.start..closing.end
- } else {
- opening.end..closing.start
- };
-
- Some(
- map.clip_point(result.start.to_display_point(map), Bias::Left)
- ..map.clip_point(result.end.to_display_point(map), Bias::Right),
- )
-}
-
-#[cfg(test)]
-mod test {
- use indoc::indoc;
-
- use crate::{
- state::Mode,
- test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
- };
-
- const WORD_LOCATIONS: &'static str = indoc! {"
- The quick ˇbrowˇnˇ•••
- fox ˇjuˇmpsˇ over
- the lazy dogˇ••
- ˇ
- ˇ
- ˇ
- Thˇeˇ-ˇquˇickˇ ˇbrownˇ•
- ˇ••
- ˇ••
- ˇ fox-jumpˇs over
- the lazy dogˇ•
- ˇ
- "
- };
-
- #[gpui::test]
- async fn test_change_word_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.assert_binding_matches_all(["c", "i", "w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all(["c", "i", "shift-w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all(["c", "a", "w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all(["c", "a", "shift-w"], WORD_LOCATIONS)
- .await;
- }
-
- #[gpui::test]
- async fn test_delete_word_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.assert_binding_matches_all(["d", "i", "w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all(["d", "i", "shift-w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all(["d", "a", "w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all(["d", "a", "shift-w"], WORD_LOCATIONS)
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- /*
- cx.set_shared_state("The quick ˇbrown\nfox").await;
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state("The quick «bˇ»rown\nfox").await;
- cx.simulate_shared_keystrokes(["i", "w"]).await;
- cx.assert_shared_state("The quick «brownˇ»\nfox").await;
- */
- cx.set_shared_state("The quick brown\nˇ\nfox").await;
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state("The quick brown\n«\nˇ»fox").await;
- cx.simulate_shared_keystrokes(["i", "w"]).await;
- cx.assert_shared_state("The quick brown\n«\nˇ»fox").await;
-
- cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
- .await;
- cx.assert_binding_matches_all_exempted(
- ["v", "h", "i", "w"],
- WORD_LOCATIONS,
- ExemptionFeatures::NonEmptyVisualTextObjects,
- )
- .await;
- cx.assert_binding_matches_all_exempted(
- ["v", "l", "i", "w"],
- WORD_LOCATIONS,
- ExemptionFeatures::NonEmptyVisualTextObjects,
- )
- .await;
- cx.assert_binding_matches_all(["v", "i", "shift-w"], WORD_LOCATIONS)
- .await;
-
- cx.assert_binding_matches_all_exempted(
- ["v", "i", "h", "shift-w"],
- WORD_LOCATIONS,
- ExemptionFeatures::NonEmptyVisualTextObjects,
- )
- .await;
- cx.assert_binding_matches_all_exempted(
- ["v", "i", "l", "shift-w"],
- WORD_LOCATIONS,
- ExemptionFeatures::NonEmptyVisualTextObjects,
- )
- .await;
-
- cx.assert_binding_matches_all_exempted(
- ["v", "a", "w"],
- WORD_LOCATIONS,
- ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
- )
- .await;
- cx.assert_binding_matches_all_exempted(
- ["v", "a", "shift-w"],
- WORD_LOCATIONS,
- ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
- )
- .await;
- }
-
- const SENTENCE_EXAMPLES: &[&'static str] = &[
- "ˇThe quick ˇbrownˇ?ˇ ˇFox Jˇumpsˇ!ˇ Ovˇer theˇ lazyˇ.",
- indoc! {"
- ˇThe quick ˇbrownˇ
- fox jumps over
- the lazy doˇgˇ.ˇ ˇThe quick ˇ
- brown fox jumps over
- "},
- indoc! {"
- The quick brown fox jumps.
- Over the lazy dog
- ˇ
- ˇ
- ˇ fox-jumpˇs over
- the lazy dog.ˇ
- ˇ
- "},
- r#"ˇThe ˇquick brownˇ.)ˇ]ˇ'ˇ" Brown ˇfox jumpsˇ.ˇ "#,
- ];
-
- #[gpui::test]
- async fn test_change_sentence_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["c", "i", "s"]);
- cx.add_initial_state_exemptions(
- "The quick brown fox jumps.\nOver the lazy dog\nˇ\nˇ\n fox-jumps over\nthe lazy dog.\n\n",
- ExemptionFeatures::SentenceOnEmptyLines);
- cx.add_initial_state_exemptions(
- "The quick brown fox jumps.\nOver the lazy dog\n\n\nˇ foxˇ-ˇjumpˇs over\nthe lazy dog.\n\n",
- ExemptionFeatures::SentenceAtStartOfLineWithWhitespace);
- cx.add_initial_state_exemptions(
- "The quick brown fox jumps.\nOver the lazy dog\n\n\n fox-jumps over\nthe lazy dog.ˇ\nˇ\n",
- ExemptionFeatures::SentenceAfterPunctuationAtEndOfFile);
- for sentence_example in SENTENCE_EXAMPLES {
- cx.assert_all(sentence_example).await;
- }
-
- let mut cx = cx.binding(["c", "a", "s"]);
- cx.add_initial_state_exemptions(
- "The quick brown?ˇ Fox Jumps! Over the lazy.",
- ExemptionFeatures::IncorrectLandingPosition,
- );
- cx.add_initial_state_exemptions(
- "The quick brown.)]\'\" Brown fox jumps.ˇ ",
- ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
- );
-
- for sentence_example in SENTENCE_EXAMPLES {
- cx.assert_all(sentence_example).await;
- }
- }
-
- #[gpui::test]
- async fn test_delete_sentence_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["d", "i", "s"]);
- cx.add_initial_state_exemptions(
- "The quick brown fox jumps.\nOver the lazy dog\nˇ\nˇ\n fox-jumps over\nthe lazy dog.\n\n",
- ExemptionFeatures::SentenceOnEmptyLines);
- cx.add_initial_state_exemptions(
- "The quick brown fox jumps.\nOver the lazy dog\n\n\nˇ foxˇ-ˇjumpˇs over\nthe lazy dog.\n\n",
- ExemptionFeatures::SentenceAtStartOfLineWithWhitespace);
- cx.add_initial_state_exemptions(
- "The quick brown fox jumps.\nOver the lazy dog\n\n\n fox-jumps over\nthe lazy dog.ˇ\nˇ\n",
- ExemptionFeatures::SentenceAfterPunctuationAtEndOfFile);
-
- for sentence_example in SENTENCE_EXAMPLES {
- cx.assert_all(sentence_example).await;
- }
-
- let mut cx = cx.binding(["d", "a", "s"]);
- cx.add_initial_state_exemptions(
- "The quick brown?ˇ Fox Jumps! Over the lazy.",
- ExemptionFeatures::IncorrectLandingPosition,
- );
- cx.add_initial_state_exemptions(
- "The quick brown.)]\'\" Brown fox jumps.ˇ ",
- ExemptionFeatures::AroundObjectLeavesWhitespaceAtEndOfLine,
- );
-
- for sentence_example in SENTENCE_EXAMPLES {
- cx.assert_all(sentence_example).await;
- }
- }
-
- #[gpui::test]
- async fn test_visual_sentence_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx)
- .await
- .binding(["v", "i", "s"]);
- for sentence_example in SENTENCE_EXAMPLES {
- cx.assert_all_exempted(sentence_example, ExemptionFeatures::SentenceOnEmptyLines)
- .await;
- }
-
- let mut cx = cx.binding(["v", "a", "s"]);
- for sentence_example in SENTENCE_EXAMPLES {
- cx.assert_all_exempted(
- sentence_example,
- ExemptionFeatures::AroundSentenceStartingBetweenIncludesWrongWhitespace,
- )
- .await;
- }
- }
-
- // Test string with "`" for opening surrounders and "'" for closing surrounders
- const SURROUNDING_MARKER_STRING: &str = indoc! {"
- ˇTh'ˇe ˇ`ˇ'ˇquˇi`ˇck broˇ'wn`
- 'ˇfox juˇmps ovˇ`ˇer
- the ˇlazy dˇ'ˇoˇ`ˇg"};
-
- const SURROUNDING_OBJECTS: &[(char, char)] = &[
- ('\'', '\''), // Quote
- ('`', '`'), // Back Quote
- ('"', '"'), // Double Quote
- ('(', ')'), // Parentheses
- ('[', ']'), // SquareBrackets
- ('{', '}'), // CurlyBrackets
- ('<', '>'), // AngleBrackets
- ];
-
- #[gpui::test]
- async fn test_change_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for (start, end) in SURROUNDING_OBJECTS {
- let marked_string = SURROUNDING_MARKER_STRING
- .replace('`', &start.to_string())
- .replace('\'', &end.to_string());
-
- cx.assert_binding_matches_all(["c", "i", &start.to_string()], &marked_string)
- .await;
- cx.assert_binding_matches_all(["c", "i", &end.to_string()], &marked_string)
- .await;
- cx.assert_binding_matches_all(["c", "a", &start.to_string()], &marked_string)
- .await;
- cx.assert_binding_matches_all(["c", "a", &end.to_string()], &marked_string)
- .await;
- }
- }
- #[gpui::test]
- async fn test_singleline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.set_shared_wrap(12).await;
-
- cx.set_shared_state(indoc! {
- "helˇlo \"world\"!"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
- cx.assert_shared_state(indoc! {
- "hello \"«worldˇ»\"!"
- })
- .await;
-
- cx.set_shared_state(indoc! {
- "hello \"wˇorld\"!"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
- cx.assert_shared_state(indoc! {
- "hello \"«worldˇ»\"!"
- })
- .await;
-
- cx.set_shared_state(indoc! {
- "hello \"wˇorld\"!"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
- cx.assert_shared_state(indoc! {
- "hello« \"world\"ˇ»!"
- })
- .await;
-
- cx.set_shared_state(indoc! {
- "hello \"wˇorld\" !"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
- cx.assert_shared_state(indoc! {
- "hello «\"world\" ˇ»!"
- })
- .await;
-
- cx.set_shared_state(indoc! {
- "hello \"wˇorld\"•
- goodbye"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
- cx.assert_shared_state(indoc! {
- "hello «\"world\" ˇ»
- goodbye"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "func empty(a string) bool {
- if a == \"\" {
- return true
- }
- ˇreturn false
- }"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
- cx.assert_shared_state(indoc! {"
- func empty(a string) bool {
- « if a == \"\" {
- return true
- }
- return false
- ˇ»}"})
- .await;
- cx.set_shared_state(indoc! {
- "func empty(a string) bool {
- if a == \"\" {
- ˇreturn true
- }
- return false
- }"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
- cx.assert_shared_state(indoc! {"
- func empty(a string) bool {
- if a == \"\" {
- « return true
- ˇ» }
- return false
- }"})
- .await;
-
- cx.set_shared_state(indoc! {
- "func empty(a string) bool {
- if a == \"\" ˇ{
- return true
- }
- return false
- }"
- })
- .await;
- cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
- cx.assert_shared_state(indoc! {"
- func empty(a string) bool {
- if a == \"\" {
- « return true
- ˇ» }
- return false
- }"})
- .await;
- }
-
- #[gpui::test]
- async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
- cx.set_state(
- indoc! {"
- fn boop() {
- baz(ˇ|a, b| { bar(|j, k| { })})
- }"
- },
- Mode::Normal,
- );
- cx.simulate_keystrokes(["c", "i", "|"]);
- cx.assert_state(
- indoc! {"
- fn boop() {
- baz(|ˇ| { bar(|j, k| { })})
- }"
- },
- Mode::Insert,
- );
- cx.simulate_keystrokes(["escape", "1", "8", "|"]);
- cx.assert_state(
- indoc! {"
- fn boop() {
- baz(|| { bar(ˇ|j, k| { })})
- }"
- },
- Mode::Normal,
- );
-
- cx.simulate_keystrokes(["v", "a", "|"]);
- cx.assert_state(
- indoc! {"
- fn boop() {
- baz(|| { bar(«|j, k| ˇ»{ })})
- }"
- },
- Mode::Visual,
- );
- }
-
- #[gpui::test]
- async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- for (start, end) in SURROUNDING_OBJECTS {
- let marked_string = SURROUNDING_MARKER_STRING
- .replace('`', &start.to_string())
- .replace('\'', &end.to_string());
-
- cx.assert_binding_matches_all(["d", "i", &start.to_string()], &marked_string)
- .await;
- cx.assert_binding_matches_all(["d", "i", &end.to_string()], &marked_string)
- .await;
- cx.assert_binding_matches_all(["d", "a", &start.to_string()], &marked_string)
- .await;
- cx.assert_binding_matches_all(["d", "a", &end.to_string()], &marked_string)
- .await;
- }
- }
-}
@@ -1,234 +0,0 @@
-use std::{ops::Range, sync::Arc};
-
-use gpui::{Action, KeyContext};
-use language::CursorShape;
-use serde::{Deserialize, Serialize};
-use workspace::searchable::Direction;
-
-use crate::motion::Motion;
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub enum Mode {
- Normal,
- Insert,
- Visual,
- VisualLine,
- VisualBlock,
-}
-
-impl Mode {
- pub fn is_visual(&self) -> bool {
- match self {
- Mode::Normal | Mode::Insert => false,
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
- }
- }
-}
-
-impl Default for Mode {
- fn default() -> Self {
- Self::Normal
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
-pub enum Operator {
- Change,
- Delete,
- Yank,
- Replace,
- Object { around: bool },
- FindForward { before: bool },
- FindBackward { after: bool },
-}
-
-#[derive(Default, Clone)]
-pub struct EditorState {
- pub mode: Mode,
- pub last_mode: Mode,
-
- /// pre_count is the number before an operator is specified (3 in 3d2d)
- pub pre_count: Option<usize>,
- /// post_count is the number after an operator is specified (2 in 3d2d)
- pub post_count: Option<usize>,
-
- pub operator_stack: Vec<Operator>,
-}
-
-#[derive(Default, Clone, Debug)]
-pub enum RecordedSelection {
- #[default]
- None,
- Visual {
- rows: u32,
- cols: u32,
- },
- SingleLine {
- cols: u32,
- },
- VisualBlock {
- rows: u32,
- cols: u32,
- },
- VisualLine {
- rows: u32,
- },
-}
-
-#[derive(Default, Clone)]
-pub struct WorkspaceState {
- pub search: SearchState,
- pub last_find: Option<Motion>,
-
- pub recording: bool,
- pub stop_recording_after_next_action: bool,
- pub replaying: bool,
- pub recorded_count: Option<usize>,
- pub recorded_actions: Vec<ReplayableAction>,
- pub recorded_selection: RecordedSelection,
-}
-
-#[derive(Debug)]
-pub enum ReplayableAction {
- Action(Box<dyn Action>),
- Insertion {
- text: Arc<str>,
- utf16_range_to_replace: Option<Range<isize>>,
- },
-}
-
-impl Clone for ReplayableAction {
- fn clone(&self) -> Self {
- match self {
- Self::Action(action) => Self::Action(action.boxed_clone()),
- Self::Insertion {
- text,
- utf16_range_to_replace,
- } => Self::Insertion {
- text: text.clone(),
- utf16_range_to_replace: utf16_range_to_replace.clone(),
- },
- }
- }
-}
-
-#[derive(Clone)]
-pub struct SearchState {
- pub direction: Direction,
- pub count: usize,
- pub initial_query: String,
-}
-
-impl Default for SearchState {
- fn default() -> Self {
- Self {
- direction: Direction::Next,
- count: 1,
- initial_query: "".to_string(),
- }
- }
-}
-
-impl EditorState {
- pub fn cursor_shape(&self) -> CursorShape {
- match self.mode {
- Mode::Normal => {
- if self.operator_stack.is_empty() {
- CursorShape::Block
- } else {
- CursorShape::Underscore
- }
- }
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
- Mode::Insert => CursorShape::Bar,
- }
- }
-
- pub fn vim_controlled(&self) -> bool {
- !matches!(self.mode, Mode::Insert)
- || matches!(
- self.operator_stack.last(),
- Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
- )
- }
-
- pub fn should_autoindent(&self) -> bool {
- !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
- }
-
- pub fn clip_at_line_ends(&self) -> bool {
- match self.mode {
- Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false,
- Mode::Normal => true,
- }
- }
-
- pub fn active_operator(&self) -> Option<Operator> {
- self.operator_stack.last().copied()
- }
-
- pub fn keymap_context_layer(&self) -> KeyContext {
- let mut context = KeyContext::default();
- context.add("VimEnabled");
- context.set(
- "vim_mode",
- match self.mode {
- Mode::Normal => "normal",
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
- Mode::Insert => "insert",
- },
- );
-
- if self.vim_controlled() {
- context.add("VimControl");
- }
-
- if self.active_operator().is_none() && self.pre_count.is_some()
- || self.active_operator().is_some() && self.post_count.is_some()
- {
- context.add("VimCount");
- }
-
- let active_operator = self.active_operator();
-
- if let Some(active_operator) = active_operator {
- for context_flag in active_operator.context_flags().into_iter() {
- context.add(*context_flag);
- }
- }
-
- context.set(
- "vim_operator",
- active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
- );
-
- context
- }
-}
-
-impl Operator {
- pub fn id(&self) -> &'static str {
- match self {
- Operator::Object { around: false } => "i",
- Operator::Object { around: true } => "a",
- Operator::Change => "c",
- Operator::Delete => "d",
- Operator::Yank => "y",
- Operator::Replace => "r",
- Operator::FindForward { before: false } => "f",
- Operator::FindForward { before: true } => "t",
- Operator::FindBackward { after: false } => "F",
- Operator::FindBackward { after: true } => "T",
- }
- }
-
- pub fn context_flags(&self) -> &'static [&'static str] {
- match self {
- Operator::Object { .. } => &["VimObject"],
- Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
- &["VimWaiting"]
- }
- _ => &[],
- }
- }
-}
@@ -1,752 +0,0 @@
-mod neovim_backed_binding_test_context;
-mod neovim_backed_test_context;
-mod neovim_connection;
-mod vim_test_context;
-
-use command_palette::CommandPalette;
-use editor::DisplayPoint;
-pub use neovim_backed_binding_test_context::*;
-pub use neovim_backed_test_context::*;
-pub use vim_test_context::*;
-
-use indoc::indoc;
-use search::BufferSearchBar;
-
-use crate::{state::Mode, ModeIndicator};
-
-#[gpui::test]
-async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, false).await;
- cx.simulate_keystrokes(["h", "j", "k", "l"]);
- cx.assert_editor_state("hjklˇ");
-}
-
-#[gpui::test]
-async fn test_neovim(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.simulate_shared_keystroke("i").await;
- cx.assert_state_matches().await;
- cx.simulate_shared_keystrokes([
- "shift-T", "e", "s", "t", " ", "t", "e", "s", "t", "escape", "0", "d", "w",
- ])
- .await;
- cx.assert_state_matches().await;
- cx.assert_editor_state("ˇtest");
-}
-
-#[gpui::test]
-async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.simulate_keystroke("i");
- assert_eq!(cx.mode(), Mode::Insert);
-
- // Editor acts as though vim is disabled
- cx.disable_vim();
- cx.simulate_keystrokes(["h", "j", "k", "l"]);
- cx.assert_editor_state("hjklˇ");
-
- // Selections aren't changed if editor is blurred but vim-mode is still disabled.
- cx.set_state("«hjklˇ»", Mode::Normal);
- cx.assert_editor_state("«hjklˇ»");
- cx.update_editor(|_, cx| cx.blur());
- cx.assert_editor_state("«hjklˇ»");
- cx.update_editor(|_, cx| cx.focus_self());
- cx.assert_editor_state("«hjklˇ»");
-
- // Enabling dynamically sets vim mode again and restores normal mode
- cx.enable_vim();
- assert_eq!(cx.mode(), Mode::Normal);
- cx.simulate_keystrokes(["h", "h", "h", "l"]);
- assert_eq!(cx.buffer_text(), "hjkl".to_owned());
- cx.assert_editor_state("hˇjkl");
- cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
- cx.assert_editor_state("hTestˇjkl");
-
- // Disabling and enabling resets to normal mode
- assert_eq!(cx.mode(), Mode::Insert);
- cx.disable_vim();
- cx.enable_vim();
- assert_eq!(cx.mode(), Mode::Normal);
-}
-
-#[gpui::test]
-async fn test_buffer_search(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state(
- indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"},
- Mode::Normal,
- );
- cx.simulate_keystroke("/");
-
- let search_bar = cx.workspace(|workspace, cx| {
- workspace
- .active_pane()
- .read(cx)
- .toolbar()
- .read(cx)
- .item_of_type::<BufferSearchBar>()
- .expect("Buffer search bar should be deployed")
- });
-
- cx.update_view(search_bar, |bar, cx| {
- assert_eq!(bar.query(cx), "");
- })
-}
-
-#[gpui::test]
-async fn test_count_down(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state(indoc! {"aˇa\nbb\ncc\ndd\nee"}, Mode::Normal);
- cx.simulate_keystrokes(["2", "down"]);
- cx.assert_editor_state("aa\nbb\ncˇc\ndd\nee");
- cx.simulate_keystrokes(["9", "down"]);
- cx.assert_editor_state("aa\nbb\ncc\ndd\neˇe");
-}
-
-#[gpui::test]
-async fn test_end_of_document_710(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- // goes to end by default
- cx.set_state(indoc! {"aˇa\nbb\ncc"}, Mode::Normal);
- cx.simulate_keystrokes(["shift-g"]);
- cx.assert_editor_state("aa\nbb\ncˇc");
-
- // can go to line 1 (https://github.com/zed-industries/community/issues/710)
- cx.simulate_keystrokes(["1", "shift-g"]);
- cx.assert_editor_state("aˇa\nbb\ncc");
-}
-
-#[gpui::test]
-async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- // works in normal mode
- cx.set_state(indoc! {"aa\nbˇb\ncc"}, Mode::Normal);
- cx.simulate_keystrokes([">", ">"]);
- cx.assert_editor_state("aa\n bˇb\ncc");
- cx.simulate_keystrokes(["<", "<"]);
- cx.assert_editor_state("aa\nbˇb\ncc");
-
- // works in visuial mode
- cx.simulate_keystrokes(["shift-v", "down", ">"]);
- cx.assert_editor_state("aa\n b«b\n ccˇ»");
-}
-
-#[gpui::test]
-async fn test_escape_command_palette(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state("aˇbc\n", Mode::Normal);
- cx.simulate_keystrokes(["i", "cmd-shift-p"]);
-
- assert!(cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
- cx.simulate_keystroke("escape");
- cx.run_until_parked();
- assert!(!cx.workspace(|workspace, cx| workspace.active_modal::<CommandPalette>(cx).is_some()));
- cx.assert_state("aˇbc\n", Mode::Insert);
-}
-
-#[gpui::test]
-async fn test_escape_cancels(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state("aˇbˇc", Mode::Normal);
- cx.simulate_keystrokes(["escape"]);
-
- cx.assert_state("aˇbc", Mode::Normal);
-}
-
-#[gpui::test]
-async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state(indoc! {"aa\nbˇb\ncc\ncc\ncc\n"}, Mode::Normal);
- cx.simulate_keystrokes(["/", "c", "c"]);
-
- let search_bar = cx.workspace(|workspace, cx| {
- workspace
- .active_pane()
- .read(cx)
- .toolbar()
- .read(cx)
- .item_of_type::<BufferSearchBar>()
- .expect("Buffer search bar should be deployed")
- });
-
- cx.update_view(search_bar, |bar, cx| {
- assert_eq!(bar.query(cx), "cc");
- });
-
- cx.update_editor(|editor, cx| {
- let highlights = editor.all_text_background_highlights(cx);
- assert_eq!(3, highlights.len());
- assert_eq!(
- DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
- highlights[0].0
- )
- });
- cx.simulate_keystrokes(["enter"]);
-
- cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
- cx.simulate_keystrokes(["n"]);
- cx.assert_state(indoc! {"aa\nbb\ncc\nˇcc\ncc\n"}, Mode::Normal);
- cx.simulate_keystrokes(["shift-n"]);
- cx.assert_state(indoc! {"aa\nbb\nˇcc\ncc\ncc\n"}, Mode::Normal);
-}
-
-#[gpui::test]
-async fn test_status_indicator(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- let mode_indicator = cx.workspace(|workspace, cx| {
- let status_bar = workspace.status_bar().read(cx);
- let mode_indicator = status_bar.item_of_type::<ModeIndicator>();
- assert!(mode_indicator.is_some());
- mode_indicator.unwrap()
- });
-
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Normal)
- );
-
- // shows the correct mode
- cx.simulate_keystrokes(["i"]);
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Insert)
- );
-
- // shows even in search
- cx.simulate_keystrokes(["escape", "v", "/"]);
- assert_eq!(
- cx.workspace(|_, cx| mode_indicator.read(cx).mode),
- Some(Mode::Visual)
- );
-
- // hides if vim mode is disabled
- cx.disable_vim();
- cx.run_until_parked();
- cx.workspace(|workspace, cx| {
- let status_bar = workspace.status_bar().read(cx);
- let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
- assert!(mode_indicator.read(cx).mode.is_none());
- });
-
- cx.enable_vim();
- cx.run_until_parked();
- cx.workspace(|workspace, cx| {
- let status_bar = workspace.status_bar().read(cx);
- let mode_indicator = status_bar.item_of_type::<ModeIndicator>().unwrap();
- assert!(mode_indicator.read(cx).mode.is_some());
- });
-}
-
-#[gpui::test]
-async fn test_word_characters(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new_typescript(cx).await;
- cx.set_state(
- indoc! { "
- class A {
- #ˇgoop = 99;
- $ˇgoop () { return this.#gˇoop };
- };
- console.log(new A().$gooˇp())
- "},
- Mode::Normal,
- );
- cx.simulate_keystrokes(["v", "i", "w"]);
- cx.assert_state(
- indoc! {"
- class A {
- «#goopˇ» = 99;
- «$goopˇ» () { return this.«#goopˇ» };
- };
- console.log(new A().«$goopˇ»())
- "},
- Mode::Visual,
- )
-}
-
-#[gpui::test]
-async fn test_join_lines(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ˇone
- two
- three
- four
- five
- six
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-j"]).await;
- cx.assert_shared_state(indoc! {"
- oneˇ two
- three
- four
- five
- six
- "})
- .await;
- cx.simulate_shared_keystrokes(["3", "shift-j"]).await;
- cx.assert_shared_state(indoc! {"
- one two threeˇ four
- five
- six
- "})
- .await;
-
- cx.set_shared_state(indoc! {"
- ˇone
- two
- three
- four
- five
- six
- "})
- .await;
- cx.simulate_shared_keystrokes(["j", "v", "3", "j", "shift-j"])
- .await;
- cx.assert_shared_state(indoc! {"
- one
- two three fourˇ five
- six
- "})
- .await;
-}
-
-#[gpui::test]
-async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_wrap(12).await;
- // tests line wrap as follows:
- // 1: twelve char
- // twelve char
- // 2: twelve char
- cx.set_shared_state(indoc! { "
- tˇwelve char twelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["j"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char twelve char
- tˇwelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["k"]).await;
- cx.assert_shared_state(indoc! { "
- tˇwelve char twelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["g", "j"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char tˇwelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["g", "j"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char twelve char
- tˇwelve char
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["g", "k"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char tˇwelve char
- twelve char
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["g", "^"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char ˇtwelve char
- twelve char
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["^"]).await;
- cx.assert_shared_state(indoc! { "
- ˇtwelve char twelve char
- twelve char
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["g", "$"]).await;
- cx.assert_shared_state(indoc! { "
- twelve charˇ twelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["$"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char twelve chaˇr
- twelve char
- "})
- .await;
-
- cx.set_shared_state(indoc! { "
- tˇwelve char twelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["enter"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char twelve char
- ˇtwelve char
- "})
- .await;
-
- cx.set_shared_state(indoc! { "
- twelve char
- tˇwelve char twelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char
- twelve char twelve char
- ˇo
- twelve char
- "})
- .await;
-
- cx.set_shared_state(indoc! { "
- twelve char
- tˇwelve char twelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
- .await;
- cx.assert_shared_state(indoc! { "
- twelve char
- twelve char twelve charˇa
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
- .await;
- cx.assert_shared_state(indoc! { "
- twelve char
- ˇitwelve char twelve chara
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-d"]).await;
- cx.assert_shared_state(indoc! { "
- twelve char
- ˇ
- twelve char
- "})
- .await;
-
- cx.set_shared_state(indoc! { "
- twelve char
- twelve char tˇwelve char
- twelve char
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
- .await;
- cx.assert_shared_state(indoc! { "
- twelve char
- ˇo
- twelve char twelve char
- twelve char
- "})
- .await;
-
- // line wraps as:
- // fourteen ch
- // ar
- // fourteen ch
- // ar
- cx.set_shared_state(indoc! { "
- fourteen chaˇr
- fourteen char
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["d", "i", "w"]).await;
- cx.assert_shared_state(indoc! {"
- fourteenˇ•
- fourteen char
- "})
- .await;
- cx.simulate_shared_keystrokes(["j", "shift-f", "e", "f", "r"])
- .await;
- cx.assert_shared_state(indoc! {"
- fourteen•
- fourteen chaˇr
- "})
- .await;
-}
-
-#[gpui::test]
-async fn test_folds(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.set_neovim_option("foldmethod=manual").await;
-
- cx.set_shared_state(indoc! { "
- fn boop() {
- ˇbarp()
- bazp()
- }
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
- .await;
-
- // visual display is now:
- // fn boop () {
- // [FOLDED]
- // }
-
- // TODO: this should not be needed but currently zf does not
- // return to normal mode.
- cx.simulate_shared_keystrokes(["escape"]).await;
-
- // skip over fold downward
- cx.simulate_shared_keystrokes(["g", "g"]).await;
- cx.assert_shared_state(indoc! { "
- ˇfn boop() {
- barp()
- bazp()
- }
- "})
- .await;
-
- cx.simulate_shared_keystrokes(["j", "j"]).await;
- cx.assert_shared_state(indoc! { "
- fn boop() {
- barp()
- bazp()
- ˇ}
- "})
- .await;
-
- // skip over fold upward
- cx.simulate_shared_keystrokes(["2", "k"]).await;
- cx.assert_shared_state(indoc! { "
- ˇfn boop() {
- barp()
- bazp()
- }
- "})
- .await;
-
- // yank the fold
- cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
- cx.assert_shared_clipboard(" barp()\n bazp()\n").await;
-
- // re-open
- cx.simulate_shared_keystrokes(["z", "o"]).await;
- cx.assert_shared_state(indoc! { "
- fn boop() {
- ˇ barp()
- bazp()
- }
- "})
- .await;
-}
-
-#[gpui::test]
-async fn test_folds_panic(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.set_neovim_option("foldmethod=manual").await;
-
- cx.set_shared_state(indoc! { "
- fn boop() {
- ˇbarp()
- bazp()
- }
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
- .await;
- cx.simulate_shared_keystrokes(["escape"]).await;
- cx.simulate_shared_keystrokes(["g", "g"]).await;
- cx.simulate_shared_keystrokes(["5", "d", "j"]).await;
- cx.assert_shared_state(indoc! { "ˇ"}).await;
-}
-
-#[gpui::test]
-async fn test_clear_counts(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- The quick brown
- fox juˇmps over
- the lazy dog"})
- .await;
-
- cx.simulate_shared_keystrokes(["4", "escape", "3", "d", "l"])
- .await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox juˇ over
- the lazy dog"})
- .await;
-}
-
-#[gpui::test]
-async fn test_zero(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- The quˇick brown
- fox jumps over
- the lazy dog"})
- .await;
-
- cx.simulate_shared_keystrokes(["0"]).await;
- cx.assert_shared_state(indoc! {"
- ˇThe quick brown
- fox jumps over
- the lazy dog"})
- .await;
-
- cx.simulate_shared_keystrokes(["1", "0", "l"]).await;
- cx.assert_shared_state(indoc! {"
- The quick ˇbrown
- fox jumps over
- the lazy dog"})
- .await;
-}
-
-#[gpui::test]
-async fn test_selection_goal(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- ;;ˇ;
- Lorem Ipsum"})
- .await;
-
- cx.simulate_shared_keystrokes(["a", "down", "up", ";", "down", "up"])
- .await;
- cx.assert_shared_state(indoc! {"
- ;;;;ˇ
- Lorem Ipsum"})
- .await;
-}
-
-#[gpui::test]
-async fn test_wrapped_motions(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_wrap(12).await;
-
- cx.set_shared_state(indoc! {"
- aaˇaa
- 😃😃"
- })
- .await;
- cx.simulate_shared_keystrokes(["j"]).await;
- cx.assert_shared_state(indoc! {"
- aaaa
- 😃ˇ😃"
- })
- .await;
-
- cx.set_shared_state(indoc! {"
- 123456789012aaˇaa
- 123456789012😃😃"
- })
- .await;
- cx.simulate_shared_keystrokes(["j"]).await;
- cx.assert_shared_state(indoc! {"
- 123456789012aaaa
- 123456789012😃ˇ😃"
- })
- .await;
-
- cx.set_shared_state(indoc! {"
- 123456789012aaˇaa
- 123456789012😃😃"
- })
- .await;
- cx.simulate_shared_keystrokes(["j"]).await;
- cx.assert_shared_state(indoc! {"
- 123456789012aaaa
- 123456789012😃ˇ😃"
- })
- .await;
-
- cx.set_shared_state(indoc! {"
- 123456789012aaaaˇaaaaaaaa123456789012
- wow
- 123456789012😃😃😃😃😃😃123456789012"
- })
- .await;
- cx.simulate_shared_keystrokes(["j", "j"]).await;
- cx.assert_shared_state(indoc! {"
- 123456789012aaaaaaaaaaaa123456789012
- wow
- 123456789012😃😃ˇ😃😃😃😃123456789012"
- })
- .await;
-}
-
-#[gpui::test]
-async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- one
- ˇ
- two"})
- .await;
-
- cx.simulate_shared_keystrokes(["}", "}"]).await;
- cx.assert_shared_state(indoc! {"
- one
-
- twˇo"})
- .await;
-
- cx.simulate_shared_keystrokes(["{", "{", "{"]).await;
- cx.assert_shared_state(indoc! {"
- ˇone
-
- two"})
- .await;
-}
-
-#[gpui::test]
-async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state(
- indoc! {"
- defmodule Test do
- def test(a, ˇ[_, _] = b), do: IO.puts('hi')
- end
- "},
- Mode::Normal,
- );
- cx.simulate_keystrokes(["g", "a"]);
- cx.assert_state(
- indoc! {"
- defmodule Test do
- def test(a, «[ˇ»_, _] = b), do: IO.puts('hi')
- end
- "},
- Mode::Visual,
- );
-}
@@ -1,95 +0,0 @@
-use std::ops::{Deref, DerefMut};
-
-use crate::state::Mode;
-
-use super::{ExemptionFeatures, NeovimBackedTestContext, SUPPORTED_FEATURES};
-
-pub struct NeovimBackedBindingTestContext<'a, const COUNT: usize> {
- cx: NeovimBackedTestContext<'a>,
- keystrokes_under_test: [&'static str; COUNT],
-}
-
-impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> {
- pub fn new(
- keystrokes_under_test: [&'static str; COUNT],
- cx: NeovimBackedTestContext<'a>,
- ) -> Self {
- Self {
- cx,
- keystrokes_under_test,
- }
- }
-
- pub fn consume(self) -> NeovimBackedTestContext<'a> {
- self.cx
- }
-
- pub fn binding<const NEW_COUNT: usize>(
- self,
- keystrokes: [&'static str; NEW_COUNT],
- ) -> NeovimBackedBindingTestContext<'a, NEW_COUNT> {
- self.consume().binding(keystrokes)
- }
-
- pub async fn assert(&mut self, marked_positions: &str) {
- self.cx
- .assert_binding_matches(self.keystrokes_under_test, marked_positions)
- .await;
- }
-
- pub async fn assert_exempted(&mut self, marked_positions: &str, feature: ExemptionFeatures) {
- if SUPPORTED_FEATURES.contains(&feature) {
- self.cx
- .assert_binding_matches(self.keystrokes_under_test, marked_positions)
- .await
- }
- }
-
- pub fn assert_manual(
- &mut self,
- initial_state: &str,
- mode_before: Mode,
- state_after: &str,
- mode_after: Mode,
- ) {
- self.cx.assert_binding(
- self.keystrokes_under_test,
- initial_state,
- mode_before,
- state_after,
- mode_after,
- );
- }
-
- pub async fn assert_all(&mut self, marked_positions: &str) {
- self.cx
- .assert_binding_matches_all(self.keystrokes_under_test, marked_positions)
- .await
- }
-
- pub async fn assert_all_exempted(
- &mut self,
- marked_positions: &str,
- feature: ExemptionFeatures,
- ) {
- if SUPPORTED_FEATURES.contains(&feature) {
- self.cx
- .assert_binding_matches_all(self.keystrokes_under_test, marked_positions)
- .await
- }
- }
-}
-
-impl<'a, const COUNT: usize> Deref for NeovimBackedBindingTestContext<'a, COUNT> {
- type Target = NeovimBackedTestContext<'a>;
-
- fn deref(&self) -> &Self::Target {
- &self.cx
- }
-}
-
-impl<'a, const COUNT: usize> DerefMut for NeovimBackedBindingTestContext<'a, COUNT> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.cx
- }
-}
@@ -1,439 +0,0 @@
-use editor::{scroll::VERTICAL_SCROLL_MARGIN, test::editor_test_context::ContextHandle};
-use gpui::{px, size, Context};
-use indoc::indoc;
-use settings::SettingsStore;
-use std::{
- ops::{Deref, DerefMut},
- panic, thread,
-};
-
-use collections::{HashMap, HashSet};
-use language::language_settings::{AllLanguageSettings, SoftWrap};
-use util::test::marked_text_offsets;
-
-use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
-use crate::state::Mode;
-
-pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[];
-
-/// Enum representing features we have tests for but which don't work, yet. Used
-/// to add exemptions and automatically
-#[derive(PartialEq, Eq)]
-pub enum ExemptionFeatures {
- // MOTIONS
- // When an operator completes at the end of the file, an extra newline is left
- OperatorLastNewlineRemains,
-
- // OBJECTS
- // Resulting position after the operation is slightly incorrect for unintuitive reasons.
- IncorrectLandingPosition,
- // Operator around the text object at the end of the line doesn't remove whitespace.
- AroundObjectLeavesWhitespaceAtEndOfLine,
- // Sentence object on empty lines
- SentenceOnEmptyLines,
- // Whitespace isn't included with text objects at the start of the line
- SentenceAtStartOfLineWithWhitespace,
- // Whitespace around sentences is slightly incorrect when starting between sentences
- AroundSentenceStartingBetweenIncludesWrongWhitespace,
- // Non empty selection with text objects in visual mode
- NonEmptyVisualTextObjects,
- // Sentence Doesn't backtrack when its at the end of the file
- SentenceAfterPunctuationAtEndOfFile,
-}
-
-impl ExemptionFeatures {
- pub fn supported(&self) -> bool {
- SUPPORTED_FEATURES.contains(self)
- }
-}
-
-pub struct NeovimBackedTestContext<'a> {
- cx: VimTestContext<'a>,
- // Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
- // bindings are exempted. If None, all bindings are ignored for that insertion text.
- exemptions: HashMap<String, Option<HashSet<String>>>,
- neovim: NeovimConnection,
-
- last_set_state: Option<String>,
- recent_keystrokes: Vec<String>,
-
- is_dirty: bool,
-}
-
-impl<'a> NeovimBackedTestContext<'a> {
- pub async fn new(cx: &'a mut gpui::TestAppContext) -> NeovimBackedTestContext<'a> {
- // rust stores the name of the test on the current thread.
- // We use this to automatically name a file that will store
- // the neovim connection's requests/responses so that we can
- // run without neovim on CI.
- let thread = thread::current();
- let test_name = thread
- .name()
- .expect("thread is not named")
- .split(":")
- .last()
- .unwrap()
- .to_string();
- Self {
- cx: VimTestContext::new(cx, true).await,
- exemptions: Default::default(),
- neovim: NeovimConnection::new(test_name).await,
-
- last_set_state: None,
- recent_keystrokes: Default::default(),
- is_dirty: false,
- }
- }
-
- pub fn add_initial_state_exemptions(
- &mut self,
- marked_positions: &str,
- missing_feature: ExemptionFeatures, // Feature required to support this exempted test case
- ) {
- if !missing_feature.supported() {
- let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
-
- for cursor_offset in cursor_offsets.iter() {
- let mut marked_text = unmarked_text.clone();
- marked_text.insert(*cursor_offset, 'ˇ');
-
- // None represents all key bindings being exempted for that initial state
- self.exemptions.insert(marked_text, None);
- }
- }
- }
-
- pub async fn simulate_shared_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
- self.neovim.send_keystroke(keystroke_text).await;
- self.simulate_keystroke(keystroke_text)
- }
-
- pub async fn simulate_shared_keystrokes<const COUNT: usize>(
- &mut self,
- keystroke_texts: [&str; COUNT],
- ) {
- for keystroke_text in keystroke_texts.into_iter() {
- self.recent_keystrokes.push(keystroke_text.to_string());
- self.neovim.send_keystroke(keystroke_text).await;
- }
- self.simulate_keystrokes(keystroke_texts);
- }
-
- pub async fn set_shared_state(&mut self, marked_text: &str) {
- let mode = if marked_text.contains("»") {
- Mode::Visual
- } else {
- Mode::Normal
- };
- self.set_state(marked_text, mode);
- self.last_set_state = Some(marked_text.to_string());
- self.recent_keystrokes = Vec::new();
- self.neovim.set_state(marked_text).await;
- self.is_dirty = true;
- }
-
- pub async fn set_shared_wrap(&mut self, columns: u32) {
- if columns < 12 {
- panic!("nvim doesn't support columns < 12")
- }
- self.neovim.set_option("wrap").await;
- self.neovim
- .set_option(&format!("columns={}", columns))
- .await;
-
- self.update(|cx| {
- cx.update_global(|settings: &mut SettingsStore, cx| {
- settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
- settings.defaults.soft_wrap = Some(SoftWrap::PreferredLineLength);
- settings.defaults.preferred_line_length = Some(columns);
- });
- })
- })
- }
-
- pub async fn set_scroll_height(&mut self, rows: u32) {
- // match Zed's scrolling behavior
- self.neovim
- .set_option(&format!("scrolloff={}", VERTICAL_SCROLL_MARGIN))
- .await;
- // +2 to account for the vim command UI at the bottom.
- self.neovim.set_option(&format!("lines={}", rows + 2)).await;
- let (line_height, visible_line_count) = self.editor(|editor, cx| {
- (
- editor
- .style()
- .unwrap()
- .text
- .line_height_in_pixels(cx.rem_size()),
- editor.visible_line_count().unwrap(),
- )
- });
-
- let window = self.window;
- let margin = self
- .update_window(window, |_, cx| {
- cx.viewport_size().height - line_height * visible_line_count
- })
- .unwrap();
-
- self.simulate_window_resize(
- self.window,
- size(px(1000.), margin + (rows as f32) * line_height),
- );
- }
-
- pub async fn set_neovim_option(&mut self, option: &str) {
- self.neovim.set_option(option).await;
- }
-
- pub async fn assert_shared_state(&mut self, marked_text: &str) {
- self.is_dirty = false;
- let marked_text = marked_text.replace("•", " ");
- let neovim = self.neovim_state().await;
- let editor = self.editor_state();
- if neovim == marked_text && neovim == editor {
- return;
- }
- let initial_state = self
- .last_set_state
- .as_ref()
- .unwrap_or(&"N/A".to_string())
- .clone();
-
- let message = if neovim != marked_text {
- "Test is incorrect (currently expected != neovim_state)"
- } else {
- "Editor does not match nvim behaviour"
- };
- panic!(
- indoc! {"{}
- # initial state:
- {}
- # keystrokes:
- {}
- # currently expected:
- {}
- # neovim state:
- {}
- # zed state:
- {}"},
- message,
- initial_state,
- self.recent_keystrokes.join(" "),
- marked_text.replace(" \n", "•\n"),
- neovim.replace(" \n", "•\n"),
- editor.replace(" \n", "•\n")
- )
- }
-
- pub async fn assert_shared_clipboard(&mut self, text: &str) {
- let neovim = self.neovim.read_register('"').await;
- let editor = self.read_from_clipboard().unwrap().text().clone();
-
- if text == neovim && text == editor {
- return;
- }
-
- let message = if neovim != text {
- "Test is incorrect (currently expected != neovim)"
- } else {
- "Editor does not match nvim behaviour"
- };
-
- let initial_state = self
- .last_set_state
- .as_ref()
- .unwrap_or(&"N/A".to_string())
- .clone();
-
- panic!(
- indoc! {"{}
- # initial state:
- {}
- # keystrokes:
- {}
- # currently expected:
- {}
- # neovim clipboard:
- {}
- # zed clipboard:
- {}"},
- message,
- initial_state,
- self.recent_keystrokes.join(" "),
- text,
- neovim,
- editor
- )
- }
-
- pub async fn neovim_state(&mut self) -> String {
- self.neovim.marked_text().await
- }
-
- pub async fn neovim_mode(&mut self) -> Mode {
- self.neovim.mode().await.unwrap()
- }
-
- pub async fn assert_state_matches(&mut self) {
- self.is_dirty = false;
- let neovim = self.neovim_state().await;
- let editor = self.editor_state();
- let initial_state = self
- .last_set_state
- .as_ref()
- .unwrap_or(&"N/A".to_string())
- .clone();
-
- if neovim != editor {
- panic!(
- indoc! {"Test failed (zed does not match nvim behaviour)
- # initial state:
- {}
- # keystrokes:
- {}
- # neovim state:
- {}
- # zed state:
- {}"},
- initial_state,
- self.recent_keystrokes.join(" "),
- neovim,
- editor,
- )
- }
- }
-
- pub async fn assert_binding_matches<const COUNT: usize>(
- &mut self,
- keystrokes: [&str; COUNT],
- initial_state: &str,
- ) {
- if let Some(possible_exempted_keystrokes) = self.exemptions.get(initial_state) {
- match possible_exempted_keystrokes {
- Some(exempted_keystrokes) => {
- if exempted_keystrokes.contains(&format!("{keystrokes:?}")) {
- // This keystroke was exempted for this insertion text
- return;
- }
- }
- None => {
- // All keystrokes for this insertion text are exempted
- return;
- }
- }
- }
-
- let _state_context = self.set_shared_state(initial_state).await;
- let _keystroke_context = self.simulate_shared_keystrokes(keystrokes).await;
- self.assert_state_matches().await;
- }
-
- pub async fn assert_binding_matches_all<const COUNT: usize>(
- &mut self,
- keystrokes: [&str; COUNT],
- marked_positions: &str,
- ) {
- let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
-
- for cursor_offset in cursor_offsets.iter() {
- let mut marked_text = unmarked_text.clone();
- marked_text.insert(*cursor_offset, 'ˇ');
-
- self.assert_binding_matches(keystrokes, &marked_text).await;
- }
- }
-
- pub fn each_marked_position(&self, marked_positions: &str) -> Vec<String> {
- let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
- let mut ret = Vec::with_capacity(cursor_offsets.len());
-
- for cursor_offset in cursor_offsets.iter() {
- let mut marked_text = unmarked_text.clone();
- marked_text.insert(*cursor_offset, 'ˇ');
- ret.push(marked_text)
- }
-
- ret
- }
-
- pub async fn assert_neovim_compatible<const COUNT: usize>(
- &mut self,
- marked_positions: &str,
- keystrokes: [&str; COUNT],
- ) {
- self.set_shared_state(&marked_positions).await;
- self.simulate_shared_keystrokes(keystrokes).await;
- self.assert_state_matches().await;
- }
-
- pub async fn assert_matches_neovim<const COUNT: usize>(
- &mut self,
- marked_positions: &str,
- keystrokes: [&str; COUNT],
- result: &str,
- ) {
- self.set_shared_state(marked_positions).await;
- self.simulate_shared_keystrokes(keystrokes).await;
- self.assert_shared_state(result).await;
- }
-
- pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
- &mut self,
- keystrokes: [&str; COUNT],
- marked_positions: &str,
- feature: ExemptionFeatures,
- ) {
- if SUPPORTED_FEATURES.contains(&feature) {
- self.assert_binding_matches_all(keystrokes, marked_positions)
- .await
- }
- }
-
- pub fn binding<const COUNT: usize>(
- self,
- keystrokes: [&'static str; COUNT],
- ) -> NeovimBackedBindingTestContext<'a, COUNT> {
- NeovimBackedBindingTestContext::new(keystrokes, self)
- }
-}
-
-impl<'a> Deref for NeovimBackedTestContext<'a> {
- type Target = VimTestContext<'a>;
-
- fn deref(&self) -> &Self::Target {
- &self.cx
- }
-}
-
-impl<'a> DerefMut for NeovimBackedTestContext<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.cx
- }
-}
-
-// a common mistake in tests is to call set_shared_state when
-// you mean asswert_shared_state. This notices that and lets
-// you know.
-impl<'a> Drop for NeovimBackedTestContext<'a> {
- fn drop(&mut self) {
- if self.is_dirty {
- panic!("Test context was dropped after set_shared_state before assert_shared_state")
- }
- }
-}
-
-#[cfg(test)]
-mod test {
- use gpui::TestAppContext;
-
- use crate::test::NeovimBackedTestContext;
-
- #[gpui::test]
- async fn neovim_backed_test_context_works(cx: &mut TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
- cx.assert_state_matches().await;
- cx.set_shared_state("This is a tesˇt").await;
- cx.assert_state_matches().await;
- }
-}
@@ -1,599 +0,0 @@
-use std::path::PathBuf;
-#[cfg(feature = "neovim")]
-use std::{
- cmp,
- ops::{Deref, DerefMut, Range},
-};
-
-#[cfg(feature = "neovim")]
-use async_compat::Compat;
-#[cfg(feature = "neovim")]
-use async_trait::async_trait;
-#[cfg(feature = "neovim")]
-use gpui::Keystroke;
-
-#[cfg(feature = "neovim")]
-use language::Point;
-
-#[cfg(feature = "neovim")]
-use nvim_rs::{
- create::tokio::new_child_cmd, error::LoopError, Handler, Neovim, UiAttachOptions, Value,
-};
-#[cfg(feature = "neovim")]
-use parking_lot::ReentrantMutex;
-use serde::{Deserialize, Serialize};
-#[cfg(feature = "neovim")]
-use tokio::{
- process::{Child, ChildStdin, Command},
- task::JoinHandle,
-};
-
-use crate::state::Mode;
-use collections::VecDeque;
-
-// Neovim doesn't like to be started simultaneously from multiple threads. We use this lock
-// to ensure we are only constructing one neovim connection at a time.
-#[cfg(feature = "neovim")]
-static NEOVIM_LOCK: ReentrantMutex<()> = ReentrantMutex::new(());
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
-pub enum NeovimData {
- Put { state: String },
- Key(String),
- Get { state: String, mode: Option<Mode> },
- ReadRegister { name: char, value: String },
- SetOption { value: String },
-}
-
-pub struct NeovimConnection {
- data: VecDeque<NeovimData>,
- #[cfg(feature = "neovim")]
- test_case_id: String,
- #[cfg(feature = "neovim")]
- nvim: Neovim<nvim_rs::compat::tokio::Compat<ChildStdin>>,
- #[cfg(feature = "neovim")]
- _join_handle: JoinHandle<Result<(), Box<LoopError>>>,
- #[cfg(feature = "neovim")]
- _child: Child,
-}
-
-impl NeovimConnection {
- pub async fn new(test_case_id: String) -> Self {
- #[cfg(feature = "neovim")]
- let handler = NvimHandler {};
- #[cfg(feature = "neovim")]
- let (nvim, join_handle, child) = Compat::new(async {
- // Ensure we don't create neovim connections in parallel
- let _lock = NEOVIM_LOCK.lock();
- let (nvim, join_handle, child) = new_child_cmd(
- &mut Command::new("nvim")
- .arg("--embed")
- .arg("--clean")
- // disable swap (otherwise after about 1000 test runs you run out of swap file names)
- .arg("-n")
- // disable writing files (just in case)
- .arg("-m"),
- handler,
- )
- .await
- .expect("Could not connect to neovim process");
-
- nvim.ui_attach(100, 100, &UiAttachOptions::default())
- .await
- .expect("Could not attach to ui");
-
- // Makes system act a little more like zed in terms of indentation
- nvim.set_option("smartindent", nvim_rs::Value::Boolean(true))
- .await
- .expect("Could not set smartindent on startup");
-
- (nvim, join_handle, child)
- })
- .await;
-
- Self {
- #[cfg(feature = "neovim")]
- data: Default::default(),
- #[cfg(not(feature = "neovim"))]
- data: Self::read_test_data(&test_case_id),
- #[cfg(feature = "neovim")]
- test_case_id,
- #[cfg(feature = "neovim")]
- nvim,
- #[cfg(feature = "neovim")]
- _join_handle: join_handle,
- #[cfg(feature = "neovim")]
- _child: child,
- }
- }
-
- // Sends a keystroke to the neovim process.
- #[cfg(feature = "neovim")]
- pub async fn send_keystroke(&mut self, keystroke_text: &str) {
- let mut keystroke = Keystroke::parse(keystroke_text).unwrap();
-
- if keystroke.key == "<" {
- keystroke.key = "lt".to_string()
- }
-
- let special = keystroke.modifiers.shift
- || keystroke.modifiers.control
- || keystroke.modifiers.alt
- || keystroke.modifiers.command
- || keystroke.key.len() > 1;
- let start = if special { "<" } else { "" };
- let shift = if keystroke.modifiers.shift { "S-" } else { "" };
- let ctrl = if keystroke.modifiers.control {
- "C-"
- } else {
- ""
- };
- let alt = if keystroke.modifiers.alt { "M-" } else { "" };
- let cmd = if keystroke.modifiers.command {
- "D-"
- } else {
- ""
- };
- let end = if special { ">" } else { "" };
-
- let key = format!("{start}{shift}{ctrl}{alt}{cmd}{}{end}", keystroke.key);
-
- self.data
- .push_back(NeovimData::Key(keystroke_text.to_string()));
- self.nvim
- .input(&key)
- .await
- .expect("Could not input keystroke");
- }
-
- #[cfg(not(feature = "neovim"))]
- pub async fn send_keystroke(&mut self, keystroke_text: &str) {
- if matches!(self.data.front(), Some(NeovimData::Get { .. })) {
- self.data.pop_front();
- }
- assert_eq!(
- self.data.pop_front(),
- Some(NeovimData::Key(keystroke_text.to_string())),
- "operation does not match recorded script. re-record with --features=neovim"
- );
- }
-
- #[cfg(feature = "neovim")]
- pub async fn set_state(&mut self, marked_text: &str) {
- let (text, selections) = parse_state(&marked_text);
-
- let nvim_buffer = self
- .nvim
- .get_current_buf()
- .await
- .expect("Could not get neovim buffer");
- let lines = text
- .split('\n')
- .map(|line| line.to_string())
- .collect::<Vec<_>>();
-
- nvim_buffer
- .set_lines(0, -1, false, lines)
- .await
- .expect("Could not set nvim buffer text");
-
- self.nvim
- .input("<escape>")
- .await
- .expect("Could not send escape to nvim");
- self.nvim
- .input("<escape>")
- .await
- .expect("Could not send escape to nvim");
-
- let nvim_window = self
- .nvim
- .get_current_win()
- .await
- .expect("Could not get neovim window");
-
- if selections.len() != 1 {
- panic!("must have one selection");
- }
- let selection = &selections[0];
-
- let cursor = selection.start;
- nvim_window
- .set_cursor((cursor.row as i64 + 1, cursor.column as i64))
- .await
- .expect("Could not set nvim cursor position");
-
- if !selection.is_empty() {
- self.nvim
- .input("v")
- .await
- .expect("could not enter visual mode");
-
- let cursor = selection.end;
- nvim_window
- .set_cursor((cursor.row as i64 + 1, cursor.column as i64))
- .await
- .expect("Could not set nvim cursor position");
- }
-
- if let Some(NeovimData::Get { mode, state }) = self.data.back() {
- if *mode == Some(Mode::Normal) && *state == marked_text {
- return;
- }
- }
- self.data.push_back(NeovimData::Put {
- state: marked_text.to_string(),
- })
- }
-
- #[cfg(not(feature = "neovim"))]
- pub async fn set_state(&mut self, marked_text: &str) {
- if let Some(NeovimData::Get { mode, state: text }) = self.data.front() {
- if *mode == Some(Mode::Normal) && *text == marked_text {
- return;
- }
- self.data.pop_front();
- }
- assert_eq!(
- self.data.pop_front(),
- Some(NeovimData::Put {
- state: marked_text.to_string()
- }),
- "operation does not match recorded script. re-record with --features=neovim"
- );
- }
-
- #[cfg(feature = "neovim")]
- pub async fn set_option(&mut self, value: &str) {
- self.nvim
- .command_output(format!("set {}", value).as_str())
- .await
- .unwrap();
-
- self.data.push_back(NeovimData::SetOption {
- value: value.to_string(),
- })
- }
-
- #[cfg(not(feature = "neovim"))]
- pub async fn set_option(&mut self, value: &str) {
- if let Some(NeovimData::Get { .. }) = self.data.front() {
- self.data.pop_front();
- };
- assert_eq!(
- self.data.pop_front(),
- Some(NeovimData::SetOption {
- value: value.to_string(),
- }),
- "operation does not match recorded script. re-record with --features=neovim"
- );
- }
-
- #[cfg(not(feature = "neovim"))]
- pub async fn read_register(&mut self, register: char) -> String {
- if let Some(NeovimData::Get { .. }) = self.data.front() {
- self.data.pop_front();
- };
- if let Some(NeovimData::ReadRegister { name, value }) = self.data.pop_front() {
- if name == register {
- return value;
- }
- }
-
- panic!("operation does not match recorded script. re-record with --features=neovim")
- }
-
- #[cfg(feature = "neovim")]
- pub async fn read_register(&mut self, name: char) -> String {
- let value = self
- .nvim
- .command_output(format!("echo getreg('{}')", name).as_str())
- .await
- .unwrap();
-
- self.data.push_back(NeovimData::ReadRegister {
- name,
- value: value.clone(),
- });
-
- value
- }
-
- #[cfg(feature = "neovim")]
- async fn read_position(&mut self, cmd: &str) -> u32 {
- self.nvim
- .command_output(cmd)
- .await
- .unwrap()
- .parse::<u32>()
- .unwrap()
- }
-
- #[cfg(feature = "neovim")]
- pub async fn state(&mut self) -> (Option<Mode>, String) {
- let nvim_buffer = self
- .nvim
- .get_current_buf()
- .await
- .expect("Could not get neovim buffer");
- let text = nvim_buffer
- .get_lines(0, -1, false)
- .await
- .expect("Could not get buffer text")
- .join("\n");
-
- // nvim columns are 1-based, so -1.
- let mut cursor_row = self.read_position("echo line('.')").await - 1;
- let mut cursor_col = self.read_position("echo col('.')").await - 1;
- let mut selection_row = self.read_position("echo line('v')").await - 1;
- let mut selection_col = self.read_position("echo col('v')").await - 1;
- let total_rows = self.read_position("echo line('$')").await - 1;
-
- let nvim_mode_text = self
- .nvim
- .get_mode()
- .await
- .expect("Could not get mode")
- .into_iter()
- .find_map(|(key, value)| {
- if key.as_str() == Some("mode") {
- Some(value.as_str().unwrap().to_owned())
- } else {
- None
- }
- })
- .expect("Could not find mode value");
-
- let mode = match nvim_mode_text.as_ref() {
- "i" => Some(Mode::Insert),
- "n" => Some(Mode::Normal),
- "v" => Some(Mode::Visual),
- "V" => Some(Mode::VisualLine),
- "\x16" => Some(Mode::VisualBlock),
- _ => None,
- };
-
- let mut selections = Vec::new();
- // Vim uses the index of the first and last character in the selection
- // Zed uses the index of the positions between the characters, so we need
- // to add one to the end in visual mode.
- match mode {
- Some(Mode::VisualBlock) if selection_row != cursor_row => {
- // in zed we fake a block selecrtion by using multiple cursors (one per line)
- // this code emulates that.
- // to deal with casees where the selection is not perfectly rectangular we extract
- // the content of the selection via the "a register to get the shape correctly.
- self.nvim.input("\"aygv").await.unwrap();
- let content = self.nvim.command_output("echo getreg('a')").await.unwrap();
- let lines = content.split("\n").collect::<Vec<_>>();
- let top = cmp::min(selection_row, cursor_row);
- let left = cmp::min(selection_col, cursor_col);
- for row in top..=cmp::max(selection_row, cursor_row) {
- let content = if row - top >= lines.len() as u32 {
- ""
- } else {
- lines[(row - top) as usize]
- };
- let line_len = self
- .read_position(format!("echo strlen(getline({}))", row + 1).as_str())
- .await;
-
- if left > line_len {
- continue;
- }
-
- let start = Point::new(row, left);
- let end = Point::new(row, left + content.len() as u32);
- if cursor_col >= selection_col {
- selections.push(start..end)
- } else {
- selections.push(end..start)
- }
- }
- }
- Some(Mode::Visual) | Some(Mode::VisualLine) | Some(Mode::VisualBlock) => {
- if selection_col > cursor_col {
- let selection_line_length =
- self.read_position("echo strlen(getline(line('v')))").await;
- if selection_line_length > selection_col {
- selection_col += 1;
- } else if selection_row < total_rows {
- selection_col = 0;
- selection_row += 1;
- }
- } else {
- let cursor_line_length =
- self.read_position("echo strlen(getline(line('.')))").await;
- if cursor_line_length > cursor_col {
- cursor_col += 1;
- } else if cursor_row < total_rows {
- cursor_col = 0;
- cursor_row += 1;
- }
- }
- selections.push(
- Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col),
- )
- }
- Some(Mode::Insert) | Some(Mode::Normal) | None => selections
- .push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
- }
-
- let ranges = encode_ranges(&text, &selections);
- let state = NeovimData::Get {
- mode,
- state: ranges.clone(),
- };
-
- if self.data.back() != Some(&state) {
- self.data.push_back(state.clone());
- }
-
- (mode, ranges)
- }
-
- #[cfg(not(feature = "neovim"))]
- pub async fn state(&mut self) -> (Option<Mode>, String) {
- if let Some(NeovimData::Get { state: raw, mode }) = self.data.front() {
- (*mode, raw.to_string())
- } else {
- panic!("operation does not match recorded script. re-record with --features=neovim");
- }
- }
-
- pub async fn mode(&mut self) -> Option<Mode> {
- self.state().await.0
- }
-
- pub async fn marked_text(&mut self) -> String {
- self.state().await.1
- }
-
- fn test_data_path(test_case_id: &str) -> PathBuf {
- let mut data_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- data_path.push("test_data");
- data_path.push(format!("{}.json", test_case_id));
- data_path
- }
-
- #[cfg(not(feature = "neovim"))]
- fn read_test_data(test_case_id: &str) -> VecDeque<NeovimData> {
- let path = Self::test_data_path(test_case_id);
- let json = std::fs::read_to_string(path).expect(
- "Could not read test data. Is it generated? Try running test with '--features neovim'",
- );
-
- let mut result = VecDeque::new();
- for line in json.lines() {
- result.push_back(
- serde_json::from_str(line)
- .expect("invalid test data. regenerate it with '--features neovim'"),
- );
- }
- result
- }
-
- #[cfg(feature = "neovim")]
- fn write_test_data(test_case_id: &str, data: &VecDeque<NeovimData>) {
- let path = Self::test_data_path(test_case_id);
- let mut json = Vec::new();
- for entry in data {
- serde_json::to_writer(&mut json, entry).unwrap();
- json.push(b'\n');
- }
- std::fs::create_dir_all(path.parent().unwrap())
- .expect("could not create test data directory");
- std::fs::write(path, json).expect("could not write out test data");
- }
-}
-
-#[cfg(feature = "neovim")]
-impl Deref for NeovimConnection {
- type Target = Neovim<nvim_rs::compat::tokio::Compat<ChildStdin>>;
-
- fn deref(&self) -> &Self::Target {
- &self.nvim
- }
-}
-
-#[cfg(feature = "neovim")]
-impl DerefMut for NeovimConnection {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.nvim
- }
-}
-
-#[cfg(feature = "neovim")]
-impl Drop for NeovimConnection {
- fn drop(&mut self) {
- Self::write_test_data(&self.test_case_id, &self.data);
- }
-}
-
-#[cfg(feature = "neovim")]
-#[derive(Clone)]
-struct NvimHandler {}
-
-#[cfg(feature = "neovim")]
-#[async_trait]
-impl Handler for NvimHandler {
- type Writer = nvim_rs::compat::tokio::Compat<ChildStdin>;
-
- async fn handle_request(
- &self,
- _event_name: String,
- _arguments: Vec<Value>,
- _neovim: Neovim<Self::Writer>,
- ) -> Result<Value, Value> {
- unimplemented!();
- }
-
- async fn handle_notify(
- &self,
- _event_name: String,
- _arguments: Vec<Value>,
- _neovim: Neovim<Self::Writer>,
- ) {
- }
-}
-
-#[cfg(feature = "neovim")]
-fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) {
- let (text, ranges) = util::test::marked_text_ranges(marked_text, true);
- let point_ranges = ranges
- .into_iter()
- .map(|byte_range| {
- let mut point_range = Point::zero()..Point::zero();
- let mut ix = 0;
- let mut position = Point::zero();
- for c in text.chars().chain(['\0']) {
- if ix == byte_range.start {
- point_range.start = position;
- }
- if ix == byte_range.end {
- point_range.end = position;
- }
- let len_utf8 = c.len_utf8();
- ix += len_utf8;
- if c == '\n' {
- position.row += 1;
- position.column = 0;
- } else {
- position.column += len_utf8 as u32;
- }
- }
- point_range
- })
- .collect::<Vec<_>>();
- (text, point_ranges)
-}
-
-#[cfg(feature = "neovim")]
-fn encode_ranges(text: &str, point_ranges: &Vec<Range<Point>>) -> String {
- let byte_ranges = point_ranges
- .into_iter()
- .map(|range| {
- let mut byte_range = 0..0;
- let mut ix = 0;
- let mut position = Point::zero();
- for c in text.chars().chain(['\0']) {
- if position == range.start {
- byte_range.start = ix;
- }
- if position == range.end {
- byte_range.end = ix;
- }
- let len_utf8 = c.len_utf8();
- ix += len_utf8;
- if c == '\n' {
- position.row += 1;
- position.column = 0;
- } else {
- position.column += len_utf8 as u32;
- }
- }
- byte_range
- })
- .collect::<Vec<_>>();
- util::test::generate_marked_text(text, &byte_ranges[..], true)
-}
@@ -1,177 +0,0 @@
-use std::ops::{Deref, DerefMut};
-
-use editor::test::{
- editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
-};
-use futures::Future;
-use gpui::{Context, View, VisualContext};
-use lsp::request;
-use search::BufferSearchBar;
-
-use crate::{state::Operator, *};
-
-pub struct VimTestContext<'a> {
- cx: EditorLspTestContext<'a>,
-}
-
-impl<'a> VimTestContext<'a> {
- pub fn init(cx: &mut gpui::TestAppContext) {
- if cx.has_global::<Vim>() {
- dbg!("OOPS");
- return;
- }
- cx.update(|cx| {
- search::init(cx);
- let settings = SettingsStore::test(cx);
- cx.set_global(settings);
- command_palette::init(cx);
- crate::init(cx);
- });
- }
-
- pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
- Self::init(cx);
- let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
- Self::new_with_lsp(lsp, enabled)
- }
-
- pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
- Self::init(cx);
- Self::new_with_lsp(
- EditorLspTestContext::new_typescript(Default::default(), cx).await,
- true,
- )
- }
-
- pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
- cx.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
- });
- settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap();
- settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
- });
-
- // Setup search toolbars and keypress hook
- cx.update_workspace(|workspace, cx| {
- observe_keystrokes(cx);
- workspace.active_pane().update(cx, |pane, cx| {
- pane.toolbar().update(cx, |toolbar, cx| {
- let buffer_search_bar = cx.new_view(BufferSearchBar::new);
- toolbar.add_item(buffer_search_bar, cx);
- // todo!();
- // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
- // toolbar.add_item(project_search_bar, cx);
- })
- });
- workspace.status_bar().update(cx, |status_bar, cx| {
- let vim_mode_indicator = cx.new_view(ModeIndicator::new);
- status_bar.add_right_item(vim_mode_indicator, cx);
- });
- });
-
- Self { cx }
- }
-
- pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
- where
- T: 'static,
- F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
- {
- let window = self.window.clone();
- self.update_window(window, move |_, cx| view.update(cx, update))
- .unwrap()
- }
-
- pub fn workspace<F, T>(&mut self, update: F) -> T
- where
- F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
- {
- self.cx.update_workspace(update)
- }
-
- pub fn enable_vim(&mut self) {
- self.cx.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true));
- });
- })
- }
-
- pub fn disable_vim(&mut self) {
- self.cx.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false));
- });
- })
- }
-
- pub fn mode(&mut self) -> Mode {
- self.cx.read(|cx| cx.global::<Vim>().state().mode)
- }
-
- pub fn active_operator(&mut self) -> Option<Operator> {
- self.cx
- .read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
- }
-
- pub fn set_state(&mut self, text: &str, mode: Mode) {
- let window = self.window;
- self.cx.set_state(text);
- self.update_window(window, |_, cx| {
- Vim::update(cx, |vim, cx| {
- vim.switch_mode(mode, true, cx);
- })
- })
- .unwrap();
- self.cx.cx.cx.run_until_parked();
- }
-
- #[track_caller]
- pub fn assert_state(&mut self, text: &str, mode: Mode) {
- self.assert_editor_state(text);
- assert_eq!(self.mode(), mode, "{}", self.assertion_context());
- }
-
- pub fn assert_binding<const COUNT: usize>(
- &mut self,
- keystrokes: [&str; COUNT],
- initial_state: &str,
- initial_mode: Mode,
- state_after: &str,
- mode_after: Mode,
- ) {
- self.set_state(initial_state, initial_mode);
- self.cx.simulate_keystrokes(keystrokes);
- self.cx.assert_editor_state(state_after);
- assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
- assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
- }
-
- pub fn handle_request<T, F, Fut>(
- &self,
- handler: F,
- ) -> futures::channel::mpsc::UnboundedReceiver<()>
- where
- T: 'static + request::Request,
- T::Params: 'static + Send,
- F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
- Fut: 'static + Send + Future<Output = Result<T::Result>>,
- {
- self.cx.handle_request::<T, F, Fut>(handler)
- }
-}
-
-impl<'a> Deref for VimTestContext<'a> {
- type Target = EditorTestContext<'a>;
-
- fn deref(&self) -> &Self::Target {
- &self.cx
- }
-}
-
-impl<'a> DerefMut for VimTestContext<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.cx
- }
-}
@@ -1,50 +0,0 @@
-use editor::{ClipboardSelection, Editor};
-use gpui::{AppContext, ClipboardItem};
-use language::Point;
-
-pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
- let selections = editor.selections.all_adjusted(cx);
- let buffer = editor.buffer().read(cx).snapshot(cx);
- let mut text = String::new();
- let mut clipboard_selections = Vec::with_capacity(selections.len());
- {
- let mut is_first = true;
- for selection in selections.iter() {
- let mut start = selection.start;
- let end = selection.end;
- if is_first {
- is_first = false;
- } else {
- text.push_str("\n");
- }
- let initial_len = text.len();
-
- // if the file does not end with \n, and our line-mode selection ends on
- // that line, we will have expanded the start of the selection to ensure it
- // contains a newline (so that delete works as expected). We undo that change
- // here.
- let is_last_line = linewise
- && end.row == buffer.max_buffer_row()
- && buffer.max_point().column > 0
- && start.row < buffer.max_buffer_row()
- && start == Point::new(start.row, buffer.line_len(start.row));
-
- if is_last_line {
- start = Point::new(start.row + 1, 0);
- }
- for chunk in buffer.text_for_range(start..end) {
- text.push_str(chunk);
- }
- if is_last_line {
- text.push_str("\n");
- }
- clipboard_selections.push(ClipboardSelection {
- len: text.len() - initial_len,
- is_entire_line: linewise,
- first_line_indent: buffer.indent_size_for_line(start.row).len,
- });
- }
- }
-
- cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
-}
@@ -1,607 +0,0 @@
-#[cfg(test)]
-mod test;
-
-mod command;
-mod editor_events;
-mod insert;
-mod mode_indicator;
-mod motion;
-mod normal;
-mod object;
-mod state;
-mod utils;
-mod visual;
-
-use anyhow::Result;
-use collections::{CommandPaletteFilter, HashMap};
-use command_palette::CommandPaletteInterceptor;
-use editor::{movement, Editor, EditorEvent, EditorMode};
-use gpui::{
- actions, impl_actions, Action, AppContext, EntityId, KeyContext, Subscription, View,
- ViewContext, WeakView, WindowContext,
-};
-use language::{CursorShape, Point, Selection, SelectionGoal};
-pub use mode_indicator::ModeIndicator;
-use motion::Motion;
-use normal::normal_replace;
-use serde::Deserialize;
-use settings::{update_settings_file, Settings, SettingsStore};
-use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
-use std::{ops::Range, sync::Arc};
-use visual::{visual_block_motion, visual_replace};
-use workspace::{self, Workspace};
-
-use crate::state::ReplayableAction;
-
-pub struct VimModeSetting(pub bool);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct SwitchMode(pub Mode);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct PushOperator(pub Operator);
-
-#[derive(Clone, Deserialize, PartialEq)]
-struct Number(usize);
-
-actions!(
- vim,
- [Tab, Enter, Object, InnerObject, FindForward, FindBackward]
-);
-// in the workspace namespace so it's not filtered out when vim is disabled.
-actions!(workspace, [ToggleVimMode]);
-
-impl_actions!(vim, [SwitchMode, PushOperator, Number]);
-
-pub fn init(cx: &mut AppContext) {
- cx.set_global(Vim::default());
- VimModeSetting::register(cx);
-
- editor_events::init(cx);
-
- cx.observe_new_views(|workspace: &mut Workspace, cx| register(workspace, cx))
- .detach();
-
- // Any time settings change, update vim mode to match. The Vim struct
- // will be initialized as disabled by default, so we filter its commands
- // out when starting up.
- cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
- filter.hidden_namespaces.insert("vim");
- });
- cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
- vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
- });
- cx.observe_global::<SettingsStore>(|cx| {
- cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
- vim.set_enabled(VimModeSetting::get_global(cx).0, cx)
- });
- })
- .detach();
-}
-
-fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- workspace.register_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
- Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
- });
- workspace.register_action(
- |_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
- Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
- },
- );
- workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
- Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
- });
-
- workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
- Vim::active_editor_input_ignored(" ".into(), cx)
- });
-
- workspace.register_action(|_: &mut Workspace, _: &Enter, cx| {
- Vim::active_editor_input_ignored("\n".into(), cx)
- });
-
- workspace.register_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
- let fs = workspace.app_state().fs.clone();
- let currently_enabled = VimModeSetting::get_global(cx).0;
- update_settings_file::<VimModeSetting>(fs, cx, move |setting| {
- *setting = Some(!currently_enabled)
- })
- });
-
- normal::register(workspace, cx);
- insert::register(workspace, cx);
- motion::register(workspace, cx);
- command::register(workspace, cx);
- object::register(workspace, cx);
- visual::register(workspace, cx);
-}
-
-pub fn observe_keystrokes(cx: &mut WindowContext) {
- cx.observe_keystrokes(|keystroke_event, cx| {
- if let Some(action) = keystroke_event
- .action
- .as_ref()
- .map(|action| action.boxed_clone())
- {
- Vim::update(cx, |vim, _| {
- if vim.workspace_state.recording {
- vim.workspace_state
- .recorded_actions
- .push(ReplayableAction::Action(action.boxed_clone()));
-
- if vim.workspace_state.stop_recording_after_next_action {
- vim.workspace_state.recording = false;
- vim.workspace_state.stop_recording_after_next_action = false;
- }
- }
- });
-
- // Keystroke is handled by the vim system, so continue forward
- if action.name().starts_with("vim::") {
- return;
- }
- } else if cx.has_pending_keystrokes() {
- return;
- }
-
- Vim::update(cx, |vim, cx| match vim.active_operator() {
- Some(
- Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace,
- ) => {}
- Some(_) => {
- vim.clear_operator(cx);
- }
- _ => {}
- });
- })
- .detach()
-}
-
-#[derive(Default)]
-pub struct Vim {
- active_editor: Option<WeakView<Editor>>,
- editor_subscription: Option<Subscription>,
- enabled: bool,
- editor_states: HashMap<EntityId, EditorState>,
- workspace_state: WorkspaceState,
- default_state: EditorState,
-}
-
-impl Vim {
- fn read(cx: &mut AppContext) -> &Self {
- cx.global::<Self>()
- }
-
- fn update<F, S>(cx: &mut WindowContext, update: F) -> S
- where
- F: FnOnce(&mut Self, &mut WindowContext) -> S,
- {
- cx.update_global(update)
- }
-
- fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
- self.active_editor = Some(editor.clone().downgrade());
- self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
- EditorEvent::SelectionsChanged { local: true } => {
- let editor = editor.read(cx);
- if editor.leader_peer_id().is_none() {
- let newest = editor.selections.newest::<usize>(cx);
- local_selections_changed(newest, cx);
- }
- }
- EditorEvent::InputIgnored { text } => {
- Vim::active_editor_input_ignored(text.clone(), cx);
- Vim::record_insertion(text, None, cx)
- }
- EditorEvent::InputHandled {
- text,
- utf16_range_to_replace: range_to_replace,
- } => Vim::record_insertion(text, range_to_replace.clone(), cx),
- _ => {}
- }));
-
- if self.enabled {
- let editor = editor.read(cx);
- let editor_mode = editor.mode();
- let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
-
- if editor_mode == EditorMode::Full
- && !newest_selection_empty
- && self.state().mode == Mode::Normal
- // When following someone, don't switch vim mode.
- && editor.leader_peer_id().is_none()
- {
- self.switch_mode(Mode::Visual, true, cx);
- }
- }
-
- self.sync_vim_settings(cx);
- }
-
- fn record_insertion(
- text: &Arc<str>,
- range_to_replace: Option<Range<isize>>,
- cx: &mut WindowContext,
- ) {
- Vim::update(cx, |vim, _| {
- if vim.workspace_state.recording {
- vim.workspace_state
- .recorded_actions
- .push(ReplayableAction::Insertion {
- text: text.clone(),
- utf16_range_to_replace: range_to_replace,
- });
- if vim.workspace_state.stop_recording_after_next_action {
- vim.workspace_state.recording = false;
- vim.workspace_state.stop_recording_after_next_action = false;
- }
- }
- });
- }
-
- fn update_active_editor<S>(
- &self,
- cx: &mut WindowContext,
- update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
- ) -> Option<S> {
- let editor = self.active_editor.clone()?.upgrade()?;
- Some(editor.update(cx, update))
- }
-
- pub fn start_recording(&mut self, cx: &mut WindowContext) {
- if !self.workspace_state.replaying {
- self.workspace_state.recording = true;
- self.workspace_state.recorded_actions = Default::default();
- self.workspace_state.recorded_count = None;
-
- let selections = self
- .active_editor
- .as_ref()
- .and_then(|editor| editor.upgrade())
- .map(|editor| {
- let editor = editor.read(cx);
- (
- editor.selections.oldest::<Point>(cx),
- editor.selections.newest::<Point>(cx),
- )
- });
-
- if let Some((oldest, newest)) = selections {
- self.workspace_state.recorded_selection = match self.state().mode {
- Mode::Visual if newest.end.row == newest.start.row => {
- RecordedSelection::SingleLine {
- cols: newest.end.column - newest.start.column,
- }
- }
- Mode::Visual => RecordedSelection::Visual {
- rows: newest.end.row - newest.start.row,
- cols: newest.end.column,
- },
- Mode::VisualLine => RecordedSelection::VisualLine {
- rows: newest.end.row - newest.start.row,
- },
- Mode::VisualBlock => RecordedSelection::VisualBlock {
- rows: newest.end.row.abs_diff(oldest.start.row),
- cols: newest.end.column.abs_diff(oldest.start.column),
- },
- _ => RecordedSelection::None,
- }
- } else {
- self.workspace_state.recorded_selection = RecordedSelection::None;
- }
- }
- }
-
- pub fn stop_recording(&mut self) {
- if self.workspace_state.recording {
- self.workspace_state.stop_recording_after_next_action = true;
- }
- }
-
- pub fn stop_recording_immediately(&mut self, action: Box<dyn Action>) {
- if self.workspace_state.recording {
- self.workspace_state
- .recorded_actions
- .push(ReplayableAction::Action(action.boxed_clone()));
- self.workspace_state.recording = false;
- self.workspace_state.stop_recording_after_next_action = false;
- }
- }
-
- pub fn record_current_action(&mut self, cx: &mut WindowContext) {
- self.start_recording(cx);
- self.stop_recording();
- }
-
- fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
- let state = self.state();
- let last_mode = state.mode;
- let prior_mode = state.last_mode;
- self.update_state(|state| {
- state.last_mode = last_mode;
- state.mode = mode;
- state.operator_stack.clear();
- });
- if mode != Mode::Insert {
- self.take_count(cx);
- }
-
- // Sync editor settings like clip mode
- self.sync_vim_settings(cx);
-
- if leave_selections {
- return;
- }
-
- // Adjust selections
- self.update_active_editor(cx, |editor, cx| {
- if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
- {
- visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
- }
-
- editor.change_selections(None, cx, |s| {
- // we cheat with visual block mode and use multiple cursors.
- // the cost of this cheat is we need to convert back to a single
- // cursor whenever vim would.
- if last_mode == Mode::VisualBlock
- && (mode != Mode::VisualBlock && mode != Mode::Insert)
- {
- let tail = s.oldest_anchor().tail();
- let head = s.newest_anchor().head();
- s.select_anchor_ranges(vec![tail..head]);
- } else if last_mode == Mode::Insert
- && prior_mode == Mode::VisualBlock
- && mode != Mode::VisualBlock
- {
- let pos = s.first_anchor().head();
- s.select_anchor_ranges(vec![pos..pos])
- }
-
- s.move_with(|map, selection| {
- if last_mode.is_visual() && !mode.is_visual() {
- let mut point = selection.head();
- if !selection.reversed && !selection.is_empty() {
- point = movement::left(map, selection.head());
- }
- selection.collapse_to(point, selection.goal)
- } else if !last_mode.is_visual() && mode.is_visual() {
- if selection.is_empty() {
- selection.end = movement::right(map, selection.start);
- }
- }
- });
- })
- });
- }
-
- fn push_count_digit(&mut self, number: usize, cx: &mut WindowContext) {
- if self.active_operator().is_some() {
- self.update_state(|state| {
- state.post_count = Some(state.post_count.unwrap_or(0) * 10 + number)
- })
- } else {
- self.update_state(|state| {
- state.pre_count = Some(state.pre_count.unwrap_or(0) * 10 + number)
- })
- }
- // update the keymap so that 0 works
- self.sync_vim_settings(cx)
- }
-
- fn take_count(&mut self, cx: &mut WindowContext) -> Option<usize> {
- if self.workspace_state.replaying {
- return self.workspace_state.recorded_count;
- }
-
- let count = if self.state().post_count == None && self.state().pre_count == None {
- return None;
- } else {
- Some(self.update_state(|state| {
- state.post_count.take().unwrap_or(1) * state.pre_count.take().unwrap_or(1)
- }))
- };
- if self.workspace_state.recording {
- self.workspace_state.recorded_count = count;
- }
- self.sync_vim_settings(cx);
- count
- }
-
- fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
- if matches!(
- operator,
- Operator::Change | Operator::Delete | Operator::Replace
- ) {
- self.start_recording(cx)
- };
- self.update_state(|state| state.operator_stack.push(operator));
- self.sync_vim_settings(cx);
- }
-
- fn maybe_pop_operator(&mut self) -> Option<Operator> {
- self.update_state(|state| state.operator_stack.pop())
- }
-
- fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
- let popped_operator = self.update_state( |state| state.operator_stack.pop()
- ) .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
- self.sync_vim_settings(cx);
- popped_operator
- }
- fn clear_operator(&mut self, cx: &mut WindowContext) {
- self.take_count(cx);
- self.update_state(|state| state.operator_stack.clear());
- self.sync_vim_settings(cx);
- }
-
- fn active_operator(&self) -> Option<Operator> {
- self.state().operator_stack.last().copied()
- }
-
- fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
- if text.is_empty() {
- return;
- }
-
- match Vim::read(cx).active_operator() {
- Some(Operator::FindForward { before }) => {
- let find = Motion::FindForward {
- before,
- char: text.chars().next().unwrap(),
- };
- Vim::update(cx, |vim, _| {
- vim.workspace_state.last_find = Some(find.clone())
- });
- motion::motion(find, cx)
- }
- Some(Operator::FindBackward { after }) => {
- let find = Motion::FindBackward {
- after,
- char: text.chars().next().unwrap(),
- };
- Vim::update(cx, |vim, _| {
- vim.workspace_state.last_find = Some(find.clone())
- });
- motion::motion(find, cx)
- }
- Some(Operator::Replace) => match Vim::read(cx).state().mode {
- Mode::Normal => normal_replace(text, cx),
- Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_replace(text, cx),
- _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
- },
- _ => {}
- }
- }
-
- fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
- if self.enabled != enabled {
- self.enabled = enabled;
-
- cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
- if self.enabled {
- filter.hidden_namespaces.remove("vim");
- } else {
- filter.hidden_namespaces.insert("vim");
- }
- });
-
- if self.enabled {
- cx.set_global::<CommandPaletteInterceptor>(Box::new(command::command_interceptor));
- } else if cx.has_global::<CommandPaletteInterceptor>() {
- let _ = cx.remove_global::<CommandPaletteInterceptor>();
- }
-
- if let Some(active_window) = cx.active_window() {
- active_window
- .update(cx, |root_view, cx| {
- if self.enabled {
- let active_editor = root_view
- .downcast::<Workspace>()
- .ok()
- .and_then(|workspace| workspace.read(cx).active_item(cx))
- .and_then(|item| item.downcast::<Editor>());
- if let Some(active_editor) = active_editor {
- self.set_active_editor(active_editor, cx);
- }
- self.switch_mode(Mode::Normal, false, cx);
- }
- self.sync_vim_settings(cx);
- })
- .ok();
- }
- }
- }
-
- pub fn state(&self) -> &EditorState {
- if let Some(active_editor) = self.active_editor.as_ref() {
- if let Some(state) = self.editor_states.get(&active_editor.entity_id()) {
- return state;
- }
- }
-
- &self.default_state
- }
-
- pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
- let mut state = self.state().clone();
- let ret = func(&mut state);
-
- if let Some(active_editor) = self.active_editor.as_ref() {
- self.editor_states.insert(active_editor.entity_id(), state);
- }
-
- ret
- }
-
- fn sync_vim_settings(&self, cx: &mut WindowContext) {
- let state = self.state();
- let cursor_shape = state.cursor_shape();
-
- self.update_active_editor(cx, |editor, cx| {
- if self.enabled && editor.mode() == EditorMode::Full {
- editor.set_cursor_shape(cursor_shape, cx);
- editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
- editor.set_collapse_matches(true);
- editor.set_input_enabled(!state.vim_controlled());
- editor.set_autoindent(state.should_autoindent());
- editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
- let context_layer = state.keymap_context_layer();
- editor.set_keymap_context_layer::<Self>(context_layer, cx);
- } else {
- // Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur,
- // but we need collapse_matches to persist when the search bar is focused.
- editor.set_collapse_matches(false);
- self.unhook_vim_settings(editor, cx);
- }
- });
- }
-
- fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
- editor.set_cursor_shape(CursorShape::Bar, cx);
- editor.set_clip_at_line_ends(false, cx);
- editor.set_input_enabled(true);
- editor.set_autoindent(true);
- editor.selections.line_mode = false;
-
- // we set the VimEnabled context on all editors so that we
- // can distinguish between vim mode and non-vim mode in the BufferSearchBar.
- // This is a bit of a hack, but currently the search crate does not depend on vim,
- // and it seems nice to keep it that way.
- if self.enabled {
- let mut context = KeyContext::default();
- context.add("VimEnabled");
- editor.set_keymap_context_layer::<Self>(context, cx)
- } else {
- editor.remove_keymap_context_layer::<Self>(cx);
- }
- }
-}
-
-impl Settings for VimModeSetting {
- const KEY: Option<&'static str> = Some("vim_mode");
-
- type FileContent = Option<bool>;
-
- fn load(
- default_value: &Self::FileContent,
- user_values: &[&Self::FileContent],
- _: &mut AppContext,
- ) -> Result<Self> {
- Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
- default_value.ok_or_else(Self::missing_default)?,
- )))
- }
-}
-
-fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
- if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
- vim.switch_mode(Mode::VisualBlock, false, cx);
- } else {
- vim.switch_mode(Mode::Visual, false, cx)
- }
- }
- })
-}
@@ -1,1034 +0,0 @@
-use anyhow::Result;
-use std::sync::Arc;
-
-use collections::HashMap;
-use editor::{
- display_map::{DisplaySnapshot, ToDisplayPoint},
- movement,
- scroll::autoscroll::Autoscroll,
- Bias, DisplayPoint, Editor,
-};
-use gpui::{actions, ViewContext, WindowContext};
-use language::{Selection, SelectionGoal};
-use workspace::Workspace;
-
-use crate::{
- motion::{start_of_line, Motion},
- object::Object,
- state::{Mode, Operator},
- utils::copy_selections_content,
- Vim,
-};
-
-actions!(
- vim,
- [
- ToggleVisual,
- ToggleVisualLine,
- ToggleVisualBlock,
- VisualDelete,
- VisualYank,
- OtherEnd,
- SelectNext,
- SelectPrevious,
- ]
-);
-
-pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- workspace.register_action(|_, _: &ToggleVisual, cx: &mut ViewContext<Workspace>| {
- toggle_mode(Mode::Visual, cx)
- });
- workspace.register_action(|_, _: &ToggleVisualLine, cx: &mut ViewContext<Workspace>| {
- toggle_mode(Mode::VisualLine, cx)
- });
- workspace.register_action(
- |_, _: &ToggleVisualBlock, cx: &mut ViewContext<Workspace>| {
- toggle_mode(Mode::VisualBlock, cx)
- },
- );
- workspace.register_action(other_end);
- workspace.register_action(delete);
- workspace.register_action(yank);
-
- workspace.register_action(|workspace, action, cx| {
- select_next(workspace, action, cx).ok();
- });
- workspace.register_action(|workspace, action, cx| {
- select_previous(workspace, action, cx).ok();
- });
-}
-
-pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |editor, cx| {
- let text_layout_details = editor.text_layout_details(cx);
- if vim.state().mode == Mode::VisualBlock
- && !matches!(
- motion,
- Motion::EndOfLine {
- display_lines: false
- }
- )
- {
- let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
- visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
- motion.move_point(map, point, goal, times, &text_layout_details)
- })
- } else {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let was_reversed = selection.reversed;
- let mut current_head = selection.head();
-
- // our motions assume the current character is after the cursor,
- // but in (forward) visual mode the current character is just
- // before the end of the selection.
-
- // If the file ends with a newline (which is common) we don't do this.
- // so that if you go to the end of such a file you can use "up" to go
- // to the previous line and have it work somewhat as expected.
- if !selection.reversed
- && !selection.is_empty()
- && !(selection.end.column() == 0 && selection.end == map.max_point())
- {
- current_head = movement::left(map, selection.end)
- }
-
- let Some((new_head, goal)) = motion.move_point(
- map,
- current_head,
- selection.goal,
- times,
- &text_layout_details,
- ) else {
- return;
- };
-
- selection.set_head(new_head, goal);
-
- // ensure the current character is included in the selection.
- if !selection.reversed {
- let next_point = if vim.state().mode == Mode::VisualBlock {
- movement::saturating_right(map, selection.end)
- } else {
- movement::right(map, selection.end)
- };
-
- if !(next_point.column() == 0 && next_point == map.max_point()) {
- selection.end = next_point;
- }
- }
-
- // vim always ensures the anchor character stays selected.
- // if our selection has reversed, we need to move the opposite end
- // to ensure the anchor is still selected.
- if was_reversed && !selection.reversed {
- selection.start = movement::left(map, selection.start);
- } else if !was_reversed && selection.reversed {
- selection.end = movement::right(map, selection.end);
- }
- })
- });
- }
- });
- });
-}
-
-pub fn visual_block_motion(
- preserve_goal: bool,
- editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
- mut move_selection: impl FnMut(
- &DisplaySnapshot,
- DisplayPoint,
- SelectionGoal,
- ) -> Option<(DisplayPoint, SelectionGoal)>,
-) {
- let text_layout_details = editor.text_layout_details(cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let map = &s.display_map();
- let mut head = s.newest_anchor().head().to_display_point(map);
- let mut tail = s.oldest_anchor().tail().to_display_point(map);
-
- let mut head_x = map.x_for_display_point(head, &text_layout_details);
- let mut tail_x = map.x_for_display_point(tail, &text_layout_details);
-
- let (start, end) = match s.newest_anchor().goal {
- SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end),
- SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start),
- _ => (tail_x.0, head_x.0),
- };
- let mut goal = SelectionGoal::HorizontalRange { start, end };
-
- let was_reversed = tail_x > head_x;
- if !was_reversed && !preserve_goal {
- head = movement::saturating_left(map, head);
- }
-
- let Some((new_head, _)) = move_selection(&map, head, goal) else {
- return;
- };
- head = new_head;
- head_x = map.x_for_display_point(head, &text_layout_details);
-
- let is_reversed = tail_x > head_x;
- if was_reversed && !is_reversed {
- tail = movement::saturating_left(map, tail);
- tail_x = map.x_for_display_point(tail, &text_layout_details);
- } else if !was_reversed && is_reversed {
- tail = movement::saturating_right(map, tail);
- tail_x = map.x_for_display_point(tail, &text_layout_details);
- }
- if !is_reversed && !preserve_goal {
- head = movement::saturating_right(map, head);
- head_x = map.x_for_display_point(head, &text_layout_details);
- }
-
- let positions = if is_reversed {
- head_x..tail_x
- } else {
- tail_x..head_x
- };
-
- if !preserve_goal {
- goal = SelectionGoal::HorizontalRange {
- start: positions.start.0,
- end: positions.end.0,
- };
- }
-
- let mut selections = Vec::new();
- let mut row = tail.row();
-
- loop {
- let layed_out_line = map.layout_row(row, &text_layout_details);
- let start = DisplayPoint::new(
- row,
- layed_out_line.closest_index_for_x(positions.start) as u32,
- );
- let mut end = DisplayPoint::new(
- row,
- layed_out_line.closest_index_for_x(positions.end) as u32,
- );
- if end <= start {
- if start.column() == map.line_len(start.row()) {
- end = start;
- } else {
- end = movement::saturating_right(map, start);
- }
- }
-
- if positions.start <= layed_out_line.width {
- let selection = Selection {
- id: s.new_selection_id(),
- start: start.to_point(map),
- end: end.to_point(map),
- reversed: is_reversed,
- goal: goal.clone(),
- };
-
- selections.push(selection);
- }
- if row == head.row() {
- break;
- }
- if tail.row() > head.row() {
- row -= 1
- } else {
- row += 1
- }
- }
-
- s.select(selections);
- })
-}
-
-pub fn visual_object(object: Object, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- if let Some(Operator::Object { around }) = vim.active_operator() {
- vim.pop_operator(cx);
- let current_mode = vim.state().mode;
- let target_mode = object.target_visual_mode(current_mode);
- if target_mode != current_mode {
- vim.switch_mode(target_mode, true, cx);
- }
-
- vim.update_active_editor(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut head = selection.head();
-
- // all our motions assume that the current character is
- // after the cursor; however in the case of a visual selection
- // the current character is before the cursor.
- if !selection.reversed {
- head = movement::left(map, head);
- }
-
- if let Some(range) = object.range(map, head, around) {
- if !range.is_empty() {
- let expand_both_ways = object.always_expands_both_ways()
- || selection.is_empty()
- || movement::right(map, selection.start) == selection.end;
-
- if expand_both_ways {
- selection.start = range.start;
- selection.end = range.end;
- } else if selection.reversed {
- selection.start = range.start;
- } else {
- selection.end = range.end;
- }
- }
- }
- });
- });
- });
- }
- });
-}
-
-fn toggle_mode(mode: Mode, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- if vim.state().mode == mode {
- vim.switch_mode(Mode::Normal, false, cx);
- } else {
- vim.switch_mode(mode, false, cx);
- }
- })
-}
-
-pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |editor, cx| {
- editor.change_selections(None, cx, |s| {
- s.move_with(|_, selection| {
- selection.reversed = !selection.reversed;
- })
- })
- })
- });
-}
-
-pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.record_current_action(cx);
- vim.update_active_editor(cx, |editor, cx| {
- let mut original_columns: HashMap<_, _> = Default::default();
- let line_mode = editor.selections.line_mode;
-
- editor.transact(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- if line_mode {
- let mut position = selection.head();
- if !selection.reversed {
- position = movement::left(map, position);
- }
- original_columns.insert(selection.id, position.to_point(map).column);
- }
- selection.goal = SelectionGoal::None;
- });
- });
- copy_selections_content(editor, line_mode, cx);
- editor.insert("", cx);
-
- // Fixup cursor position after the deletion
- editor.set_clip_at_line_ends(true, cx);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.move_with(|map, selection| {
- let mut cursor = selection.head().to_point(map);
-
- if let Some(column) = original_columns.get(&selection.id) {
- cursor.column = *column
- }
- let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
- selection.collapse_to(cursor, selection.goal)
- });
- if vim.state().mode == Mode::VisualBlock {
- s.select_anchors(vec![s.first_anchor()])
- }
- });
- })
- });
- vim.switch_mode(Mode::Normal, true, cx);
- });
-}
-
-pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
- Vim::update(cx, |vim, cx| {
- vim.update_active_editor(cx, |editor, cx| {
- let line_mode = editor.selections.line_mode;
- copy_selections_content(editor, line_mode, cx);
- editor.change_selections(None, cx, |s| {
- s.move_with(|map, selection| {
- if line_mode {
- selection.start = start_of_line(map, false, selection.start);
- };
- selection.collapse_to(selection.start, SelectionGoal::None)
- });
- if vim.state().mode == Mode::VisualBlock {
- s.select_anchors(vec![s.first_anchor()])
- }
- });
- });
- vim.switch_mode(Mode::Normal, true, cx);
- });
-}
-
-pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
- Vim::update(cx, |vim, cx| {
- vim.stop_recording();
- vim.update_active_editor(cx, |editor, cx| {
- editor.transact(cx, |editor, cx| {
- let (display_map, selections) = editor.selections.all_adjusted_display(cx);
-
- // Selections are biased right at the start. So we need to store
- // anchors that are biased left so that we can restore the selections
- // after the change
- let stable_anchors = editor
- .selections
- .disjoint_anchors()
- .into_iter()
- .map(|selection| {
- let start = selection.start.bias_left(&display_map.buffer_snapshot);
- start..start
- })
- .collect::<Vec<_>>();
-
- let mut edits = Vec::new();
- for selection in selections.iter() {
- let selection = selection.clone();
- for row_range in
- movement::split_display_range_by_lines(&display_map, selection.range())
- {
- let range = row_range.start.to_offset(&display_map, Bias::Right)
- ..row_range.end.to_offset(&display_map, Bias::Right);
- let text = text.repeat(range.len());
- edits.push((range, text));
- }
- }
-
- editor.buffer().update(cx, |buffer, cx| {
- buffer.edit(edits, None, cx);
- });
- editor.change_selections(None, cx, |s| s.select_ranges(stable_anchors));
- });
- });
- vim.switch_mode(Mode::Normal, false, cx);
- });
-}
-
-pub fn select_next(
- _: &mut Workspace,
- _: &SelectNext,
- cx: &mut ViewContext<Workspace>,
-) -> Result<()> {
- Vim::update(cx, |vim, cx| {
- let count =
- vim.take_count(cx)
- .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
- vim.update_active_editor(cx, |editor, cx| {
- for _ in 0..count {
- match editor.select_next(&Default::default(), cx) {
- Err(a) => return Err(a),
- _ => {}
- }
- }
- Ok(())
- })
- })
- .unwrap_or(Ok(()))
-}
-
-pub fn select_previous(
- _: &mut Workspace,
- _: &SelectPrevious,
- cx: &mut ViewContext<Workspace>,
-) -> Result<()> {
- Vim::update(cx, |vim, cx| {
- let count =
- vim.take_count(cx)
- .unwrap_or_else(|| if vim.state().mode.is_visual() { 1 } else { 2 });
- vim.update_active_editor(cx, |editor, cx| {
- for _ in 0..count {
- match editor.select_previous(&Default::default(), cx) {
- Err(a) => return Err(a),
- _ => {}
- }
- }
- Ok(())
- })
- })
- .unwrap_or(Ok(()))
-}
-
-#[cfg(test)]
-mod test {
- use indoc::indoc;
- use workspace::item::Item;
-
- use crate::{
- state::Mode,
- test::{NeovimBackedTestContext, VimTestContext},
- };
-
- #[gpui::test]
- async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "The ˇquick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
-
- // entering visual mode should select the character
- // under cursor
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state(indoc! { "The «qˇ»uick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
-
- // forwards motions should extend the selection
- cx.simulate_shared_keystrokes(["w", "j"]).await;
- cx.assert_shared_state(indoc! { "The «quick brown
- fox jumps oˇ»ver
- the lazy dog"})
- .await;
-
- cx.simulate_shared_keystrokes(["escape"]).await;
- assert_eq!(Mode::Normal, cx.neovim_mode().await);
- cx.assert_shared_state(indoc! { "The quick brown
- fox jumps ˇover
- the lazy dog"})
- .await;
-
- // motions work backwards
- cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
- cx.assert_shared_state(indoc! { "The «ˇquick brown
- fox jumps o»ver
- the lazy dog"})
- .await;
-
- // works on empty lines
- cx.set_shared_state(indoc! {"
- a
- ˇ
- b
- "})
- .await;
- let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state(indoc! {"
- a
- «
- ˇ»b
- "})
- .await;
- cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
-
- // toggles off again
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state(indoc! {"
- a
- ˇ
- b
- "})
- .await;
-
- // works at the end of a document
- cx.set_shared_state(indoc! {"
- a
- b
- ˇ"})
- .await;
-
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state(indoc! {"
- a
- b
- ˇ"})
- .await;
- assert_eq!(cx.mode(), cx.neovim_mode().await);
- }
-
- #[gpui::test]
- async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "The ˇquick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["shift-v"]).await;
- cx.assert_shared_state(indoc! { "The «qˇ»uick brown
- fox jumps over
- the lazy dog"})
- .await;
- assert_eq!(cx.mode(), cx.neovim_mode().await);
- cx.simulate_shared_keystrokes(["x"]).await;
- cx.assert_shared_state(indoc! { "fox ˇjumps over
- the lazy dog"})
- .await;
-
- // it should work on empty lines
- cx.set_shared_state(indoc! {"
- a
- ˇ
- b"})
- .await;
- cx.simulate_shared_keystrokes(["shift-v"]).await;
- cx.assert_shared_state(indoc! { "
- a
- «
- ˇ»b"})
- .await;
- cx.simulate_shared_keystrokes(["x"]).await;
- cx.assert_shared_state(indoc! { "
- a
- ˇb"})
- .await;
-
- // it should work at the end of the document
- cx.set_shared_state(indoc! {"
- a
- b
- ˇ"})
- .await;
- let cursor = cx.update_editor(|editor, cx| editor.pixel_position_of_cursor(cx));
- cx.simulate_shared_keystrokes(["shift-v"]).await;
- cx.assert_shared_state(indoc! {"
- a
- b
- ˇ"})
- .await;
- assert_eq!(cx.mode(), cx.neovim_mode().await);
- cx.update_editor(|editor, cx| assert_eq!(cursor, editor.pixel_position_of_cursor(cx)));
- cx.simulate_shared_keystrokes(["x"]).await;
- cx.assert_shared_state(indoc! {"
- a
- ˇb"})
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
- .await;
-
- cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
- .await;
- cx.assert_binding_matches(
- ["v", "w", "j", "x"],
- indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"},
- )
- .await;
- // Test pasting code copied on delete
- cx.simulate_shared_keystrokes(["j", "p"]).await;
- cx.assert_state_matches().await;
-
- let mut cx = cx.binding(["v", "w", "j", "x"]);
- cx.assert_all(indoc! {"
- The ˇquick brown
- fox jumps over
- the ˇlazy dog"})
- .await;
- let mut cx = cx.binding(["v", "b", "k", "x"]);
- cx.assert_all(indoc! {"
- The ˇquick brown
- fox jumps ˇover
- the ˇlazy dog"})
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {"
- The quˇick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
- cx.assert_state_matches().await;
-
- // Test pasting code copied on delete
- cx.simulate_shared_keystroke("p").await;
- cx.assert_state_matches().await;
-
- cx.set_shared_state(indoc! {"
- The quick brown
- fox jumps over
- the laˇzy dog"})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "x"]).await;
- cx.assert_state_matches().await;
- cx.assert_shared_clipboard("the lazy dog\n").await;
-
- for marked_text in cx.each_marked_position(indoc! {"
- The quˇick brown
- fox jumps over
- the lazy dog"})
- {
- cx.set_shared_state(&marked_text).await;
- cx.simulate_shared_keystrokes(["shift-v", "j", "x"]).await;
- cx.assert_state_matches().await;
- // Test pasting code copied on delete
- cx.simulate_shared_keystroke("p").await;
- cx.assert_state_matches().await;
- }
-
- cx.set_shared_state(indoc! {"
- The ˇlong line
- should not
- crash
- "})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
- cx.assert_state_matches().await;
- }
-
- #[gpui::test]
- async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state("The quick ˇbrown").await;
- cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
- cx.assert_shared_state("The quick ˇbrown").await;
- cx.assert_shared_clipboard("brown").await;
-
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
- cx.assert_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.assert_shared_clipboard(indoc! {"
- quick brown
- fox jumps o"})
- .await;
-
- cx.set_shared_state(indoc! {"
- The quick brown
- fox jumps over
- the ˇlazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "w", "j", "y"]).await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- fox jumps over
- the ˇlazy dog"})
- .await;
- cx.assert_shared_clipboard("lazy d").await;
- cx.simulate_shared_keystrokes(["shift-v", "y"]).await;
- cx.assert_shared_clipboard("the lazy dog\n").await;
-
- let mut cx = cx.binding(["v", "b", "k", "y"]);
- cx.set_shared_state(indoc! {"
- The ˇquick brown
- fox jumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["v", "b", "k", "y"]).await;
- cx.assert_shared_state(indoc! {"
- ˇThe quick brown
- fox jumps over
- the lazy dog"})
- .await;
- assert_eq!(
- cx.read_from_clipboard()
- .map(|item| item.text().clone())
- .unwrap(),
- "The q"
- );
-
- cx.set_shared_state(indoc! {"
- The quick brown
- fox ˇjumps over
- the lazy dog"})
- .await;
- cx.simulate_shared_keystrokes(["shift-v", "shift-g", "shift-y"])
- .await;
- cx.assert_shared_state(indoc! {"
- The quick brown
- ˇfox jumps over
- the lazy dog"})
- .await;
- cx.assert_shared_clipboard("fox jumps over\nthe lazy dog\n")
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_block_mode(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "The ˇquick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v"]).await;
- cx.assert_shared_state(indoc! {
- "The «qˇ»uick brown
- fox jumps over
- the lazy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["2", "down"]).await;
- cx.assert_shared_state(indoc! {
- "The «qˇ»uick brown
- fox «jˇ»umps over
- the «lˇ»azy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["e"]).await;
- cx.assert_shared_state(indoc! {
- "The «quicˇ»k brown
- fox «jumpˇ»s over
- the «lazyˇ» dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["^"]).await;
- cx.assert_shared_state(indoc! {
- "«ˇThe q»uick brown
- «ˇfox j»umps over
- «ˇthe l»azy dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["$"]).await;
- cx.assert_shared_state(indoc! {
- "The «quick brownˇ»
- fox «jumps overˇ»
- the «lazy dogˇ»"
- })
- .await;
- cx.simulate_shared_keystrokes(["shift-f", " "]).await;
- cx.assert_shared_state(indoc! {
- "The «quickˇ» brown
- fox «jumpsˇ» over
- the «lazy ˇ»dog"
- })
- .await;
-
- // toggling through visual mode works as expected
- cx.simulate_shared_keystrokes(["v"]).await;
- cx.assert_shared_state(indoc! {
- "The «quick brown
- fox jumps over
- the lazy ˇ»dog"
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v"]).await;
- cx.assert_shared_state(indoc! {
- "The «quickˇ» brown
- fox «jumpsˇ» over
- the «lazy ˇ»dog"
- })
- .await;
-
- cx.set_shared_state(indoc! {
- "The ˇquick
- brown
- fox
- jumps over the
-
- lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "down", "down"])
- .await;
- cx.assert_shared_state(indoc! {
- "The«ˇ q»uick
- bro«ˇwn»
- foxˇ
- jumps over the
-
- lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["down"]).await;
- cx.assert_shared_state(indoc! {
- "The «qˇ»uick
- brow«nˇ»
- fox
- jump«sˇ» over the
-
- lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystroke("left").await;
- cx.assert_shared_state(indoc! {
- "The«ˇ q»uick
- bro«ˇwn»
- foxˇ
- jum«ˇps» over the
-
- lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["s", "o", "escape"]).await;
- cx.assert_shared_state(indoc! {
- "Theˇouick
- broo
- foxo
- jumo over the
-
- lazy dog
- "
- })
- .await;
-
- //https://github.com/zed-industries/community/issues/1950
- cx.set_shared_state(indoc! {
- "Theˇ quick brown
-
- fox jumps over
- the lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["l", "ctrl-v", "j", "j"])
- .await;
- cx.assert_shared_state(indoc! {
- "The «qˇ»uick brown
-
- fox «jˇ»umps over
- the lazy dog
- "
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_block_issue_2123(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "The ˇquick brown
- fox jumps over
- the lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "right", "down"])
- .await;
- cx.assert_shared_state(indoc! {
- "The «quˇ»ick brown
- fox «juˇ»mps over
- the lazy dog
- "
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state(indoc! {
- "ˇThe quick brown
- fox jumps over
- the lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
- cx.assert_shared_state(indoc! {
- "«Tˇ»he quick brown
- «fˇ»ox jumps over
- «tˇ»he lazy dog
- ˇ"
- })
- .await;
-
- cx.simulate_shared_keystrokes(["shift-i", "k", "escape"])
- .await;
- cx.assert_shared_state(indoc! {
- "ˇkThe quick brown
- kfox jumps over
- kthe lazy dog
- k"
- })
- .await;
-
- cx.set_shared_state(indoc! {
- "ˇThe quick brown
- fox jumps over
- the lazy dog
- "
- })
- .await;
- cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await;
- cx.assert_shared_state(indoc! {
- "«Tˇ»he quick brown
- «fˇ»ox jumps over
- «tˇ»he lazy dog
- ˇ"
- })
- .await;
- cx.simulate_shared_keystrokes(["c", "k", "escape"]).await;
- cx.assert_shared_state(indoc! {
- "ˇkhe quick brown
- kox jumps over
- khe lazy dog
- k"
- })
- .await;
- }
-
- #[gpui::test]
- async fn test_visual_object(cx: &mut gpui::TestAppContext) {
- let mut cx = NeovimBackedTestContext::new(cx).await;
-
- cx.set_shared_state("hello (in [parˇens] o)").await;
- cx.simulate_shared_keystrokes(["ctrl-v", "l"]).await;
- cx.simulate_shared_keystrokes(["a", "]"]).await;
- cx.assert_shared_state("hello (in «[parens]ˇ» o)").await;
- assert_eq!(cx.mode(), Mode::Visual);
- cx.simulate_shared_keystrokes(["i", "("]).await;
- cx.assert_shared_state("hello («in [parens] oˇ»)").await;
-
- cx.set_shared_state("hello in a wˇord again.").await;
- cx.simulate_shared_keystrokes(["ctrl-v", "l", "i", "w"])
- .await;
- cx.assert_shared_state("hello in a w«ordˇ» again.").await;
- assert_eq!(cx.mode(), Mode::VisualBlock);
- cx.simulate_shared_keystrokes(["o", "a", "s"]).await;
- cx.assert_shared_state("«ˇhello in a word» again.").await;
- assert_eq!(cx.mode(), Mode::Visual);
- }
-
- #[gpui::test]
- async fn test_mode_across_command(cx: &mut gpui::TestAppContext) {
- let mut cx = VimTestContext::new(cx, true).await;
-
- cx.set_state("aˇbc", Mode::Normal);
- cx.simulate_keystrokes(["ctrl-v"]);
- assert_eq!(cx.mode(), Mode::VisualBlock);
- cx.simulate_keystrokes(["cmd-shift-p", "escape"]);
- assert_eq!(cx.mode(), Mode::VisualBlock);
- }
-}
@@ -1,3 +0,0 @@
-{"Get":{"state":"ˇ","mode":"Normal"}}
-{"Put":{"state":"This is a tesˇt"}}
-{"Get":{"state":"This is a tesˇt","mode":"Normal"}}
@@ -1,6 +0,0 @@
-{"Put":{"state":"The qˇuick"}}
-{"Key":"a"}
-{"Get":{"state":"The quˇick","mode":"Insert"}}
-{"Put":{"state":"The quicˇk"}}
-{"Key":"a"}
-{"Get":{"state":"The quickˇ","mode":"Insert"}}
@@ -1,54 +0,0 @@
-{"Put":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe"}}
-{"Key":"b"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"ˇThe quick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe"}}
-{"Key":"shift-b"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
@@ -1,9 +0,0 @@
-{"Put":{"state":"ˇThe quick\nbrown"}}
-{"Key":"backspace"}
-{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown"}}
-{"Key":"backspace"}
-{"Get":{"state":"The ˇquick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown"}}
-{"Key":"backspace"}
-{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
@@ -1,570 +0,0 @@
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
-{"Key":"1"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
-{"Key":"1"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
-{"Key":"2"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
-{"Key":"2"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
-{"Key":"3"}
-{"Key":"shift-f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n"}}
-{"Key":"3"}
-{"Key":"shift-t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n \nˇb\n","mode":"Normal"}}
@@ -1,24 +0,0 @@
-{"Put":{"state":"ˇ"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The ˇquick"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quˇick\nbrown fox\njumps over"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"The quick\nˇ\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"c"}
-{"Key":"c"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"c"}
-{"Key":"0"}
-{"Get":{"state":"ˇuick\nbrown fox","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"c"}
-{"Key":"0"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}
@@ -1,24 +0,0 @@
-{"Put":{"state":"Teˇst Test"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"ˇst Test","mode":"Insert"}}
-{"Put":{"state":"Test ˇtest"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"ˇtest","mode":"Insert"}}
-{"Put":{"state":"Test1 test2 ˇtest3"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"Test1 ˇtest3","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇtest"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"Test ˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"c"}
-{"Key":"b"}
-{"Get":{"state":"Test ˇ\n\ntest","mode":"Insert"}}
-{"Put":{"state":"Test test-test ˇtest"}}
-{"Key":"c"}
-{"Key":"shift-b"}
-{"Get":{"state":"Test ˇtest","mode":"Insert"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"Teˇst"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"Tˇst","mode":"Insert"}}
-{"Put":{"state":"Tˇest"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"ˇest","mode":"Insert"}}
-{"Put":{"state":"ˇTest"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"ˇTest","mode":"Insert"}}
-{"Put":{"state":"Test\nˇtest"}}
-{"Key":"c"}
-{"Key":"backspace"}
-{"Get":{"state":"Testˇtest","mode":"Insert"}}
@@ -1,23 +0,0 @@
-{"Put":{"state":"ˇabC\n"}}
-{"Key":"~"}
-{"Get":{"state":"AˇbC\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"~"}
-{"Get":{"state":"ABˇc\n","mode":"Normal"}}
-{"Put":{"state":"a😀C«dÉ1*fˇ»\n"}}
-{"Key":"~"}
-{"Get":{"state":"a😀CˇDé1*F\n","mode":"Normal"}}
-{"Key":"~"}
-{"Put":{"state":"aˇC😀é1*F\n"}}
-{"Key":"4"}
-{"Key":"~"}
-{"Get":{"state":"ac😀É1ˇ*F\n","mode":"Normal"}}
-{"Put":{"state":"abˇC\n"}}
-{"Key":"shift-v"}
-{"Key":"~"}
-{"Get":{"state":"ˇABc\n","mode":"Normal"}}
-{"Put":{"state":"ˇaa\nbb\ncc"}}
-{"Key":"ctrl-v"}
-{"Key":"j"}
-{"Key":"~"}
-{"Get":{"state":"ˇAa\nBb\ncc","mode":"Normal"}}
@@ -1,24 +0,0 @@
-{"Put":{"state":"Teˇst Test"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Teˇ Test","mode":"Insert"}}
-{"Put":{"state":"Tˇest test"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Tˇ test","mode":"Insert"}}
-{"Put":{"state":"Test teˇst\ntest"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Test teˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test tesˇt\ntest"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Test tesˇ","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"c"}
-{"Key":"e"}
-{"Get":{"state":"Test test\nˇ","mode":"Insert"}}
-{"Put":{"state":"Test teˇst-test test"}}
-{"Key":"c"}
-{"Key":"shift-e"}
-{"Get":{"state":"Test teˇ test","mode":"Insert"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nˇ"}}
-{"Key":"c"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"c"}
-{"Key":"$"}
-{"Get":{"state":"The qˇ\nbrown fox","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"c"}
-{"Key":"$"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Insert"}}
@@ -1,20 +0,0 @@
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\njumps over\nthe lazy","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over\nthe lazy","mode":"Insert"}}
-{"Put":{"state":"ˇ\nbrown fox\njumps over\nthe lazy"}}
-{"Key":"c"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over\nthe lazy","mode":"Insert"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"Teˇst"}}
-{"Key":"c"}
-{"Key":"h"}
-{"Get":{"state":"Tˇst","mode":"Insert"}}
-{"Put":{"state":"Tˇest"}}
-{"Key":"c"}
-{"Key":"h"}
-{"Get":{"state":"ˇest","mode":"Insert"}}
-{"Put":{"state":"ˇTest"}}
-{"Key":"c"}
-{"Key":"h"}
-{"Get":{"state":"ˇTest","mode":"Insert"}}
-{"Put":{"state":"Test\nˇtest"}}
-{"Key":"c"}
-{"Key":"h"}
-{"Get":{"state":"Test\nˇtest","mode":"Insert"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"c"}
-{"Key":"j"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"c"}
-{"Key":"j"}
-{"Get":{"state":"The quick\nbrown fox\njumps ˇover","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"c"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\nˇ"}}
-{"Key":"c"}
-{"Key":"j"}
-{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"c"}
-{"Key":"k"}
-{"Get":{"state":"ˇ\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"c"}
-{"Key":"k"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"c"}
-{"Key":"k"}
-{"Get":{"state":"The qˇuick\nbrown fox\njumps over","mode":"Normal"}}
-{"Put":{"state":"ˇ\nbrown fox\njumps over"}}
-{"Key":"c"}
-{"Key":"k"}
-{"Get":{"state":"ˇ\nbrown fox\njumps over","mode":"Normal"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":"Teˇst"}}
-{"Key":"c"}
-{"Key":"l"}
-{"Get":{"state":"Teˇt","mode":"Insert"}}
-{"Put":{"state":"Tesˇt"}}
-{"Key":"c"}
-{"Key":"l"}
-{"Get":{"state":"Tesˇ","mode":"Insert"}}
@@ -1,270 +0,0 @@
-{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown?ˇ Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown?ˇFox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇOver the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'\" Brown fox jumps.ˇ "}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" Brown fox jumps.ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" ˇ","mode":"Insert"}}
@@ -1,2380 +0,0 @@
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck broˇ\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck broˇ\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck broˇ\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck broˇ\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck broˇ\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck broˇ\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Insert"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Insert"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Insert"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Insert"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Insert"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
@@ -1,28 +0,0 @@
-{"Put":{"state":"Teˇst"}}
-{"Key":"c"}
-{"Key":"w"}
-{"Get":{"state":"Teˇ","mode":"Insert"}}
-{"Put":{"state":"Tˇest test"}}
-{"Key":"c"}
-{"Key":"w"}
-{"Get":{"state":"Tˇ test","mode":"Insert"}}
-{"Put":{"state":"Testˇ test"}}
-{"Key":"c"}
-{"Key":"w"}
-{"Get":{"state":"Testˇtest","mode":"Insert"}}
-{"Put":{"state":"Test teˇst\ntest"}}
-{"Key":"c"}
-{"Key":"w"}
-{"Get":{"state":"Test teˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test tesˇt\ntest"}}
-{"Key":"c"}
-{"Key":"w"}
-{"Get":{"state":"Test tesˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"c"}
-{"Key":"w"}
-{"Get":{"state":"Test test\nˇ\ntest","mode":"Insert"}}
-{"Put":{"state":"Test teˇst-test test"}}
-{"Key":"c"}
-{"Key":"shift-w"}
-{"Get":{"state":"Test teˇ test","mode":"Insert"}}
@@ -1,460 +0,0 @@
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumpsˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ\n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ\n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-ˇ over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ\n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumpsˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ\n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ\n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n ˇ over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ\n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"c"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumpsˇ\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇ\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-ˇover\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick ˇ\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumpsˇ\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇ\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ over\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n ˇover\nthe lazy dog \n\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"c"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ","mode":"Insert"}}
@@ -1,7 +0,0 @@
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
-{"Key":"4"}
-{"Key":"escape"}
-{"Key":"3"}
-{"Key":"d"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\nfox juˇ over\nthe lazy dog","mode":"Normal"}}
@@ -1,17 +0,0 @@
-{"Put":{"state":"ˇone two three four"}}
-{"Key":"f"}
-{"Key":"o"}
-{"Get":{"state":"one twˇo three four","mode":"Normal"}}
-{"Key":","}
-{"Get":{"state":"ˇone two three four","mode":"Normal"}}
-{"Key":"2"}
-{"Key":";"}
-{"Get":{"state":"one two three fˇour","mode":"Normal"}}
-{"Key":"shift-t"}
-{"Key":"e"}
-{"Get":{"state":"one two threeˇ four","mode":"Normal"}}
-{"Key":"3"}
-{"Key":";"}
-{"Get":{"state":"oneˇ two three four","mode":"Normal"}}
-{"Key":","}
-{"Get":{"state":"one two thˇree four","mode":"Normal"}}
@@ -1,6 +0,0 @@
-{"Put":{"state":"ˇa\nb\nc"}}
-{"Key":":"}
-{"Key":"j"}
-{"Key":"enter"}
-{"Key":"^"}
-{"Get":{"state":"ˇa b\nc","mode":"Normal"}}
@@ -1,5 +0,0 @@
-{"Put":{"state":"ˇa\nb\nc"}}
-{"Key":":"}
-{"Key":"3"}
-{"Key":"enter"}
-{"Get":{"state":"a\nb\nˇc","mode":"Normal"}}
@@ -1,22 +0,0 @@
-{"Put":{"state":"ˇa\nb\nc"}}
-{"Key":":"}
-{"Key":"%"}
-{"Key":"s"}
-{"Key":"/"}
-{"Key":"b"}
-{"Key":"/"}
-{"Key":"d"}
-{"Key":"enter"}
-{"Get":{"state":"a\nˇd\nc","mode":"Normal"}}
-{"Key":":"}
-{"Key":"%"}
-{"Key":"s"}
-{"Key":":"}
-{"Key":"."}
-{"Key":":"}
-{"Key":"\\"}
-{"Key":"0"}
-{"Key":"\\"}
-{"Key":"0"}
-{"Key":"enter"}
-{"Get":{"state":"aa\ndd\nˇcc","mode":"Normal"}}
@@ -1,11 +0,0 @@
-{"Put":{"state":"ˇa\nb\na\nc"}}
-{"Key":":"}
-{"Key":"/"}
-{"Key":"b"}
-{"Key":"enter"}
-{"Get":{"state":"a\nˇb\na\nc","mode":"Normal"}}
-{"Key":":"}
-{"Key":"?"}
-{"Key":"a"}
-{"Key":"enter"}
-{"Get":{"state":"ˇa\nb\na\nc","mode":"Normal"}}
@@ -1,22 +0,0 @@
-{"SetOption":{"value":"scrolloff=3"}}
-{"SetOption":{"value":"lines=12"}}
-{"Put":{"state":"ˇaa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz"}}
-{"Key":"4"}
-{"Key":"j"}
-{"Key":"ctrl-d"}
-{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\nˇjj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
-{"Key":"ctrl-d"}
-{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\nˇoo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
-{"Key":"g"}
-{"Key":"g"}
-{"Key":"ctrl-d"}
-{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nˇii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
-{"Key":"ctrl-u"}
-{"Get":{"state":"aa\nbb\ncc\nˇdd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
-{"Key":"ctrl-d"}
-{"Key":"ctrl-d"}
-{"Key":"4"}
-{"Key":"j"}
-{"Key":"ctrl-u"}
-{"Key":"ctrl-u"}
-{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nˇhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
@@ -1,24 +0,0 @@
-{"Put":{"state":"ˇ"}}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"ˇ","mode":"Normal"}}
-{"Put":{"state":"The ˇquick"}}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"ˇ","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"brownˇ fox\njumps over","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"The quick\njumps ˇover","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"The quick\nbrown ˇfox","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"The quick\nˇbrown fox","mode":"Normal"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"d"}
-{"Key":"0"}
-{"Get":{"state":"ˇuick\nbrown fox","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"d"}
-{"Key":"0"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Normal"}}
@@ -1,24 +0,0 @@
-{"Put":{"state":"Teˇst Test"}}
-{"Key":"d"}
-{"Key":"b"}
-{"Get":{"state":"ˇst Test","mode":"Normal"}}
-{"Put":{"state":"Test ˇtest"}}
-{"Key":"d"}
-{"Key":"b"}
-{"Get":{"state":"ˇtest","mode":"Normal"}}
-{"Put":{"state":"Test1 test2 ˇtest3"}}
-{"Key":"d"}
-{"Key":"b"}
-{"Get":{"state":"Test1 ˇtest3","mode":"Normal"}}
-{"Put":{"state":"Test test\nˇtest"}}
-{"Key":"d"}
-{"Key":"b"}
-{"Get":{"state":"Testˇ \ntest","mode":"Normal"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"d"}
-{"Key":"b"}
-{"Get":{"state":"Testˇ \n\ntest","mode":"Normal"}}
-{"Put":{"state":"Test test-test ˇtest"}}
-{"Key":"d"}
-{"Key":"shift-b"}
-{"Get":{"state":"Test ˇtest","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"d"}
-{"Key":"shift-g"}
-{"Get":{"state":"The qˇuick","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"d"}
-{"Key":"shift-g"}
-{"Get":{"state":"The qˇuick","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
-{"Key":"d"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nbrown fox\njumpsˇ over","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nˇ"}}
-{"Key":"d"}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\nbrown fox\nˇjumps over","mode":"Normal"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"d"}
-{"Key":"$"}
-{"Get":{"state":"The ˇq\nbrown fox","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"d"}
-{"Key":"$"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Normal"}}
@@ -1,20 +0,0 @@
-{"Put":{"state":"The quick\nbrownˇ fox\njumps over\nthe lazy"}}
-{"Key":"d"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"jumpsˇ over\nthe lazy","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\njumps over\nthe lˇazy"}}
-{"Key":"d"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over\nthe lazy"}}
-{"Key":"d"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"brownˇ fox\njumps over\nthe lazy","mode":"Normal"}}
-{"Put":{"state":"ˇ\nbrown fox\njumps over\nthe lazy"}}
-{"Key":"d"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇbrown fox\njumps over\nthe lazy","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"Teˇst"}}
-{"Key":"d"}
-{"Key":"h"}
-{"Get":{"state":"Tˇst","mode":"Normal"}}
-{"Put":{"state":"Tˇest"}}
-{"Key":"d"}
-{"Key":"h"}
-{"Get":{"state":"ˇest","mode":"Normal"}}
-{"Put":{"state":"ˇTest"}}
-{"Key":"d"}
-{"Key":"h"}
-{"Get":{"state":"ˇTest","mode":"Normal"}}
-{"Put":{"state":"Test\nˇtest"}}
-{"Key":"d"}
-{"Key":"h"}
-{"Get":{"state":"Test\nˇtest","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"d"}
-{"Key":"j"}
-{"Get":{"state":"The quˇick","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"d"}
-{"Key":"j"}
-{"Get":{"state":"The quick\nbrown fox\njumps ˇover","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"d"}
-{"Key":"j"}
-{"Get":{"state":"jumpsˇ over","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\nˇ"}}
-{"Key":"d"}
-{"Key":"j"}
-{"Get":{"state":"The quick\nbrown fox\nˇ","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"d"}
-{"Key":"k"}
-{"Get":{"state":"jumps ˇover","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"d"}
-{"Key":"k"}
-{"Get":{"state":"The quˇick","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"d"}
-{"Key":"k"}
-{"Get":{"state":"The qˇuick\nbrown fox\njumps over","mode":"Normal"}}
-{"Put":{"state":"ˇbrown fox\njumps over"}}
-{"Key":"d"}
-{"Key":"k"}
-{"Get":{"state":"ˇbrown fox\njumps over","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"ˇTest"}}
-{"Key":"d"}
-{"Key":"l"}
-{"Get":{"state":"ˇest","mode":"Normal"}}
-{"Put":{"state":"Teˇst"}}
-{"Key":"d"}
-{"Key":"l"}
-{"Get":{"state":"Teˇt","mode":"Normal"}}
-{"Put":{"state":"Tesˇt"}}
-{"Key":"d"}
-{"Key":"l"}
-{"Get":{"state":"Teˇs","mode":"Normal"}}
-{"Put":{"state":"Tesˇt\ntest"}}
-{"Key":"d"}
-{"Key":"l"}
-{"Get":{"state":"Teˇs\ntest","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"ˇTest"}}
-{"Key":"shift-x"}
-{"Get":{"state":"ˇTest","mode":"Normal"}}
-{"Put":{"state":"Tˇest"}}
-{"Key":"shift-x"}
-{"Get":{"state":"ˇest","mode":"Normal"}}
-{"Put":{"state":"Teˇst"}}
-{"Key":"shift-x"}
-{"Get":{"state":"Tˇst","mode":"Normal"}}
-{"Put":{"state":"Tesˇt"}}
-{"Key":"shift-x"}
-{"Get":{"state":"Teˇt","mode":"Normal"}}
-{"Put":{"state":"Test\nˇtest"}}
-{"Key":"shift-x"}
-{"Get":{"state":"Test\nˇtest","mode":"Normal"}}
@@ -1,12 +0,0 @@
-{"Put":{"state":"Test teˇst\ntest"}}
-{"Key":"d"}
-{"Key":"e"}
-{"Get":{"state":"Test tˇe\ntest","mode":"Normal"}}
-{"Put":{"state":"Test tesˇt\ntest"}}
-{"Key":"d"}
-{"Key":"e"}
-{"Get":{"state":"Test teˇs","mode":"Normal"}}
-{"Put":{"state":"Test teˇst-test test"}}
-{"Key":"d"}
-{"Key":"shift-e"}
-{"Get":{"state":"Test teˇ test","mode":"Normal"}}
@@ -1,270 +0,0 @@
-{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Fox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown?ˇ Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown?ˇFox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇ Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇOver the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"ˇ Brown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" ˇ ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'\" Brown fox jumps.ˇ "}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\" Brown fox jumpsˇ.","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown? Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown? Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ? Fox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇFox Jumps! Over the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? ˇFox Jumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jˇumps! Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumpsˇ! Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? ˇOver the lazy.","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps!ˇ Over the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Ovˇer the lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over theˇ lazy."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
-{"Put":{"state":"The quick brown? Fox Jumps! Over the lazyˇ."}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown.)]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ.)]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)ˇ]'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]ˇ'\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'ˇ\" Brown fox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"ˇBrown fox jumps. ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'\" Brown ˇfox jumps. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\"ˇ ","mode":"Normal"}}
-{"Put":{"state":"The quick brown.)]'\" Brown fox jumpsˇ. "}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"The quick brown.)]'\"ˇ ","mode":"Normal"}}
@@ -1,2372 +0,0 @@
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''ˇ'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'ˇ'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'ˇ'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇ'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck brˇo\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'ˇe ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ˇ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Thˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e 'ˇ'qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''ˇqui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''quˇi'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e 'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ˇck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''quiˇwn'\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck broˇ'wn'\n'fox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck brˇo\n'fox jumps ov'er\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'ˇfox jumps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox juˇmps ov'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ovˇ'er\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\nˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'ˇer\nthe lazy d'o'g","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe ˇlazy d'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇ'o'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'ˇo'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'oˇ'g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"'"}
-{"Get":{"state":"Th'e ''qui'ck bro'wn'\n'fox jumps ov'er\nthe lazy d'o'ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``ˇ`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`ˇ`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`ˇ`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇ`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck brˇo\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`ˇe ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ˇ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Thˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e `ˇ`qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``ˇqui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``quˇi`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e `ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ˇck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``quiˇwn`\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck broˇ`wn`\n`fox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck brˇo\n`fox jumps ov`er\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`ˇfox jumps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox juˇmps ov`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ovˇ`er\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\nˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`ˇer\nthe lazy d`o`g","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe ˇlazy d`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇ`o`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`ˇo`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`oˇ`g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"`"}
-{"Get":{"state":"Th`e ``qui`ck bro`wn`\n`fox jumps ov`er\nthe lazy d`o`ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"ˇ\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"ˇ\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇ\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇ\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck brˇo\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"ˇe \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e ˇ\"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Thˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"ˇ\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"ˇqui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"quˇi\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ˇck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"quiˇwn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck broˇ\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck brˇo\n\"fox jumps ov\"er\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"ˇfox jumps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox juˇmps ov\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ovˇ\"er\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\nˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"ˇer\nthe lazy d\"o\"g","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe ˇlazy d\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇ\"o\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"ˇo\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"oˇ\"g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy dˇg","mode":"Normal"}}
-{"Put":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"Th\"e \"\"qui\"ck bro\"wn\"\n\"fox jumps ov\"er\nthe lazy d\"o\"ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇ)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"("}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"ˇTh)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)ˇe ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ˇ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e (ˇ)qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()ˇqui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()quˇi(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ˇck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck broˇ)wn(\n)fox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()quiˇwn(\n)fox jumps ov(er\nthe lazy d)o(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)ˇfox jumps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox juˇmps ov(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇ(er\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(ˇer\nthe lazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe ˇlazy d)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy dˇ)o(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ovˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)ˇo(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)oˇ(g","mode":"Normal"}}
-{"Put":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":")"}
-{"Get":{"state":"Th)e ()qui(ck bro)wn(\n)fox jumps ov(er\nthe lazy d)o(ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇ]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"["}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"ˇTh]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]ˇe []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e ˇ[]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e [ˇ]qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []ˇqui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []quˇi[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ˇck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck broˇ]wn[\n]fox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []quiˇwn[\n]fox jumps ov[er\nthe lazy d]o[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]ˇfox jumps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox juˇmps ov[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇ[er\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[ˇer\nthe lazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe ˇlazy d]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy dˇ]o[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ovˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]ˇo[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]oˇ[g","mode":"Normal"}}
-{"Put":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"Th]e []qui[ck bro]wn[\n]fox jumps ov[er\nthe lazy d]o[ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇ}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"{"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"ˇTh}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}ˇe {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e ˇ{}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {ˇ}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}ˇqui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}quˇi{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ˇck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck broˇ}wn{\n}fox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}quiˇwn{\n}fox jumps ov{er\nthe lazy d}o{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}ˇfox jumps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox juˇmps ov{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇ{er\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{ˇer\nthe lazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe ˇlazy d}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy dˇ}o{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ovˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}ˇo{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}oˇ{g","mode":"Normal"}}
-{"Put":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"}"}
-{"Get":{"state":"Th}e {}qui{ck bro}wn{\n}fox jumps ov{er\nthe lazy d}o{ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇ>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"<"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
-{"Put":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"ˇTh>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>ˇe <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e ˇ<>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <ˇ>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>ˇqui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>quˇi<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ˇck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck broˇ>wn<\n>fox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>quiˇwn<\n>fox jumps ov<er\nthe lazy d>o<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>ˇfox jumps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox juˇmps ov<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇ<er\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<ˇer\nthe lazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe ˇlazy d>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy dˇ>o<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ovˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>ˇo<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>oˇ<g","mode":"Normal"}}
-{"Put":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":">"}
-{"Get":{"state":"Th>e <>qui<ck bro>wn<\n>fox jumps ov<er\nthe lazy d>o<ˇg","mode":"Normal"}}
@@ -1,6 +0,0 @@
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"shift-d"}
-{"Get":{"state":"The ˇq\nbrown fox","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"shift-d"}
-{"Get":{"state":"The quick\nˇ\nbrown fox","mode":"Normal"}}
@@ -1,28 +0,0 @@
-{"Put":{"state":"Test tesˇt\n test"}}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"Test teˇs\n test","mode":"Normal"}}
-{"Put":{"state":"Teˇst"}}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"Tˇe","mode":"Normal"}}
-{"Put":{"state":"Tˇest test"}}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"Tˇtest","mode":"Normal"}}
-{"Put":{"state":"Test teˇst\ntest"}}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"Test tˇe\ntest","mode":"Normal"}}
-{"Put":{"state":"Test tesˇt\ntest"}}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"Test teˇs\ntest","mode":"Normal"}}
-{"Put":{"state":"Test test\nˇ\ntest"}}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"Test test\nˇtest","mode":"Normal"}}
-{"Put":{"state":"Test teˇst-test test"}}
-{"Key":"d"}
-{"Key":"shift-w"}
-{"Get":{"state":"Test teˇtest","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"d"}
-{"Key":"2"}
-{"Key":"d"}
-{"Get":{"state":"the ˇlazy dog","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"2"}
-{"Key":"d"}
-{"Key":"d"}
-{"Get":{"state":"the ˇlazy dog","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe moon,\na star, and\nthe lazy dog"}}
-{"Key":"2"}
-{"Key":"d"}
-{"Key":"2"}
-{"Key":"d"}
-{"Get":{"state":"the ˇlazy dog","mode":"Normal"}}
@@ -1,460 +0,0 @@
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick browˇn\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumpsˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick browˇn\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ\n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ\n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-ˇ over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy doˇg\n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick ˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick browˇn\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumpsˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick browˇn\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ\n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ\n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇfox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n ˇ over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy doˇg\n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumpˇs\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-ˇover\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy doˇg\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nˇthe lazy dog ","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quickˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brownˇ jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox ˇover\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumpˇs\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy doˇg\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ over\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n ˇover\nthe lazy dog \n\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy doˇg\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"d"}
-{"Key":"a"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nˇthe lazy dog ","mode":"Normal"}}
@@ -1,38 +0,0 @@
-{"Put":{"state":"ˇhello"}}
-{"Key":"o"}
-{"Key":"w"}
-{"Key":"o"}
-{"Key":"r"}
-{"Key":"l"}
-{"Key":"d"}
-{"Key":"escape"}
-{"Get":{"state":"hello\nworlˇd","mode":"Normal"}}
-{"Key":"."}
-{"Get":{"state":"hello\nworld\nworlˇd","mode":"Normal"}}
-{"Key":"^"}
-{"Key":"d"}
-{"Key":"f"}
-{"Key":"o"}
-{"Key":"g"}
-{"Key":"g"}
-{"Key":"."}
-{"Get":{"state":"ˇ\nworld\nrld","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"y"}
-{"Key":"y"}
-{"Key":"p"}
-{"Key":"shift-g"}
-{"Key":"y"}
-{"Key":"y"}
-{"Key":"."}
-{"Get":{"state":"\nworld\nworld\nrld\nˇrld","mode":"Normal"}}
-{"Put":{"state":"ˇthe quick brown fox"}}
-{"Key":"2"}
-{"Key":"~"}
-{"Key":"."}
-{"Put":{"state":"THE ˇquick brown fox"}}
-{"Key":"3"}
-{"Key":"."}
-{"Put":{"state":"THE QUIˇck brown fox"}}
-{"Key":"."}
-{"Get":{"state":"THE QUICK ˇbrown fox","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrown fox jumps\nover ˇthe lazy dog","mode":"Normal"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrown fox jumps\nover ˇthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick\n\nbrown fox jumps\nover the laˇzy dog"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrown fox jumps\nover the laˇzy dog","mode":"Normal"}}
-{"Put":{"state":"\n\nbrown fox jumps\nover the laˇzy dog"}}
-{"Key":"shift-g"}
-{"Get":{"state":"\n\nbrown fox jumps\nover the laˇzy dog","mode":"Normal"}}
-{"Put":{"state":"ˇ\n\nbrown fox jumps\nover the lazydog"}}
-{"Key":"2"}
-{"Key":"shift-g"}
-{"Get":{"state":"\nˇ\nbrown fox jumps\nover the lazydog","mode":"Normal"}}
@@ -1,32 +0,0 @@
-{"Put":{"state":"Thˇe quick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"e"}
-{"Get":{"state":"The quicˇk-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"e"}
-{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"e"}
-{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumpˇs over\nthe","mode":"Normal"}}
-{"Key":"e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps oveˇr\nthe","mode":"Normal"}}
-{"Key":"e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
-{"Key":"e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
-{"Put":{"state":"Thˇe quick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quicˇk-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumpˇs over\nthe","mode":"Normal"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps oveˇr\nthe","mode":"Normal"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
-{"Key":"shift-e"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
@@ -1,11 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\nfox jumps"}}
-{"Key":"enter"}
-{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
-{"Put":{"state":"The qˇuick brown\nfox jumps"}}
-{"Key":"enter"}
-{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
-{"Put":{"state":"The quick broˇwn\nfox jumps"}}
-{"Key":"enter"}
-{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
-{"Key":"enter"}
-{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"VisualLine"}}
-{"Key":"x"}
-{"Get":{"state":"fox ˇjumps over\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"a\nˇ\nb"}}
-{"Key":"shift-v"}
-{"Get":{"state":"a\n«\nˇ»b","mode":"VisualLine"}}
-{"Key":"x"}
-{"Get":{"state":"a\nˇb","mode":"Normal"}}
-{"Put":{"state":"a\nb\nˇ"}}
-{"Key":"shift-v"}
-{"Get":{"state":"a\nb\nˇ","mode":"VisualLine"}}
-{"Key":"x"}
-{"Get":{"state":"a\nˇb","mode":"Normal"}}
@@ -1,20 +0,0 @@
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"Visual"}}
-{"Key":"w"}
-{"Key":"j"}
-{"Get":{"state":"The «quick brown\nfox jumps oˇ»ver\nthe lazy dog","mode":"Visual"}}
-{"Key":"escape"}
-{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog","mode":"Normal"}}
-{"Key":"v"}
-{"Key":"k"}
-{"Key":"b"}
-{"Get":{"state":"The «ˇquick brown\nfox jumps o»ver\nthe lazy dog","mode":"Visual"}}
-{"Put":{"state":"a\nˇ\nb\n"}}
-{"Key":"v"}
-{"Get":{"state":"a\n«\nˇ»b\n","mode":"Visual"}}
-{"Key":"v"}
-{"Get":{"state":"a\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"a\nb\nˇ"}}
-{"Key":"v"}
-{"Get":{"state":"a\nb\nˇ","mode":"Visual"}}
@@ -1,557 +0,0 @@
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
-{"Key":"1"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaˇab b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n ˇ baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
-{"Key":"1"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
-{"Key":"2"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaaˇ bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
-{"Key":"2"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
-{"Key":"3"}
-{"Key":"f"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇ bb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ bb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaˇabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b ˇbb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bˇb aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bbˇ aaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aˇaabaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaaˇbaaa\n baaa bbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\nˇ baaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n ˇbaaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n bˇaaa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaˇa bbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa ˇbbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bˇbb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbˇb\n\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\nˇ\nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n"}}
-{"Key":"3"}
-{"Key":"t"}
-{"Key":"b"}
-{"Get":{"state":"aaab b bb aaabaaa\n baaa bbb\n\nˇb\n","mode":"Normal"}}
@@ -1,23 +0,0 @@
-{"SetOption":{"value":"foldmethod=manual"}}
-{"Put":{"state":"fn boop() {\n ˇbarp()\n bazp()\n}\n"}}
-{"Key":"shift-v"}
-{"Key":"j"}
-{"Key":"z"}
-{"Key":"f"}
-{"Key":"escape"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇfn boop() {\n barp()\n bazp()\n}\n","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"j"}
-{"Get":{"state":"fn boop() {\n barp()\n bazp()\nˇ}\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"k"}
-{"Get":{"state":"ˇfn boop() {\n barp()\n bazp()\n}\n","mode":"Normal"}}
-{"Key":"down"}
-{"Key":"y"}
-{"Key":"y"}
-{"ReadRegister":{"name":"\"","value":" barp()\n bazp()\n"}}
-{"Key":"z"}
-{"Key":"o"}
-{"Get":{"state":"fn boop() {\nˇ barp()\n bazp()\n}\n","mode":"Normal"}}
@@ -1,13 +0,0 @@
-{"SetOption":{"value":"foldmethod=manual"}}
-{"Put":{"state":"fn boop() {\n ˇbarp()\n bazp()\n}\n"}}
-{"Key":"shift-v"}
-{"Key":"j"}
-{"Key":"z"}
-{"Key":"f"}
-{"Key":"escape"}
-{"Key":"g"}
-{"Key":"g"}
-{"Key":"5"}
-{"Key":"d"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Normal"}}
@@ -1,21 +0,0 @@
-{"Put":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog"}}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick\n\nbrown fox jumps\nover ˇthe lazy dog"}}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"The qˇuick\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick\n\nbrown fox jumps\nover the laˇzy dog"}}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"The quicˇk\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
-{"Put":{"state":"\n\nbrown fox jumps\nover the laˇzy dog"}}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"ˇ\n\nbrown fox jumps\nover the lazy dog","mode":"Normal"}}
-{"Put":{"state":"ˇ\n\nbrown fox jumps\nover the lazydog"}}
-{"Key":"2"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"\nˇ\nbrown fox jumps\nover the lazydog","mode":"Normal"}}
@@ -1,9 +0,0 @@
-{"Put":{"state":"ˇThe quick\nbrown"}}
-{"Key":"h"}
-{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown"}}
-{"Key":"h"}
-{"Get":{"state":"The ˇquick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown"}}
-{"Key":"h"}
-{"Get":{"state":"The quick\nˇbrown","mode":"Normal"}}
@@ -1,12 +0,0 @@
-{"Put":{"state":"Testˇ├──┐Test"}}
-{"Key":"h"}
-{"Get":{"state":"Tesˇt├──┐Test","mode":"Normal"}}
-{"Put":{"state":"Test├ˇ──┐Test"}}
-{"Key":"h"}
-{"Get":{"state":"Testˇ├──┐Test","mode":"Normal"}}
-{"Put":{"state":"Test├──ˇ┐Test"}}
-{"Key":"h"}
-{"Get":{"state":"Test├─ˇ─┐Test","mode":"Normal"}}
-{"Put":{"state":"Test├──┐ˇTest"}}
-{"Key":"h"}
-{"Get":{"state":"Test├──ˇ┐Test","mode":"Normal"}}
@@ -1,16 +0,0 @@
-{"Put":{"state":"1ˇ2\n"}}
-{"Key":"ctrl-a"}
-{"Get":{"state":"1ˇ3\n","mode":"Normal"}}
-{"Key":"ctrl-x"}
-{"Get":{"state":"1ˇ2\n","mode":"Normal"}}
-{"Key":"9"}
-{"Key":"9"}
-{"Key":"ctrl-a"}
-{"Get":{"state":"11ˇ1\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"1"}
-{"Key":"1"}
-{"Key":"ctrl-x"}
-{"Get":{"state":"ˇ0\n","mode":"Normal"}}
-{"Key":"."}
-{"Get":{"state":"-11ˇ1\n","mode":"Normal"}}
@@ -1,18 +0,0 @@
-{"Put":{"state":"ˇ total: 0xff"}}
-{"Key":"ctrl-a"}
-{"Get":{"state":" total: 0x10ˇ0","mode":"Normal"}}
-{"Put":{"state":"ˇ total: 0xff"}}
-{"Key":"ctrl-x"}
-{"Get":{"state":" total: 0xfˇe","mode":"Normal"}}
-{"Put":{"state":"ˇ total: 0xFF"}}
-{"Key":"ctrl-x"}
-{"Get":{"state":" total: 0xFˇE","mode":"Normal"}}
-{"Put":{"state":"(ˇ0b10f)"}}
-{"Key":"ctrl-a"}
-{"Get":{"state":"(0b1ˇ1f)","mode":"Normal"}}
-{"Put":{"state":"ˇ-1"}}
-{"Key":"ctrl-a"}
-{"Get":{"state":"ˇ0","mode":"Normal"}}
-{"Put":{"state":"banˇana"}}
-{"Key":"ctrl-a"}
-{"Get":{"state":"banˇana","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"ˇ1\n1\n1 2\n1\n1"}}
-{"Key":"j"}
-{"Key":"v"}
-{"Key":"shift-g"}
-{"Key":"g"}
-{"Key":"ctrl-a"}
-{"Get":{"state":"1\nˇ2\n3 2\n4\n5","mode":"Normal"}}
-{"Key":"shift-g"}
-{"Key":"ctrl-v"}
-{"Key":"g"}
-{"Key":"g"}
-{"Get":{"state":"«1ˇ»\n«2ˇ»\n«3ˇ» 2\n«4ˇ»\n«5ˇ»","mode":"VisualBlock"}}
-{"Key":"g"}
-{"Key":"ctrl-x"}
-{"Get":{"state":"ˇ0\n0\n0 2\n0\n0","mode":"Normal"}}
@@ -1,9 +0,0 @@
-{"Put":{"state":"ˇ\nThe quick\nbrown fox "}}
-{"Key":"shift-a"}
-{"Get":{"state":"ˇ\nThe quick\nbrown fox ","mode":"Insert"}}
-{"Put":{"state":"\nThe qˇuick\nbrown fox "}}
-{"Key":"shift-a"}
-{"Get":{"state":"\nThe quickˇ\nbrown fox ","mode":"Insert"}}
-{"Put":{"state":"\nThe quick\nbrown ˇfox "}}
-{"Key":"shift-a"}
-{"Get":{"state":"\nThe quick\nbrown fox ˇ","mode":"Insert"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"The qˇuick"}}
-{"Key":"shift-i"}
-{"Get":{"state":"ˇThe quick","mode":"Insert"}}
-{"Put":{"state":" The qˇuick"}}
-{"Key":"shift-i"}
-{"Get":{"state":" ˇThe quick","mode":"Insert"}}
-{"Put":{"state":"ˇ"}}
-{"Key":"shift-i"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"shift-i"}
-{"Get":{"state":"ˇThe quick\nbrown fox","mode":"Insert"}}
-{"Put":{"state":"ˇ\nThe quick"}}
-{"Key":"shift-i"}
-{"Get":{"state":"ˇ\nThe quick","mode":"Insert"}}
@@ -1,18 +0,0 @@
-{"Put":{"state":"ˇ"}}
-{"Key":"shift-o"}
-{"Get":{"state":"ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The ˇquick"}}
-{"Key":"shift-o"}
-{"Get":{"state":"ˇ\nThe quick","mode":"Insert"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"shift-o"}
-{"Get":{"state":"ˇ\nThe quick\nbrown fox\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"shift-o"}
-{"Get":{"state":"The quick\nˇ\nbrown fox\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"shift-o"}
-{"Get":{"state":"The quick\nbrown fox\nˇ\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"shift-o"}
-{"Get":{"state":"The quick\nˇ\n\nbrown fox","mode":"Insert"}}
@@ -1,36 +0,0 @@
-{"Put":{"state":"ˇhello\n"}}
-{"Key":"5"}
-{"Key":"i"}
-{"Key":"-"}
-{"Key":"escape"}
-{"Get":{"state":"----ˇ-hello\n","mode":"Normal"}}
-{"Put":{"state":"ˇhello\n"}}
-{"Key":"5"}
-{"Key":"a"}
-{"Key":"-"}
-{"Key":"escape"}
-{"Get":{"state":"h----ˇ-ello\n","mode":"Normal"}}
-{"Key":"4"}
-{"Key":"shift-i"}
-{"Key":"-"}
-{"Key":"escape"}
-{"Get":{"state":"---ˇ-h-----ello\n","mode":"Normal"}}
-{"Key":"3"}
-{"Key":"shift-a"}
-{"Key":"-"}
-{"Key":"escape"}
-{"Get":{"state":"----h-----ello--ˇ-\n","mode":"Normal"}}
-{"Put":{"state":"ˇhello\n"}}
-{"Key":"3"}
-{"Key":"o"}
-{"Key":"o"}
-{"Key":"i"}
-{"Key":"escape"}
-{"Get":{"state":"hello\noi\noi\noˇi\n","mode":"Normal"}}
-{"Put":{"state":"ˇhello\n"}}
-{"Key":"3"}
-{"Key":"shift-o"}
-{"Key":"o"}
-{"Key":"i"}
-{"Key":"escape"}
-{"Get":{"state":"oi\noi\noˇi\nhello\n","mode":"Normal"}}
@@ -1,23 +0,0 @@
-{"Put":{"state":"ˇhello\n"}}
-{"Key":"3"}
-{"Key":"i"}
-{"Key":"-"}
-{"Key":"escape"}
-{"Get":{"state":"--ˇ-hello\n","mode":"Normal"}}
-{"Key":"."}
-{"Get":{"state":"----ˇ--hello\n","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"."}
-{"Get":{"state":"-----ˇ---hello\n","mode":"Normal"}}
-{"Put":{"state":"ˇhello\n"}}
-{"Key":"2"}
-{"Key":"o"}
-{"Key":"k"}
-{"Key":"k"}
-{"Key":"escape"}
-{"Get":{"state":"hello\nkk\nkˇk\n","mode":"Normal"}}
-{"Key":"."}
-{"Get":{"state":"hello\nkk\nkk\nkk\nkˇk\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"."}
-{"Get":{"state":"hello\nkk\nkk\nkk\nkk\nkˇk\n","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"aaˇaa\n😃😃"}}
-{"Key":"j"}
-{"Get":{"state":"aaaa\n😃ˇ😃","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\nfox jumps"}}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
-{"Put":{"state":"The qˇuick brown\nfox jumps"}}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nfox jˇumps","mode":"Normal"}}
-{"Put":{"state":"The quick broˇwn\nfox jumps"}}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nfox jumpˇs","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nˇfox jumps"}}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}
@@ -1,13 +0,0 @@
-{"Put":{"state":"ˇone\ntwo\nthree\nfour\nfive\nsix\n"}}
-{"Key":"shift-j"}
-{"Get":{"state":"oneˇ two\nthree\nfour\nfive\nsix\n","mode":"Normal"}}
-{"Key":"3"}
-{"Key":"shift-j"}
-{"Get":{"state":"one two threeˇ four\nfive\nsix\n","mode":"Normal"}}
-{"Put":{"state":"ˇone\ntwo\nthree\nfour\nfive\nsix\n"}}
-{"Key":"j"}
-{"Key":"v"}
-{"Key":"3"}
-{"Key":"j"}
-{"Key":"shift-j"}
-{"Get":{"state":"one\ntwo three fourˇ five\nsix\n","mode":"Normal"}}
@@ -1,14 +0,0 @@
-{"Put":{"state":"The ˇquick\n\nbrown fox jumps\nover the lazy dog"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrown fox jumps\noverˇ the lazy dog","mode":"Normal"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrown fox jumps\noverˇ the lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick\n\nbrown fox jumps\nover the lazy doˇg"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrown fox jumps\nover the lazy doˇg","mode":"Normal"}}
-{"Put":{"state":"The quiˇck\n\nbrown"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nbrowˇn","mode":"Normal"}}
-{"Put":{"state":"The quiˇck\n\n"}}
-{"Key":"shift-g"}
-{"Get":{"state":"The quick\n\nˇ","mode":"Normal"}}
@@ -1,18 +0,0 @@
-{"Put":{"state":"The qˇuick"}}
-{"Key":"^"}
-{"Get":{"state":"ˇThe quick","mode":"Normal"}}
-{"Put":{"state":" The qˇuick"}}
-{"Key":"^"}
-{"Get":{"state":" ˇThe quick","mode":"Normal"}}
-{"Put":{"state":"ˇ"}}
-{"Key":"^"}
-{"Get":{"state":"ˇ","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox"}}
-{"Key":"^"}
-{"Get":{"state":"ˇThe quick\nbrown fox","mode":"Normal"}}
-{"Put":{"state":"ˇ\nThe quick"}}
-{"Key":"^"}
-{"Get":{"state":"ˇ\nThe quick","mode":"Normal"}}
-{"Put":{"state":" ˇ \nThe quick"}}
-{"Key":"^"}
-{"Get":{"state":" ˇ \nThe quick","mode":"Normal"}}
@@ -1,28 +0,0 @@
-{"Put":{"state":"ˇThe quick\nbrown"}}
-{"Key":"$"}
-{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown"}}
-{"Key":"$"}
-{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
-{"Key":"$"}
-{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown"}}
-{"Key":"$"}
-{"Get":{"state":"The quick\nbrowˇn","mode":"Normal"}}
-{"Key":"$"}
-{"Get":{"state":"The quick\nbrowˇn","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick\nbrown"}}
-{"Key":"0"}
-{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown"}}
-{"Key":"0"}
-{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quicˇk\nbrown"}}
-{"Key":"0"}
-{"Get":{"state":"ˇThe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown"}}
-{"Key":"0"}
-{"Get":{"state":"The quick\nˇbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrowˇn"}}
-{"Key":"0"}
-{"Get":{"state":"The quick\nˇbrown","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"ˇThe quick\nbrown fox jumps"}}
-{"Key":"k"}
-{"Get":{"state":"ˇThe quick\nbrown fox jumps","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown fox jumps"}}
-{"Key":"k"}
-{"Get":{"state":"The qˇuick\nbrown fox jumps","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown fox jumps"}}
-{"Key":"k"}
-{"Get":{"state":"ˇThe quick\nbrown fox jumps","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fˇox jumps"}}
-{"Key":"k"}
-{"Get":{"state":"The quiˇck\nbrown fox jumps","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrown fox jumˇps"}}
-{"Key":"k"}
-{"Get":{"state":"The quicˇk\nbrown fox jumps","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"ˇThe quick\nbrown"}}
-{"Key":"l"}
-{"Get":{"state":"Tˇhe quick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The qˇuick\nbrown"}}
-{"Key":"l"}
-{"Get":{"state":"The quˇick\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quicˇk\nbrown"}}
-{"Key":"l"}
-{"Get":{"state":"The quicˇk\nbrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nˇbrown"}}
-{"Key":"l"}
-{"Get":{"state":"The quick\nbˇrown","mode":"Normal"}}
-{"Put":{"state":"The quick\nbrowˇn"}}
-{"Key":"l"}
-{"Get":{"state":"The quick\nbrowˇn","mode":"Normal"}}
@@ -1,17 +0,0 @@
-{"Put":{"state":"func ˇ(a string) {\n do(something(with<Types>.and_arrays[0, 2]))\n}"}}
-{"Key":"%"}
-{"Get":{"state":"func (a stringˇ) {\n do(something(with<Types>.and_arrays[0, 2]))\n}","mode":"Normal"}}
-{"Put":{"state":"func (a string) ˇ{\ndo(something(with<Types>.and_arrays[0, 2]))\n}"}}
-{"Key":"%"}
-{"Get":{"state":"func (a string) {\ndo(something(with<Types>.and_arrays[0, 2]))\nˇ}","mode":"Normal"}}
-{"Put":{"state":"ˇ{()}"}}
-{"Key":"%"}
-{"Get":{"state":"{()ˇ}","mode":"Normal"}}
-{"Key":"%"}
-{"Get":{"state":"ˇ{()}","mode":"Normal"}}
-{"Put":{"state":"{\n ˇ{()}\n}"}}
-{"Key":"%"}
-{"Get":{"state":"{\n {()ˇ}\n}","mode":"Normal"}}
-{"Put":{"state":"func ˇboop() {\n}"}}
-{"Key":"%"}
-{"Get":{"state":"func boop(ˇ) {\n}","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n return true\n }\n ˇreturn false\n}"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"func empty(a string) bool {\n« if a == \"\" {\n return true\n }\n return false\nˇ»}","mode":"Visual"}}
-{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n ˇreturn true\n }\n return false\n}"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":"Visual"}}
-{"Put":{"state":"func empty(a string) bool {\n if a == \"\" ˇ{\n return true\n }\n return false\n}"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"{"}
-{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":"Visual"}}
@@ -1,16 +0,0 @@
-{"Key":"i"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Key":"shift-T"}
-{"Key":"e"}
-{"Key":"s"}
-{"Key":"t"}
-{"Key":" "}
-{"Key":"t"}
-{"Key":"e"}
-{"Key":"s"}
-{"Key":"t"}
-{"Key":"escape"}
-{"Key":"0"}
-{"Key":"d"}
-{"Key":"w"}
-{"Get":{"state":"ˇtest","mode":"Normal"}}
@@ -1,3 +0,0 @@
-{"Put":{"state":"ˇone\n two\nthree"}}
-{"Key":"enter"}
-{"Get":{"state":"one\n ˇtwo\nthree","mode":"Normal"}}
@@ -1,18 +0,0 @@
-{"Put":{"state":"ˇ"}}
-{"Key":"o"}
-{"Get":{"state":"\nˇ","mode":"Insert"}}
-{"Put":{"state":"The ˇquick"}}
-{"Key":"o"}
-{"Get":{"state":"The quick\nˇ","mode":"Insert"}}
-{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}}
-{"Key":"o"}
-{"Get":{"state":"The quick\nˇ\nbrown fox\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}}
-{"Key":"o"}
-{"Get":{"state":"The quick\nbrown fox\nˇ\njumps over","mode":"Insert"}}
-{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}}
-{"Key":"o"}
-{"Get":{"state":"The quick\nbrown fox\njumps over\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick\nˇ\nbrown fox"}}
-{"Key":"o"}
-{"Get":{"state":"The quick\n\nˇ\nbrown fox","mode":"Insert"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":"one\nˇ\ntwo"}}
-{"Key":"}"}
-{"Key":"}"}
-{"Get":{"state":"one\n\ntwˇo","mode":"Normal"}}
-{"Key":"{"}
-{"Key":"{"}
-{"Key":"{"}
-{"Get":{"state":"ˇone\n\ntwo","mode":"Normal"}}
@@ -1,31 +0,0 @@
-{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"y"}
-{"ReadRegister":{"name":"\"","value":"jumps o"}}
-{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
-{"Key":"p"}
-{"Get":{"state":"The quick brown\nfox jumps overjumps ˇo\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
-{"Key":"shift-p"}
-{"Get":{"state":"The quick brown\nfox jumps ovejumps ˇor\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
-{"Key":"d"}
-{"Key":"d"}
-{"ReadRegister":{"name":"\"","value":"fox jumps over\n"}}
-{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
-{"Key":"p"}
-{"Get":{"state":"The quick brown\nthe lazy dog\nˇfox jumps over","mode":"Normal"}}
-{"Key":"k"}
-{"Key":"shift-p"}
-{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog\nfox jumps over","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"j"}
-{"Key":"y"}
-{"ReadRegister":{"name":"\"","value":"over\nthe lazy do"}}
-{"Key":"p"}
-{"Get":{"state":"The quick brown\nfox jumps oˇover\nthe lazy dover\nthe lazy dog","mode":"Normal"}}
-{"Key":"u"}
-{"Key":"shift-p"}
-{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy doover\nthe lazy dog","mode":"Normal"}}
@@ -1,42 +0,0 @@
-{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Key":"y"}
-{"Get":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"w"}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Key":"p"}
-{"Get":{"state":"The quick brown\nfox jumps jumpˇs\nthe lazy dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"over"}}
-{"Key":"up"}
-{"Key":"shift-v"}
-{"Key":"shift-p"}
-{"Get":{"state":"ˇover\nfox jumps jumps\nthe lazy dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"over"}}
-{"Key":"ctrl-v"}
-{"Key":"down"}
-{"Key":"down"}
-{"Key":"p"}
-{"Get":{"state":"oveˇrver\noverox jumps jumps\noverhe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"d"}
-{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Key":"p"}
-{"Get":{"state":"The quick brown\nthe \nˇfox jumps over\n dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"lazy"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"d"}
-{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
-{"Key":"k"}
-{"Key":"shift-v"}
-{"Key":"p"}
-{"Get":{"state":"ˇfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"The quick brown\n"}}
@@ -1,31 +0,0 @@
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"ctrl-v"}
-{"Key":"2"}
-{"Key":"j"}
-{"Key":"y"}
-{"ReadRegister":{"name":"\"","value":"q\nj\nl"}}
-{"Key":"p"}
-{"Get":{"state":"The qˇquick brown\nfox jjumps over\nthe llazy dog","mode":"Normal"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Key":"shift-p"}
-{"Get":{"state":"The ˇq brown\nfox jjjumps over\nthe lllazy dog","mode":"Normal"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Key":"shift-p"}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"ctrl-v"}
-{"Key":"j"}
-{"Key":"y"}
-{"ReadRegister":{"name":"\"","value":"q\nj"}}
-{"Key":"l"}
-{"Key":"ctrl-v"}
-{"Key":"2"}
-{"Key":"j"}
-{"Key":"shift-p"}
-{"Get":{"state":"The qˇqick brown\nfox jjmps over\nthe lzy dog","mode":"Normal"}}
-{"Key":"shift-v"}
-{"Key":"p"}
-{"Get":{"state":"ˇq\nj\nfox jjmps over\nthe lzy dog","mode":"Normal"}}
@@ -1,58 +0,0 @@
-{"Put":{"state":"ˇconsole.log(var);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log(varˇ);","mode":"Normal"}}
-{"Put":{"state":"console.logˇ(var);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log(varˇ);","mode":"Normal"}}
-{"Put":{"state":"console.log(ˇvar);"}}
-{"Key":"%"}
-{"Get":{"state":"console.logˇ(var);","mode":"Normal"}}
-{"Put":{"state":"console.log(vaˇr);"}}
-{"Key":"%"}
-{"Get":{"state":"console.logˇ(var);","mode":"Normal"}}
-{"Put":{"state":"console.log(varˇ);"}}
-{"Key":"%"}
-{"Get":{"state":"console.logˇ(var);","mode":"Normal"}}
-{"Put":{"state":"console.log(var)ˇ;"}}
-{"Key":"%"}
-{"Get":{"state":"console.log(var)ˇ;","mode":"Normal"}}
-{"Put":{"state":"ˇconsole.log('var', [1, 2, 3]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', [1, 2, 3]ˇ);","mode":"Normal"}}
-{"Put":{"state":"console.logˇ('var', [1, 2, 3]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', [1, 2, 3]ˇ);","mode":"Normal"}}
-{"Put":{"state":"console.log(ˇ'var', [1, 2, 3]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', [1, 2, 3ˇ]);","mode":"Normal"}}
-{"Put":{"state":"console.log('var', ˇ[1, 2, 3]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', [1, 2, 3ˇ]);","mode":"Normal"}}
-{"Put":{"state":"console.log('var', [ˇ1, 2, 3]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', ˇ[1, 2, 3]);","mode":"Normal"}}
-{"Put":{"state":"console.log('var', [1, ˇ2, 3]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', ˇ[1, 2, 3]);","mode":"Normal"}}
-{"Put":{"state":"console.log('var', [1, 2, 3ˇ]);"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', ˇ[1, 2, 3]);","mode":"Normal"}}
-{"Put":{"state":"console.log('var', [1, 2, 3]ˇ);"}}
-{"Key":"%"}
-{"Get":{"state":"console.logˇ('var', [1, 2, 3]);","mode":"Normal"}}
-{"Put":{"state":"console.log('var', [1, 2, 3])ˇ;"}}
-{"Key":"%"}
-{"Get":{"state":"console.log('var', [1, 2, 3])ˇ;","mode":"Normal"}}
-{"Put":{"state":"let result = curried_funˇ()();"}}
-{"Key":"%"}
-{"Get":{"state":"let result = curried_fun(ˇ)();","mode":"Normal"}}
-{"Key":"%"}
-{"Get":{"state":"let result = curried_funˇ()();","mode":"Normal"}}
-{"Put":{"state":"let result = curried_fun()ˇ();"}}
-{"Key":"%"}
-{"Get":{"state":"let result = curried_fun()(ˇ);","mode":"Normal"}}
-{"Key":"%"}
-{"Get":{"state":"let result = curried_fun()ˇ();","mode":"Normal"}}
-{"Put":{"state":"let result = curried_fun()()ˇ;"}}
-{"Key":"%"}
-{"Get":{"state":"let result = curried_fun()()ˇ;","mode":"Normal"}}
@@ -1,13 +0,0 @@
-{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"3"}
-{"Key":"d"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"ˇ brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"."}
-{"Get":{"state":" brown\nˇ over\nthe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"2"}
-{"Key":"."}
-{"Get":{"state":" brown\n over\nˇe lazy dog","mode":"Normal"}}
@@ -1,51 +0,0 @@
-{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Key":"s"}
-{"Key":"o"}
-{"Key":"escape"}
-{"Get":{"state":"ˇo quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"w"}
-{"Key":"."}
-{"Get":{"state":"o quick brown\nfox ˇops over\nthe lazy dog","mode":"Normal"}}
-{"Key":"f"}
-{"Key":"r"}
-{"Key":"."}
-{"Get":{"state":"o quick brown\nfox ops oveˇothe lazy dog","mode":"Normal"}}
-{"Put":{"state":"the ˇquick brown\nfox jumps over\nfox jumps over\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"j"}
-{"Key":"x"}
-{"Get":{"state":"the ˇumps over\nfox jumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"."}
-{"Get":{"state":"the ˇumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"w"}
-{"Key":"."}
-{"Get":{"state":"the umps ˇumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"."}
-{"Get":{"state":"the umps umps over\nthe ˇog","mode":"Normal"}}
-{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"ctrl-v"}
-{"Key":"j"}
-{"Key":"j"}
-{"Key":"shift-i"}
-{"Key":"o"}
-{"Key":"escape"}
-{"Get":{"state":"ˇothe quick brown\nofox jumps over\nothe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"4"}
-{"Key":"l"}
-{"Key":"."}
-{"Get":{"state":"othe quick brown\nofoxˇo jumps over\notheo lazy dog","mode":"Normal"}}
-{"Put":{"state":"ˇthe quick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"shift-r"}
-{"Key":"o"}
-{"Key":"escape"}
-{"Get":{"state":"ˇo\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"."}
-{"Get":{"state":"o\nˇo\nthe lazy dog","mode":"Normal"}}
@@ -1,275 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox ˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The quick ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\nˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox ˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"The ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"The quick ˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox ˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nfox ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"The ˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"The quick ˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\nˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\n\nˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"ˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"The ˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"The quick ˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\nˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"b"}
-{"Get":{"state":"The quick brown\nˇ\nthe lazy dog\n","mode":"Insert"}}
@@ -1,275 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"ˇ quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quickˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick browˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\nˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox ˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"ˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quickˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick browˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox ˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"ˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quickˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick browˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\nˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"ˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quickˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick browˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\nˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox ˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"ˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quickˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick browˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\nˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox ˇ dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"e"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
@@ -1,275 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"ˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"j"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ","mode":"Insert"}}
@@ -1,275 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"ˇhe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quˇck brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quickˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nˇox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox ˇumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇover\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇer\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"1"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇhe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"ˇe quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quˇk brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quickˇrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nˇx jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox ˇmps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇver\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇer\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇr\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"2"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"ˇ quick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quˇ brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quickˇown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nˇ jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox ˇps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇer\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇr\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"3"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇ lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"ˇquick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quickˇwn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nˇjumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox ˇs-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇr\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"4"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇlazy dog\n","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"ˇuick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quˇrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quickˇn\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick browˇ\n\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nˇumps-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox ˇ-over\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-oˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"c"}
-{"Key":"5"}
-{"Key":"l"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇazy dog\n","mode":"Insert"}}
@@ -1,214 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The ˇquick brown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick ˇbrown\n\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"2"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"3"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"4"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quˇick brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quickˇ brown\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick browˇn\n\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nˇ\nfox jumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nˇfox jumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe ˇlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox ˇjumps-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy ˇdog\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumpsˇ-over\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-ˇover\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-oˇver\nthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
-{"Put":{"state":"The quick brown\n\nfox jumps-over\nˇthe lazy dog\n"}}
-{"Key":"5"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n\nfox jumps-over\nthe lazy dog\nˇ","mode":"Normal"}}
@@ -1,8 +0,0 @@
-{"Put":{"state":";;ˇ;\nLorem Ipsum"}}
-{"Key":"a"}
-{"Key":"down"}
-{"Key":"up"}
-{"Key":";"}
-{"Key":"down"}
-{"Key":"up"}
-{"Get":{"state":";;;;ˇ\nLorem Ipsum","mode":"Insert"}}
@@ -1,27 +0,0 @@
-{"SetOption":{"value":"wrap"}}
-{"SetOption":{"value":"columns=12"}}
-{"Put":{"state":"helˇlo \"world\"!"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"hello \"«worldˇ»\"!","mode":"Visual"}}
-{"Put":{"state":"hello \"wˇorld\"!"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"\""}
-{"Get":{"state":"hello \"«worldˇ»\"!","mode":"Visual"}}
-{"Put":{"state":"hello \"wˇorld\"!"}}
-{"Key":"v"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"hello« \"world\"ˇ»!","mode":"Visual"}}
-{"Put":{"state":"hello \"wˇorld\" !"}}
-{"Key":"v"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"hello «\"world\" ˇ»!","mode":"Visual"}}
-{"Put":{"state":"hello \"wˇorld\"•\ngoodbye"}}
-{"Key":"v"}
-{"Key":"a"}
-{"Key":"\""}
-{"Get":{"state":"hello «\"world\" ˇ»\ngoodbye","mode":"Visual"}}
@@ -1,13 +0,0 @@
-{"Put":{"state":"ˇabc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinal"}}
-{"Key":"}"}
-{"Get":{"state":"abc\ndef\nˇ\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}}
-{"Key":"{"}
-{"Get":{"state":"ˇabc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"}"}
-{"Get":{"state":"abc\ndef\n\nparagraph\nthe second\nˇ\n\n\nthird and\nfinal","mode":"Normal"}}
-{"Key":"}"}
-{"Get":{"state":"abc\ndef\n\nparagraph\nthe second\n\n\n\nthird and\nfinaˇl","mode":"Normal"}}
-{"Key":"2"}
-{"Key":"{"}
-{"Get":{"state":"abc\ndef\nˇ\nparagraph\nthe second\n\n\n\nthird and\nfinal","mode":"Normal"}}
@@ -1,29 +0,0 @@
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
-{"Key":"shift-s"}
-{"Key":"o"}
-{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
-{"Key":"v"}
-{"Key":"k"}
-{"Key":"shift-s"}
-{"Key":"o"}
-{"Get":{"state":"oˇ\nthe lazy dog\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
-{"Key":"ctrl-v"}
-{"Key":"j"}
-{"Key":"shift-s"}
-{"Key":"o"}
-{"Get":{"state":"The quick brown\noˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
-{"Key":"v"}
-{"Key":"$"}
-{"Key":"shift-s"}
-{"Key":"o"}
-{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}}
-{"SetOption":{"value":"shiftwidth=4"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
-{"Key":">"}
-{"Key":">"}
-{"Key":"shift-s"}
-{"Key":"o"}
-{"Get":{"state":"The quick brown\n oˇ\nthe lazy dog\n","mode":"Insert"}}
@@ -1,18 +0,0 @@
-{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
-{"Key":"ctrl-v"}
-{"Key":"9"}
-{"Key":"down"}
-{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
-{"Key":"shift-i"}
-{"Key":"k"}
-{"Key":"escape"}
-{"Get":{"state":"ˇkThe quick brown\nkfox jumps over\nkthe lazy dog\nk","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
-{"Key":"ctrl-v"}
-{"Key":"9"}
-{"Key":"down"}
-{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
-{"Key":"c"}
-{"Key":"k"}
-{"Key":"escape"}
-{"Get":{"state":"ˇkhe quick brown\nkox jumps over\nkhe lazy dog\nk","mode":"Normal"}}
@@ -1,5 +0,0 @@
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog\n"}}
-{"Key":"ctrl-v"}
-{"Key":"right"}
-{"Key":"down"}
-{"Get":{"state":"The «quˇ»ick brown\nfox «juˇ»mps over\nthe lazy dog\n","mode":"VisualBlock"}}
@@ -1,38 +0,0 @@
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"ctrl-v"}
-{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"VisualBlock"}}
-{"Key":"2"}
-{"Key":"down"}
-{"Get":{"state":"The «qˇ»uick brown\nfox «jˇ»umps over\nthe «lˇ»azy dog","mode":"VisualBlock"}}
-{"Key":"e"}
-{"Get":{"state":"The «quicˇ»k brown\nfox «jumpˇ»s over\nthe «lazyˇ» dog","mode":"VisualBlock"}}
-{"Key":"^"}
-{"Get":{"state":"«ˇThe q»uick brown\n«ˇfox j»umps over\n«ˇthe l»azy dog","mode":"VisualBlock"}}
-{"Key":"$"}
-{"Get":{"state":"The «quick brownˇ»\nfox «jumps overˇ»\nthe «lazy dogˇ»","mode":"VisualBlock"}}
-{"Key":"shift-f"}
-{"Key":" "}
-{"Get":{"state":"The «quickˇ» brown\nfox «jumpsˇ» over\nthe «lazy ˇ»dog","mode":"VisualBlock"}}
-{"Key":"v"}
-{"Get":{"state":"The «quick brown\nfox jumps over\nthe lazy ˇ»dog","mode":"Visual"}}
-{"Key":"ctrl-v"}
-{"Get":{"state":"The «quickˇ» brown\nfox «jumpsˇ» over\nthe «lazy ˇ»dog","mode":"VisualBlock"}}
-{"Put":{"state":"The ˇquick\nbrown\nfox\njumps over the\n\nlazy dog\n"}}
-{"Key":"ctrl-v"}
-{"Key":"down"}
-{"Key":"down"}
-{"Get":{"state":"The«ˇ q»uick\nbro«ˇwn»\nfoxˇ\njumps over the\n\nlazy dog\n","mode":"VisualBlock"}}
-{"Key":"down"}
-{"Get":{"state":"The «qˇ»uick\nbrow«nˇ»\nfox\njump«sˇ» over the\n\nlazy dog\n","mode":"VisualBlock"}}
-{"Key":"left"}
-{"Get":{"state":"The«ˇ q»uick\nbro«ˇwn»\nfoxˇ\njum«ˇps» over the\n\nlazy dog\n","mode":"VisualBlock"}}
-{"Key":"s"}
-{"Key":"o"}
-{"Key":"escape"}
-{"Get":{"state":"Theˇouick\nbroo\nfoxo\njumo over the\n\nlazy dog\n","mode":"Normal"}}
-{"Put":{"state":"Theˇ quick brown\n\nfox jumps over\nthe lazy dog\n"}}
-{"Key":"l"}
-{"Key":"ctrl-v"}
-{"Key":"j"}
-{"Key":"j"}
-{"Get":{"state":"The «qˇ»uick brown\n\nfox «jˇ»umps over\nthe lazy dog\n","mode":"VisualBlock"}}
@@ -1,47 +0,0 @@
-{"Put":{"state":"The quick ˇbrown"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"c"}
-{"Get":{"state":"The quick ˇ","mode":"Insert"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"k"}
-{"Key":"c"}
-{"Get":{"state":"The ˇrown\nfox jumps over\nthe lazy dog","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nfox jumps ˇhe lazy dog","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"k"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nˇver\nthe lazy dog","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"k"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nfox jumpsˇazy dog","mode":"Insert"}}
@@ -1,48 +0,0 @@
-{"Put":{"state":"The quick ˇbrown"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Get":{"state":"The quick «brownˇ»","mode":"Visual"}}
-{"Put":{"state":"The quick ˇbrown"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"x"}
-{"Get":{"state":"The quickˇ ","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"x"}
-{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"p"}
-{"Get":{"state":"The ver\nthe lˇquick brown\nfox jumps oazy dog","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"x"}
-{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"x"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Normal"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"b"}
-{"Key":"k"}
-{"Key":"x"}
-{"Get":{"state":"ˇuick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"b"}
-{"Key":"k"}
-{"Key":"x"}
-{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
-{"Key":"v"}
-{"Key":"b"}
-{"Key":"k"}
-{"Key":"x"}
-{"Get":{"state":"The quick brown\nˇazy dog","mode":"Normal"}}
@@ -1,35 +0,0 @@
-{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"c"}
-{"Get":{"state":"ˇ\nfox jumps over\nthe lazy dog","mode":"Insert"}}
-{"Key":"escape"}
-{"Key":"j"}
-{"Key":"p"}
-{"Get":{"state":"\nfox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nˇ\nthe lazy dog","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
-{"Key":"shift-v"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nfox jumps over\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"ˇ\nthe lazy dog","mode":"Insert"}}
-{"Key":"escape"}
-{"Key":"j"}
-{"Key":"p"}
-{"Get":{"state":"\nthe lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nˇ","mode":"Insert"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
-{"Key":"shift-v"}
-{"Key":"j"}
-{"Key":"c"}
-{"Get":{"state":"The quick brown\nfox jumps over\nˇ","mode":"Insert"}}
@@ -1,23 +0,0 @@
-{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"x"}
-{"Get":{"state":"fox juˇmps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"p"}
-{"Get":{"state":"fox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
-{"Key":"shift-v"}
-{"Key":"x"}
-{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
-{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"j"}
-{"Key":"x"}
-{"Get":{"state":"the laˇzy dog","mode":"Normal"}}
-{"Key":"p"}
-{"Get":{"state":"the lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
-{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
-{"Key":"shift-v"}
-{"Key":"$"}
-{"Key":"x"}
-{"Get":{"state":"should noˇt\ncrash\n","mode":"Normal"}}
@@ -1,19 +0,0 @@
-{"Put":{"state":"hello (in [parˇens] o)"}}
-{"Key":"ctrl-v"}
-{"Key":"l"}
-{"Key":"a"}
-{"Key":"]"}
-{"Get":{"state":"hello (in «[parens]ˇ» o)","mode":"Visual"}}
-{"Key":"i"}
-{"Key":"("}
-{"Get":{"state":"hello («in [parens] oˇ»)","mode":"Visual"}}
-{"Put":{"state":"hello in a wˇord again."}}
-{"Key":"ctrl-v"}
-{"Key":"l"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"hello in a w«ordˇ» again.","mode":"VisualBlock"}}
-{"Key":"o"}
-{"Key":"a"}
-{"Key":"s"}
-{"Get":{"state":"«ˇhello in a word» again.","mode":"VisualBlock"}}
@@ -1,236 +0,0 @@
-{"Put":{"state":"The quick brown\nˇ\nfox"}}
-{"Key":"v"}
-{"Get":{"state":"The quick brown\n«\nˇ»fox","mode":"Visual"}}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown\n«\nˇ»fox","mode":"Visual"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«Theˇ»-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe«-ˇ»quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-«jumpsˇ» over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":"Visual"}}
-{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n «fox-jumpsˇ» over\nthe lazy dog \n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":"Visual"}}
-{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
-{"Key":"v"}
-{"Key":"i"}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":"Visual"}}
@@ -1,35 +0,0 @@
-{"Put":{"state":"The quick ˇbrown"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"y"}
-{"Get":{"state":"The quick ˇbrown","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"brown"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"y"}
-{"Get":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"quick brown\nfox jumps o"}}
-{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
-{"Key":"v"}
-{"Key":"w"}
-{"Key":"j"}
-{"Key":"y"}
-{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"lazy d"}}
-{"Key":"shift-v"}
-{"Key":"y"}
-{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
-{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"v"}
-{"Key":"b"}
-{"Key":"k"}
-{"Key":"y"}
-{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
-{"Key":"shift-v"}
-{"Key":"shift-g"}
-{"Key":"shift-y"}
-{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"ReadRegister":{"name":"\"","value":"fox jumps over\nthe lazy dog\n"}}
@@ -1,40 +0,0 @@
-{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"w"}
-{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
-{"Key":"w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
-{"Put":{"state":"The ˇquick-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Put":{"state":"The quick-ˇbrown\n\n\nfox_jumps over\nthe"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\nˇ\n\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\n\nˇ\nfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\n\n\nˇfox_jumps over\nthe","mode":"Normal"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps ˇover\nthe","mode":"Normal"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nˇthe","mode":"Normal"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
-{"Key":"shift-w"}
-{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
@@ -1,61 +0,0 @@
-{"SetOption":{"value":"wrap"}}
-{"SetOption":{"value":"columns=12"}}
-{"Put":{"state":"tˇwelve char twelve char\ntwelve char\n"}}
-{"Key":"j"}
-{"Get":{"state":"twelve char twelve char\ntˇwelve char\n","mode":"Normal"}}
-{"Key":"k"}
-{"Get":{"state":"tˇwelve char twelve char\ntwelve char\n","mode":"Normal"}}
-{"Key":"g"}
-{"Key":"j"}
-{"Get":{"state":"twelve char tˇwelve char\ntwelve char\n","mode":"Normal"}}
-{"Key":"g"}
-{"Key":"j"}
-{"Get":{"state":"twelve char twelve char\ntˇwelve char\n","mode":"Normal"}}
-{"Key":"g"}
-{"Key":"k"}
-{"Get":{"state":"twelve char tˇwelve char\ntwelve char\n","mode":"Normal"}}
-{"Key":"g"}
-{"Key":"^"}
-{"Get":{"state":"twelve char ˇtwelve char\ntwelve char\n","mode":"Normal"}}
-{"Key":"^"}
-{"Get":{"state":"ˇtwelve char twelve char\ntwelve char\n","mode":"Normal"}}
-{"Key":"g"}
-{"Key":"$"}
-{"Get":{"state":"twelve charˇ twelve char\ntwelve char\n","mode":"Normal"}}
-{"Key":"$"}
-{"Get":{"state":"twelve char twelve chaˇr\ntwelve char\n","mode":"Normal"}}
-{"Put":{"state":"tˇwelve char twelve char\ntwelve char\n"}}
-{"Key":"enter"}
-{"Get":{"state":"twelve char twelve char\nˇtwelve char\n","mode":"Normal"}}
-{"Put":{"state":"twelve char\ntˇwelve char twelve char\ntwelve char\n"}}
-{"Key":"o"}
-{"Key":"o"}
-{"Key":"escape"}
-{"Get":{"state":"twelve char\ntwelve char twelve char\nˇo\ntwelve char\n","mode":"Normal"}}
-{"Put":{"state":"twelve char\ntˇwelve char twelve char\ntwelve char\n"}}
-{"Key":"shift-a"}
-{"Key":"a"}
-{"Key":"escape"}
-{"Get":{"state":"twelve char\ntwelve char twelve charˇa\ntwelve char\n","mode":"Normal"}}
-{"Key":"shift-i"}
-{"Key":"i"}
-{"Key":"escape"}
-{"Get":{"state":"twelve char\nˇitwelve char twelve chara\ntwelve char\n","mode":"Normal"}}
-{"Key":"shift-d"}
-{"Get":{"state":"twelve char\nˇ\ntwelve char\n","mode":"Normal"}}
-{"Put":{"state":"twelve char\ntwelve char tˇwelve char\ntwelve char\n"}}
-{"Key":"shift-o"}
-{"Key":"o"}
-{"Key":"escape"}
-{"Get":{"state":"twelve char\nˇo\ntwelve char twelve char\ntwelve char\n","mode":"Normal"}}
-{"Put":{"state":"fourteen chaˇr\nfourteen char\n"}}
-{"Key":"d"}
-{"Key":"i"}
-{"Key":"w"}
-{"Get":{"state":"fourteenˇ \nfourteen char\n","mode":"Normal"}}
-{"Key":"j"}
-{"Key":"shift-f"}
-{"Key":"e"}
-{"Key":"f"}
-{"Key":"r"}
-{"Get":{"state":"fourteen \nfourteen chaˇr\n","mode":"Normal"}}
@@ -1,15 +0,0 @@
-{"SetOption":{"value":"wrap"}}
-{"SetOption":{"value":"columns=12"}}
-{"Put":{"state":"aaˇaa\n😃😃"}}
-{"Key":"j"}
-{"Get":{"state":"aaaa\n😃ˇ😃","mode":"Normal"}}
-{"Put":{"state":"123456789012aaˇaa\n123456789012😃😃"}}
-{"Key":"j"}
-{"Get":{"state":"123456789012aaaa\n123456789012😃ˇ😃","mode":"Normal"}}
-{"Put":{"state":"123456789012aaˇaa\n123456789012😃😃"}}
-{"Key":"j"}
-{"Get":{"state":"123456789012aaaa\n123456789012😃ˇ😃","mode":"Normal"}}
-{"Put":{"state":"123456789012aaaaˇaaaaaaaa123456789012\nwow\n123456789012😃😃😃😃😃😃123456789012"}}
-{"Key":"j"}
-{"Key":"j"}
-{"Get":{"state":"123456789012aaaaaaaaaaaa123456789012\nwow\n123456789012😃😃ˇ😃😃😃😃123456789012","mode":"Normal"}}
@@ -1,12 +0,0 @@
-{"Put":{"state":"ˇTest"}}
-{"Key":"x"}
-{"Get":{"state":"ˇest","mode":"Normal"}}
-{"Put":{"state":"Teˇst"}}
-{"Key":"x"}
-{"Get":{"state":"Teˇt","mode":"Normal"}}
-{"Put":{"state":"Tesˇt"}}
-{"Key":"x"}
-{"Get":{"state":"Teˇs","mode":"Normal"}}
-{"Put":{"state":"Tesˇt\ntest"}}
-{"Key":"x"}
-{"Get":{"state":"Teˇs\ntest","mode":"Normal"}}
@@ -1,7 +0,0 @@
-{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
-{"Key":"0"}
-{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
-{"Key":"1"}
-{"Key":"0"}
-{"Key":"l"}
-{"Get":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
@@ -26,7 +26,7 @@ theme_selector = { path = "../theme_selector" }
util = { path = "../util" }
picker = { package = "picker2", path = "../picker2" }
workspace = { package = "workspace2", path = "../workspace2" }
-vim = { package = "vim2", path = "../vim2" }
+vim = { path = "../vim" }
anyhow.workspace = true
log.workspace = true
@@ -25,21 +25,21 @@ channel = { package = "channel2", path = "../channel2" }
cli = { path = "../cli" }
collab_ui = { path = "../collab_ui" }
collections = { path = "../collections" }
-command_palette = { package="command_palette2", path = "../command_palette2" }
+command_palette = { path = "../command_palette" }
# component_test = { path = "../component_test" }
client = { package = "client2", path = "../client2" }
# clock = { path = "../clock" }
copilot = { package = "copilot2", path = "../copilot2" }
copilot_button = { package = "copilot_button2", path = "../copilot_button2" }
-diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
+diagnostics = { path = "../diagnostics" }
db = { package = "db2", path = "../db2" }
editor = { package="editor2", path = "../editor2" }
feedback = { package="feedback2", path = "../feedback2" }
-file_finder = { package="file_finder2", path = "../file_finder2" }
+file_finder = { path = "../file_finder" }
search = { package = "search2", path = "../search2" }
fs = { package = "fs2", path = "../fs2" }
fsevent = { path = "../fsevent" }
-go_to_line = { package = "go_to_line2", path = "../go_to_line2" }
+go_to_line = { path = "../go_to_line" }
gpui = { package = "gpui2", path = "../gpui2" }
install_cli = { package = "install_cli2", path = "../install_cli2" }
journal = { package = "journal2", path = "../journal2" }
@@ -70,7 +70,7 @@ theme = { package = "theme2", path = "../theme2" }
theme_selector = { path = "../theme_selector" }
util = { path = "../util" }
semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
-vim = { package = "vim2", path = "../vim2" }
+vim = { path = "../vim" }
workspace = { package = "workspace2", path = "../workspace2" }
welcome = { path = "../welcome" }
zed_actions = {package = "zed_actions2", path = "../zed_actions2"}