Fix vim selection to include entire range
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
e6f3e0ab9c
commit
b53fb8633e
21 changed files with 489 additions and 335 deletions
|
@ -266,22 +266,8 @@
|
||||||
"o": "vim::InsertLineBelow",
|
"o": "vim::InsertLineBelow",
|
||||||
"shift-o": "vim::InsertLineAbove",
|
"shift-o": "vim::InsertLineAbove",
|
||||||
"~": "vim::ChangeCase",
|
"~": "vim::ChangeCase",
|
||||||
"v": [
|
"v": "vim::ToggleVisual",
|
||||||
"vim::SwitchMode",
|
"shift-v": "vim::ToggleVisualLine",
|
||||||
{
|
|
||||||
"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",
|
||||||
|
|
|
@ -35,6 +35,12 @@ pub enum FoldStatus {
|
||||||
Foldable,
|
Foldable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Clip {
|
||||||
|
None,
|
||||||
|
EndOfLine,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ToDisplayPoint {
|
pub trait ToDisplayPoint {
|
||||||
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
|
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +56,7 @@ pub struct DisplayMap {
|
||||||
wrap_map: ModelHandle<WrapMap>,
|
wrap_map: ModelHandle<WrapMap>,
|
||||||
block_map: BlockMap,
|
block_map: BlockMap,
|
||||||
text_highlights: TextHighlights,
|
text_highlights: TextHighlights,
|
||||||
pub clip_at_line_ends: bool,
|
pub default_clip: Clip,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for DisplayMap {
|
impl Entity for DisplayMap {
|
||||||
|
@ -85,7 +91,7 @@ impl DisplayMap {
|
||||||
wrap_map,
|
wrap_map,
|
||||||
block_map,
|
block_map,
|
||||||
text_highlights: Default::default(),
|
text_highlights: Default::default(),
|
||||||
clip_at_line_ends: false,
|
default_clip: Clip::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +115,7 @@ impl DisplayMap {
|
||||||
wrap_snapshot,
|
wrap_snapshot,
|
||||||
block_snapshot,
|
block_snapshot,
|
||||||
text_highlights: self.text_highlights.clone(),
|
text_highlights: self.text_highlights.clone(),
|
||||||
clip_at_line_ends: self.clip_at_line_ends,
|
default_clip: self.default_clip,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +302,7 @@ pub struct DisplaySnapshot {
|
||||||
wrap_snapshot: wrap_map::WrapSnapshot,
|
wrap_snapshot: wrap_map::WrapSnapshot,
|
||||||
block_snapshot: block_map::BlockSnapshot,
|
block_snapshot: block_map::BlockSnapshot,
|
||||||
text_highlights: TextHighlights,
|
text_highlights: TextHighlights,
|
||||||
clip_at_line_ends: bool,
|
default_clip: Clip,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplaySnapshot {
|
impl DisplaySnapshot {
|
||||||
|
@ -577,21 +583,33 @@ impl DisplaySnapshot {
|
||||||
column
|
column
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
|
pub fn move_left(&self, point: DisplayPoint, clip: Clip) -> DisplayPoint {
|
||||||
let mut clipped = self.block_snapshot.clip_point(point.0, bias);
|
self.clip_point_with(
|
||||||
if self.clip_at_line_ends {
|
DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
|
||||||
clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
|
Bias::Left,
|
||||||
}
|
clip,
|
||||||
DisplayPoint(clipped)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
|
pub fn move_right(&self, point: DisplayPoint, clip: Clip) -> DisplayPoint {
|
||||||
let mut point = point.0;
|
self.clip_point_with(
|
||||||
if point.column == self.line_len(point.row) {
|
DisplayPoint::new(point.row(), point.column() + 1),
|
||||||
point.column = point.column.saturating_sub(1);
|
Bias::Right,
|
||||||
point = self.block_snapshot.clip_point(point, Bias::Left);
|
clip,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
DisplayPoint(point)
|
|
||||||
|
pub fn clip_point_with(&self, point: DisplayPoint, bias: Bias, clip: Clip) -> DisplayPoint {
|
||||||
|
let new_point = DisplayPoint(self.block_snapshot.clip_point(point.0, bias));
|
||||||
|
if clip == Clip::EndOfLine && new_point.column() == self.line_len(new_point.row()) {
|
||||||
|
self.move_left(new_point, Clip::None)
|
||||||
|
} else {
|
||||||
|
new_point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
|
||||||
|
self.clip_point_with(point, bias, self.default_clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
|
pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Range<Anchor>>
|
||||||
|
@ -1580,7 +1598,7 @@ pub mod tests {
|
||||||
|
|
||||||
fn assert(text: &str, cx: &mut gpui::AppContext) {
|
fn assert(text: &str, cx: &mut gpui::AppContext) {
|
||||||
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
|
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
|
||||||
unmarked_snapshot.clip_at_line_ends = true;
|
unmarked_snapshot.default_clip = Clip::EndOfLine;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unmarked_snapshot.clip_point(markers[1], Bias::Left),
|
unmarked_snapshot.clip_point(markers[1], Bias::Left),
|
||||||
markers[0]
|
markers[0]
|
||||||
|
|
|
@ -1544,10 +1544,10 @@ impl Editor {
|
||||||
range.clone()
|
range.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
pub fn set_default_clip(&mut self, clip: Clip, cx: &mut ViewContext<Self>) {
|
||||||
if self.display_map.read(cx).clip_at_line_ends != clip {
|
if self.display_map.read(cx).default_clip != clip {
|
||||||
self.display_map
|
self.display_map
|
||||||
.update(cx, |map, _| map.clip_at_line_ends = clip);
|
.update(cx, |map, _| map.default_clip = clip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ enum FoldMarkers {}
|
||||||
|
|
||||||
struct SelectionLayout {
|
struct SelectionLayout {
|
||||||
head: DisplayPoint,
|
head: DisplayPoint,
|
||||||
|
reversed: bool,
|
||||||
cursor_shape: CursorShape,
|
cursor_shape: CursorShape,
|
||||||
is_newest: bool,
|
is_newest: bool,
|
||||||
range: Range<DisplayPoint>,
|
range: Range<DisplayPoint>,
|
||||||
|
@ -78,6 +79,7 @@ impl SelectionLayout {
|
||||||
let point_range = map.expand_to_line(selection.range());
|
let point_range = map.expand_to_line(selection.range());
|
||||||
Self {
|
Self {
|
||||||
head: selection.head().to_display_point(map),
|
head: selection.head().to_display_point(map),
|
||||||
|
reversed: selection.reversed,
|
||||||
cursor_shape,
|
cursor_shape,
|
||||||
is_newest,
|
is_newest,
|
||||||
range: point_range.start.to_display_point(map)
|
range: point_range.start.to_display_point(map)
|
||||||
|
@ -87,6 +89,7 @@ impl SelectionLayout {
|
||||||
let selection = selection.map(|p| p.to_display_point(map));
|
let selection = selection.map(|p| p.to_display_point(map));
|
||||||
Self {
|
Self {
|
||||||
head: selection.head(),
|
head: selection.head(),
|
||||||
|
reversed: selection.reversed,
|
||||||
cursor_shape,
|
cursor_shape,
|
||||||
is_newest,
|
is_newest,
|
||||||
range: selection.range(),
|
range: selection.range(),
|
||||||
|
@ -844,6 +847,7 @@ impl EditorElement {
|
||||||
|
|
||||||
if editor.show_local_cursors(cx) || replica_id != local_replica_id {
|
if editor.show_local_cursors(cx) || replica_id != local_replica_id {
|
||||||
let cursor_position = selection.head;
|
let cursor_position = selection.head;
|
||||||
|
|
||||||
if layout
|
if layout
|
||||||
.visible_display_row_range
|
.visible_display_row_range
|
||||||
.contains(&cursor_position.row())
|
.contains(&cursor_position.row())
|
||||||
|
@ -851,7 +855,15 @@ impl EditorElement {
|
||||||
let cursor_row_layout = &layout.position_map.line_layouts
|
let cursor_row_layout = &layout.position_map.line_layouts
|
||||||
[(cursor_position.row() - start_row) as usize]
|
[(cursor_position.row() - start_row) as usize]
|
||||||
.line;
|
.line;
|
||||||
let cursor_column = cursor_position.column() as usize;
|
let mut cursor_column = cursor_position.column() as usize;
|
||||||
|
|
||||||
|
if CursorShape::Block == selection.cursor_shape
|
||||||
|
&& !selection.range.is_empty()
|
||||||
|
&& !selection.reversed
|
||||||
|
&& cursor_column > 0
|
||||||
|
{
|
||||||
|
cursor_column -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
||||||
let mut block_width =
|
let mut block_width =
|
||||||
|
@ -863,7 +875,10 @@ impl EditorElement {
|
||||||
layout
|
layout
|
||||||
.position_map
|
.position_map
|
||||||
.snapshot
|
.snapshot
|
||||||
.chars_at(cursor_position)
|
.chars_at(DisplayPoint::new(
|
||||||
|
cursor_position.row(),
|
||||||
|
cursor_column as u32,
|
||||||
|
))
|
||||||
.next()
|
.next()
|
||||||
.and_then(|(character, _)| {
|
.and_then(|(character, _)| {
|
||||||
let font_id =
|
let font_id =
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use editor::{
|
use editor::{
|
||||||
char_kind,
|
char_kind,
|
||||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
display_map::{Clip, DisplaySnapshot, ToDisplayPoint},
|
||||||
movement, Bias, CharKind, DisplayPoint, ToOffset,
|
movement, Bias, CharKind, DisplayPoint, ToOffset,
|
||||||
};
|
};
|
||||||
use gpui::{actions, impl_actions, AppContext, WindowContext};
|
use gpui::{actions, impl_actions, AppContext, WindowContext};
|
||||||
|
@ -295,7 +295,11 @@ impl Motion {
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
EndOfParagraph => (
|
EndOfParagraph => (
|
||||||
map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
|
map.clip_point_with(
|
||||||
|
movement::end_of_paragraph(map, point, times),
|
||||||
|
Bias::Left,
|
||||||
|
Clip::EndOfLine,
|
||||||
|
),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
CurrentLine => (end_of_line(map, point), SelectionGoal::None),
|
CurrentLine => (end_of_line(map, point), SelectionGoal::None),
|
||||||
|
@ -383,8 +387,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 = map.move_left(point, Clip::None);
|
||||||
point = map.clip_point(point, Bias::Left);
|
|
||||||
if point.column() == 0 {
|
if point.column() == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -425,9 +428,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 = map.clip_point(map.move_right(point, Clip::None), Bias::Right);
|
||||||
*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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, Bias, ClipboardSelection,
|
display_map::{Clip, ToDisplayPoint},
|
||||||
DisplayPoint,
|
scroll::autoscroll::Autoscroll,
|
||||||
|
Anchor, Bias, ClipboardSelection, DisplayPoint,
|
||||||
};
|
};
|
||||||
use gpui::{actions, AppContext, ViewContext, WindowContext};
|
use gpui::{actions, AppContext, ViewContext, WindowContext};
|
||||||
use language::{AutoindentMode, Point, SelectionGoal};
|
use language::{AutoindentMode, Point, SelectionGoal};
|
||||||
|
@ -254,7 +255,7 @@ fn paste(_: &mut Workspace, _: &Paste, 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.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
if let Some(item) = cx.read_from_clipboard() {
|
if let Some(item) = cx.read_from_clipboard() {
|
||||||
let mut clipboard_text = Cow::Borrowed(item.text());
|
let mut clipboard_text = Cow::Borrowed(item.text());
|
||||||
if let Some(mut clipboard_selections) =
|
if let Some(mut clipboard_selections) =
|
||||||
|
@ -382,7 +383,7 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
|
||||||
editor.insert(&clipboard_text, cx);
|
editor.insert(&clipboard_text, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
editor.set_default_clip(Clip::EndOfLine, cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -392,7 +393,7 @@ pub(crate) fn normal_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| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
let (map, display_selections) = editor.selections.all_display(cx);
|
let (map, display_selections) = editor.selections.all_display(cx);
|
||||||
// Selections are biased right at the start. So we need to store
|
// Selections are biased right at the start. So we need to store
|
||||||
// anchors that are biased left so that we can restore the selections
|
// anchors that are biased left so that we can restore the selections
|
||||||
|
@ -425,7 +426,7 @@ pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
|
||||||
editor.buffer().update(cx, |buffer, cx| {
|
editor.buffer().update(cx, |buffer, cx| {
|
||||||
buffer.edit(edits, None, cx);
|
buffer.edit(edits, None, cx);
|
||||||
});
|
});
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
editor.set_default_clip(Clip::EndOfLine, cx);
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.select_anchor_ranges(stable_anchors);
|
s.select_anchor_ranges(stable_anchors);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
|
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
|
||||||
use editor::{
|
use editor::{
|
||||||
char_kind, display_map::DisplaySnapshot, movement, scroll::autoscroll::Autoscroll, CharKind,
|
char_kind,
|
||||||
DisplayPoint,
|
display_map::{Clip, DisplaySnapshot},
|
||||||
|
movement,
|
||||||
|
scroll::autoscroll::Autoscroll,
|
||||||
|
CharKind, DisplayPoint,
|
||||||
};
|
};
|
||||||
use gpui::WindowContext;
|
use gpui::WindowContext;
|
||||||
use language::Selection;
|
use language::Selection;
|
||||||
|
@ -15,7 +18,7 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &m
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, 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| {
|
||||||
motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion
|
motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion
|
||||||
|
@ -42,7 +45,7 @@ pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo
|
||||||
let mut objects_found = false;
|
let mut objects_found = false;
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(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| {
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
|
use editor::{
|
||||||
|
display_map::{Clip, ToDisplayPoint},
|
||||||
|
scroll::autoscroll::Autoscroll,
|
||||||
|
Bias,
|
||||||
|
};
|
||||||
use gpui::WindowContext;
|
use gpui::WindowContext;
|
||||||
|
|
||||||
pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
let mut original_columns: HashMap<_, _> = Default::default();
|
let mut original_columns: HashMap<_, _> = Default::default();
|
||||||
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| {
|
||||||
|
@ -19,7 +23,7 @@ pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &m
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
editor.set_default_clip(Clip::EndOfLine, 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 mut cursor = selection.head();
|
let mut cursor = selection.head();
|
||||||
|
@ -39,7 +43,7 @@ pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &m
|
||||||
pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
|
pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
// Emulates behavior in vim where if we expanded backwards to include a newline
|
// Emulates behavior in vim where if we expanded backwards to include a newline
|
||||||
// the cursor gets set back to the start of the line
|
// the cursor gets set back to the start of the line
|
||||||
let mut should_move_to_start: HashSet<_> = Default::default();
|
let mut should_move_to_start: HashSet<_> = Default::default();
|
||||||
|
@ -77,7 +81,7 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
editor.set_default_clip(Clip::EndOfLine, 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 mut cursor = selection.head();
|
let mut cursor = selection.head();
|
||||||
|
|
|
@ -4,8 +4,8 @@ use language::Point;
|
||||||
use crate::{motion::Motion, Mode, Vim};
|
use crate::{motion::Motion, 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) {
|
||||||
|
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| {
|
||||||
|
@ -21,9 +21,7 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
editor.set_clip_at_line_ends(true, cx);
|
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Insert, true, cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use editor::display_map::Clip;
|
||||||
use gpui::WindowContext;
|
use gpui::WindowContext;
|
||||||
|
|
||||||
pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
@ -28,7 +29,7 @@ pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut
|
||||||
pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
|
pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
let mut original_positions: HashMap<_, _> = Default::default();
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
|
|
|
@ -481,6 +481,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(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use editor::display_map::Clip;
|
||||||
use gpui::keymap_matcher::KeymapContext;
|
use gpui::keymap_matcher::KeymapContext;
|
||||||
use language::CursorShape;
|
use language::CursorShape;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -12,6 +13,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 +88,11 @@ impl VimState {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_at_line_end(&self) -> bool {
|
pub fn default_clip(&self) -> Clip {
|
||||||
!matches!(self.mode, Mode::Insert | Mode::Visual { .. })
|
match self.mode {
|
||||||
|
Mode::Insert | Mode::Visual { .. } => Clip::None,
|
||||||
|
Mode::Normal => Clip::EndOfLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn neovim_selection(&mut self) -> Range<usize> {
|
pub async fn neovim_mode(&mut self) -> Mode {
|
||||||
let mut neovim_selection = self.neovim.selection().await;
|
self.neovim.mode().await.unwrap()
|
||||||
// Zed selections adjust themselves to make the end point visually make sense
|
|
||||||
if neovim_selection.start > neovim_selection.end {
|
|
||||||
neovim_selection.start.column += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn neovim_selection(&mut self) -> Range<usize> {
|
||||||
|
let neovim_selection = self.neovim.selection().await;
|
||||||
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,11 @@ 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 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 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>()
|
|
||||||
.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 +265,32 @@ 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 > 0 {
|
||||||
.expect("Could not get neovim buffer");
|
selection_col += 1;
|
||||||
let (start_row, start_col) = nvim_buffer
|
|
||||||
.get_mark("<")
|
|
||||||
.await
|
|
||||||
.expect("Could not get selection start");
|
|
||||||
let (end_row, end_col) = nvim_buffer
|
|
||||||
.get_mark(">")
|
|
||||||
.await
|
|
||||||
.expect("Could not get selection end");
|
|
||||||
self.nvim
|
|
||||||
.input("gv")
|
|
||||||
.await
|
|
||||||
.expect("Could not reselect visual selection");
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
(
|
let cursor_line_length =
|
||||||
|
self.read_position("echo strlen(getline(line('.')))").await;
|
||||||
|
if cursor_line_length > 0 {
|
||||||
|
cursor_col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Mode::Insert) | Some(Mode::Normal) | None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (start, end) = (
|
||||||
|
Point::new(selection_row, selection_col),
|
||||||
Point::new(cursor_row, cursor_col),
|
Point::new(cursor_row, cursor_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_id = self.window_id;
|
let window_id = self.window_id;
|
||||||
|
let context_handle = self.cx.set_state(text);
|
||||||
self.update_window(window_id, |cx| {
|
self.update_window(window_id, |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::{display_map::Clip, 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 = map.move_left(selection.head(), Clip::None);
|
||||||
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 = map.move_right(selection.start, Clip::None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -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_default_clip(state.default_clip(), 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 });
|
||||||
|
@ -326,7 +331,7 @@ impl Vim {
|
||||||
|
|
||||||
fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||||
editor.set_cursor_shape(CursorShape::Bar, cx);
|
editor.set_cursor_shape(CursorShape::Bar, cx);
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_default_clip(Clip::None, cx);
|
||||||
editor.set_input_enabled(true);
|
editor.set_input_enabled(true);
|
||||||
editor.selections.line_mode = false;
|
editor.selections.line_mode = false;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,10 @@ use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection,
|
display_map::{Clip, ToDisplayPoint},
|
||||||
|
movement,
|
||||||
|
scroll::autoscroll::Autoscroll,
|
||||||
|
Bias, ClipboardSelection,
|
||||||
};
|
};
|
||||||
use gpui::{actions, AppContext, ViewContext, WindowContext};
|
use gpui::{actions, AppContext, ViewContext, WindowContext};
|
||||||
use language::{AutoindentMode, SelectionGoal};
|
use language::{AutoindentMode, SelectionGoal};
|
||||||
|
@ -16,9 +19,21 @@ use crate::{
|
||||||
Vim,
|
Vim,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(vim, [VisualDelete, VisualChange, VisualYank, VisualPaste]);
|
actions!(
|
||||||
|
vim,
|
||||||
|
[
|
||||||
|
ToggleVisual,
|
||||||
|
ToggleVisualLine,
|
||||||
|
VisualDelete,
|
||||||
|
VisualChange,
|
||||||
|
VisualYank,
|
||||||
|
VisualPaste
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
cx.add_action(toggle_visual);
|
||||||
|
cx.add_action(toggle_visual_line);
|
||||||
cx.add_action(change);
|
cx.add_action(change);
|
||||||
cx.add_action(delete);
|
cx.add_action(delete);
|
||||||
cx.add_action(yank);
|
cx.add_action(yank);
|
||||||
|
@ -32,23 +47,32 @@ 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)
|
|
||||||
{
|
// our motions assume the current character is after the cursor,
|
||||||
|
// but in (forward) visual mode the current character is just
|
||||||
|
// before the end of the selection.
|
||||||
|
if !selection.reversed {
|
||||||
|
current_head = map.move_left(current_head, Clip::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((new_head, goal)) =
|
||||||
|
motion.move_point(map, current_head, selection.goal, times) else { return };
|
||||||
|
|
||||||
selection.set_head(new_head, goal);
|
selection.set_head(new_head, goal);
|
||||||
|
|
||||||
if was_reversed && !selection.reversed {
|
// ensure the current character is included in the selection.
|
||||||
// Head was at the start of the selection, and now is at the end. We need to move the start
|
if !selection.reversed {
|
||||||
// back by one if possible in order to compensate for this change.
|
selection.end = map.move_right(selection.end, Clip::None);
|
||||||
*selection.start.column_mut() =
|
|
||||||
selection.start.column().saturating_sub(1);
|
|
||||||
selection.start = map.clip_point(selection.start, Bias::Left);
|
|
||||||
} else if !was_reversed && selection.reversed {
|
|
||||||
// Head was at the end of the selection, and now is at the start. We need to move the end
|
|
||||||
// forward by one if possible in order to compensate for this change.
|
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
|
||||||
selection.end = map.clip_point(selection.end, Bias::Right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 = map.move_left(selection.start, Clip::None);
|
||||||
|
} else if !was_reversed && selection.reversed {
|
||||||
|
selection.end = map.move_right(selection.end, Clip::None);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -64,14 +88,30 @@ 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() {
|
// all our motions assume that the current character is
|
||||||
if let Some((_, end)) = map.reverse_chars_at(range.end).next() {
|
// after the cursor; however in the case of a visual selection
|
||||||
range.end = end;
|
// the current character is before the cursor.
|
||||||
|
if !selection.reversed {
|
||||||
|
head = map.move_left(head, Clip::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if selection.is_empty() {
|
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
|
||||||
|
};
|
||||||
|
dbg!(expand_both_ways);
|
||||||
|
|
||||||
|
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,10 +128,35 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
|
pub fn change(_: &mut Workspace, _: &VisualChange, 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
|
// Compute edits and resulting anchor selections. If in line mode, adjust
|
||||||
// the anchor location and additional newline
|
// the anchor location and additional newline
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
|
@ -99,13 +164,6 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
|
||||||
let line_mode = editor.selections.line_mode;
|
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(|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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_mode {
|
if line_mode {
|
||||||
let range = selection.map(|p| p.to_point(map)).range();
|
let range = selection.map(|p| p.to_point(map)).range();
|
||||||
let expanded_range = map.expand_to_line(range);
|
let expanded_range = map.expand_to_line(range);
|
||||||
|
@ -134,26 +192,23 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
|
||||||
s.select_anchors(new_selections);
|
s.select_anchors(new_selections);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vim.switch_mode(Mode::Insert, false, cx);
|
vim.switch_mode(Mode::Insert, true, 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 = map.move_left(position, Clip::None);
|
||||||
// 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;
|
||||||
});
|
});
|
||||||
|
@ -162,7 +217,6 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
|
|
||||||
// Fixup cursor position after the deletion
|
// Fixup cursor position after the deletion
|
||||||
editor.set_clip_at_line_ends(true, 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 mut cursor = selection.head().to_point(map);
|
let mut cursor = selection.head().to_point(map);
|
||||||
|
@ -170,32 +224,23 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
|
||||||
if let Some(column) = original_columns.get(&selection.id) {
|
if let Some(column) = original_columns.get(&selection.id) {
|
||||||
cursor.column = *column
|
cursor.column = *column
|
||||||
}
|
}
|
||||||
let cursor = map.clip_point(cursor.to_display_point(map), Bias::Left);
|
let cursor = map.clip_point_with(
|
||||||
|
cursor.to_display_point(map),
|
||||||
|
Bias::Left,
|
||||||
|
Clip::EndOfLine,
|
||||||
|
);
|
||||||
selection.collapse_to(cursor, selection.goal)
|
selection.collapse_to(cursor, selection.goal)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
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 +248,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 +301,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 +352,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 +377,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())
|
||||||
{
|
{
|
||||||
|
@ -375,19 +409,42 @@ 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;
|
.await;
|
||||||
let mut cx = cx.binding(["v", "b", "k"]);
|
|
||||||
cx.assert_all(indoc! {"
|
// entering visual mode should select the character
|
||||||
The ˇquick brown
|
// under cursor
|
||||||
|
cx.simulate_shared_keystrokes(["v"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "The «qˇ»uick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// forwards motions should extend the selection
|
||||||
|
cx.simulate_shared_keystrokes(["w", "j"]).await;
|
||||||
|
cx.assert_shared_state(indoc! { "The «quick brown
|
||||||
|
fox jumps oˇ»ver
|
||||||
|
the lazy dog"})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.simulate_shared_keystrokes(["escape"]).await;
|
||||||
|
assert_eq!(Mode::Normal, cx.neovim_mode().await);
|
||||||
|
cx.assert_shared_state(indoc! { "The quick brown
|
||||||
fox jumps ˇover
|
fox jumps ˇover
|
||||||
the ˇlazy dog"})
|
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;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,23 +518,34 @@ mod test {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_visual_change(cx: &mut gpui::TestAppContext) {
|
async fn test_visual_change(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx)
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
.await
|
|
||||||
.binding(["v", "w", "c"]);
|
cx.set_shared_state("The quick ˇbrown").await;
|
||||||
cx.assert("The quick ˇbrown").await;
|
cx.simulate_shared_keystrokes(["v", "w", "c"]).await;
|
||||||
let mut cx = cx.binding(["v", "w", "j", "c"]);
|
cx.assert_shared_state("The quick ˇ").await;
|
||||||
cx.assert_all(indoc! {"
|
|
||||||
|
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
|
The ˇquick brown
|
||||||
fox jumps ˇover
|
fox jumps ˇover
|
||||||
the ˇlazy dog"})
|
the ˇlazy dog"});
|
||||||
|
for initial_state in cases {
|
||||||
|
cx.assert_neovim_compatible(&initial_state, ["v", "w", "j", "c"])
|
||||||
.await;
|
.await;
|
||||||
let mut cx = cx.binding(["v", "b", "k", "c"]);
|
cx.assert_neovim_compatible(&initial_state, ["v", "w", "k", "c"])
|
||||||
cx.assert_all(indoc! {"
|
|
||||||
The ˇquick brown
|
|
||||||
fox jumps ˇover
|
|
||||||
the ˇlazy dog"})
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
|
async fn test_visual_line_change(cx: &mut gpui::TestAppContext) {
|
||||||
|
@ -605,7 +673,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 +697,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 +711,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");
|
||||||
|
|
|
@ -1,30 +1,12 @@
|
||||||
{"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":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
|
||||||
{"Key":"v"}
|
|
||||||
{"Key":"b"}
|
|
||||||
{"Key":"k"}
|
|
||||||
{"Get":{"state":"The quick brown\n«ˇfox jumps over\nthe »lazy dog","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,13 +1,19 @@
|
||||||
|
{"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"}
|
||||||
|
@ -17,17 +23,17 @@
|
||||||
{"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"}
|
||||||
|
@ -52,37 +58,37 @@
|
||||||
{"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"}
|
||||||
|
@ -102,12 +108,12 @@
|
||||||
{"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"}
|
||||||
|
@ -117,12 +123,12 @@
|
||||||
{"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"}
|
||||||
|
@ -132,17 +138,17 @@
|
||||||
{"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"}
|
||||||
|
@ -167,37 +173,37 @@
|
||||||
{"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"}
|
||||||
|
@ -217,12 +223,12 @@
|
||||||
{"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"}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue