mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
vim: Implement Go To Previous Word End (#7505)
Activated by keystrokes g-e. Release Notes: - vim: Added `ge` and `gE` for go to Previous Word End. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
e6766e102e
commit
96dcc385dd
@ -117,6 +117,8 @@
|
|||||||
"ctrl-e": "vim::LineDown",
|
"ctrl-e": "vim::LineDown",
|
||||||
"ctrl-y": "vim::LineUp",
|
"ctrl-y": "vim::LineUp",
|
||||||
// "g" commands
|
// "g" commands
|
||||||
|
"g e": "vim::PreviousWordEnd",
|
||||||
|
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||||
"g g": "vim::StartOfDocument",
|
"g g": "vim::StartOfDocument",
|
||||||
"g h": "editor::Hover",
|
"g h": "editor::Hover",
|
||||||
"g t": "pane::ActivateNextItem",
|
"g t": "pane::ActivateNextItem",
|
||||||
|
@ -5,6 +5,7 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
|||||||
use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint};
|
use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint};
|
||||||
use gpui::{px, Pixels, WindowTextSystem};
|
use gpui::{px, Pixels, WindowTextSystem};
|
||||||
use language::Point;
|
use language::Point;
|
||||||
|
use multi_buffer::MultiBufferSnapshot;
|
||||||
|
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
@ -254,7 +255,7 @@ pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> Displa
|
|||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
|
|
||||||
find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
|
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
|
||||||
(char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace())
|
(char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace())
|
||||||
|| left == '\n'
|
|| left == '\n'
|
||||||
})
|
})
|
||||||
@ -267,7 +268,7 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis
|
|||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
|
|
||||||
find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
|
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
|
||||||
let is_word_start =
|
let is_word_start =
|
||||||
char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace();
|
char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace();
|
||||||
let is_subword_start =
|
let is_subword_start =
|
||||||
@ -366,16 +367,16 @@ pub fn end_of_paragraph(
|
|||||||
/// indicated by the given predicate returning true.
|
/// indicated by the given predicate returning true.
|
||||||
/// The predicate is called with the character to the left and right of the candidate boundary location.
|
/// The predicate is called with the character to the left and right of the candidate boundary location.
|
||||||
/// If FindRange::SingleLine is specified and no boundary is found before the start of the current line, the start of the current line will be returned.
|
/// If FindRange::SingleLine is specified and no boundary is found before the start of the current line, the start of the current line will be returned.
|
||||||
pub fn find_preceding_boundary(
|
pub fn find_preceding_boundary_point(
|
||||||
map: &DisplaySnapshot,
|
buffer_snapshot: &MultiBufferSnapshot,
|
||||||
from: DisplayPoint,
|
from: Point,
|
||||||
find_range: FindRange,
|
find_range: FindRange,
|
||||||
mut is_boundary: impl FnMut(char, char) -> bool,
|
mut is_boundary: impl FnMut(char, char) -> bool,
|
||||||
) -> DisplayPoint {
|
) -> Point {
|
||||||
let mut prev_ch = None;
|
let mut prev_ch = None;
|
||||||
let mut offset = from.to_point(map).to_offset(&map.buffer_snapshot);
|
let mut offset = from.to_offset(&buffer_snapshot);
|
||||||
|
|
||||||
for ch in map.buffer_snapshot.reversed_chars_at(offset) {
|
for ch in buffer_snapshot.reversed_chars_at(offset) {
|
||||||
if find_range == FindRange::SingleLine && ch == '\n' {
|
if find_range == FindRange::SingleLine && ch == '\n' {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -389,7 +390,26 @@ pub fn find_preceding_boundary(
|
|||||||
prev_ch = Some(ch);
|
prev_ch = Some(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.clip_point(offset.to_display_point(map), Bias::Left)
|
offset.to_point(&buffer_snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scans for a boundary preceding the given start point `from` until a boundary is found,
|
||||||
|
/// indicated by the given predicate returning true.
|
||||||
|
/// The predicate is called with the character to the left and right of the candidate boundary location.
|
||||||
|
/// If FindRange::SingleLine is specified and no boundary is found before the start of the current line, the start of the current line will be returned.
|
||||||
|
pub fn find_preceding_boundary_display_point(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
from: DisplayPoint,
|
||||||
|
find_range: FindRange,
|
||||||
|
is_boundary: impl FnMut(char, char) -> bool,
|
||||||
|
) -> DisplayPoint {
|
||||||
|
let result = find_preceding_boundary_point(
|
||||||
|
&map.buffer_snapshot,
|
||||||
|
from.to_point(map),
|
||||||
|
find_range,
|
||||||
|
is_boundary,
|
||||||
|
);
|
||||||
|
map.clip_point(result.to_display_point(map), Bias::Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scans for a boundary following the given start point until a boundary is found, indicated by the
|
/// Scans for a boundary following the given start point until a boundary is found, indicated by the
|
||||||
@ -626,7 +646,7 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
find_preceding_boundary(
|
find_preceding_boundary_display_point(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
display_points[1],
|
display_points[1],
|
||||||
FindRange::MultiLine,
|
FindRange::MultiLine,
|
||||||
@ -700,7 +720,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
find_preceding_boundary(
|
find_preceding_boundary_display_point(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
buffer_snapshot.len().to_display_point(&snapshot),
|
buffer_snapshot.len().to_display_point(&snapshot),
|
||||||
FindRange::MultiLine,
|
FindRange::MultiLine,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use editor::{
|
use editor::{
|
||||||
display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
|
display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
|
||||||
movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
|
movement::{
|
||||||
|
self, find_boundary, find_preceding_boundary_display_point, FindRange, TextLayoutDetails,
|
||||||
|
},
|
||||||
Bias, DisplayPoint, ToOffset,
|
Bias, DisplayPoint, ToOffset,
|
||||||
};
|
};
|
||||||
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
|
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
|
||||||
@ -27,6 +29,7 @@ pub enum Motion {
|
|||||||
NextWordStart { ignore_punctuation: bool },
|
NextWordStart { ignore_punctuation: bool },
|
||||||
NextWordEnd { ignore_punctuation: bool },
|
NextWordEnd { ignore_punctuation: bool },
|
||||||
PreviousWordStart { ignore_punctuation: bool },
|
PreviousWordStart { ignore_punctuation: bool },
|
||||||
|
PreviousWordEnd { ignore_punctuation: bool },
|
||||||
FirstNonWhitespace { display_lines: bool },
|
FirstNonWhitespace { display_lines: bool },
|
||||||
CurrentLine,
|
CurrentLine,
|
||||||
StartOfLine { display_lines: bool },
|
StartOfLine { display_lines: bool },
|
||||||
@ -70,6 +73,13 @@ struct PreviousWordStart {
|
|||||||
ignore_punctuation: bool,
|
ignore_punctuation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct PreviousWordEnd {
|
||||||
|
#[serde(default)]
|
||||||
|
ignore_punctuation: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct Up {
|
pub(crate) struct Up {
|
||||||
@ -114,6 +124,7 @@ impl_actions!(
|
|||||||
Down,
|
Down,
|
||||||
Up,
|
Up,
|
||||||
PreviousWordStart,
|
PreviousWordStart,
|
||||||
|
PreviousWordEnd,
|
||||||
NextWordEnd,
|
NextWordEnd,
|
||||||
NextWordStart
|
NextWordStart
|
||||||
]
|
]
|
||||||
@ -263,6 +274,11 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
|||||||
workspace.register_action(|_: &mut Workspace, &WindowBottom, cx: _| {
|
workspace.register_action(|_: &mut Workspace, &WindowBottom, cx: _| {
|
||||||
motion(Motion::WindowBottom, cx)
|
motion(Motion::WindowBottom, cx)
|
||||||
});
|
});
|
||||||
|
workspace.register_action(
|
||||||
|
|_: &mut Workspace, &PreviousWordEnd { ignore_punctuation }, cx: _| {
|
||||||
|
motion(Motion::PreviousWordEnd { ignore_punctuation }, cx)
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
|
pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
|
||||||
@ -315,6 +331,7 @@ impl Motion {
|
|||||||
| GoToColumn
|
| GoToColumn
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
|
| PreviousWordEnd { .. }
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. }
|
| FindBackward { .. }
|
||||||
| RepeatFind { .. }
|
| RepeatFind { .. }
|
||||||
@ -351,6 +368,7 @@ impl Motion {
|
|||||||
| WindowTop
|
| WindowTop
|
||||||
| WindowMiddle
|
| WindowMiddle
|
||||||
| WindowBottom
|
| WindowBottom
|
||||||
|
| PreviousWordEnd { .. }
|
||||||
| NextLineStart => false,
|
| NextLineStart => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,6 +389,7 @@ impl Motion {
|
|||||||
| WindowTop
|
| WindowTop
|
||||||
| WindowMiddle
|
| WindowMiddle
|
||||||
| WindowBottom
|
| WindowBottom
|
||||||
|
| PreviousWordEnd { .. }
|
||||||
| NextLineStart => true,
|
| NextLineStart => true,
|
||||||
Left
|
Left
|
||||||
| Backspace
|
| Backspace
|
||||||
@ -431,6 +450,10 @@ impl Motion {
|
|||||||
previous_word_start(map, point, *ignore_punctuation, times),
|
previous_word_start(map, point, *ignore_punctuation, times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
|
PreviousWordEnd { ignore_punctuation } => (
|
||||||
|
previous_word_end(map, point, *ignore_punctuation, times),
|
||||||
|
SelectionGoal::None,
|
||||||
|
),
|
||||||
FirstNonWhitespace { display_lines } => (
|
FirstNonWhitespace { display_lines } => (
|
||||||
first_non_whitespace(map, *display_lines, point),
|
first_non_whitespace(map, *display_lines, point),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
@ -840,13 +863,17 @@ fn previous_word_start(
|
|||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
// This works even though find_preceding_boundary is called for every character in the line containing
|
// This works even though find_preceding_boundary is called for every character in the line containing
|
||||||
// cursor because the newline is checked only once.
|
// cursor because the newline is checked only once.
|
||||||
let new_point =
|
let new_point = movement::find_preceding_boundary_display_point(
|
||||||
movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
|
map,
|
||||||
|
point,
|
||||||
|
FindRange::MultiLine,
|
||||||
|
|left, right| {
|
||||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||||
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||||
|
|
||||||
(left_kind != right_kind && !right.is_whitespace()) || left == '\n'
|
(left_kind != right_kind && !right.is_whitespace()) || left == '\n'
|
||||||
});
|
},
|
||||||
|
);
|
||||||
if point == new_point {
|
if point == new_point {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1023,7 +1050,9 @@ fn find_backward(
|
|||||||
|
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
let new_to =
|
let new_to =
|
||||||
find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
|
find_preceding_boundary_display_point(map, to, FindRange::SingleLine, |_, right| {
|
||||||
|
right == target
|
||||||
|
});
|
||||||
if to == new_to {
|
if to == new_to {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1147,6 +1176,44 @@ fn window_bottom(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn previous_word_end(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
point: DisplayPoint,
|
||||||
|
ignore_punctuation: bool,
|
||||||
|
times: usize,
|
||||||
|
) -> DisplayPoint {
|
||||||
|
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
|
||||||
|
let mut point = point.to_point(map);
|
||||||
|
|
||||||
|
if point.column < map.buffer_snapshot.line_len(point.row) {
|
||||||
|
point.column += 1;
|
||||||
|
}
|
||||||
|
for _ in 0..times {
|
||||||
|
let new_point = movement::find_preceding_boundary_point(
|
||||||
|
&map.buffer_snapshot,
|
||||||
|
point,
|
||||||
|
FindRange::MultiLine,
|
||||||
|
|left, right| {
|
||||||
|
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||||
|
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||||
|
match (left_kind, right_kind) {
|
||||||
|
(CharKind::Punctuation, CharKind::Whitespace)
|
||||||
|
| (CharKind::Punctuation, CharKind::Word)
|
||||||
|
| (CharKind::Word, CharKind::Whitespace)
|
||||||
|
| (CharKind::Word, CharKind::Punctuation) => true,
|
||||||
|
(CharKind::Whitespace, CharKind::Whitespace) => left == '\n' && right == '\n',
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if new_point == point {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
point = new_point;
|
||||||
|
}
|
||||||
|
movement::saturating_left(map, point.to_display_point(map))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
@ -1564,4 +1631,98 @@ mod test {
|
|||||||
"})
|
"})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_previous_word_end(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
cx.set_shared_state(indoc! {r"
|
||||||
|
456 5ˇ67 678
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
45ˇ6 567 678
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Test times
|
||||||
|
cx.set_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
456 5ˇ67 678
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["4", "g", "e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
12ˇ3 234 345
|
||||||
|
456 567 678
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// With punctuation
|
||||||
|
cx.set_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
4;5.6 5ˇ67 678
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
4;5.ˇ6 567 678
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// With punctuation and count
|
||||||
|
cx.set_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
4;5.6 5ˇ67 678
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["5", "g", "e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
ˇ4;5.6 567 678
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// newlines
|
||||||
|
cx.set_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
|
||||||
|
78ˇ9 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
ˇ
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
123 234 34ˇ5
|
||||||
|
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// With punctuation
|
||||||
|
cx.set_shared_state(indoc! {r"
|
||||||
|
123 234 345
|
||||||
|
4;5.ˇ6 567 678
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["g", "shift-e"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {r"
|
||||||
|
123 234 34ˇ5
|
||||||
|
4;5.6 567 678
|
||||||
|
789 890 901
|
||||||
|
"})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ fn in_word(
|
|||||||
let scope = map
|
let scope = map
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.language_scope_at(relative_to.to_point(map));
|
.language_scope_at(relative_to.to_point(map));
|
||||||
let start = movement::find_preceding_boundary(
|
let start = movement::find_preceding_boundary_display_point(
|
||||||
map,
|
map,
|
||||||
right(map, relative_to, 1),
|
right(map, relative_to, 1),
|
||||||
movement::FindRange::SingleLine,
|
movement::FindRange::SingleLine,
|
||||||
@ -281,7 +281,7 @@ fn around_next_word(
|
|||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.language_scope_at(relative_to.to_point(map));
|
.language_scope_at(relative_to.to_point(map));
|
||||||
// Get the start of the word
|
// Get the start of the word
|
||||||
let start = movement::find_preceding_boundary(
|
let start = movement::find_preceding_boundary_display_point(
|
||||||
map,
|
map,
|
||||||
right(map, relative_to, 1),
|
right(map, relative_to, 1),
|
||||||
FindRange::SingleLine,
|
FindRange::SingleLine,
|
||||||
|
29
crates/vim/test_data/test_previous_word_end.json
Normal file
29
crates/vim/test_data/test_previous_word_end.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{"Put":{"state":"456 5ˇ67 678\n"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"e"}
|
||||||
|
{"Get":{"state":"45ˇ6 567 678\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"123 234 345\n456 5ˇ67 678\n"}}
|
||||||
|
{"Key":"4"}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"e"}
|
||||||
|
{"Get":{"state":"12ˇ3 234 345\n456 567 678\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"123 234 345\n4;5.6 5ˇ67 678\n789 890 901\n"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"e"}
|
||||||
|
{"Get":{"state":"123 234 345\n4;5.ˇ6 567 678\n789 890 901\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"123 234 345\n4;5.6 5ˇ67 678\n789 890 901\n"}}
|
||||||
|
{"Key":"5"}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"e"}
|
||||||
|
{"Get":{"state":"123 234 345\nˇ4;5.6 567 678\n789 890 901\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"123 234 345\n\n78ˇ9 890 901\n"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"e"}
|
||||||
|
{"Get":{"state":"123 234 345\nˇ\n789 890 901\n","mode":"Normal"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"e"}
|
||||||
|
{"Get":{"state":"123 234 34ˇ5\n\n789 890 901\n","mode":"Normal"}}
|
||||||
|
{"Put":{"state":"123 234 345\n4;5.ˇ6 567 678\n789 890 901\n"}}
|
||||||
|
{"Key":"g"}
|
||||||
|
{"Key":"shift-e"}
|
||||||
|
{"Get":{"state":"123 234 34ˇ5\n4;5.6 567 678\n789 890 901\n","mode":"Normal"}}
|
Loading…
Reference in New Issue
Block a user