vim2 compiling (but mostly commented out)

This commit is contained in:
Conrad Irwin 2023-12-08 18:47:14 +00:00
parent 7a9f764aa0
commit 32837d67be
146 changed files with 22013 additions and 10 deletions

35
Cargo.lock generated
View file

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

View file

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

View file

@ -1899,9 +1899,9 @@ impl Editor {
self.buffer.read(cx).replica_id()
}
// pub fn leader_peer_id(&self) -> Option<PeerId> {
// self.leader_peer_id
// }
pub fn leader_peer_id(&self) -> Option<PeerId> {
self.leader_peer_id
}
pub fn buffer(&self) -> &Model<MultiBuffer> {
&self.buffer
@ -9072,7 +9072,7 @@ impl Editor {
}
}
fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
// todo!()
// let blurred_event = EditorBlurred(cx.handle());
// cx.emit_global(blurred_event);

View file

@ -86,7 +86,8 @@ mod test {
// no panic when blurring an editor in a different window.
cx.update_editor(|editor1, cx| {
editor1.focus_out(cx.handle().into_any(), cx);
todo!()
// editor1.focus_out(cx.handle().into_any(), cx);
});
}
}

View file

@ -1,3 +1,5 @@
#![allow(unused)]
#[cfg(test)]
mod test;

53
crates/vim2/Cargo.toml Normal file
View file

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

438
crates/vim2/src/command.rs Normal file
View file

@ -0,0 +1,438 @@
use command_palette::CommandInterceptResult;
use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
use gpui::{Action, AppContext};
use serde_derive::Deserialize;
use workspace::{SaveIntent, Workspace};
use crate::{
motion::{EndOfDocument, Motion},
normal::{
move_cursor,
search::{FindCommand, ReplaceCommand},
JoinLines,
},
state::Mode,
Vim,
};
#[derive(Action, Debug, Clone, PartialEq, Deserialize)]
pub struct GoToLine {
pub line: u32,
}
pub fn init(cx: &mut AppContext) {
// todo!()
// cx.add_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())
todo!(); // Quit is no longer in zed actions :/
}
// pane management
"sp" | "spl" | "spli" | "split" => ("split", workspace::SplitUp.boxed_clone()),
"vs" | "vsp" | "vspl" | "vspli" | "vsplit" => {
("vsplit", workspace::SplitLeft.boxed_clone())
}
"new" => (
"new",
workspace::NewFileInDirection(workspace::SplitDirection::Up).boxed_clone(),
),
"vne" | "vnew" => (
"vnew",
workspace::NewFileInDirection(workspace::SplitDirection::Left).boxed_clone(),
),
"tabe" | "tabed" | "tabedi" | "tabedit" => ("tabedit", workspace::NewFile.boxed_clone()),
"tabnew" => ("tabnew", workspace::NewFile.boxed_clone()),
"tabn" | "tabne" | "tabnex" | "tabnext" => {
("tabnext", workspace::ActivateNextItem.boxed_clone())
}
"tabp" | "tabpr" | "tabpre" | "tabprev" | "tabprevi" | "tabprevio" | "tabpreviou"
| "tabprevious" => ("tabprevious", workspace::ActivatePrevItem.boxed_clone()),
"tabN" | "tabNe" | "tabNex" | "tabNext" => {
("tabNext", workspace::ActivatePrevItem.boxed_clone())
}
"tabc" | "tabcl" | "tabclo" | "tabclos" | "tabclose" => (
"tabclose",
workspace::CloseActiveItem {
save_intent: Some(SaveIntent::Close),
}
.boxed_clone(),
),
// quickfix / loclist (merged together for now)
"cl" | "cli" | "clis" | "clist" => ("clist", diagnostics::Deploy.boxed_clone()),
"cc" => ("cc", editor::Hover.boxed_clone()),
"ll" => ("ll", editor::Hover.boxed_clone()),
"cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
"lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
"cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => {
("cprevious", editor::GoToPrevDiagnostic.boxed_clone())
}
"cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()),
"lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => {
("lprevious", editor::GoToPrevDiagnostic.boxed_clone())
}
"lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()),
// modify the buffer (should accept [range])
"j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()),
"d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl"
| "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => {
("delete", editor::DeleteLine.boxed_clone())
}
"sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()),
"sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()),
// goto (other ranges handled under _ => )
"$" => ("$", EndOfDocument.boxed_clone()),
_ => {
if query.starts_with("/") || query.starts_with("?") {
(
query,
FindCommand {
query: query[1..].to_string(),
backwards: query.starts_with("?"),
}
.boxed_clone(),
)
} else if query.starts_with("%") {
(
query,
ReplaceCommand {
query: query.to_string(),
}
.boxed_clone(),
)
} else if let Ok(line) = query.parse::<u32>() {
(query, GoToLine { line }.boxed_clone())
} else {
return None;
}
}
};
let string = ":".to_owned() + name;
let positions = generate_positions(&string, query);
Some(CommandInterceptResult {
action,
string,
positions,
})
}
fn generate_positions(string: &str, query: &str) -> Vec<usize> {
let mut positions = Vec::new();
let mut chars = query.chars().into_iter();
let Some(mut current) = chars.next() else {
return positions;
};
for (i, c) in string.chars().enumerate() {
if c == current {
positions.push(i);
if let Some(c) = chars.next() {
current = c;
} else {
break;
}
}
}
positions
}
// #[cfg(test)]
// mod test {
// use std::path::Path;
// use crate::test::{NeovimBackedTestContext, VimTestContext};
// use gpui::TestAppContext;
// use indoc::indoc;
// #[gpui::test]
// async fn test_command_basics(cx: &mut TestAppContext) {
// if let Foreground::Deterministic { cx_id: _, executor } = cx.foreground().as_ref() {
// executor.run_until_parked();
// }
// let mut cx = NeovimBackedTestContext::new(cx).await;
// cx.set_shared_state(indoc! {"
// ˇa
// b
// c"})
// .await;
// cx.simulate_shared_keystrokes([":", "j", "enter"]).await;
// // hack: our cursor positionining after a join command is wrong
// cx.simulate_shared_keystrokes(["^"]).await;
// cx.assert_shared_state(indoc! {
// "ˇa b
// c"
// })
// .await;
// }
// #[gpui::test]
// async fn test_command_goto(cx: &mut TestAppContext) {
// let mut cx = NeovimBackedTestContext::new(cx).await;
// cx.set_shared_state(indoc! {"
// ˇa
// b
// c"})
// .await;
// cx.simulate_shared_keystrokes([":", "3", "enter"]).await;
// cx.assert_shared_state(indoc! {"
// a
// b
// ˇc"})
// .await;
// }
// #[gpui::test]
// async fn test_command_replace(cx: &mut TestAppContext) {
// let mut cx = NeovimBackedTestContext::new(cx).await;
// cx.set_shared_state(indoc! {"
// ˇa
// b
// c"})
// .await;
// cx.simulate_shared_keystrokes([":", "%", "s", "/", "b", "/", "d", "enter"])
// .await;
// cx.assert_shared_state(indoc! {"
// a
// ˇd
// c"})
// .await;
// cx.simulate_shared_keystrokes([
// ":", "%", "s", ":", ".", ":", "\\", "0", "\\", "0", "enter",
// ])
// .await;
// cx.assert_shared_state(indoc! {"
// aa
// dd
// ˇcc"})
// .await;
// }
// #[gpui::test]
// async fn test_command_search(cx: &mut TestAppContext) {
// let mut cx = NeovimBackedTestContext::new(cx).await;
// cx.set_shared_state(indoc! {"
// ˇa
// b
// a
// c"})
// .await;
// cx.simulate_shared_keystrokes([":", "/", "b", "enter"])
// .await;
// cx.assert_shared_state(indoc! {"
// a
// ˇb
// a
// c"})
// .await;
// cx.simulate_shared_keystrokes([":", "?", "a", "enter"])
// .await;
// cx.assert_shared_state(indoc! {"
// ˇa
// b
// a
// c"})
// .await;
// }
// #[gpui::test]
// async fn test_command_write(cx: &mut TestAppContext) {
// let mut cx = VimTestContext::new(cx, true).await;
// let path = Path::new("/root/dir/file.rs");
// let fs = cx.workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
// cx.simulate_keystrokes(["i", "@", "escape"]);
// cx.simulate_keystrokes([":", "w", "enter"]);
// assert_eq!(fs.load(&path).await.unwrap(), "@\n");
// fs.as_fake()
// .write_file_internal(path, "oops\n".to_string())
// .unwrap();
// // conflict!
// cx.simulate_keystrokes(["i", "@", "escape"]);
// cx.simulate_keystrokes([":", "w", "enter"]);
// let window = cx.window;
// assert!(window.has_pending_prompt(cx.cx));
// // "Cancel"
// window.simulate_prompt_answer(0, cx.cx);
// assert_eq!(fs.load(&path).await.unwrap(), "oops\n");
// assert!(!window.has_pending_prompt(cx.cx));
// // force overwrite
// cx.simulate_keystrokes([":", "w", "!", "enter"]);
// assert!(!window.has_pending_prompt(cx.cx));
// assert_eq!(fs.load(&path).await.unwrap(), "@@\n");
// }
// #[gpui::test]
// async fn test_command_quit(cx: &mut TestAppContext) {
// let mut cx = VimTestContext::new(cx, true).await;
// cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
// cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
// cx.simulate_keystrokes([":", "q", "enter"]);
// cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
// cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
// cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
// cx.simulate_keystrokes([":", "q", "a", "enter"]);
// cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 0));
// }
// }

View file

@ -0,0 +1,101 @@
use crate::{Vim, VimEvent};
use editor::{EditorBlurred, EditorFocused, EditorReleased};
use gpui::AppContext;
pub fn init(cx: &mut AppContext) {
// todo!()
// cx.subscribe_global(focused).detach();
// cx.subscribe_global(blurred).detach();
// cx.subscribe_global(released).detach();
}
fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
todo!();
// if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() {
// previously_active_editor.window_handle().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)
// });
// });
// });
// }
// 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,
// });
// }
// });
// });
}
fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
todo!();
// 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;
// }
// }
// editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
// });
// });
}
fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
todo!();
// 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())
// });
// });
}
// #[cfg(test)]
// mod test {
// use crate::{test::VimTestContext, Vim};
// use editor::Editor;
// use gpui::{Context, Entity};
// use language::Buffer;
// // regression test for blur called with a different active editor
// #[gpui::test]
// async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
// let mut cx = VimTestContext::new(cx, true).await;
// let buffer = cx.build_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n"));
// let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
// let editor2 = cx
// .update(|cx| window2.update(cx, |editor, cx| cx.view()))
// .unwrap();
// cx.update(|cx| {
// let vim = Vim::read(cx);
// assert_eq!(
// vim.active_editor.unwrap().entity_id().unwrap(),
// editor2.entity_id()
// )
// });
// // no panic when blurring an editor in a different window.
// cx.update_editor(|editor1, cx| {
// editor1.focus_out(cx.handle().into_any(), cx);
// });
// }
// }

136
crates/vim2/src/insert.rs Normal file
View file

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

View file

@ -0,0 +1,91 @@
use gpui::{div, AnyElement, Div, Element, Entity, IntoElement, Render, Subscription, ViewContext};
use settings::{Settings, SettingsStore};
use workspace::{item::ItemHandle, ui::Label, StatusItemView};
use crate::{state::Mode, Vim, VimEvent, VimModeSetting};
pub struct ModeIndicator {
pub mode: Option<Mode>,
// _subscription: Subscription,
}
impl ModeIndicator {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let handle = cx.view().downgrade();
// 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 VimModeSetting::get_global(cx).0 {
mode_indicator.mode = cx
.has_global::<Vim>()
.then(|| cx.global::<Vim>().state().mode);
} else {
mode_indicator.mode.take();
}
})
.detach();
// Vim doesn't exist in some tests
let mode = cx
.has_global::<Vim>()
.then(|| {
let vim = cx.global::<Vim>();
vim.enabled.then(|| vim.state().mode)
})
.flatten();
Self {
mode,
// _subscription,
}
}
pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
if self.mode != Some(mode) {
self.mode = Some(mode);
cx.notify();
}
}
}
impl Render for ModeIndicator {
type Element = AnyElement;
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
let Some(mode) = self.mode.as_ref() else {
return div().into_any();
};
let text = match mode {
Mode::Normal => "-- NORMAL --",
Mode::Insert => "-- INSERT --",
Mode::Visual => "-- VISUAL --",
Mode::VisualLine => "-- VISUAL LINE --",
Mode::VisualBlock => "-- VISUAL BLOCK --",
};
Label::new(text).into_any_element()
}
}
impl StatusItemView for ModeIndicator {
fn set_active_pane_item(
&mut self,
_active_pane_item: Option<&dyn ItemHandle>,
_cx: &mut ViewContext<Self>,
) {
// nothing to do.
}
}

1072
crates/vim2/src/motion.rs Normal file

File diff suppressed because it is too large Load diff

903
crates/vim2/src/normal.rs Normal file
View file

@ -0,0 +1,903 @@
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, AppContext, 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!(
InsertAfter,
InsertBefore,
InsertFirstNonWhitespace,
InsertEndOfLine,
InsertLineAbove,
InsertLineBelow,
DeleteLeft,
DeleteRight,
ChangeToEndOfLine,
DeleteToEndOfLine,
Yank,
YankLine,
ChangeCase,
JoinLines,
);
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| {
// 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| {
// 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| {
// 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,
// );
// })
// });
// cx.add_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,
// );
// })
// });
// cx.add_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)
// }
// })
// })
// })
// })
}
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;
// }
// }

View file

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

View file

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

View file

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

View file

@ -0,0 +1,277 @@
use std::ops::Range;
use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
use gpui::{Action, AppContext, WindowContext};
use language::{Bias, Point};
use serde::Deserialize;
use workspace::Workspace;
use crate::{state::Mode, Vim};
#[derive(Action, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Increment {
#[serde(default)]
step: bool,
}
#[derive(Action, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Decrement {
#[serde(default)]
step: bool,
}
pub fn init(cx: &mut AppContext) {
// todo!();
// cx.add_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)
// })
// });
// cx.add_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;
// }
// }

View file

@ -0,0 +1,475 @@
use std::{borrow::Cow, cmp};
use editor::{
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
DisplayPoint,
};
use gpui::{Action, AppContext, ViewContext};
use language::{Bias, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
use crate::{state::Mode, utils::copy_selections_content, Vim};
#[derive(Action, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Paste {
#[serde(default)]
before: bool,
#[serde(default)]
preserve_clipboard: bool,
}
pub(crate) fn init(cx: &mut AppContext) {
// todo!()
// cx.add_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,
// );
// }
// }

View file

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

View file

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

View file

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

View file

@ -0,0 +1,277 @@
use editor::movement;
use gpui::{actions, AppContext, WindowContext};
use language::Point;
use workspace::Workspace;
use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
actions!(Substitute, SubstituteLine);
pub(crate) fn init(cx: &mut AppContext) {
// todo!()
// cx.add_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);
// })
// });
// cx.add_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;
// }
// }

View file

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

1009
crates/vim2/src/object.rs Normal file

File diff suppressed because it is too large Load diff

234
crates/vim2/src/state.rs Normal file
View file

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

759
crates/vim2/src/test.rs Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

50
crates/vim2/src/utils.rs Normal file
View file

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

603
crates/vim2/src/vim.rs Normal file
View file

@ -0,0 +1,603 @@
#[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, Action, AppContext, EntityId, KeyContext, Subscription, View, ViewContext, WeakModel,
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(Action, Clone, Deserialize, PartialEq)]
pub struct SwitchMode(pub Mode);
#[derive(Action, Clone, Deserialize, PartialEq)]
pub struct PushOperator(pub Operator);
#[derive(Action, Clone, Deserialize, PartialEq)]
struct Number(usize);
actions!(Tab, Enter, Object, InnerObject, FindForward, FindBackward);
// todo!
// actions!(workspace, [ToggleVimMode]);
#[derive(Copy, Clone, Debug)]
enum VimEvent {
ModeChanged { mode: Mode },
}
pub fn init(cx: &mut AppContext) {
cx.set_global(Vim::default());
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
// todo!()
// cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
// Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
// });
// cx.add_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: _| {
// Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
// });
// cx.add_action(|_: &mut Workspace, _: &Tab, cx| {
// Vim::active_editor_input_ignored(" ".into(), cx)
// });
// cx.add_action(|_: &mut Workspace, _: &Enter, cx| {
// Vim::active_editor_input_ignored("\n".into(), cx)
// });
// cx.add_action(|workspace: &mut Workspace, _: &ToggleVimMode, cx| {
// let fs = workspace.app_state().fs.clone();
// let currently_enabled = settings::get::<VimModeSetting>(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_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();
}
pub fn observe_keystrokes(cx: &mut WindowContext) {
// todo!()
// cx.observe_keystrokes(|_keystroke, result, handled_by, cx| {
// if result == &MatchResult::Pending {
// return true;
// }
// if let Some(handled_by) = handled_by {
// Vim::update(cx, |vim, _| {
// if vim.workspace_state.recording {
// vim.workspace_state
// .recorded_actions
// .push(ReplayableAction::Action(handled_by.boxed_clone()));
// if vim.workspace_state.stop_recording_after_next_action {
// vim.workspace_state.recording = false;
// vim.workspace_state.stop_recording_after_next_action = false;
// }
// }
// });
// // Keystroke is handled by the vim system, so continue forward
// if handled_by.namespace() == "vim" {
// return true;
// }
// }
// Vim::update(cx, |vim, cx| match vim.active_operator() {
// Some(
// Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace,
// ) => {}
// Some(_) => {
// vim.clear_operator(cx);
// }
// _ => {}
// });
// true
// })
// .detach()
}
#[derive(Default)]
pub struct Vim {
active_editor: Option<WeakView<Editor>>,
editor_subscription: Option<Subscription>,
enabled: bool,
editor_states: HashMap<EntityId, EditorState>,
workspace_state: WorkspaceState,
default_state: EditorState,
}
impl Vim {
fn read(cx: &mut AppContext) -> &Self {
cx.default_global()
}
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);
}
// todo!()
// cx.emit_global(VimEvent::ModeChanged { mode });
// 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>();
}
// todo!();
// 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);
// });
}
}
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)
}
}
})
}

1023
crates/vim2/src/visual.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
{"Get":{"state":"ˇ","mode":"Normal"}}
{"Put":{"state":"This is a tesˇt"}}
{"Get":{"state":"This is a tesˇt","mode":"Normal"}}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
{"Put":{"state":"ˇa\nb\nc"}}
{"Key":":"}
{"Key":"j"}
{"Key":"enter"}
{"Key":"^"}
{"Get":{"state":"ˇa b\nc","mode":"Normal"}}

View file

@ -0,0 +1,5 @@
{"Put":{"state":"ˇa\nb\nc"}}
{"Key":":"}
{"Key":"3"}
{"Key":"enter"}
{"Get":{"state":"a\nb\nˇc","mode":"Normal"}}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more