Fix vim selection to include entire range (#2787)
Update vim mode to have vim selection and editor selections match. Before this we had to adjust between vim selections and real selections when making changes; now we have to adjust when making selections. Release Notes: - vim: Ensure editor selection matches the vim selection ([#1796](https://github.com/zed-industries/community/issues/1796)). - vim: Fix `s` in visual line mode - vim: Add `o` and `shift-o` to toggle direction of visual selection - vim: Fix `v` and `shift-v` to toggle back to normal mode - vim: Fix block selections like `vi}` to contain correct whitespace
This commit is contained in:
commit
404b1aa65a
25 changed files with 1007 additions and 454 deletions
|
@ -101,6 +101,8 @@
|
||||||
"vim::SwitchMode",
|
"vim::SwitchMode",
|
||||||
"Normal"
|
"Normal"
|
||||||
],
|
],
|
||||||
|
"v": "vim::ToggleVisual",
|
||||||
|
"shift-v": "vim::ToggleVisualLine",
|
||||||
"*": "vim::MoveToNext",
|
"*": "vim::MoveToNext",
|
||||||
"#": "vim::MoveToPrev",
|
"#": "vim::MoveToPrev",
|
||||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||||
|
@ -274,22 +276,6 @@
|
||||||
"o": "vim::InsertLineBelow",
|
"o": "vim::InsertLineBelow",
|
||||||
"shift-o": "vim::InsertLineAbove",
|
"shift-o": "vim::InsertLineAbove",
|
||||||
"~": "vim::ChangeCase",
|
"~": "vim::ChangeCase",
|
||||||
"v": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
{
|
|
||||||
"Visual": {
|
|
||||||
"line": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shift-v": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
{
|
|
||||||
"Visual": {
|
|
||||||
"line": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"p": "vim::Paste",
|
"p": "vim::Paste",
|
||||||
"u": "editor::Undo",
|
"u": "editor::Undo",
|
||||||
"ctrl-r": "editor::Redo",
|
"ctrl-r": "editor::Redo",
|
||||||
|
@ -382,12 +368,14 @@
|
||||||
"context": "Editor && vim_mode == visual && !VimWaiting",
|
"context": "Editor && vim_mode == visual && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"u": "editor::Undo",
|
"u": "editor::Undo",
|
||||||
"c": "vim::VisualChange",
|
"o": "vim::OtherEnd",
|
||||||
|
"shift-o": "vim::OtherEnd",
|
||||||
"d": "vim::VisualDelete",
|
"d": "vim::VisualDelete",
|
||||||
"x": "vim::VisualDelete",
|
"x": "vim::VisualDelete",
|
||||||
"y": "vim::VisualYank",
|
"y": "vim::VisualYank",
|
||||||
"p": "vim::VisualPaste",
|
"p": "vim::VisualPaste",
|
||||||
"s": "vim::Substitute",
|
"s": "vim::Substitute",
|
||||||
|
"c": "vim::Substitute",
|
||||||
"~": "vim::ChangeCase",
|
"~": "vim::ChangeCase",
|
||||||
"r": [
|
"r": [
|
||||||
"vim::PushOperator",
|
"vim::PushOperator",
|
||||||
|
|
|
@ -214,7 +214,9 @@
|
||||||
"copilot": {
|
"copilot": {
|
||||||
// The set of glob patterns for which copilot should be disabled
|
// The set of glob patterns for which copilot should be disabled
|
||||||
// in any matching file.
|
// in any matching file.
|
||||||
"disabled_globs": [".env"]
|
"disabled_globs": [
|
||||||
|
".env"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
// Settings specific to journaling
|
// Settings specific to journaling
|
||||||
"journal": {
|
"journal": {
|
||||||
|
|
|
@ -353,19 +353,26 @@ impl DisplaySnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used by line_mode selections and tries to match vim behaviour
|
||||||
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
||||||
let mut new_start = self.prev_line_boundary(range.start).0;
|
let new_start = if range.start.row == 0 {
|
||||||
let mut new_end = self.next_line_boundary(range.end).0;
|
Point::new(0, 0)
|
||||||
|
} else if range.start.row == self.max_buffer_row()
|
||||||
|
|| (range.end.column > 0 && range.end.row == self.max_buffer_row())
|
||||||
|
{
|
||||||
|
Point::new(range.start.row - 1, self.line_len(range.start.row - 1))
|
||||||
|
} else {
|
||||||
|
self.prev_line_boundary(range.start).0
|
||||||
|
};
|
||||||
|
|
||||||
if new_start.row == range.start.row && new_end.row == range.end.row {
|
let new_end = if range.end.column == 0 {
|
||||||
if new_end.row < self.buffer_snapshot.max_point().row {
|
range.end
|
||||||
new_end.row += 1;
|
} else if range.end.row < self.max_buffer_row() {
|
||||||
new_end.column = 0;
|
self.buffer_snapshot
|
||||||
} else if new_start.row > 0 {
|
.clip_point(Point::new(range.end.row + 1, 0), Bias::Left)
|
||||||
new_start.row -= 1;
|
} else {
|
||||||
new_start.column = self.buffer_snapshot.line_len(new_start.row);
|
self.buffer_snapshot.max_point()
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
new_start..new_end
|
new_start..new_end
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ struct SelectionLayout {
|
||||||
cursor_shape: CursorShape,
|
cursor_shape: CursorShape,
|
||||||
is_newest: bool,
|
is_newest: bool,
|
||||||
range: Range<DisplayPoint>,
|
range: Range<DisplayPoint>,
|
||||||
|
active_rows: Range<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionLayout {
|
impl SelectionLayout {
|
||||||
|
@ -73,25 +74,44 @@ impl SelectionLayout {
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
is_newest: bool,
|
is_newest: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||||
|
let display_selection = point_selection.map(|p| p.to_display_point(map));
|
||||||
|
let mut range = display_selection.range();
|
||||||
|
let mut head = display_selection.head();
|
||||||
|
let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
|
||||||
|
..map.next_line_boundary(point_selection.end).1.row();
|
||||||
|
|
||||||
|
// vim visual line mode
|
||||||
if line_mode {
|
if line_mode {
|
||||||
let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
let point_range = map.expand_to_line(point_selection.range());
|
||||||
let point_range = map.expand_to_line(selection.range());
|
range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
|
||||||
Self {
|
}
|
||||||
head: selection.head().to_display_point(map),
|
|
||||||
cursor_shape,
|
// any vim visual mode (including line mode)
|
||||||
is_newest,
|
if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
|
||||||
range: point_range.start.to_display_point(map)
|
if head.column() > 0 {
|
||||||
..point_range.end.to_display_point(map),
|
head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
|
||||||
}
|
} else if head.row() > 0 && head != map.max_point() {
|
||||||
} else {
|
head = map.clip_point(
|
||||||
let selection = selection.map(|p| p.to_display_point(map));
|
DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
|
||||||
Self {
|
Bias::Left,
|
||||||
head: selection.head(),
|
);
|
||||||
cursor_shape,
|
// updating range.end is a no-op unless you're cursor is
|
||||||
is_newest,
|
// on the newline containing a multi-buffer divider
|
||||||
range: selection.range(),
|
// in which case the clip_point may have moved the head up
|
||||||
|
// an additional row.
|
||||||
|
range.end = DisplayPoint::new(head.row() + 1, 0);
|
||||||
|
active_rows.end = head.row();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
head,
|
||||||
|
cursor_shape,
|
||||||
|
is_newest,
|
||||||
|
range,
|
||||||
|
active_rows,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2152,22 +2172,37 @@ impl Element<Editor> for EditorElement {
|
||||||
}
|
}
|
||||||
selections.extend(remote_selections);
|
selections.extend(remote_selections);
|
||||||
|
|
||||||
|
let mut newest_selection_head = None;
|
||||||
|
|
||||||
if editor.show_local_selections {
|
if editor.show_local_selections {
|
||||||
let mut local_selections = editor
|
let mut local_selections: Vec<Selection<Point>> = editor
|
||||||
.selections
|
.selections
|
||||||
.disjoint_in_range(start_anchor..end_anchor, cx);
|
.disjoint_in_range(start_anchor..end_anchor, cx);
|
||||||
local_selections.extend(editor.selections.pending(cx));
|
local_selections.extend(editor.selections.pending(cx));
|
||||||
|
let mut layouts = Vec::new();
|
||||||
let newest = editor.selections.newest(cx);
|
let newest = editor.selections.newest(cx);
|
||||||
for selection in &local_selections {
|
for selection in local_selections.drain(..) {
|
||||||
let is_empty = selection.start == selection.end;
|
let is_empty = selection.start == selection.end;
|
||||||
let selection_start = snapshot.prev_line_boundary(selection.start).1;
|
let is_newest = selection == newest;
|
||||||
let selection_end = snapshot.next_line_boundary(selection.end).1;
|
|
||||||
for row in cmp::max(selection_start.row(), start_row)
|
let layout = SelectionLayout::new(
|
||||||
..=cmp::min(selection_end.row(), end_row)
|
selection,
|
||||||
|
editor.selections.line_mode,
|
||||||
|
editor.cursor_shape,
|
||||||
|
&snapshot.display_snapshot,
|
||||||
|
is_newest,
|
||||||
|
);
|
||||||
|
if is_newest {
|
||||||
|
newest_selection_head = Some(layout.head);
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in cmp::max(layout.active_rows.start, start_row)
|
||||||
|
..=cmp::min(layout.active_rows.end, end_row)
|
||||||
{
|
{
|
||||||
let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
|
let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
|
||||||
*contains_non_empty_selection |= !is_empty;
|
*contains_non_empty_selection |= !is_empty;
|
||||||
}
|
}
|
||||||
|
layouts.push(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the local selections in the leader's color when following.
|
// Render the local selections in the leader's color when following.
|
||||||
|
@ -2175,22 +2210,7 @@ impl Element<Editor> for EditorElement {
|
||||||
.leader_replica_id
|
.leader_replica_id
|
||||||
.unwrap_or_else(|| editor.replica_id(cx));
|
.unwrap_or_else(|| editor.replica_id(cx));
|
||||||
|
|
||||||
selections.push((
|
selections.push((local_replica_id, layouts));
|
||||||
local_replica_id,
|
|
||||||
local_selections
|
|
||||||
.into_iter()
|
|
||||||
.map(|selection| {
|
|
||||||
let is_newest = selection == newest;
|
|
||||||
SelectionLayout::new(
|
|
||||||
selection,
|
|
||||||
editor.selections.line_mode,
|
|
||||||
editor.cursor_shape,
|
|
||||||
&snapshot.display_snapshot,
|
|
||||||
is_newest,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
||||||
|
@ -2295,28 +2315,26 @@ impl Element<Editor> for EditorElement {
|
||||||
snapshot = editor.snapshot(cx);
|
snapshot = editor.snapshot(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let newest_selection_head = editor
|
|
||||||
.selections
|
|
||||||
.newest::<usize>(cx)
|
|
||||||
.head()
|
|
||||||
.to_display_point(&snapshot);
|
|
||||||
let style = editor.style(cx);
|
let style = editor.style(cx);
|
||||||
|
|
||||||
let mut context_menu = None;
|
let mut context_menu = None;
|
||||||
let mut code_actions_indicator = None;
|
let mut code_actions_indicator = None;
|
||||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
if let Some(newest_selection_head) = newest_selection_head {
|
||||||
if editor.context_menu_visible() {
|
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||||
context_menu = editor.render_context_menu(newest_selection_head, style.clone(), cx);
|
if editor.context_menu_visible() {
|
||||||
|
context_menu =
|
||||||
|
editor.render_context_menu(newest_selection_head, style.clone(), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let active = matches!(
|
||||||
|
editor.context_menu,
|
||||||
|
Some(crate::ContextMenu::CodeActions(_))
|
||||||
|
);
|
||||||
|
|
||||||
|
code_actions_indicator = editor
|
||||||
|
.render_code_actions_indicator(&style, active, cx)
|
||||||
|
.map(|indicator| (newest_selection_head.row(), indicator));
|
||||||
}
|
}
|
||||||
|
|
||||||
let active = matches!(
|
|
||||||
editor.context_menu,
|
|
||||||
Some(crate::ContextMenu::CodeActions(_))
|
|
||||||
);
|
|
||||||
|
|
||||||
code_actions_indicator = editor
|
|
||||||
.render_code_actions_indicator(&style, active, cx)
|
|
||||||
.map(|indicator| (newest_selection_head.row(), indicator));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let visible_rows = start_row..start_row + line_layouts.len() as u32;
|
let visible_rows = start_row..start_row + line_layouts.len() as u32;
|
||||||
|
@ -2995,6 +3013,154 @@ mod tests {
|
||||||
assert_eq!(layouts.len(), 6);
|
assert_eq!(layouts.len(), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_vim_visual_selections(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let editor = cx
|
||||||
|
.add_window(|cx| {
|
||||||
|
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
|
||||||
|
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||||
|
})
|
||||||
|
.root(cx);
|
||||||
|
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||||
|
let (_, state) = editor.update(cx, |editor, cx| {
|
||||||
|
editor.cursor_shape = CursorShape::Block;
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.select_ranges([
|
||||||
|
Point::new(0, 0)..Point::new(1, 0),
|
||||||
|
Point::new(3, 2)..Point::new(3, 3),
|
||||||
|
Point::new(5, 6)..Point::new(6, 0),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
let mut new_parents = Default::default();
|
||||||
|
let mut notify_views_if_parents_change = Default::default();
|
||||||
|
let mut layout_cx = LayoutContext::new(
|
||||||
|
cx,
|
||||||
|
&mut new_parents,
|
||||||
|
&mut notify_views_if_parents_change,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
element.layout(
|
||||||
|
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||||
|
editor,
|
||||||
|
&mut layout_cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
assert_eq!(state.selections.len(), 1);
|
||||||
|
let local_selections = &state.selections[0].1;
|
||||||
|
assert_eq!(local_selections.len(), 3);
|
||||||
|
// moves cursor back one line
|
||||||
|
assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
|
||||||
|
assert_eq!(
|
||||||
|
local_selections[0].range,
|
||||||
|
DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// moves cursor back one column
|
||||||
|
assert_eq!(
|
||||||
|
local_selections[1].range,
|
||||||
|
DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
|
||||||
|
);
|
||||||
|
assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
|
||||||
|
|
||||||
|
// leaves cursor on the max point
|
||||||
|
assert_eq!(
|
||||||
|
local_selections[2].range,
|
||||||
|
DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
|
||||||
|
);
|
||||||
|
assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
|
||||||
|
|
||||||
|
// active lines does not include 1 (even though the range of the selection does)
|
||||||
|
assert_eq!(
|
||||||
|
state.active_rows.keys().cloned().collect::<Vec<u32>>(),
|
||||||
|
vec![0, 3, 5, 6]
|
||||||
|
);
|
||||||
|
|
||||||
|
// multi-buffer support
|
||||||
|
// in DisplayPoint co-ordinates, this is what we're dealing with:
|
||||||
|
// 0: [[file
|
||||||
|
// 1: header]]
|
||||||
|
// 2: aaaaaa
|
||||||
|
// 3: bbbbbb
|
||||||
|
// 4: cccccc
|
||||||
|
// 5:
|
||||||
|
// 6: ...
|
||||||
|
// 7: ffffff
|
||||||
|
// 8: gggggg
|
||||||
|
// 9: hhhhhh
|
||||||
|
// 10:
|
||||||
|
// 11: [[file
|
||||||
|
// 12: header]]
|
||||||
|
// 13: bbbbbb
|
||||||
|
// 14: cccccc
|
||||||
|
// 15: dddddd
|
||||||
|
let editor = cx
|
||||||
|
.add_window(|cx| {
|
||||||
|
let buffer = MultiBuffer::build_multi(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
&(sample_text(8, 6, 'a') + "\n"),
|
||||||
|
vec![
|
||||||
|
Point::new(0, 0)..Point::new(3, 0),
|
||||||
|
Point::new(4, 0)..Point::new(7, 0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&(sample_text(8, 6, 'a') + "\n"),
|
||||||
|
vec![Point::new(1, 0)..Point::new(3, 0)],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||||
|
})
|
||||||
|
.root(cx);
|
||||||
|
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||||
|
let (_, state) = editor.update(cx, |editor, cx| {
|
||||||
|
editor.cursor_shape = CursorShape::Block;
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
|
||||||
|
DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
let mut new_parents = Default::default();
|
||||||
|
let mut notify_views_if_parents_change = Default::default();
|
||||||
|
let mut layout_cx = LayoutContext::new(
|
||||||
|
cx,
|
||||||
|
&mut new_parents,
|
||||||
|
&mut notify_views_if_parents_change,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
element.layout(
|
||||||
|
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
|
||||||
|
editor,
|
||||||
|
&mut layout_cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(state.selections.len(), 1);
|
||||||
|
let local_selections = &state.selections[0].1;
|
||||||
|
assert_eq!(local_selections.len(), 2);
|
||||||
|
|
||||||
|
// moves cursor on excerpt boundary back a line
|
||||||
|
// and doesn't allow selection to bleed through
|
||||||
|
assert_eq!(
|
||||||
|
local_selections[0].range,
|
||||||
|
DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
|
||||||
|
);
|
||||||
|
assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
|
||||||
|
|
||||||
|
// moves cursor on buffer boundary back two lines
|
||||||
|
// and doesn't allow selection to bleed through
|
||||||
|
assert_eq!(
|
||||||
|
local_selections[1].range,
|
||||||
|
DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
|
||||||
|
);
|
||||||
|
assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
|
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
|
@ -13,6 +13,13 @@ pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||||
map.clip_point(point, Bias::Left)
|
map.clip_point(point, Bias::Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||||
|
if point.column() > 0 {
|
||||||
|
*point.column_mut() -= 1;
|
||||||
|
}
|
||||||
|
map.clip_point(point, Bias::Left)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||||
let max_column = map.line_len(point.row());
|
let max_column = map.line_len(point.row());
|
||||||
if point.column() < max_column {
|
if point.column() < max_column {
|
||||||
|
@ -24,6 +31,11 @@ pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||||
map.clip_point(point, Bias::Right)
|
map.clip_point(point, Bias::Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
|
||||||
|
*point.column_mut() += 1;
|
||||||
|
map.clip_point(point, Bias::Right)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn up(
|
pub fn up(
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
start: DisplayPoint,
|
start: DisplayPoint,
|
||||||
|
|
|
@ -1565,6 +1565,25 @@ impl MultiBuffer {
|
||||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_multi<const COUNT: usize>(
|
||||||
|
excerpts: [(&str, Vec<Range<Point>>); COUNT],
|
||||||
|
cx: &mut gpui::AppContext,
|
||||||
|
) -> ModelHandle<Self> {
|
||||||
|
let multi = cx.add_model(|_| Self::new(0));
|
||||||
|
for (text, ranges) in excerpts {
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
|
||||||
|
let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
|
||||||
|
context: range,
|
||||||
|
primary: None,
|
||||||
|
});
|
||||||
|
multi.update(cx, |multi, cx| {
|
||||||
|
multi.push_excerpts(buffer, excerpt_ranges, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
multi
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_from_buffer(
|
pub fn build_from_buffer(
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
cx: &mut gpui::AppContext,
|
cx: &mut gpui::AppContext,
|
||||||
|
|
|
@ -87,7 +87,7 @@ impl View for ModeIndicator {
|
||||||
Mode::Normal => "-- NORMAL --",
|
Mode::Normal => "-- NORMAL --",
|
||||||
Mode::Insert => "-- INSERT --",
|
Mode::Insert => "-- INSERT --",
|
||||||
Mode::Visual { line: false } => "-- VISUAL --",
|
Mode::Visual { line: false } => "-- VISUAL --",
|
||||||
Mode::Visual { line: true } => "VISUAL LINE ",
|
Mode::Visual { line: true } => "VISUAL LINE",
|
||||||
};
|
};
|
||||||
Label::new(text, theme.vim_mode_indicator.text.clone())
|
Label::new(text, theme.vim_mode_indicator.text.clone())
|
||||||
.contained()
|
.contained()
|
||||||
|
|
|
@ -383,8 +383,7 @@ impl Motion {
|
||||||
|
|
||||||
fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
*point.column_mut() = point.column().saturating_sub(1);
|
point = movement::saturating_left(map, point);
|
||||||
point = map.clip_point(point, Bias::Left);
|
|
||||||
if point.column() == 0 {
|
if point.column() == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -425,9 +424,7 @@ fn up(
|
||||||
|
|
||||||
pub(crate) fn right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
pub(crate) fn right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
let mut new_point = point;
|
let new_point = movement::saturating_right(map, point);
|
||||||
*new_point.column_mut() += 1;
|
|
||||||
let new_point = map.clip_point(new_point, Bias::Right);
|
|
||||||
if point == new_point {
|
if point == new_point {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod change;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod search;
|
mod search;
|
||||||
mod substitute;
|
pub mod substitute;
|
||||||
mod yank;
|
mod yank;
|
||||||
|
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
|
@ -1,34 +1,45 @@
|
||||||
use gpui::WindowContext;
|
use gpui::WindowContext;
|
||||||
use language::Point;
|
use language::Point;
|
||||||
|
|
||||||
use crate::{motion::Motion, Mode, Vim};
|
use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
|
||||||
|
|
||||||
pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
||||||
|
let line_mode = vim.state.mode == Mode::Visual { line: true };
|
||||||
|
vim.switch_mode(Mode::Insert, true, cx);
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.start == selection.end {
|
if selection.start == selection.end {
|
||||||
Motion::Right.expand_selection(map, selection, count, true);
|
Motion::Right.expand_selection(map, selection, count, true);
|
||||||
}
|
}
|
||||||
|
if line_mode {
|
||||||
|
Motion::CurrentLine.expand_selection(map, selection, None, false);
|
||||||
|
if let Some((point, _)) = Motion::FirstNonWhitespace.move_point(
|
||||||
|
map,
|
||||||
|
selection.start,
|
||||||
|
selection.goal,
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
selection.start = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
copy_selections_content(editor, line_mode, cx);
|
||||||
for selection in selections.into_iter().rev() {
|
let selections = editor.selections.all::<Point>(cx).into_iter();
|
||||||
editor.buffer().update(cx, |buffer, cx| {
|
let edits = selections.map(|selection| (selection.start..selection.end, ""));
|
||||||
buffer.edit([(selection.start..selection.end, "")], None, cx)
|
editor.edit(edits, cx);
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Insert, true, cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{state::Mode, test::VimTestContext};
|
use crate::{
|
||||||
|
state::Mode,
|
||||||
|
test::{NeovimBackedTestContext, VimTestContext},
|
||||||
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -69,5 +80,86 @@ mod test {
|
||||||
// should transactionally undo selection changes
|
// should transactionally undo selection changes
|
||||||
cx.simulate_keystrokes(["escape", "u"]);
|
cx.simulate_keystrokes(["escape", "u"]);
|
||||||
cx.assert_editor_state("ˇcàfé\n");
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -369,7 +369,7 @@ fn surrounding_markers(
|
||||||
start = Some(point)
|
start = Some(point)
|
||||||
} else {
|
} else {
|
||||||
*point.column_mut() += char.len_utf8() as u32;
|
*point.column_mut() += char.len_utf8() as u32;
|
||||||
start = Some(point);
|
start = Some(point)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -420,11 +420,38 @@ fn surrounding_markers(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(start), Some(end)) = (start, end) {
|
let (Some(mut start), Some(mut end)) = (start, end) else {
|
||||||
Some(start..end)
|
return None;
|
||||||
} else {
|
};
|
||||||
None
|
|
||||||
|
if !around {
|
||||||
|
// if a block starts with a newline, move the start to after the newline.
|
||||||
|
let mut was_newline = false;
|
||||||
|
for (char, point) in map.chars_at(start) {
|
||||||
|
if was_newline {
|
||||||
|
start = point;
|
||||||
|
} else if char == '\n' {
|
||||||
|
was_newline = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// if a block ends with a newline, then whitespace, then the delimeter,
|
||||||
|
// move the end to after the newline.
|
||||||
|
let mut new_end = end;
|
||||||
|
for (char, point) in map.reverse_chars_at(end) {
|
||||||
|
if char == '\n' {
|
||||||
|
end = new_end;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !char.is_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
new_end = point
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(start..end)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -481,6 +508,12 @@ mod test {
|
||||||
async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
|
async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("The quick ˇbrown\nfox").await;
|
||||||
|
cx.simulate_shared_keystrokes(["v"]).await;
|
||||||
|
cx.assert_shared_state("The quick «bˇ»rown\nfox").await;
|
||||||
|
cx.simulate_shared_keystrokes(["i", "w"]).await;
|
||||||
|
cx.assert_shared_state("The quick «brownˇ»\nfox").await;
|
||||||
|
|
||||||
cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
|
cx.assert_binding_matches_all(["v", "i", "w"], WORD_LOCATIONS)
|
||||||
.await;
|
.await;
|
||||||
cx.assert_binding_matches_all_exempted(
|
cx.assert_binding_matches_all_exempted(
|
||||||
|
@ -675,6 +708,48 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ˇreturn false
|
||||||
|
}"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
func empty(a string) bool {
|
||||||
|
« if a == \"\" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
ˇ»}"})
|
||||||
|
.await;
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
ˇreturn true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
« return true
|
||||||
|
ˇ» }
|
||||||
|
return false
|
||||||
|
}"})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
async fn test_delete_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
|
@ -12,6 +12,15 @@ pub enum Mode {
|
||||||
Visual { line: bool },
|
Visual { line: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub fn is_visual(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Mode::Normal | Mode::Insert => false,
|
||||||
|
Mode::Visual { .. } => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Mode {
|
impl Default for Mode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Normal
|
Self::Normal
|
||||||
|
@ -78,12 +87,11 @@ impl VimState {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_at_line_end(&self) -> bool {
|
pub fn clip_at_line_ends(&self) -> bool {
|
||||||
!matches!(self.mode, Mode::Insert | Mode::Visual { .. })
|
match self.mode {
|
||||||
}
|
Mode::Insert | Mode::Visual { .. } => false,
|
||||||
|
Mode::Normal => true,
|
||||||
pub fn empty_selections_only(&self) -> bool {
|
}
|
||||||
!matches!(self.mode, Mode::Visual { .. })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keymap_context_layer(&self) -> KeymapContext {
|
pub fn keymap_context_layer(&self) -> KeymapContext {
|
||||||
|
|
|
@ -141,7 +141,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
|
||||||
|
|
||||||
// works in visuial mode
|
// works in visuial mode
|
||||||
cx.simulate_keystrokes(["shift-v", "down", ">"]);
|
cx.simulate_keystrokes(["shift-v", "down", ">"]);
|
||||||
cx.assert_editor_state("aa\n b«b\n cˇ»c");
|
cx.assert_editor_state("aa\n b«b\n ccˇ»");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
|
@ -61,6 +61,9 @@ pub struct NeovimBackedTestContext<'a> {
|
||||||
// bindings are exempted. If None, all bindings are ignored for that insertion text.
|
// bindings are exempted. If None, all bindings are ignored for that insertion text.
|
||||||
exemptions: HashMap<String, Option<HashSet<String>>>,
|
exemptions: HashMap<String, Option<HashSet<String>>>,
|
||||||
neovim: NeovimConnection,
|
neovim: NeovimConnection,
|
||||||
|
|
||||||
|
last_set_state: Option<String>,
|
||||||
|
recent_keystrokes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NeovimBackedTestContext<'a> {
|
impl<'a> NeovimBackedTestContext<'a> {
|
||||||
|
@ -71,6 +74,9 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
cx,
|
cx,
|
||||||
exemptions: Default::default(),
|
exemptions: Default::default(),
|
||||||
neovim: NeovimConnection::new(function_name).await,
|
neovim: NeovimConnection::new(function_name).await,
|
||||||
|
|
||||||
|
last_set_state: None,
|
||||||
|
recent_keystrokes: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,13 +108,21 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
keystroke_texts: [&str; COUNT],
|
keystroke_texts: [&str; COUNT],
|
||||||
) -> ContextHandle {
|
) -> ContextHandle {
|
||||||
for keystroke_text in keystroke_texts.into_iter() {
|
for keystroke_text in keystroke_texts.into_iter() {
|
||||||
|
self.recent_keystrokes.push(keystroke_text.to_string());
|
||||||
self.neovim.send_keystroke(keystroke_text).await;
|
self.neovim.send_keystroke(keystroke_text).await;
|
||||||
}
|
}
|
||||||
self.simulate_keystrokes(keystroke_texts)
|
self.simulate_keystrokes(keystroke_texts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
|
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||||
let context_handle = self.set_state(marked_text, Mode::Normal);
|
let mode = if marked_text.contains("»") {
|
||||||
|
Mode::Visual { line: false }
|
||||||
|
} else {
|
||||||
|
Mode::Normal
|
||||||
|
};
|
||||||
|
let context_handle = 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.neovim.set_state(marked_text).await;
|
||||||
context_handle
|
context_handle
|
||||||
}
|
}
|
||||||
|
@ -116,15 +130,25 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
pub async fn assert_shared_state(&mut self, marked_text: &str) {
|
pub async fn assert_shared_state(&mut self, marked_text: &str) {
|
||||||
let neovim = self.neovim_state().await;
|
let neovim = self.neovim_state().await;
|
||||||
if neovim != marked_text {
|
if neovim != marked_text {
|
||||||
|
let initial_state = self
|
||||||
|
.last_set_state
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"N/A".to_string())
|
||||||
|
.clone();
|
||||||
panic!(
|
panic!(
|
||||||
indoc! {"Test is incorrect (currently expected != neovim state)
|
indoc! {"Test is incorrect (currently expected != neovim state)
|
||||||
|
# initial state:
|
||||||
|
{}
|
||||||
|
# keystrokes:
|
||||||
|
{}
|
||||||
# currently expected:
|
# currently expected:
|
||||||
{}
|
{}
|
||||||
# neovim state:
|
# neovim state:
|
||||||
{}
|
{}
|
||||||
# zed state:
|
# zed state:
|
||||||
{}"},
|
{}"},
|
||||||
|
initial_state,
|
||||||
|
self.recent_keystrokes.join(" "),
|
||||||
marked_text,
|
marked_text,
|
||||||
neovim,
|
neovim,
|
||||||
self.editor_state(),
|
self.editor_state(),
|
||||||
|
@ -141,28 +165,40 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn neovim_mode(&mut self) -> Mode {
|
||||||
|
self.neovim.mode().await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
async fn neovim_selection(&mut self) -> Range<usize> {
|
async fn neovim_selection(&mut self) -> Range<usize> {
|
||||||
let mut neovim_selection = self.neovim.selection().await;
|
let neovim_selection = self.neovim.selection().await;
|
||||||
// Zed selections adjust themselves to make the end point visually make sense
|
|
||||||
if neovim_selection.start > neovim_selection.end {
|
|
||||||
neovim_selection.start.column += 1;
|
|
||||||
}
|
|
||||||
neovim_selection.to_offset(&self.buffer_snapshot())
|
neovim_selection.to_offset(&self.buffer_snapshot())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assert_state_matches(&mut self) {
|
pub async fn assert_state_matches(&mut self) {
|
||||||
assert_eq!(
|
let neovim = self.neovim_state().await;
|
||||||
self.neovim.text().await,
|
let editor = self.editor_state();
|
||||||
self.buffer_text(),
|
let initial_state = self
|
||||||
"{}",
|
.last_set_state
|
||||||
self.assertion_context()
|
.as_ref()
|
||||||
);
|
.unwrap_or(&"N/A".to_string())
|
||||||
|
.clone();
|
||||||
|
|
||||||
let selections = vec![self.neovim_selection().await];
|
if neovim != editor {
|
||||||
self.assert_editor_selections(selections);
|
panic!(
|
||||||
|
indoc! {"Test failed (zed does not match nvim behaviour)
|
||||||
if let Some(neovim_mode) = self.neovim.mode().await {
|
# initial state:
|
||||||
assert_eq!(neovim_mode, self.mode(), "{}", self.assertion_context(),);
|
{}
|
||||||
|
# keystrokes:
|
||||||
|
{}
|
||||||
|
# neovim state:
|
||||||
|
{}
|
||||||
|
# zed state:
|
||||||
|
{}"},
|
||||||
|
initial_state,
|
||||||
|
self.recent_keystrokes.join(" "),
|
||||||
|
neovim,
|
||||||
|
editor,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +243,29 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_binding_matches_all_exempted<const COUNT: usize>(
|
pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keystrokes: [&str; COUNT],
|
keystrokes: [&str; COUNT],
|
||||||
|
|
|
@ -213,6 +213,16 @@ impl NeovimConnection {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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")]
|
#[cfg(feature = "neovim")]
|
||||||
pub async fn state(&mut self) -> (Option<Mode>, String, Range<Point>) {
|
pub async fn state(&mut self) -> (Option<Mode>, String, Range<Point>) {
|
||||||
let nvim_buffer = self
|
let nvim_buffer = self
|
||||||
|
@ -226,22 +236,12 @@ impl NeovimConnection {
|
||||||
.expect("Could not get buffer text")
|
.expect("Could not get buffer text")
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
let cursor_row: u32 = self
|
// nvim columns are 1-based, so -1.
|
||||||
.nvim
|
let mut cursor_row = self.read_position("echo line('.')").await - 1;
|
||||||
.command_output("echo line('.')")
|
let mut cursor_col = self.read_position("echo col('.')").await - 1;
|
||||||
.await
|
let mut selection_row = self.read_position("echo line('v')").await - 1;
|
||||||
.unwrap()
|
let mut selection_col = self.read_position("echo col('v')").await - 1;
|
||||||
.parse::<u32>()
|
let total_rows = self.read_position("echo line('$')").await - 1;
|
||||||
.unwrap()
|
|
||||||
- 1; // Neovim rows start at 1
|
|
||||||
let cursor_col: u32 = self
|
|
||||||
.nvim
|
|
||||||
.command_output("echo col('.')")
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.parse::<u32>()
|
|
||||||
.unwrap()
|
|
||||||
- 1; // Neovim columns start at 1
|
|
||||||
|
|
||||||
let nvim_mode_text = self
|
let nvim_mode_text = self
|
||||||
.nvim
|
.nvim
|
||||||
|
@ -266,46 +266,38 @@ impl NeovimConnection {
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (start, end) = if let Some(Mode::Visual { .. }) = mode {
|
// Vim uses the index of the first and last character in the selection
|
||||||
self.nvim
|
// Zed uses the index of the positions between the characters, so we need
|
||||||
.input("<escape>")
|
// to add one to the end in visual mode.
|
||||||
.await
|
match mode {
|
||||||
.expect("Could not exit visual mode");
|
Some(Mode::Visual { .. }) => {
|
||||||
let nvim_buffer = self
|
if selection_col > cursor_col {
|
||||||
.nvim
|
let selection_line_length =
|
||||||
.get_current_buf()
|
self.read_position("echo strlen(getline(line('v')))").await;
|
||||||
.await
|
if selection_line_length > selection_col {
|
||||||
.expect("Could not get neovim buffer");
|
selection_col += 1;
|
||||||
let (start_row, start_col) = nvim_buffer
|
} else if selection_row < total_rows {
|
||||||
.get_mark("<")
|
selection_col = 0;
|
||||||
.await
|
selection_row += 1;
|
||||||
.expect("Could not get selection start");
|
}
|
||||||
let (end_row, end_col) = nvim_buffer
|
} else {
|
||||||
.get_mark(">")
|
let cursor_line_length =
|
||||||
.await
|
self.read_position("echo strlen(getline(line('.')))").await;
|
||||||
.expect("Could not get selection end");
|
if cursor_line_length > cursor_col {
|
||||||
self.nvim
|
cursor_col += 1;
|
||||||
.input("gv")
|
} else if cursor_row < total_rows {
|
||||||
.await
|
cursor_col = 0;
|
||||||
.expect("Could not reselect visual selection");
|
cursor_row += 1;
|
||||||
|
}
|
||||||
if cursor_row == start_row as u32 - 1 && cursor_col == start_col as u32 {
|
}
|
||||||
(
|
|
||||||
Point::new(end_row as u32 - 1, end_col as u32),
|
|
||||||
Point::new(start_row as u32 - 1, start_col as u32),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
Point::new(start_row as u32 - 1, start_col as u32),
|
|
||||||
Point::new(end_row as u32 - 1, end_col as u32),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
Some(Mode::Insert) | Some(Mode::Normal) | None => {}
|
||||||
(
|
}
|
||||||
Point::new(cursor_row, cursor_col),
|
|
||||||
Point::new(cursor_row, cursor_col),
|
let (start, end) = (
|
||||||
)
|
Point::new(selection_row, selection_col),
|
||||||
};
|
Point::new(cursor_row, cursor_col),
|
||||||
|
);
|
||||||
|
|
||||||
let state = NeovimData::Get {
|
let state = NeovimData::Get {
|
||||||
mode,
|
mode,
|
||||||
|
|
|
@ -86,12 +86,13 @@ impl<'a> VimTestContext<'a> {
|
||||||
|
|
||||||
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
|
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
|
||||||
let window = self.window;
|
let window = self.window;
|
||||||
|
let context_handle = self.cx.set_state(text);
|
||||||
window.update(self.cx.cx.cx, |cx| {
|
window.update(self.cx.cx.cx, |cx| {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.switch_mode(mode, false, cx);
|
vim.switch_mode(mode, true, cx);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
self.cx.set_state(text)
|
context_handle
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
|
@ -13,7 +13,7 @@ mod visual;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::CommandPaletteFilter;
|
use collections::CommandPaletteFilter;
|
||||||
use editor::{Bias, Editor, EditorMode, Event};
|
use editor::{movement, Editor, EditorMode, Event};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext,
|
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext,
|
||||||
Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||||
|
@ -181,6 +181,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
|
fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
|
||||||
|
let last_mode = self.state.mode;
|
||||||
self.state.mode = mode;
|
self.state.mode = mode;
|
||||||
self.state.operator_stack.clear();
|
self.state.operator_stack.clear();
|
||||||
|
|
||||||
|
@ -197,12 +198,16 @@ impl Vim {
|
||||||
self.update_active_editor(cx, |editor, cx| {
|
self.update_active_editor(cx, |editor, cx| {
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if self.state.empty_selections_only() {
|
if last_mode.is_visual() && !mode.is_visual() {
|
||||||
let new_head = map.clip_point(selection.head(), Bias::Left);
|
let mut point = selection.head();
|
||||||
selection.collapse_to(new_head, selection.goal)
|
if !selection.reversed {
|
||||||
} else {
|
point = movement::left(map, selection.head());
|
||||||
selection
|
}
|
||||||
.set_head(map.clip_point(selection.head(), Bias::Left), selection.goal);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -265,7 +270,7 @@ impl Vim {
|
||||||
}
|
}
|
||||||
Some(Operator::Replace) => match Vim::read(cx).state.mode {
|
Some(Operator::Replace) => match Vim::read(cx).state.mode {
|
||||||
Mode::Normal => normal_replace(text, cx),
|
Mode::Normal => normal_replace(text, cx),
|
||||||
Mode::Visual { line } => visual_replace(text, line, cx),
|
Mode::Visual { .. } => visual_replace(text, cx),
|
||||||
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -309,7 +314,7 @@ impl Vim {
|
||||||
self.update_active_editor(cx, |editor, cx| {
|
self.update_active_editor(cx, |editor, cx| {
|
||||||
if self.enabled && editor.mode() == EditorMode::Full {
|
if self.enabled && editor.mode() == EditorMode::Full {
|
||||||
editor.set_cursor_shape(cursor_shape, cx);
|
editor.set_cursor_shape(cursor_shape, cx);
|
||||||
editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
|
editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
|
||||||
editor.set_collapse_matches(true);
|
editor.set_collapse_matches(true);
|
||||||
editor.set_input_enabled(!state.vim_controlled());
|
editor.set_input_enabled(!state.vim_controlled());
|
||||||
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
||||||
|
|
|
@ -16,10 +16,22 @@ use crate::{
|
||||||
Vim,
|
Vim,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(vim, [VisualDelete, VisualChange, VisualYank, VisualPaste]);
|
actions!(
|
||||||
|
vim,
|
||||||
|
[
|
||||||
|
ToggleVisual,
|
||||||
|
ToggleVisualLine,
|
||||||
|
VisualDelete,
|
||||||
|
VisualYank,
|
||||||
|
VisualPaste,
|
||||||
|
OtherEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(change);
|
cx.add_action(toggle_visual);
|
||||||
|
cx.add_action(toggle_visual_line);
|
||||||
|
cx.add_action(other_end);
|
||||||
cx.add_action(delete);
|
cx.add_action(delete);
|
||||||
cx.add_action(yank);
|
cx.add_action(yank);
|
||||||
cx.add_action(paste);
|
cx.add_action(paste);
|
||||||
|
@ -32,24 +44,45 @@ pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContex
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let was_reversed = selection.reversed;
|
let was_reversed = selection.reversed;
|
||||||
|
|
||||||
if let Some((new_head, goal)) =
|
let mut current_head = selection.head();
|
||||||
motion.move_point(map, selection.head(), selection.goal, times)
|
|
||||||
{
|
|
||||||
selection.set_head(new_head, goal);
|
|
||||||
|
|
||||||
if was_reversed && !selection.reversed {
|
// our motions assume the current character is after the cursor,
|
||||||
// Head was at the start of the selection, and now is at the end. We need to move the start
|
// but in (forward) visual mode the current character is just
|
||||||
// back by one if possible in order to compensate for this change.
|
// before the end of the selection.
|
||||||
*selection.start.column_mut() =
|
|
||||||
selection.start.column().saturating_sub(1);
|
// If the file ends with a newline (which is common) we don't do this.
|
||||||
selection.start = map.clip_point(selection.start, Bias::Left);
|
// so that if you go to the end of such a file you can use "up" to go
|
||||||
} else if !was_reversed && selection.reversed {
|
// to the previous line and have it work somewhat as expected.
|
||||||
// Head was at the end of the selection, and now is at the start. We need to move the end
|
if !selection.reversed
|
||||||
// forward by one if possible in order to compensate for this change.
|
&& !selection.is_empty()
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
&& !(selection.end.column() == 0 && selection.end == map.max_point())
|
||||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
{
|
||||||
|
current_head = movement::left(map, selection.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((new_head, goal)) =
|
||||||
|
motion.move_point(map, current_head, selection.goal, times) else { return };
|
||||||
|
|
||||||
|
selection.set_head(new_head, goal);
|
||||||
|
|
||||||
|
// ensure the current character is included in the selection.
|
||||||
|
if !selection.reversed {
|
||||||
|
// TODO: maybe try clipping left for multi-buffers
|
||||||
|
let next_point = movement::right(map, selection.end);
|
||||||
|
|
||||||
|
if !(next_point.column() == 0 && next_point == map.max_point()) {
|
||||||
|
selection.end = movement::right(map, selection.end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vim always ensures the anchor character stays selected.
|
||||||
|
// if our selection has reversed, we need to move the opposite end
|
||||||
|
// to ensure the anchor is still selected.
|
||||||
|
if was_reversed && !selection.reversed {
|
||||||
|
selection.start = movement::left(map, selection.start);
|
||||||
|
} else if !was_reversed && selection.reversed {
|
||||||
|
selection.end = movement::right(map, selection.end);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -64,14 +97,29 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
let head = selection.head();
|
let mut head = selection.head();
|
||||||
if let Some(mut range) = object.range(map, head, around) {
|
|
||||||
if !range.is_empty() {
|
|
||||||
if let Some((_, end)) = map.reverse_chars_at(range.end).next() {
|
|
||||||
range.end = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.is_empty() {
|
// all our motions assume that the current character is
|
||||||
|
// after the cursor; however in the case of a visual selection
|
||||||
|
// the current character is before the cursor.
|
||||||
|
if !selection.reversed {
|
||||||
|
head = movement::left(map, head);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(range) = object.range(map, head, around) {
|
||||||
|
if !range.is_empty() {
|
||||||
|
let expand_both_ways = if selection.is_empty() {
|
||||||
|
true
|
||||||
|
// contains only one character
|
||||||
|
} else if let Some((_, start)) =
|
||||||
|
map.reverse_chars_at(selection.end).next()
|
||||||
|
{
|
||||||
|
selection.start == start
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if expand_both_ways {
|
||||||
selection.start = range.start;
|
selection.start = range.start;
|
||||||
selection.end = range.end;
|
selection.end = range.end;
|
||||||
} else if selection.reversed {
|
} else if selection.reversed {
|
||||||
|
@ -88,72 +136,58 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
|
pub fn toggle_visual(_: &mut Workspace, _: &ToggleVisual, cx: &mut ViewContext<Workspace>) {
|
||||||
|
Vim::update(cx, |vim, cx| match vim.state.mode {
|
||||||
|
Mode::Normal | Mode::Insert | Mode::Visual { line: true } => {
|
||||||
|
vim.switch_mode(Mode::Visual { line: false }, false, cx);
|
||||||
|
}
|
||||||
|
Mode::Visual { line: false } => {
|
||||||
|
vim.switch_mode(Mode::Normal, false, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_visual_line(
|
||||||
|
_: &mut Workspace,
|
||||||
|
_: &ToggleVisualLine,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
Vim::update(cx, |vim, cx| match vim.state.mode {
|
||||||
|
Mode::Normal | Mode::Insert | Mode::Visual { line: false } => {
|
||||||
|
vim.switch_mode(Mode::Visual { line: true }, false, cx);
|
||||||
|
}
|
||||||
|
Mode::Visual { line: true } => {
|
||||||
|
vim.switch_mode(Mode::Normal, false, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn other_end(_: &mut Workspace, _: &OtherEnd, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
|
||||||
// Compute edits and resulting anchor selections. If in line mode, adjust
|
|
||||||
// the anchor location and additional newline
|
|
||||||
let mut edits = Vec::new();
|
|
||||||
let mut new_selections = Vec::new();
|
|
||||||
let line_mode = editor.selections.line_mode;
|
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|_, selection| {
|
||||||
if !selection.reversed {
|
selection.reversed = !selection.reversed;
|
||||||
// Head is at the end of the selection. Adjust the end position to
|
})
|
||||||
// to include the character under the cursor.
|
})
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
})
|
||||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_mode {
|
|
||||||
let range = selection.map(|p| p.to_point(map)).range();
|
|
||||||
let expanded_range = map.expand_to_line(range);
|
|
||||||
// If we are at the last line, the anchor needs to be after the newline so that
|
|
||||||
// it is on a line of its own. Otherwise, the anchor may be after the newline
|
|
||||||
let anchor = if expanded_range.end == map.buffer_snapshot.max_point() {
|
|
||||||
map.buffer_snapshot.anchor_after(expanded_range.end)
|
|
||||||
} else {
|
|
||||||
map.buffer_snapshot.anchor_before(expanded_range.start)
|
|
||||||
};
|
|
||||||
|
|
||||||
edits.push((expanded_range, "\n"));
|
|
||||||
new_selections.push(selection.map(|_| anchor));
|
|
||||||
} else {
|
|
||||||
let range = selection.map(|p| p.to_point(map)).range();
|
|
||||||
let anchor = map.buffer_snapshot.anchor_after(range.end);
|
|
||||||
edits.push((range, ""));
|
|
||||||
new_selections.push(selection.map(|_| anchor));
|
|
||||||
}
|
|
||||||
selection.goal = SelectionGoal::None;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
copy_selections_content(editor, editor.selections.line_mode, cx);
|
|
||||||
editor.edit_with_autoindent(edits, cx);
|
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
|
||||||
s.select_anchors(new_selections);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
vim.switch_mode(Mode::Insert, false, cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
|
pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
|
||||||
let mut original_columns: HashMap<_, _> = Default::default();
|
let mut original_columns: HashMap<_, _> = Default::default();
|
||||||
let line_mode = editor.selections.line_mode;
|
let line_mode = editor.selections.line_mode;
|
||||||
|
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if line_mode {
|
if line_mode {
|
||||||
original_columns
|
let mut position = selection.head();
|
||||||
.insert(selection.id, selection.head().to_point(map).column);
|
if !selection.reversed {
|
||||||
} else if !selection.reversed {
|
position = movement::left(map, position);
|
||||||
// Head is at the end of the selection. Adjust the end position to
|
}
|
||||||
// to include the character under the cursor.
|
original_columns.insert(selection.id, position.to_point(map).column);
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
|
||||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
|
||||||
}
|
}
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
});
|
});
|
||||||
|
@ -175,27 +209,14 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Normal, false, cx);
|
vim.switch_mode(Mode::Normal, true, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
|
pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
|
||||||
let line_mode = editor.selections.line_mode;
|
let line_mode = editor.selections.line_mode;
|
||||||
if !line_mode {
|
|
||||||
editor.change_selections(None, cx, |s| {
|
|
||||||
s.move_with(|map, selection| {
|
|
||||||
if !selection.reversed {
|
|
||||||
// Head is at the end of the selection. Adjust the end position to
|
|
||||||
// to include the character under the cursor.
|
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
|
||||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
copy_selections_content(editor, line_mode, cx);
|
copy_selections_content(editor, line_mode, cx);
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|_, selection| {
|
s.move_with(|_, selection| {
|
||||||
|
@ -203,7 +224,7 @@ pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Normal, false, cx);
|
vim.switch_mode(Mode::Normal, true, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,11 +277,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
|
||||||
|
|
||||||
let mut selection = selection.clone();
|
let mut selection = selection.clone();
|
||||||
if !selection.reversed {
|
if !selection.reversed {
|
||||||
let mut adjusted = selection.end;
|
let adjusted = selection.end;
|
||||||
// Head is at the end of the selection. Adjust the end position to
|
|
||||||
// to include the character under the cursor.
|
|
||||||
*adjusted.column_mut() = adjusted.column() + 1;
|
|
||||||
adjusted = display_map.clip_point(adjusted, Bias::Right);
|
|
||||||
// If the selection is empty, move both the start and end forward one
|
// If the selection is empty, move both the start and end forward one
|
||||||
// character
|
// character
|
||||||
if selection.is_empty() {
|
if selection.is_empty() {
|
||||||
|
@ -311,11 +328,11 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Normal, false, cx);
|
vim.switch_mode(Mode::Normal, true, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext) {
|
pub(crate) fn visual_replace(text: Arc<str>, cx: &mut WindowContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
|
@ -336,14 +353,7 @@ pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext)
|
||||||
|
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
for selection in selections.iter() {
|
for selection in selections.iter() {
|
||||||
let mut selection = selection.clone();
|
let selection = selection.clone();
|
||||||
if !line && !selection.reversed {
|
|
||||||
// Head is at the end of the selection. Adjust the end position to
|
|
||||||
// to include the character under the cursor.
|
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
|
||||||
selection.end = display_map.clip_point(selection.end, Bias::Right);
|
|
||||||
}
|
|
||||||
|
|
||||||
for row_range in
|
for row_range in
|
||||||
movement::split_display_range_by_lines(&display_map, selection.range())
|
movement::split_display_range_by_lines(&display_map, selection.range())
|
||||||
{
|
{
|
||||||
|
@ -367,6 +377,7 @@ pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext)
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use workspace::item::Item;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
state::Mode,
|
state::Mode,
|
||||||
|
@ -375,19 +386,146 @@ mod test {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
|
async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx)
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
.await
|
|
||||||
.binding(["v", "w", "j"]);
|
cx.set_shared_state(indoc! {
|
||||||
cx.assert_all(indoc! {"
|
"The ˇquick brown
|
||||||
The ˇquick brown
|
fox jumps over
|
||||||
fox jumps ˇover
|
the lazy dog"
|
||||||
the ˇlazy dog"})
|
})
|
||||||
|
.await;
|
||||||
|
let cursor = cx.update_editor(|editor, _| editor.pixel_position_of_cursor());
|
||||||
|
|
||||||
|
// entering visual mode should select the character
|
||||||
|
// under cursor
|
||||||
|
cx.simulate_shared_keystrokes(["v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "The «qˇ»uick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"})
|
||||||
.await;
|
.await;
|
||||||
let mut cx = cx.binding(["v", "b", "k"]);
|
cx.update_editor(|editor, _| assert_eq!(cursor, editor.pixel_position_of_cursor()));
|
||||||
cx.assert_all(indoc! {"
|
|
||||||
The ˇquick brown
|
// forwards motions should extend the selection
|
||||||
fox jumps ˇover
|
cx.simulate_shared_keystrokes(["w", "j"]).await;
|
||||||
the ˇlazy dog"})
|
cx.assert_shared_state(indoc! { "The «quick brown
|
||||||
|
fox jumps oˇ»ver
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.simulate_shared_keystrokes(["escape"]).await;
|
||||||
|
assert_eq!(Mode::Normal, cx.neovim_mode().await);
|
||||||
|
cx.assert_shared_state(indoc! { "The quick brown
|
||||||
|
fox jumps ˇover
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// motions work backwards
|
||||||
|
cx.simulate_shared_keystrokes(["v", "k", "b"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "The «ˇquick brown
|
||||||
|
fox jumps o»ver
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// works on empty lines
|
||||||
|
cx.set_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
ˇ
|
||||||
|
b
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
let cursor = cx.update_editor(|editor, _| editor.pixel_position_of_cursor());
|
||||||
|
cx.simulate_shared_keystrokes(["v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
«
|
||||||
|
ˇ»b
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.update_editor(|editor, _| assert_eq!(cursor, editor.pixel_position_of_cursor()));
|
||||||
|
|
||||||
|
// toggles off again
|
||||||
|
cx.simulate_shared_keystrokes(["v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
ˇ
|
||||||
|
b
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// works at the end of a document
|
||||||
|
cx.set_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
b
|
||||||
|
ˇ"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.simulate_shared_keystrokes(["v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
b
|
||||||
|
ˇ"})
|
||||||
|
.await;
|
||||||
|
assert_eq!(cx.mode(), cx.neovim_mode().await);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"The ˇquick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["shift-v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "The «qˇ»uick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
assert_eq!(cx.mode(), cx.neovim_mode().await);
|
||||||
|
cx.simulate_shared_keystrokes(["x"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "fox ˇjumps over
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// it should work on empty lines
|
||||||
|
cx.set_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
ˇ
|
||||||
|
b"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["shift-v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "
|
||||||
|
a
|
||||||
|
«
|
||||||
|
ˇ»b"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["x"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "
|
||||||
|
a
|
||||||
|
ˇb"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// it should work at the end of the document
|
||||||
|
cx.set_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
b
|
||||||
|
ˇ"})
|
||||||
|
.await;
|
||||||
|
let cursor = cx.update_editor(|editor, _| editor.pixel_position_of_cursor());
|
||||||
|
cx.simulate_shared_keystrokes(["shift-v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
b
|
||||||
|
ˇ"})
|
||||||
|
.await;
|
||||||
|
assert_eq!(cx.mode(), cx.neovim_mode().await);
|
||||||
|
cx.update_editor(|editor, _| assert_eq!(cursor, editor.pixel_position_of_cursor()));
|
||||||
|
cx.simulate_shared_keystrokes(["x"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
a
|
||||||
|
ˇb"})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,6 +533,9 @@ mod test {
|
||||||
async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
|
async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.assert_binding_matches(["v", "w"], "The quick ˇbrown")
|
||||||
|
.await;
|
||||||
|
|
||||||
cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
|
cx.assert_binding_matches(["v", "w", "x"], "The quick ˇbrown")
|
||||||
.await;
|
.await;
|
||||||
cx.assert_binding_matches(
|
cx.assert_binding_matches(
|
||||||
|
@ -457,62 +598,15 @@ mod test {
|
||||||
fox juˇmps over
|
fox juˇmps over
|
||||||
the laˇzy dog"})
|
the laˇzy dog"})
|
||||||
.await;
|
.await;
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
cx.set_shared_state(indoc! {"
|
||||||
async fn test_visual_change(cx: &mut gpui::TestAppContext) {
|
The ˇlong line
|
||||||
let mut cx = NeovimBackedTestContext::new(cx)
|
should not
|
||||||
.await
|
crash
|
||||||
.binding(["v", "w", "c"]);
|
"})
|
||||||
cx.assert("The quick ˇbrown").await;
|
|
||||||
let mut cx = cx.binding(["v", "w", "j", "c"]);
|
|
||||||
cx.assert_all(indoc! {"
|
|
||||||
The ˇquick brown
|
|
||||||
fox jumps ˇover
|
|
||||||
the ˇlazy dog"})
|
|
||||||
.await;
|
.await;
|
||||||
let mut cx = cx.binding(["v", "b", "k", "c"]);
|
cx.simulate_shared_keystrokes(["shift-v", "$", "x"]).await;
|
||||||
cx.assert_all(indoc! {"
|
|
||||||
The ˇquick brown
|
|
||||||
fox jumps ˇover
|
|
||||||
the ˇlazy dog"})
|
|
||||||
.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_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]
|
#[gpui::test]
|
||||||
|
@ -605,7 +699,7 @@ mod test {
|
||||||
cx.set_state(
|
cx.set_state(
|
||||||
indoc! {"
|
indoc! {"
|
||||||
The quick brown
|
The quick brown
|
||||||
fox «jumpˇ»s over
|
fox «jumpsˇ» over
|
||||||
the lazy dog"},
|
the lazy dog"},
|
||||||
Mode::Visual { line: false },
|
Mode::Visual { line: false },
|
||||||
);
|
);
|
||||||
|
@ -629,7 +723,7 @@ mod test {
|
||||||
cx.set_state(
|
cx.set_state(
|
||||||
indoc! {"
|
indoc! {"
|
||||||
The quick brown
|
The quick brown
|
||||||
fox juˇmps over
|
fox ju«mˇ»ps over
|
||||||
the lazy dog"},
|
the lazy dog"},
|
||||||
Mode::Visual { line: true },
|
Mode::Visual { line: true },
|
||||||
);
|
);
|
||||||
|
@ -643,7 +737,7 @@ mod test {
|
||||||
cx.set_state(
|
cx.set_state(
|
||||||
indoc! {"
|
indoc! {"
|
||||||
The quick brown
|
The quick brown
|
||||||
the «lazˇ»y dog"},
|
the «lazyˇ» dog"},
|
||||||
Mode::Visual { line: false },
|
Mode::Visual { line: false },
|
||||||
);
|
);
|
||||||
cx.simulate_keystroke("p");
|
cx.simulate_keystroke("p");
|
||||||
|
|
15
crates/vim/test_data/test_enter_visual_line_mode.json
Normal file
15
crates/vim/test_data/test_enter_visual_line_mode.json
Normal 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":{"Visual":{"line":true}}}}
|
||||||
|
{"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":{"Visual":{"line":true}}}}
|
||||||
|
{"Key":"x"}
|
||||||
|
{"Get":{"state":"a\nˇb","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"a\nb\nˇ"}}
|
||||||
|
{"Key":"shift-v"}
|
||||||
|
{"Get":{"state":"a\nb\nˇ","mode":{"Visual":{"line":true}}}}
|
||||||
|
{"Key":"x"}
|
||||||
|
{"Get":{"state":"a\nˇb","mode":"Normal"}}
|
|
@ -1,30 +1,20 @@
|
||||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
|
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Key":"j"}
|
{"Key":"j"}
|
||||||
{"Get":{"state":"The «quick brown\nfox jumps ˇ»over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The «quick brown\nfox jumps oˇ»ver\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
{"Key":"escape"}
|
||||||
|
{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog","mode":"Normal"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"w"}
|
|
||||||
{"Key":"j"}
|
|
||||||
{"Get":{"state":"The quick brown\nfox jumps «over\nˇ»the lazy dog","mode":{"Visual":{"line":false}}}}
|
|
||||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
|
||||||
{"Key":"v"}
|
|
||||||
{"Key":"w"}
|
|
||||||
{"Key":"j"}
|
|
||||||
{"Get":{"state":"The quick brown\nfox jumps over\nthe «lazy ˇ»dog","mode":{"Visual":{"line":false}}}}
|
|
||||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
|
||||||
{"Key":"v"}
|
|
||||||
{"Key":"b"}
|
|
||||||
{"Key":"k"}
|
{"Key":"k"}
|
||||||
{"Get":{"state":"«ˇThe »quick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
|
||||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
|
||||||
{"Key":"v"}
|
|
||||||
{"Key":"b"}
|
{"Key":"b"}
|
||||||
{"Key":"k"}
|
{"Get":{"state":"The «ˇquick brown\nfox jumps o»ver\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||||
{"Get":{"state":"The «ˇquick brown\nfox jumps »over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
{"Put":{"state":"a\nˇ\nb\n"}}
|
||||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"b"}
|
{"Get":{"state":"a\n«\nˇ»b\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Key":"k"}
|
{"Key":"v"}
|
||||||
{"Get":{"state":"The quick brown\n«ˇfox jumps over\nthe »lazy dog","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"a\nˇ\nb\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"a\nb\nˇ"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Get":{"state":"a\nb\nˇ","mode":{"Visual":{"line":false}}}}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n return true\n }\n ˇreturn false\n}"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"{"}
|
||||||
|
{"Get":{"state":"func empty(a string) bool {\n« if a == \"\" {\n return true\n }\n return false\nˇ»}","mode":{"Visual":{"line":false}}}}
|
||||||
|
{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n ˇreturn true\n }\n return false\n}"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"{"}
|
||||||
|
{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":{"Visual":{"line":false}}}}
|
|
@ -9,33 +9,39 @@
|
||||||
{"Key":"j"}
|
{"Key":"j"}
|
||||||
{"Key":"c"}
|
{"Key":"c"}
|
||||||
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
||||||
|
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Key":"j"}
|
||||||
|
{"Key":"c"}
|
||||||
|
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
||||||
|
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"c"}
|
||||||
|
{"Get":{"state":"The ˇrown\nfox jumps over\nthe lazy dog","mode":"Insert"}}
|
||||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Key":"j"}
|
{"Key":"j"}
|
||||||
{"Key":"c"}
|
{"Key":"c"}
|
||||||
{"Get":{"state":"The quick brown\nfox jumps ˇhe lazy dog","mode":"Insert"}}
|
{"Get":{"state":"The quick brown\nfox jumps ˇhe lazy dog","mode":"Insert"}}
|
||||||
|
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"c"}
|
||||||
|
{"Get":{"state":"The quick brown\nˇver\nthe lazy dog","mode":"Insert"}}
|
||||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Key":"j"}
|
{"Key":"j"}
|
||||||
{"Key":"c"}
|
{"Key":"c"}
|
||||||
{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Insert"}}
|
{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇog","mode":"Insert"}}
|
||||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
|
||||||
{"Key":"v"}
|
|
||||||
{"Key":"b"}
|
|
||||||
{"Key":"k"}
|
|
||||||
{"Key":"c"}
|
|
||||||
{"Get":{"state":"ˇuick brown\nfox jumps over\nthe lazy dog","mode":"Insert"}}
|
|
||||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
|
||||||
{"Key":"v"}
|
|
||||||
{"Key":"b"}
|
|
||||||
{"Key":"k"}
|
|
||||||
{"Key":"c"}
|
|
||||||
{"Get":{"state":"The ˇver\nthe lazy dog","mode":"Insert"}}
|
|
||||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"b"}
|
{"Key":"w"}
|
||||||
{"Key":"k"}
|
{"Key":"k"}
|
||||||
{"Key":"c"}
|
{"Key":"c"}
|
||||||
{"Get":{"state":"The quick brown\nˇazy dog","mode":"Insert"}}
|
{"Get":{"state":"The quick brown\nfox jumpsˇazy dog","mode":"Insert"}}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{"Put":{"state":"The quick ˇbrown"}}
|
{"Put":{"state":"The quick ˇbrown"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
|
{"Get":{"state":"The quick «brownˇ»","mode":{"Visual":{"line":false}}}}
|
||||||
|
{"Put":{"state":"The quick ˇbrown"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"w"}
|
||||||
{"Key":"x"}
|
{"Key":"x"}
|
||||||
{"Get":{"state":"The quickˇ ","mode":"Normal"}}
|
{"Get":{"state":"The quickˇ ","mode":"Normal"}}
|
||||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||||
|
|
|
@ -29,3 +29,8 @@
|
||||||
{"Key":"j"}
|
{"Key":"j"}
|
||||||
{"Key":"x"}
|
{"Key":"x"}
|
||||||
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
|
||||||
|
{"Key":"shift-v"}
|
||||||
|
{"Key":"$"}
|
||||||
|
{"Key":"x"}
|
||||||
|
{"Get":{"state":"should noˇt\ncrash\n","mode":"Normal"}}
|
||||||
|
|
|
@ -1,230 +1,236 @@
|
||||||
|
{"Put":{"state":"The quick ˇbrown\nfox"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Get":{"state":"The quick «bˇ»rown\nfox","mode":{"Visual":{"line":false}}}}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"w"}
|
||||||
|
{"Get":{"state":"The quick «brownˇ»\nfox","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"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":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"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":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown« ˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ» \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«Thˇ»e-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«Theˇ»-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe«-ˇ»quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"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":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ» \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ» \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ» fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"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","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-«jumpsˇ» over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"w"}
|
{"Key":"w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"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":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"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":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown« ˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox «jumpˇ»s over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ» \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quicˇ»k brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"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":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ» \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ» \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ» fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"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"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"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","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n «fox-jumpsˇ» over\nthe lazy dog \n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":{"Visual":{"line":false}}}}
|
||||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
||||||
{"Key":"v"}
|
{"Key":"v"}
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"shift-w"}
|
{"Key":"shift-w"}
|
||||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n","mode":{"Visual":{"line":false}}}}
|
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":{"Visual":{"line":false}}}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue