Merge branch 'main' into multi-server-completions-tailwind
This commit is contained in:
commit
ff3865a4ad
427 changed files with 43123 additions and 12861 deletions
|
@ -1,4 +1,4 @@
|
|||
use crate::Vim;
|
||||
use crate::{Vim, VimEvent};
|
||||
use editor::{EditorBlurred, EditorFocused, EditorReleased};
|
||||
use gpui::AppContext;
|
||||
|
||||
|
@ -22,6 +22,11 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
|
|||
editor.window().update(cx, |cx| {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.set_active_editor(editor.clone(), cx);
|
||||
if vim.enabled {
|
||||
cx.emit_global(VimEvent::ModeChanged {
|
||||
mode: vim.state().mode,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -48,6 +53,7 @@ fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
|
|||
vim.active_editor = None;
|
||||
}
|
||||
}
|
||||
vim.editor_states.remove(&editor.id())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ impl ModeIndicator {
|
|||
if settings::get::<VimModeSetting>(cx).0 {
|
||||
mode_indicator.mode = cx
|
||||
.has_global::<Vim>()
|
||||
.then(|| cx.global::<Vim>().state.mode);
|
||||
.then(|| cx.global::<Vim>().state().mode);
|
||||
} else {
|
||||
mode_indicator.mode.take();
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ impl ModeIndicator {
|
|||
.has_global::<Vim>()
|
||||
.then(|| {
|
||||
let vim = cx.global::<Vim>();
|
||||
vim.enabled.then(|| vim.state.mode)
|
||||
vim.enabled.then(|| vim.state().mode)
|
||||
})
|
||||
.flatten();
|
||||
|
||||
|
@ -80,14 +80,12 @@ impl View for ModeIndicator {
|
|||
|
||||
let theme = &theme::current(cx).workspace.status_bar;
|
||||
|
||||
// we always choose text to be 12 monospace characters
|
||||
// so that as the mode indicator changes, the rest of the
|
||||
// UI stays still.
|
||||
let text = match mode {
|
||||
Mode::Normal => "-- NORMAL --",
|
||||
Mode::Insert => "-- INSERT --",
|
||||
Mode::Visual { line: false } => "-- VISUAL --",
|
||||
Mode::Visual { line: true } => "VISUAL LINE",
|
||||
Mode::Visual => "-- VISUAL --",
|
||||
Mode::VisualLine => "-- VISUAL LINE --",
|
||||
Mode::VisualBlock => "-- VISUAL BLOCK --",
|
||||
};
|
||||
Label::new(text, theme.vim_mode_indicator.text.clone())
|
||||
.contained()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
use std::{cmp, sync::Arc};
|
||||
|
||||
use editor::{
|
||||
char_kind,
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
|
||||
movement, Bias, CharKind, DisplayPoint, ToOffset,
|
||||
};
|
||||
use gpui::{actions, impl_actions, AppContext, WindowContext};
|
||||
|
@ -21,16 +21,16 @@ use crate::{
|
|||
pub enum Motion {
|
||||
Left,
|
||||
Backspace,
|
||||
Down,
|
||||
Up,
|
||||
Down { display_lines: bool },
|
||||
Up { display_lines: bool },
|
||||
Right,
|
||||
NextWordStart { ignore_punctuation: bool },
|
||||
NextWordEnd { ignore_punctuation: bool },
|
||||
PreviousWordStart { ignore_punctuation: bool },
|
||||
FirstNonWhitespace,
|
||||
FirstNonWhitespace { display_lines: bool },
|
||||
CurrentLine,
|
||||
StartOfLine,
|
||||
EndOfLine,
|
||||
StartOfLine { display_lines: bool },
|
||||
EndOfLine { display_lines: bool },
|
||||
StartOfParagraph,
|
||||
EndOfParagraph,
|
||||
StartOfDocument,
|
||||
|
@ -62,6 +62,41 @@ struct PreviousWordStart {
|
|||
ignore_punctuation: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Up {
|
||||
#[serde(default)]
|
||||
display_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Down {
|
||||
#[serde(default)]
|
||||
display_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FirstNonWhitespace {
|
||||
#[serde(default)]
|
||||
display_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct EndOfLine {
|
||||
#[serde(default)]
|
||||
display_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct StartOfLine {
|
||||
#[serde(default)]
|
||||
display_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
struct RepeatFind {
|
||||
#[serde(default)]
|
||||
|
@ -73,12 +108,7 @@ actions!(
|
|||
[
|
||||
Left,
|
||||
Backspace,
|
||||
Down,
|
||||
Up,
|
||||
Right,
|
||||
FirstNonWhitespace,
|
||||
StartOfLine,
|
||||
EndOfLine,
|
||||
CurrentLine,
|
||||
StartOfParagraph,
|
||||
EndOfParagraph,
|
||||
|
@ -90,20 +120,63 @@ actions!(
|
|||
);
|
||||
impl_actions!(
|
||||
vim,
|
||||
[NextWordStart, NextWordEnd, PreviousWordStart, RepeatFind]
|
||||
[
|
||||
NextWordStart,
|
||||
NextWordEnd,
|
||||
PreviousWordStart,
|
||||
RepeatFind,
|
||||
Up,
|
||||
Down,
|
||||
FirstNonWhitespace,
|
||||
EndOfLine,
|
||||
StartOfLine,
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &Down, cx: _| motion(Motion::Down, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &Up, cx: _| motion(Motion::Up, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &FirstNonWhitespace, cx: _| {
|
||||
motion(Motion::FirstNonWhitespace, cx)
|
||||
cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {
|
||||
motion(
|
||||
Motion::Down {
|
||||
display_lines: action.display_lines,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.add_action(|_: &mut Workspace, action: &Up, cx: _| {
|
||||
motion(
|
||||
Motion::Up {
|
||||
display_lines: action.display_lines,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
|
||||
cx.add_action(|_: &mut Workspace, action: &FirstNonWhitespace, cx: _| {
|
||||
motion(
|
||||
Motion::FirstNonWhitespace {
|
||||
display_lines: action.display_lines,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.add_action(|_: &mut Workspace, action: &StartOfLine, cx: _| {
|
||||
motion(
|
||||
Motion::StartOfLine {
|
||||
display_lines: action.display_lines,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.add_action(|_: &mut Workspace, action: &EndOfLine, cx: _| {
|
||||
motion(
|
||||
Motion::EndOfLine {
|
||||
display_lines: action.display_lines,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.add_action(|_: &mut Workspace, _: &StartOfLine, cx: _| motion(Motion::StartOfLine, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &EndOfLine, cx: _| motion(Motion::EndOfLine, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));
|
||||
cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
|
||||
motion(Motion::StartOfParagraph, cx)
|
||||
|
@ -147,9 +220,9 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
|
|||
|
||||
let times = Vim::update(cx, |vim, cx| vim.pop_number_operator(cx));
|
||||
let operator = Vim::read(cx).active_operator();
|
||||
match Vim::read(cx).state.mode {
|
||||
match Vim::read(cx).state().mode {
|
||||
Mode::Normal => normal_motion(motion, operator, times, cx),
|
||||
Mode::Visual { .. } => visual_motion(motion, times, cx),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_motion(motion, times, cx),
|
||||
Mode::Insert => {
|
||||
// Shouldn't execute a motion in insert mode. Ignoring
|
||||
}
|
||||
|
@ -158,7 +231,7 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
|
|||
}
|
||||
|
||||
fn repeat_motion(backwards: bool, cx: &mut WindowContext) {
|
||||
let find = match Vim::read(cx).state.last_find.clone() {
|
||||
let find = match Vim::read(cx).workspace_state.last_find.clone() {
|
||||
Some(Motion::FindForward { before, text }) => {
|
||||
if backwards {
|
||||
Motion::FindBackward {
|
||||
|
@ -192,19 +265,25 @@ impl Motion {
|
|||
pub fn linewise(&self) -> bool {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart
|
||||
| StartOfParagraph | EndOfParagraph => true,
|
||||
EndOfLine
|
||||
Down { .. }
|
||||
| Up { .. }
|
||||
| StartOfDocument
|
||||
| EndOfDocument
|
||||
| CurrentLine
|
||||
| NextLineStart
|
||||
| StartOfParagraph
|
||||
| EndOfParagraph => true,
|
||||
EndOfLine { .. }
|
||||
| NextWordEnd { .. }
|
||||
| Matching
|
||||
| FindForward { .. }
|
||||
| Left
|
||||
| Backspace
|
||||
| Right
|
||||
| StartOfLine
|
||||
| StartOfLine { .. }
|
||||
| NextWordStart { .. }
|
||||
| PreviousWordStart { .. }
|
||||
| FirstNonWhitespace
|
||||
| FirstNonWhitespace { .. }
|
||||
| FindBackward { .. } => false,
|
||||
}
|
||||
}
|
||||
|
@ -213,21 +292,21 @@ impl Motion {
|
|||
use Motion::*;
|
||||
match self {
|
||||
StartOfDocument | EndOfDocument | CurrentLine => true,
|
||||
Down
|
||||
| Up
|
||||
| EndOfLine
|
||||
Down { .. }
|
||||
| Up { .. }
|
||||
| EndOfLine { .. }
|
||||
| NextWordEnd { .. }
|
||||
| Matching
|
||||
| FindForward { .. }
|
||||
| Left
|
||||
| Backspace
|
||||
| Right
|
||||
| StartOfLine
|
||||
| StartOfLine { .. }
|
||||
| StartOfParagraph
|
||||
| EndOfParagraph
|
||||
| NextWordStart { .. }
|
||||
| PreviousWordStart { .. }
|
||||
| FirstNonWhitespace
|
||||
| FirstNonWhitespace { .. }
|
||||
| FindBackward { .. }
|
||||
| NextLineStart => false,
|
||||
}
|
||||
|
@ -236,12 +315,12 @@ impl Motion {
|
|||
pub fn inclusive(&self) -> bool {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Down
|
||||
| Up
|
||||
Down { .. }
|
||||
| Up { .. }
|
||||
| StartOfDocument
|
||||
| EndOfDocument
|
||||
| CurrentLine
|
||||
| EndOfLine
|
||||
| EndOfLine { .. }
|
||||
| NextWordEnd { .. }
|
||||
| Matching
|
||||
| FindForward { .. }
|
||||
|
@ -249,12 +328,12 @@ impl Motion {
|
|||
Left
|
||||
| Backspace
|
||||
| Right
|
||||
| StartOfLine
|
||||
| StartOfLine { .. }
|
||||
| StartOfParagraph
|
||||
| EndOfParagraph
|
||||
| NextWordStart { .. }
|
||||
| PreviousWordStart { .. }
|
||||
| FirstNonWhitespace
|
||||
| FirstNonWhitespace { .. }
|
||||
| FindBackward { .. } => false,
|
||||
}
|
||||
}
|
||||
|
@ -272,8 +351,18 @@ impl Motion {
|
|||
let (new_point, goal) = match self {
|
||||
Left => (left(map, point, times), SelectionGoal::None),
|
||||
Backspace => (backspace(map, point, times), SelectionGoal::None),
|
||||
Down => down(map, point, goal, times),
|
||||
Up => up(map, point, goal, times),
|
||||
Down {
|
||||
display_lines: false,
|
||||
} => down(map, point, goal, times),
|
||||
Down {
|
||||
display_lines: true,
|
||||
} => down_display(map, point, goal, times),
|
||||
Up {
|
||||
display_lines: false,
|
||||
} => up(map, point, goal, times),
|
||||
Up {
|
||||
display_lines: true,
|
||||
} => up_display(map, point, goal, times),
|
||||
Right => (right(map, point, times), SelectionGoal::None),
|
||||
NextWordStart { ignore_punctuation } => (
|
||||
next_word_start(map, point, *ignore_punctuation, times),
|
||||
|
@ -287,9 +376,17 @@ impl Motion {
|
|||
previous_word_start(map, point, *ignore_punctuation, times),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None),
|
||||
StartOfLine => (start_of_line(map, point), SelectionGoal::None),
|
||||
EndOfLine => (end_of_line(map, point), SelectionGoal::None),
|
||||
FirstNonWhitespace { display_lines } => (
|
||||
first_non_whitespace(map, *display_lines, point),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
StartOfLine { display_lines } => (
|
||||
start_of_line(map, *display_lines, point),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
EndOfLine { display_lines } => {
|
||||
(end_of_line(map, *display_lines, point), SelectionGoal::None)
|
||||
}
|
||||
StartOfParagraph => (
|
||||
movement::start_of_paragraph(map, point, times),
|
||||
SelectionGoal::None,
|
||||
|
@ -298,7 +395,7 @@ impl Motion {
|
|||
map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
CurrentLine => (end_of_line(map, point), SelectionGoal::None),
|
||||
CurrentLine => (end_of_line(map, false, point), SelectionGoal::None),
|
||||
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
|
||||
EndOfDocument => (
|
||||
end_of_document(map, point, maybe_times),
|
||||
|
@ -399,6 +496,33 @@ fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Di
|
|||
}
|
||||
|
||||
fn down(
|
||||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
mut goal: SelectionGoal,
|
||||
times: usize,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
let start = map.display_point_to_fold_point(point, Bias::Left);
|
||||
|
||||
let goal_column = match goal {
|
||||
SelectionGoal::Column(column) => column,
|
||||
SelectionGoal::ColumnRange { end, .. } => end,
|
||||
_ => {
|
||||
goal = SelectionGoal::Column(start.column());
|
||||
start.column()
|
||||
}
|
||||
};
|
||||
|
||||
let new_row = cmp::min(
|
||||
start.row() + times as u32,
|
||||
map.buffer_snapshot.max_point().row,
|
||||
);
|
||||
let new_col = cmp::min(goal_column, map.fold_snapshot.line_len(new_row));
|
||||
let point = map.fold_point_to_display_point(FoldPoint::new(new_row, new_col));
|
||||
|
||||
(map.clip_point(point, Bias::Left), goal)
|
||||
}
|
||||
|
||||
fn down_display(
|
||||
map: &DisplaySnapshot,
|
||||
mut point: DisplayPoint,
|
||||
mut goal: SelectionGoal,
|
||||
|
@ -407,10 +531,35 @@ fn down(
|
|||
for _ in 0..times {
|
||||
(point, goal) = movement::down(map, point, goal, true);
|
||||
}
|
||||
|
||||
(point, goal)
|
||||
}
|
||||
|
||||
fn up(
|
||||
pub(crate) fn up(
|
||||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
mut goal: SelectionGoal,
|
||||
times: usize,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
let start = map.display_point_to_fold_point(point, Bias::Left);
|
||||
|
||||
let goal_column = match goal {
|
||||
SelectionGoal::Column(column) => column,
|
||||
SelectionGoal::ColumnRange { end, .. } => end,
|
||||
_ => {
|
||||
goal = SelectionGoal::Column(start.column());
|
||||
start.column()
|
||||
}
|
||||
};
|
||||
|
||||
let new_row = start.row().saturating_sub(times as u32);
|
||||
let new_col = cmp::min(goal_column, map.fold_snapshot.line_len(new_row));
|
||||
let point = map.fold_point_to_display_point(FoldPoint::new(new_row, new_col));
|
||||
|
||||
(map.clip_point(point, Bias::Left), goal)
|
||||
}
|
||||
|
||||
fn up_display(
|
||||
map: &DisplaySnapshot,
|
||||
mut point: DisplayPoint,
|
||||
mut goal: SelectionGoal,
|
||||
|
@ -419,6 +568,7 @@ fn up(
|
|||
for _ in 0..times {
|
||||
(point, goal) = movement::up(map, point, goal, true);
|
||||
}
|
||||
|
||||
(point, goal)
|
||||
}
|
||||
|
||||
|
@ -509,8 +659,12 @@ fn previous_word_start(
|
|||
point
|
||||
}
|
||||
|
||||
fn first_non_whitespace(map: &DisplaySnapshot, from: DisplayPoint) -> DisplayPoint {
|
||||
let mut last_point = DisplayPoint::new(from.row(), 0);
|
||||
fn first_non_whitespace(
|
||||
map: &DisplaySnapshot,
|
||||
display_lines: bool,
|
||||
from: DisplayPoint,
|
||||
) -> DisplayPoint {
|
||||
let mut last_point = start_of_line(map, display_lines, from);
|
||||
let scope = map.buffer_snapshot.language_scope_at(from.to_point(map));
|
||||
for (ch, point) in map.chars_at(last_point) {
|
||||
if ch == '\n' {
|
||||
|
@ -527,12 +681,31 @@ fn first_non_whitespace(map: &DisplaySnapshot, from: DisplayPoint) -> DisplayPoi
|
|||
map.clip_point(last_point, Bias::Left)
|
||||
}
|
||||
|
||||
fn start_of_line(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
map.prev_line_boundary(point.to_point(map)).1
|
||||
pub(crate) fn start_of_line(
|
||||
map: &DisplaySnapshot,
|
||||
display_lines: bool,
|
||||
point: DisplayPoint,
|
||||
) -> DisplayPoint {
|
||||
if display_lines {
|
||||
map.clip_point(DisplayPoint::new(point.row(), 0), Bias::Right)
|
||||
} else {
|
||||
map.prev_line_boundary(point.to_point(map)).1
|
||||
}
|
||||
}
|
||||
|
||||
fn end_of_line(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
|
||||
pub(crate) fn end_of_line(
|
||||
map: &DisplaySnapshot,
|
||||
display_lines: bool,
|
||||
point: DisplayPoint,
|
||||
) -> DisplayPoint {
|
||||
if display_lines {
|
||||
map.clip_point(
|
||||
DisplayPoint::new(point.row(), map.line_len(point.row())),
|
||||
Bias::Left,
|
||||
)
|
||||
} else {
|
||||
map.clip_point(map.next_line_boundary(point.to_point(map)).1, Bias::Left)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
|
||||
|
@ -654,8 +827,8 @@ fn find_backward(
|
|||
}
|
||||
|
||||
fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
let new_row = (point.row() + times as u32).min(map.max_buffer_row());
|
||||
map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left)
|
||||
let correct_line = down(map, point, SelectionGoal::None, times).0;
|
||||
first_non_whitespace(map, false, correct_line)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -803,4 +976,12 @@ mod test {
|
|||
cx.simulate_shared_keystrokes([","]).await;
|
||||
cx.assert_shared_state("one two thˇree four").await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_next_line_start(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.set_shared_state("ˇone\n two\nthree").await;
|
||||
cx.simulate_shared_keystrokes(["enter"]).await;
|
||||
cx.assert_shared_state("one\n ˇtwo\nthree").await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
mod case;
|
||||
mod change;
|
||||
mod delete;
|
||||
mod paste;
|
||||
mod scroll;
|
||||
mod search;
|
||||
pub mod substitute;
|
||||
mod yank;
|
||||
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
motion::Motion,
|
||||
motion::{self, Motion},
|
||||
object::Object,
|
||||
state::{Mode, Operator},
|
||||
Vim,
|
||||
};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, Bias, ClipboardSelection,
|
||||
DisplayPoint,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use editor::scroll::autoscroll::Autoscroll;
|
||||
use editor::{Bias, DisplayPoint};
|
||||
use gpui::{actions, AppContext, ViewContext, WindowContext};
|
||||
use language::{AutoindentMode, Point, SelectionGoal};
|
||||
use language::SelectionGoal;
|
||||
use log::error;
|
||||
use workspace::Workspace;
|
||||
|
||||
|
@ -44,7 +43,6 @@ actions!(
|
|||
DeleteRight,
|
||||
ChangeToEndOfLine,
|
||||
DeleteToEndOfLine,
|
||||
Paste,
|
||||
Yank,
|
||||
Substitute,
|
||||
ChangeCase,
|
||||
|
@ -80,18 +78,31 @@ pub fn init(cx: &mut AppContext) {
|
|||
cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
let times = vim.pop_number_operator(cx);
|
||||
change_motion(vim, Motion::EndOfLine, times, cx);
|
||||
change_motion(
|
||||
vim,
|
||||
Motion::EndOfLine {
|
||||
display_lines: false,
|
||||
},
|
||||
times,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
let times = vim.pop_number_operator(cx);
|
||||
delete_motion(vim, Motion::EndOfLine, times, cx);
|
||||
delete_motion(
|
||||
vim,
|
||||
Motion::EndOfLine {
|
||||
display_lines: false,
|
||||
},
|
||||
times,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
cx.add_action(paste);
|
||||
|
||||
scroll::init(cx);
|
||||
paste::init(cx);
|
||||
}
|
||||
|
||||
pub fn normal_motion(
|
||||
|
@ -116,8 +127,8 @@ pub fn normal_motion(
|
|||
|
||||
pub fn normal_object(object: Object, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
match vim.state.operator_stack.pop() {
|
||||
Some(Operator::Object { around }) => match vim.state.operator_stack.pop() {
|
||||
match vim.maybe_pop_operator() {
|
||||
Some(Operator::Object { around }) => match vim.maybe_pop_operator() {
|
||||
Some(Operator::Change) => change_object(vim, object, around, cx),
|
||||
Some(Operator::Delete) => delete_object(vim, object, around, cx),
|
||||
Some(Operator::Yank) => yank_object(vim, object, around, cx),
|
||||
|
@ -168,7 +179,10 @@ fn insert_first_non_whitespace(
|
|||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.maybe_move_cursors_with(|map, cursor, goal| {
|
||||
Motion::FirstNonWhitespace.move_point(map, cursor, goal, None)
|
||||
Motion::FirstNonWhitespace {
|
||||
display_lines: false,
|
||||
}
|
||||
.move_point(map, cursor, goal, None)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -181,7 +195,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
|
|||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.maybe_move_cursors_with(|map, cursor, goal| {
|
||||
Motion::EndOfLine.move_point(map, cursor, goal, None)
|
||||
Motion::CurrentLine.move_point(map, cursor, goal, None)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -200,19 +214,19 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
|
|||
.collect();
|
||||
let edits = selection_start_rows.into_iter().map(|row| {
|
||||
let (indent, _) = map.line_indent(row);
|
||||
let start_of_line = map
|
||||
.clip_point(DisplayPoint::new(row, 0), Bias::Left)
|
||||
.to_point(&map);
|
||||
let start_of_line =
|
||||
motion::start_of_line(&map, false, DisplayPoint::new(row, 0))
|
||||
.to_point(&map);
|
||||
let mut new_text = " ".repeat(indent as usize);
|
||||
new_text.push('\n');
|
||||
(start_of_line..start_of_line, new_text)
|
||||
});
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_cursors_with(|map, mut cursor, _| {
|
||||
*cursor.row_mut() -= 1;
|
||||
*cursor.column_mut() = map.line_len(cursor.row());
|
||||
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
||||
s.move_cursors_with(|map, cursor, _| {
|
||||
let previous_line = motion::up(map, cursor, SelectionGoal::None, 1).0;
|
||||
let insert_point = motion::end_of_line(map, false, previous_line);
|
||||
(insert_point, SelectionGoal::None)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -226,22 +240,23 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
|
|||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
let (map, old_selections) = editor.selections.all_display(cx);
|
||||
|
||||
let selection_end_rows: HashSet<u32> = old_selections
|
||||
.into_iter()
|
||||
.map(|selection| selection.end.row())
|
||||
.collect();
|
||||
let edits = selection_end_rows.into_iter().map(|row| {
|
||||
let (indent, _) = map.line_indent(row);
|
||||
let end_of_line = map
|
||||
.clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left)
|
||||
.to_point(&map);
|
||||
let end_of_line =
|
||||
motion::end_of_line(&map, false, DisplayPoint::new(row, 0)).to_point(&map);
|
||||
|
||||
let mut new_text = "\n".to_string();
|
||||
new_text.push_str(&" ".repeat(indent as usize));
|
||||
(end_of_line..end_of_line, new_text)
|
||||
});
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.maybe_move_cursors_with(|map, cursor, goal| {
|
||||
Motion::EndOfLine.move_point(map, cursor, goal, None)
|
||||
Motion::CurrentLine.move_point(map, cursor, goal, None)
|
||||
});
|
||||
});
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
|
@ -250,144 +265,6 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
|
|||
});
|
||||
}
|
||||
|
||||
fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
let mut clipboard_text = Cow::Borrowed(item.text());
|
||||
if let Some(mut clipboard_selections) =
|
||||
item.metadata::<Vec<ClipboardSelection>>()
|
||||
{
|
||||
let (display_map, selections) = editor.selections.all_display(cx);
|
||||
let all_selections_were_entire_line =
|
||||
clipboard_selections.iter().all(|s| s.is_entire_line);
|
||||
if clipboard_selections.len() != selections.len() {
|
||||
let mut newline_separated_text = String::new();
|
||||
let mut clipboard_selections =
|
||||
clipboard_selections.drain(..).peekable();
|
||||
let mut ix = 0;
|
||||
while let Some(clipboard_selection) = clipboard_selections.next() {
|
||||
newline_separated_text
|
||||
.push_str(&clipboard_text[ix..ix + clipboard_selection.len]);
|
||||
ix += clipboard_selection.len;
|
||||
if clipboard_selections.peek().is_some() {
|
||||
newline_separated_text.push('\n');
|
||||
}
|
||||
}
|
||||
clipboard_text = Cow::Owned(newline_separated_text);
|
||||
}
|
||||
|
||||
// If the pasted text is a single line, the cursor should be placed after
|
||||
// the newly pasted text. This is easiest done with an anchor after the
|
||||
// insertion, and then with a fixup to move the selection back one position.
|
||||
// However if the pasted text is linewise, the cursor should be placed at the start
|
||||
// of the new text on the following line. This is easiest done with a manually adjusted
|
||||
// point.
|
||||
// This enum lets us represent both cases
|
||||
enum NewPosition {
|
||||
Inside(Point),
|
||||
After(Anchor),
|
||||
}
|
||||
let mut new_selections: HashMap<usize, NewPosition> = Default::default();
|
||||
editor.buffer().update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let mut start_offset = 0;
|
||||
let mut edits = Vec::new();
|
||||
for (ix, selection) in selections.iter().enumerate() {
|
||||
let to_insert;
|
||||
let linewise;
|
||||
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
|
||||
let end_offset = start_offset + clipboard_selection.len;
|
||||
to_insert = &clipboard_text[start_offset..end_offset];
|
||||
linewise = clipboard_selection.is_entire_line;
|
||||
start_offset = end_offset;
|
||||
} else {
|
||||
to_insert = clipboard_text.as_str();
|
||||
linewise = all_selections_were_entire_line;
|
||||
}
|
||||
|
||||
// If the clipboard text was copied linewise, and the current selection
|
||||
// is empty, then paste the text after this line and move the selection
|
||||
// to the start of the pasted text
|
||||
let insert_at = if linewise {
|
||||
let (point, _) = display_map
|
||||
.next_line_boundary(selection.start.to_point(&display_map));
|
||||
|
||||
if !to_insert.starts_with('\n') {
|
||||
// Add newline before pasted text so that it shows up
|
||||
edits.push((point..point, "\n"));
|
||||
}
|
||||
// Drop selection at the start of the next line
|
||||
new_selections.insert(
|
||||
selection.id,
|
||||
NewPosition::Inside(Point::new(point.row + 1, 0)),
|
||||
);
|
||||
point
|
||||
} else {
|
||||
let mut point = selection.end;
|
||||
// Paste the text after the current selection
|
||||
*point.column_mut() = point.column() + 1;
|
||||
let point = display_map
|
||||
.clip_point(point, Bias::Right)
|
||||
.to_point(&display_map);
|
||||
|
||||
new_selections.insert(
|
||||
selection.id,
|
||||
if to_insert.contains('\n') {
|
||||
NewPosition::Inside(point)
|
||||
} else {
|
||||
NewPosition::After(snapshot.anchor_after(point))
|
||||
},
|
||||
);
|
||||
point
|
||||
};
|
||||
|
||||
if linewise && to_insert.ends_with('\n') {
|
||||
edits.push((
|
||||
insert_at..insert_at,
|
||||
&to_insert[0..to_insert.len().saturating_sub(1)],
|
||||
))
|
||||
} else {
|
||||
edits.push((insert_at..insert_at, to_insert));
|
||||
}
|
||||
}
|
||||
drop(snapshot);
|
||||
buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
|
||||
});
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if let Some(new_position) = new_selections.get(&selection.id) {
|
||||
match new_position {
|
||||
NewPosition::Inside(new_point) => {
|
||||
selection.collapse_to(
|
||||
new_point.to_display_point(map),
|
||||
SelectionGoal::None,
|
||||
);
|
||||
}
|
||||
NewPosition::After(after_point) => {
|
||||
let mut new_point = after_point.to_display_point(map);
|
||||
*new_point.column_mut() =
|
||||
new_point.column().saturating_sub(1);
|
||||
new_point = map.clip_point(new_point, Bias::Left);
|
||||
selection.collapse_to(new_point, SelectionGoal::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
editor.insert(&clipboard_text, cx);
|
||||
}
|
||||
}
|
||||
editor.set_clip_at_line_ends(true, cx);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
|
@ -883,36 +760,6 @@ mod test {
|
|||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_p(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["d", "d"]).await;
|
||||
cx.assert_state_matches().await;
|
||||
|
||||
cx.simulate_shared_keystroke("p").await;
|
||||
cx.assert_state_matches().await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox ˇjumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps oveˇr
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystroke("p").await;
|
||||
cx.assert_state_matches().await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_repeated_word(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
|
|
@ -13,15 +13,15 @@ pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Works
|
|||
let mut cursor_positions = Vec::new();
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
match vim.state.mode {
|
||||
Mode::Visual { line: true } => {
|
||||
match vim.state().mode {
|
||||
Mode::VisualLine => {
|
||||
let start = Point::new(selection.start.row, 0);
|
||||
let end =
|
||||
Point::new(selection.end.row, snapshot.line_len(selection.end.row));
|
||||
ranges.push(start..end);
|
||||
cursor_positions.push(start..start);
|
||||
}
|
||||
Mode::Visual { line: false } => {
|
||||
Mode::Visual | Mode::VisualBlock => {
|
||||
ranges.push(selection.start..selection.end);
|
||||
cursor_positions.push(selection.start..selection.start);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,11 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &m
|
|||
// Some motions ignore failure when switching to normal mode
|
||||
let mut motion_succeeded = matches!(
|
||||
motion,
|
||||
Motion::Left | Motion::Right | Motion::EndOfLine | Motion::Backspace | Motion::StartOfLine
|
||||
Motion::Left
|
||||
| Motion::Right
|
||||
| Motion::EndOfLine { .. }
|
||||
| Motion::Backspace
|
||||
| Motion::StartOfLine { .. }
|
||||
);
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
|
|
468
crates/vim/src/normal/paste.rs
Normal file
468
crates/vim/src/normal/paste.rs
Normal file
|
@ -0,0 +1,468 @@
|
|||
use std::{borrow::Cow, cmp};
|
||||
|
||||
use editor::{
|
||||
display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
|
||||
DisplayPoint,
|
||||
};
|
||||
use gpui::{impl_actions, AppContext, ViewContext};
|
||||
use language::{Bias, SelectionGoal};
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{state::Mode, utils::copy_selections_content, Vim};
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Paste {
|
||||
#[serde(default)]
|
||||
before: bool,
|
||||
#[serde(default)]
|
||||
preserve_clipboard: bool,
|
||||
}
|
||||
|
||||
impl_actions!(vim, [Paste]);
|
||||
|
||||
pub(crate) fn init(cx: &mut AppContext) {
|
||||
cx.add_action(paste);
|
||||
}
|
||||
|
||||
fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
|
||||
let Some(item) = cx.read_from_clipboard() else {
|
||||
return;
|
||||
};
|
||||
let clipboard_text = Cow::Borrowed(item.text());
|
||||
if clipboard_text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !action.preserve_clipboard && vim.state().mode.is_visual() {
|
||||
copy_selections_content(editor, vim.state().mode == Mode::VisualLine, cx);
|
||||
}
|
||||
|
||||
// if we are copying from multi-cursor (of visual block mode), we want
|
||||
// to
|
||||
let clipboard_selections =
|
||||
item.metadata::<Vec<ClipboardSelection>>()
|
||||
.filter(|clipboard_selections| {
|
||||
clipboard_selections.len() > 1 && vim.state().mode != Mode::VisualLine
|
||||
});
|
||||
|
||||
let (display_map, current_selections) = editor.selections.all_adjusted_display(cx);
|
||||
|
||||
// unlike zed, if you have a multi-cursor selection from vim block mode,
|
||||
// pasting it will paste it on subsequent lines, even if you don't yet
|
||||
// have a cursor there.
|
||||
let mut selections_to_process = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < current_selections.len() {
|
||||
selections_to_process
|
||||
.push((current_selections[i].start..current_selections[i].end, true));
|
||||
i += 1;
|
||||
}
|
||||
if let Some(clipboard_selections) = clipboard_selections.as_ref() {
|
||||
let left = current_selections
|
||||
.iter()
|
||||
.map(|selection| cmp::min(selection.start.column(), selection.end.column()))
|
||||
.min()
|
||||
.unwrap();
|
||||
let mut row = current_selections.last().unwrap().end.row() + 1;
|
||||
while i < clipboard_selections.len() {
|
||||
let cursor =
|
||||
display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
|
||||
selections_to_process.push((cursor..cursor, false));
|
||||
i += 1;
|
||||
row += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let first_selection_indent_column =
|
||||
clipboard_selections.as_ref().and_then(|zed_selections| {
|
||||
zed_selections
|
||||
.first()
|
||||
.map(|selection| selection.first_line_indent)
|
||||
});
|
||||
let before = action.before || vim.state().mode == Mode::VisualLine;
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut new_selections = Vec::new();
|
||||
let mut original_indent_columns = Vec::new();
|
||||
let mut start_offset = 0;
|
||||
|
||||
for (ix, (selection, preserve)) in selections_to_process.iter().enumerate() {
|
||||
let (mut to_insert, original_indent_column) =
|
||||
if let Some(clipboard_selections) = &clipboard_selections {
|
||||
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
|
||||
let end_offset = start_offset + clipboard_selection.len;
|
||||
let text = clipboard_text[start_offset..end_offset].to_string();
|
||||
start_offset = end_offset + 1;
|
||||
(text, Some(clipboard_selection.first_line_indent))
|
||||
} else {
|
||||
("".to_string(), first_selection_indent_column)
|
||||
}
|
||||
} else {
|
||||
(clipboard_text.to_string(), first_selection_indent_column)
|
||||
};
|
||||
let line_mode = to_insert.ends_with("\n");
|
||||
let is_multiline = to_insert.contains("\n");
|
||||
|
||||
if line_mode && !before {
|
||||
if selection.is_empty() {
|
||||
to_insert =
|
||||
"\n".to_owned() + &to_insert[..to_insert.len() - "\n".len()];
|
||||
} else {
|
||||
to_insert = "\n".to_owned() + &to_insert;
|
||||
}
|
||||
} else if !line_mode && vim.state().mode == Mode::VisualLine {
|
||||
to_insert = to_insert + "\n";
|
||||
}
|
||||
|
||||
let display_range = if !selection.is_empty() {
|
||||
selection.start..selection.end
|
||||
} else if line_mode {
|
||||
let point = if before {
|
||||
movement::line_beginning(&display_map, selection.start, false)
|
||||
} else {
|
||||
movement::line_end(&display_map, selection.start, false)
|
||||
};
|
||||
point..point
|
||||
} else {
|
||||
let point = if before {
|
||||
selection.start
|
||||
} else {
|
||||
movement::saturating_right(&display_map, selection.start)
|
||||
};
|
||||
point..point
|
||||
};
|
||||
|
||||
let point_range = display_range.start.to_point(&display_map)
|
||||
..display_range.end.to_point(&display_map);
|
||||
let anchor = if is_multiline || vim.state().mode == Mode::VisualLine {
|
||||
display_map.buffer_snapshot.anchor_before(point_range.start)
|
||||
} else {
|
||||
display_map.buffer_snapshot.anchor_after(point_range.end)
|
||||
};
|
||||
|
||||
if *preserve {
|
||||
new_selections.push((anchor, line_mode, is_multiline));
|
||||
}
|
||||
edits.push((point_range, to_insert));
|
||||
original_indent_columns.extend(original_indent_column);
|
||||
}
|
||||
|
||||
editor.edit_with_block_indent(edits, original_indent_columns, cx);
|
||||
|
||||
// in line_mode vim will insert the new text on the next (or previous if before) line
|
||||
// and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank).
|
||||
// otherwise vim will insert the next text at (or before) the current cursor position,
|
||||
// the cursor will go to the last (or first, if is_multiline) inserted character.
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.replace_cursors_with(|map| {
|
||||
let mut cursors = Vec::new();
|
||||
for (anchor, line_mode, is_multiline) in &new_selections {
|
||||
let mut cursor = anchor.to_display_point(map);
|
||||
if *line_mode {
|
||||
if !before {
|
||||
cursor =
|
||||
movement::down(map, cursor, SelectionGoal::None, false).0;
|
||||
}
|
||||
cursor = movement::indented_line_beginning(map, cursor, true);
|
||||
} else if !is_multiline {
|
||||
cursor = movement::saturating_left(map, cursor)
|
||||
}
|
||||
cursors.push(cursor);
|
||||
if vim.state().mode == Mode::VisualBlock {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cursors
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Normal, true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
state::Mode,
|
||||
test::{NeovimBackedTestContext, VimTestContext},
|
||||
};
|
||||
use indoc::indoc;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
// single line
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox ˇjumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "w", "y"]).await;
|
||||
cx.assert_shared_clipboard("jumps o").await;
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps oveˇr
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystroke("p").await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps overjumps ˇo
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps oveˇr
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystroke("shift-p").await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps ovejumps ˇor
|
||||
the lazy dog"})
|
||||
.await;
|
||||
|
||||
// line mode
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["d", "d"]).await;
|
||||
cx.assert_shared_clipboard("fox jumps over\n").await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystroke("p").await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
the lazy dog
|
||||
ˇfox jumps over"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["k", "shift-p"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
ˇfox jumps over
|
||||
the lazy dog
|
||||
fox jumps over"})
|
||||
.await;
|
||||
|
||||
// multiline, cursor to first character of pasted text.
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps ˇover
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "j", "y"]).await;
|
||||
cx.assert_shared_clipboard("over\nthe lazy do").await;
|
||||
|
||||
cx.simulate_shared_keystroke("p").await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps oˇover
|
||||
the lazy dover
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["u", "shift-p"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps ˇover
|
||||
the lazy doover
|
||||
the lazy dog"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_visual(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
// copy in visual mode
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jˇumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "w", "y"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox ˇjumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
// paste in visual mode
|
||||
cx.simulate_shared_keystrokes(["w", "v", "i", "w", "p"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox jumps jumpˇs
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.assert_shared_clipboard("over").await;
|
||||
// paste in visual line mode
|
||||
cx.simulate_shared_keystrokes(["up", "shift-v", "shift-p"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
ˇover
|
||||
fox jumps jumps
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.assert_shared_clipboard("over").await;
|
||||
// paste in visual block mode
|
||||
cx.simulate_shared_keystrokes(["ctrl-v", "down", "down", "p"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
oveˇrver
|
||||
overox jumps jumps
|
||||
overhe lazy dog"})
|
||||
.await;
|
||||
|
||||
// copy in visual line mode
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
// paste in visual mode
|
||||
cx.simulate_shared_keystrokes(["v", "i", "w", "p"]).await;
|
||||
cx.assert_shared_state(
|
||||
&indoc! {"
|
||||
The quick brown
|
||||
the_
|
||||
ˇfox jumps over
|
||||
_dog"}
|
||||
.replace("_", " "), // Hack for trailing whitespace
|
||||
)
|
||||
.await;
|
||||
cx.assert_shared_clipboard("lazy").await;
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick brown
|
||||
fox juˇmps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-v", "d"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The quick brown
|
||||
the laˇzy dog"})
|
||||
.await;
|
||||
// paste in visual line mode
|
||||
cx.simulate_shared_keystrokes(["k", "shift-v", "p"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
ˇfox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.assert_shared_clipboard("The quick brown\n").await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_visual_block(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
// copy in visual block mode
|
||||
cx.set_shared_state(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["ctrl-v", "2", "j", "y"])
|
||||
.await;
|
||||
cx.assert_shared_clipboard("q\nj\nl").await;
|
||||
cx.simulate_shared_keystrokes(["p"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The qˇquick brown
|
||||
fox jjumps over
|
||||
the llazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The ˇq brown
|
||||
fox jjjumps over
|
||||
the lllazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "w", "shift-p"])
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
The ˇquick brown
|
||||
fox jumps over
|
||||
the lazy dog"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["ctrl-v", "j", "y"]).await;
|
||||
cx.assert_shared_clipboard("q\nj").await;
|
||||
cx.simulate_shared_keystrokes(["l", "ctrl-v", "2", "j", "shift-p"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
The qˇqick brown
|
||||
fox jjmps over
|
||||
the lzy dog"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["shift-v", "p"]).await;
|
||||
cx.assert_shared_state(indoc! {"
|
||||
ˇq
|
||||
j
|
||||
fox jjmps over
|
||||
the lzy dog"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paste_indent(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new_typescript(cx).await;
|
||||
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
class A {ˇ
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes(["o", "a", "(", ")", "{", "escape"]);
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
class A {
|
||||
a()ˇ{}
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
// cursor goes to the first non-blank character in the line;
|
||||
cx.simulate_keystrokes(["y", "y", "p"]);
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
class A {
|
||||
a(){}
|
||||
ˇa(){}
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
// indentation is preserved when pasting
|
||||
cx.simulate_keystrokes(["u", "shift-v", "up", "y", "shift-p"]);
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
ˇclass A {
|
||||
a(){}
|
||||
class A {
|
||||
a(){}
|
||||
}
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::Vim;
|
||||
use editor::{display_map::ToDisplayPoint, scroll::scroll_amount::ScrollAmount, Editor};
|
||||
use editor::{
|
||||
display_map::ToDisplayPoint,
|
||||
scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
|
||||
DisplayPoint, Editor,
|
||||
};
|
||||
use gpui::{actions, AppContext, ViewContext};
|
||||
use language::Bias;
|
||||
use workspace::Workspace;
|
||||
|
@ -53,13 +55,9 @@ fn scroll(cx: &mut ViewContext<Workspace>, by: fn(c: Option<f32>) -> ScrollAmoun
|
|||
|
||||
fn scroll_editor(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
|
||||
let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
|
||||
|
||||
editor.scroll_screen(amount, cx);
|
||||
if should_move_cursor {
|
||||
let selection_ordering = editor.newest_selection_on_screen(cx);
|
||||
if selection_ordering.is_eq() {
|
||||
return;
|
||||
}
|
||||
|
||||
let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
|
||||
visible_rows as u32
|
||||
} else {
|
||||
|
@ -69,21 +67,19 @@ fn scroll_editor(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContex
|
|||
let top_anchor = editor.scroll_manager.anchor().anchor;
|
||||
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.replace_cursors_with(|snapshot| {
|
||||
let mut new_point = top_anchor.to_display_point(&snapshot);
|
||||
s.move_heads_with(|map, head, goal| {
|
||||
let top = top_anchor.to_display_point(map);
|
||||
let min_row = top.row() + VERTICAL_SCROLL_MARGIN as u32;
|
||||
let max_row = top.row() + visible_rows - VERTICAL_SCROLL_MARGIN as u32 - 1;
|
||||
|
||||
match selection_ordering {
|
||||
Ordering::Less => {
|
||||
new_point = snapshot.clip_point(new_point, Bias::Right);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
*new_point.row_mut() += visible_rows - 1;
|
||||
new_point = snapshot.clip_point(new_point, Bias::Left);
|
||||
}
|
||||
Ordering::Equal => unreachable!(),
|
||||
}
|
||||
|
||||
vec![new_point]
|
||||
let new_head = if head.row() < min_row {
|
||||
map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left)
|
||||
} else if head.row() > max_row {
|
||||
map.clip_point(DisplayPoint::new(max_row, head.column()), Bias::Left)
|
||||
} else {
|
||||
head
|
||||
};
|
||||
(new_head, goal)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use gpui::{actions, impl_actions, AppContext, ViewContext};
|
||||
use search::{buffer_search, BufferSearchBar, SearchOptions};
|
||||
use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
|
||||
use serde_derive::Deserialize;
|
||||
use workspace::{searchable::Direction, Pane, Workspace};
|
||||
|
||||
|
@ -65,15 +65,13 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
|
|||
cx.focus_self();
|
||||
|
||||
if query.is_empty() {
|
||||
search_bar.set_search_options(
|
||||
SearchOptions::CASE_SENSITIVE | SearchOptions::REGEX,
|
||||
cx,
|
||||
);
|
||||
search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);
|
||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
||||
}
|
||||
vim.state.search = SearchState {
|
||||
vim.workspace_state.search = SearchState {
|
||||
direction,
|
||||
count,
|
||||
initial_query: query,
|
||||
initial_query: query.clone(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -83,7 +81,7 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
|
|||
|
||||
// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
|
||||
fn search_deploy(_: &mut Pane, _: &buffer_search::Deploy, cx: &mut ViewContext<Pane>) {
|
||||
Vim::update(cx, |vim, _| vim.state.search = Default::default());
|
||||
Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
|
@ -93,8 +91,9 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
|
|||
pane.update(cx, |pane, cx| {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
let state = &mut vim.state.search;
|
||||
let state = &mut vim.workspace_state.search;
|
||||
let mut count = state.count;
|
||||
let direction = state.direction;
|
||||
|
||||
// in the case that the query has changed, the search bar
|
||||
// will have selected the next match already.
|
||||
|
@ -103,8 +102,8 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
|
|||
{
|
||||
count = count.saturating_sub(1)
|
||||
}
|
||||
search_bar.select_match(state.direction, count, cx);
|
||||
state.count = 1;
|
||||
search_bar.select_match(direction, count, cx);
|
||||
search_bar.focus_editor(&Default::default(), cx);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ use language::Point;
|
|||
use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
|
||||
|
||||
pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
||||
let line_mode = vim.state.mode == Mode::Visual { line: true };
|
||||
vim.switch_mode(Mode::Insert, true, cx);
|
||||
let line_mode = vim.state().mode == Mode::VisualLine;
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.transact(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
|
@ -15,7 +15,10 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
|||
}
|
||||
if line_mode {
|
||||
Motion::CurrentLine.expand_selection(map, selection, None, false);
|
||||
if let Some((point, _)) = Motion::FirstNonWhitespace.move_point(
|
||||
if let Some((point, _)) = (Motion::FirstNonWhitespace {
|
||||
display_lines: false,
|
||||
})
|
||||
.move_point(
|
||||
map,
|
||||
selection.start,
|
||||
selection.goal,
|
||||
|
@ -32,6 +35,7 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
|||
editor.edit(edits, cx);
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Insert, true, cx);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -52,7 +56,7 @@ mod test {
|
|||
cx.assert_editor_state("xˇbc\n");
|
||||
|
||||
// supports a selection
|
||||
cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual { line: false });
|
||||
cx.set_state(indoc! {"a«bcˇ»\n"}, Mode::Visual);
|
||||
cx.assert_editor_state("a«bcˇ»\n");
|
||||
cx.simulate_keystrokes(["s", "x"]);
|
||||
cx.assert_editor_state("axˇ\n");
|
||||
|
|
|
@ -62,9 +62,9 @@ pub fn init(cx: &mut AppContext) {
|
|||
}
|
||||
|
||||
fn object(object: Object, cx: &mut WindowContext) {
|
||||
match Vim::read(cx).state.mode {
|
||||
match Vim::read(cx).state().mode {
|
||||
Mode::Normal => normal_object(object, cx),
|
||||
Mode::Visual { .. } => visual_object(object, cx),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_object(object, cx),
|
||||
Mode::Insert => {
|
||||
// Shouldn't execute a text object in insert mode. Ignoring
|
||||
}
|
||||
|
@ -72,6 +72,47 @@ fn object(object: Object, cx: &mut WindowContext) {
|
|||
}
|
||||
|
||||
impl Object {
|
||||
pub fn is_multiline(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. } | Object::Quotes | Object::BackQuotes | Object::DoubleQuotes => {
|
||||
false
|
||||
}
|
||||
Object::Sentence
|
||||
| Object::Parentheses
|
||||
| Object::AngleBrackets
|
||||
| Object::CurlyBrackets
|
||||
| Object::SquareBrackets => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn always_expands_both_ways(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. } | Object::Sentence => false,
|
||||
Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::DoubleQuotes
|
||||
| Object::Parentheses
|
||||
| Object::SquareBrackets
|
||||
| Object::CurlyBrackets
|
||||
| Object::AngleBrackets => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn target_visual_mode(self, current_mode: Mode) -> Mode {
|
||||
match self {
|
||||
Object::Word { .. } if current_mode == Mode::VisualLine => Mode::Visual,
|
||||
Object::Word { .. } => current_mode,
|
||||
Object::Sentence
|
||||
| Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::DoubleQuotes
|
||||
| Object::Parentheses
|
||||
| Object::SquareBrackets
|
||||
| Object::CurlyBrackets
|
||||
| Object::AngleBrackets => Mode::Visual,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(
|
||||
self,
|
||||
map: &DisplaySnapshot,
|
||||
|
@ -87,13 +128,27 @@ impl Object {
|
|||
}
|
||||
}
|
||||
Object::Sentence => sentence(map, relative_to, around),
|
||||
Object::Quotes => surrounding_markers(map, relative_to, around, false, '\'', '\''),
|
||||
Object::BackQuotes => surrounding_markers(map, relative_to, around, false, '`', '`'),
|
||||
Object::DoubleQuotes => surrounding_markers(map, relative_to, around, false, '"', '"'),
|
||||
Object::Parentheses => surrounding_markers(map, relative_to, around, true, '(', ')'),
|
||||
Object::SquareBrackets => surrounding_markers(map, relative_to, around, true, '[', ']'),
|
||||
Object::CurlyBrackets => surrounding_markers(map, relative_to, around, true, '{', '}'),
|
||||
Object::AngleBrackets => surrounding_markers(map, relative_to, around, true, '<', '>'),
|
||||
Object::Quotes => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'')
|
||||
}
|
||||
Object::BackQuotes => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '`', '`')
|
||||
}
|
||||
Object::DoubleQuotes => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
|
||||
}
|
||||
Object::Parentheses => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
|
||||
}
|
||||
Object::SquareBrackets => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')
|
||||
}
|
||||
Object::CurlyBrackets => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '{', '}')
|
||||
}
|
||||
Object::AngleBrackets => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,14 +9,16 @@ use crate::motion::Motion;
|
|||
pub enum Mode {
|
||||
Normal,
|
||||
Insert,
|
||||
Visual { line: bool },
|
||||
Visual,
|
||||
VisualLine,
|
||||
VisualBlock,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn is_visual(&self) -> bool {
|
||||
match self {
|
||||
Mode::Normal | Mode::Insert => false,
|
||||
Mode::Visual { .. } => true,
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,15 +41,20 @@ pub enum Operator {
|
|||
FindBackward { after: bool },
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct VimState {
|
||||
#[derive(Default, Clone)]
|
||||
pub struct EditorState {
|
||||
pub mode: Mode,
|
||||
pub last_mode: Mode,
|
||||
pub operator_stack: Vec<Operator>,
|
||||
pub search: SearchState,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct WorkspaceState {
|
||||
pub search: SearchState,
|
||||
pub last_find: Option<Motion>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SearchState {
|
||||
pub direction: Direction,
|
||||
pub count: usize,
|
||||
|
@ -64,7 +71,7 @@ impl Default for SearchState {
|
|||
}
|
||||
}
|
||||
|
||||
impl VimState {
|
||||
impl EditorState {
|
||||
pub fn cursor_shape(&self) -> CursorShape {
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
|
@ -74,7 +81,7 @@ impl VimState {
|
|||
CursorShape::Underscore
|
||||
}
|
||||
}
|
||||
Mode::Visual { .. } => CursorShape::Block,
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
|
||||
Mode::Insert => CursorShape::Bar,
|
||||
}
|
||||
}
|
||||
|
@ -87,9 +94,13 @@ impl VimState {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn should_autoindent(&self) -> bool {
|
||||
!(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
|
||||
}
|
||||
|
||||
pub fn clip_at_line_ends(&self) -> bool {
|
||||
match self.mode {
|
||||
Mode::Insert | Mode::Visual { .. } => false,
|
||||
Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false,
|
||||
Mode::Normal => true,
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +112,7 @@ impl VimState {
|
|||
"vim_mode",
|
||||
match self.mode {
|
||||
Mode::Normal => "normal",
|
||||
Mode::Visual { .. } => "visual",
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
|
||||
Mode::Insert => "insert",
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
mod neovim_backed_binding_test_context;
|
||||
mod neovim_backed_test_context;
|
||||
mod neovim_connection;
|
||||
mod vim_binding_test_context;
|
||||
mod vim_test_context;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
@ -10,7 +9,6 @@ use command_palette::CommandPalette;
|
|||
use editor::DisplayPoint;
|
||||
pub use neovim_backed_binding_test_context::*;
|
||||
pub use neovim_backed_test_context::*;
|
||||
pub use vim_binding_test_context::*;
|
||||
pub use vim_test_context::*;
|
||||
|
||||
use indoc::indoc;
|
||||
|
@ -241,7 +239,7 @@ async fn test_status_indicator(
|
|||
deterministic.run_until_parked();
|
||||
assert_eq!(
|
||||
cx.workspace(|_, cx| mode_indicator.read(cx).mode),
|
||||
Some(Mode::Visual { line: false })
|
||||
Some(Mode::Visual)
|
||||
);
|
||||
|
||||
// hides if vim mode is disabled
|
||||
|
@ -261,3 +259,244 @@ async fn test_status_indicator(
|
|||
assert!(mode_indicator.read(cx).mode.is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_word_characters(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new_typescript(cx).await;
|
||||
cx.set_state(
|
||||
indoc! { "
|
||||
class A {
|
||||
#ˇgoop = 99;
|
||||
$ˇgoop () { return this.#gˇoop };
|
||||
};
|
||||
console.log(new A().$gooˇp())
|
||||
"},
|
||||
Mode::Normal,
|
||||
);
|
||||
cx.simulate_keystrokes(["v", "i", "w"]);
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
class A {
|
||||
«#goopˇ» = 99;
|
||||
«$goopˇ» () { return this.«#goopˇ» };
|
||||
};
|
||||
console.log(new A().«$goopˇ»())
|
||||
"},
|
||||
Mode::Visual,
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_wrapped_lines(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_wrap(12).await;
|
||||
// tests line wrap as follows:
|
||||
// 1: twelve char
|
||||
// twelve char
|
||||
// 2: twelve char
|
||||
cx.set_shared_state(indoc! { "
|
||||
tˇwelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["j"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char twelve char
|
||||
tˇwelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["k"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
tˇwelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["g", "j"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char tˇwelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["g", "j"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char twelve char
|
||||
tˇwelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["g", "k"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char tˇwelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["g", "^"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char ˇtwelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["^"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
ˇtwelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["g", "$"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve charˇ twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["$"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char twelve chaˇr
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! { "
|
||||
tˇwelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["enter"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char twelve char
|
||||
ˇtwelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! { "
|
||||
twelve char
|
||||
tˇwelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["o", "o", "escape"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char
|
||||
twelve char twelve char
|
||||
ˇo
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! { "
|
||||
twelve char
|
||||
tˇwelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-a", "a", "escape"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char
|
||||
twelve char twelve charˇa
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-i", "i", "escape"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char
|
||||
ˇitwelve char twelve chara
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-d"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char
|
||||
ˇ
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! { "
|
||||
twelve char
|
||||
twelve char tˇwelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-o", "o", "escape"])
|
||||
.await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
twelve char
|
||||
ˇo
|
||||
twelve char twelve char
|
||||
twelve char
|
||||
"})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_folds(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.set_neovim_option("foldmethod=manual").await;
|
||||
|
||||
cx.set_shared_state(indoc! { "
|
||||
fn boop() {
|
||||
ˇbarp()
|
||||
bazp()
|
||||
}
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["shift-v", "j", "z", "f"])
|
||||
.await;
|
||||
|
||||
// visual display is now:
|
||||
// fn boop () {
|
||||
// [FOLDED]
|
||||
// }
|
||||
|
||||
// TODO: this should not be needed but currently zf does not
|
||||
// return to normal mode.
|
||||
cx.simulate_shared_keystrokes(["escape"]).await;
|
||||
|
||||
// skip over fold downward
|
||||
cx.simulate_shared_keystrokes(["g", "g"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
ˇfn boop() {
|
||||
barp()
|
||||
bazp()
|
||||
}
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(["j", "j"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
fn boop() {
|
||||
barp()
|
||||
bazp()
|
||||
ˇ}
|
||||
"})
|
||||
.await;
|
||||
|
||||
// skip over fold upward
|
||||
cx.simulate_shared_keystrokes(["2", "k"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
ˇfn boop() {
|
||||
barp()
|
||||
bazp()
|
||||
}
|
||||
"})
|
||||
.await;
|
||||
|
||||
// yank the fold
|
||||
cx.simulate_shared_keystrokes(["down", "y", "y"]).await;
|
||||
cx.assert_shared_clipboard(" barp()\n bazp()\n").await;
|
||||
|
||||
// re-open
|
||||
cx.simulate_shared_keystrokes(["z", "o"]).await;
|
||||
cx.assert_shared_state(indoc! { "
|
||||
fn boop() {
|
||||
ˇ barp()
|
||||
bazp()
|
||||
}
|
||||
"})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use indoc::indoc;
|
||||
use settings::SettingsStore;
|
||||
use std::ops::{Deref, DerefMut, Range};
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::ContextHandle;
|
||||
use language::OffsetRangeExt;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, SoftWrap},
|
||||
OffsetRangeExt,
|
||||
};
|
||||
use util::test::{generate_marked_text, marked_text_offsets};
|
||||
|
||||
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
|
||||
|
@ -116,7 +120,7 @@ impl<'a> NeovimBackedTestContext<'a> {
|
|||
|
||||
pub async fn set_shared_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
let mode = if marked_text.contains("»") {
|
||||
Mode::Visual { line: false }
|
||||
Mode::Visual
|
||||
} else {
|
||||
Mode::Normal
|
||||
};
|
||||
|
@ -127,16 +131,46 @@ impl<'a> NeovimBackedTestContext<'a> {
|
|||
context_handle
|
||||
}
|
||||
|
||||
pub async fn set_shared_wrap(&mut self, columns: u32) {
|
||||
if columns < 12 {
|
||||
panic!("nvim doesn't support columns < 12")
|
||||
}
|
||||
self.neovim.set_option("wrap").await;
|
||||
self.neovim.set_option("columns=12").await;
|
||||
|
||||
self.update(|cx| {
|
||||
cx.update_global(|settings: &mut SettingsStore, cx| {
|
||||
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.soft_wrap = Some(SoftWrap::PreferredLineLength);
|
||||
settings.defaults.preferred_line_length = Some(columns);
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn set_neovim_option(&mut self, option: &str) {
|
||||
self.neovim.set_option(option).await;
|
||||
}
|
||||
|
||||
pub async fn assert_shared_state(&mut self, marked_text: &str) {
|
||||
let neovim = self.neovim_state().await;
|
||||
if neovim != marked_text {
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
panic!(
|
||||
indoc! {"Test is incorrect (currently expected != neovim state)
|
||||
let editor = self.editor_state();
|
||||
if neovim == marked_text && neovim == editor {
|
||||
return;
|
||||
}
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
|
||||
let message = if neovim != marked_text {
|
||||
"Test is incorrect (currently expected != neovim_state)"
|
||||
} else {
|
||||
"Editor does not match nvim behaviour"
|
||||
};
|
||||
panic!(
|
||||
indoc! {"{}
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
|
@ -147,20 +181,65 @@ impl<'a> NeovimBackedTestContext<'a> {
|
|||
{}
|
||||
# zed state:
|
||||
{}"},
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
marked_text,
|
||||
neovim,
|
||||
self.editor_state(),
|
||||
)
|
||||
message,
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
marked_text,
|
||||
neovim,
|
||||
editor
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn assert_shared_clipboard(&mut self, text: &str) {
|
||||
let neovim = self.neovim.read_register('"').await;
|
||||
let editor = self
|
||||
.platform()
|
||||
.read_from_clipboard()
|
||||
.unwrap()
|
||||
.text()
|
||||
.clone();
|
||||
|
||||
if text == neovim && text == editor {
|
||||
return;
|
||||
}
|
||||
self.assert_editor_state(marked_text)
|
||||
|
||||
let message = if neovim != text {
|
||||
"Test is incorrect (currently expected != neovim)"
|
||||
} else {
|
||||
"Editor does not match nvim behaviour"
|
||||
};
|
||||
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
|
||||
panic!(
|
||||
indoc! {"{}
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# currently expected:
|
||||
{}
|
||||
# neovim clipboard:
|
||||
{}
|
||||
# zed clipboard:
|
||||
{}"},
|
||||
message,
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
text,
|
||||
neovim,
|
||||
editor
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn neovim_state(&mut self) -> String {
|
||||
generate_marked_text(
|
||||
self.neovim.text().await.as_str(),
|
||||
&vec![self.neovim_selection().await],
|
||||
&self.neovim_selections().await[..],
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
@ -169,9 +248,12 @@ impl<'a> NeovimBackedTestContext<'a> {
|
|||
self.neovim.mode().await.unwrap()
|
||||
}
|
||||
|
||||
async fn neovim_selection(&mut self) -> Range<usize> {
|
||||
let neovim_selection = self.neovim.selection().await;
|
||||
neovim_selection.to_offset(&self.buffer_snapshot())
|
||||
async fn neovim_selections(&mut self) -> Vec<Range<usize>> {
|
||||
let neovim_selections = self.neovim.selections().await;
|
||||
neovim_selections
|
||||
.into_iter()
|
||||
.map(|selection| selection.to_offset(&self.buffer_snapshot()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn assert_state_matches(&mut self) {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#[cfg(feature = "neovim")]
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{
|
||||
cmp,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use std::{ops::Range, path::PathBuf};
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
|
@ -37,6 +40,8 @@ pub enum NeovimData {
|
|||
Put { state: String },
|
||||
Key(String),
|
||||
Get { state: String, mode: Option<Mode> },
|
||||
ReadRegister { name: char, value: String },
|
||||
SetOption { value: String },
|
||||
}
|
||||
|
||||
pub struct NeovimConnection {
|
||||
|
@ -135,7 +140,7 @@ impl NeovimConnection {
|
|||
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn set_state(&mut self, marked_text: &str) {
|
||||
let (text, selection) = parse_state(&marked_text);
|
||||
let (text, selections) = parse_state(&marked_text);
|
||||
|
||||
let nvim_buffer = self
|
||||
.nvim
|
||||
|
@ -167,6 +172,11 @@ impl NeovimConnection {
|
|||
.await
|
||||
.expect("Could not get neovim window");
|
||||
|
||||
if selections.len() != 1 {
|
||||
panic!("must have one selection");
|
||||
}
|
||||
let selection = &selections[0];
|
||||
|
||||
let cursor = selection.start;
|
||||
nvim_window
|
||||
.set_cursor((cursor.row as i64 + 1, cursor.column as i64))
|
||||
|
@ -213,6 +223,59 @@ impl NeovimConnection {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn set_option(&mut self, value: &str) {
|
||||
self.nvim
|
||||
.command_output(format!("set {}", value).as_str())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.data.push_back(NeovimData::SetOption {
|
||||
value: value.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "neovim"))]
|
||||
pub async fn set_option(&mut self, value: &str) {
|
||||
assert_eq!(
|
||||
self.data.pop_front(),
|
||||
Some(NeovimData::SetOption {
|
||||
value: value.to_string(),
|
||||
}),
|
||||
"operation does not match recorded script. re-record with --features=neovim"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "neovim"))]
|
||||
pub async fn read_register(&mut self, register: char) -> String {
|
||||
if let Some(NeovimData::Get { .. }) = self.data.front() {
|
||||
self.data.pop_front();
|
||||
};
|
||||
if let Some(NeovimData::ReadRegister { name, value }) = self.data.pop_front() {
|
||||
if name == register {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("operation does not match recorded script. re-record with --features=neovim")
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn read_register(&mut self, name: char) -> String {
|
||||
let value = self
|
||||
.nvim
|
||||
.command_output(format!("echo getreg('{}')", name).as_str())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.data.push_back(NeovimData::ReadRegister {
|
||||
name,
|
||||
value: value.clone(),
|
||||
});
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
async fn read_position(&mut self, cmd: &str) -> u32 {
|
||||
self.nvim
|
||||
|
@ -224,7 +287,7 @@ impl NeovimConnection {
|
|||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn state(&mut self) -> (Option<Mode>, String, Range<Point>) {
|
||||
pub async fn state(&mut self) -> (Option<Mode>, String, Vec<Range<Point>>) {
|
||||
let nvim_buffer = self
|
||||
.nvim
|
||||
.get_current_buf()
|
||||
|
@ -261,16 +324,51 @@ impl NeovimConnection {
|
|||
let mode = match nvim_mode_text.as_ref() {
|
||||
"i" => Some(Mode::Insert),
|
||||
"n" => Some(Mode::Normal),
|
||||
"v" => Some(Mode::Visual { line: false }),
|
||||
"V" => Some(Mode::Visual { line: true }),
|
||||
"v" => Some(Mode::Visual),
|
||||
"V" => Some(Mode::VisualLine),
|
||||
"\x16" => Some(Mode::VisualBlock),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut selections = Vec::new();
|
||||
// Vim uses the index of the first and last character in the selection
|
||||
// Zed uses the index of the positions between the characters, so we need
|
||||
// to add one to the end in visual mode.
|
||||
match mode {
|
||||
Some(Mode::Visual { .. }) => {
|
||||
Some(Mode::VisualBlock) if selection_row != cursor_row => {
|
||||
// in zed we fake a block selecrtion by using multiple cursors (one per line)
|
||||
// this code emulates that.
|
||||
// to deal with casees where the selection is not perfectly rectangular we extract
|
||||
// the content of the selection via the "a register to get the shape correctly.
|
||||
self.nvim.input("\"aygv").await.unwrap();
|
||||
let content = self.nvim.command_output("echo getreg('a')").await.unwrap();
|
||||
let lines = content.split("\n").collect::<Vec<_>>();
|
||||
let top = cmp::min(selection_row, cursor_row);
|
||||
let left = cmp::min(selection_col, cursor_col);
|
||||
for row in top..=cmp::max(selection_row, cursor_row) {
|
||||
let content = if row - top >= lines.len() as u32 {
|
||||
""
|
||||
} else {
|
||||
lines[(row - top) as usize]
|
||||
};
|
||||
let line_len = self
|
||||
.read_position(format!("echo strlen(getline({}))", row + 1).as_str())
|
||||
.await;
|
||||
|
||||
if left > line_len {
|
||||
continue;
|
||||
}
|
||||
|
||||
let start = Point::new(row, left);
|
||||
let end = Point::new(row, left + content.len() as u32);
|
||||
if cursor_col >= selection_col {
|
||||
selections.push(start..end)
|
||||
} else {
|
||||
selections.push(end..start)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Mode::Visual) | Some(Mode::VisualLine) | Some(Mode::VisualBlock) => {
|
||||
if selection_col > cursor_col {
|
||||
let selection_line_length =
|
||||
self.read_position("echo strlen(getline(line('v')))").await;
|
||||
|
@ -290,38 +388,37 @@ impl NeovimConnection {
|
|||
cursor_row += 1;
|
||||
}
|
||||
}
|
||||
selections.push(
|
||||
Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col),
|
||||
)
|
||||
}
|
||||
Some(Mode::Insert) | Some(Mode::Normal) | None => {}
|
||||
Some(Mode::Insert) | Some(Mode::Normal) | None => selections
|
||||
.push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
|
||||
}
|
||||
|
||||
let (start, end) = (
|
||||
Point::new(selection_row, selection_col),
|
||||
Point::new(cursor_row, cursor_col),
|
||||
);
|
||||
|
||||
let state = NeovimData::Get {
|
||||
mode,
|
||||
state: encode_range(&text, start..end),
|
||||
state: encode_ranges(&text, &selections),
|
||||
};
|
||||
|
||||
if self.data.back() != Some(&state) {
|
||||
self.data.push_back(state.clone());
|
||||
}
|
||||
|
||||
(mode, text, start..end)
|
||||
(mode, text, selections)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "neovim"))]
|
||||
pub async fn state(&mut self) -> (Option<Mode>, String, Range<Point>) {
|
||||
pub async fn state(&mut self) -> (Option<Mode>, String, Vec<Range<Point>>) {
|
||||
if let Some(NeovimData::Get { state: text, mode }) = self.data.front() {
|
||||
let (text, range) = parse_state(text);
|
||||
(*mode, text, range)
|
||||
let (text, ranges) = parse_state(text);
|
||||
(*mode, text, ranges)
|
||||
} else {
|
||||
panic!("operation does not match recorded script. re-record with --features=neovim");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn selection(&mut self) -> Range<Point> {
|
||||
pub async fn selections(&mut self) -> Vec<Range<Point>> {
|
||||
self.state().await.2
|
||||
}
|
||||
|
||||
|
@ -421,51 +518,62 @@ impl Handler for NvimHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_state(marked_text: &str) -> (String, Range<Point>) {
|
||||
fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) {
|
||||
let (text, ranges) = util::test::marked_text_ranges(marked_text, true);
|
||||
let byte_range = ranges[0].clone();
|
||||
let mut point_range = Point::zero()..Point::zero();
|
||||
let mut ix = 0;
|
||||
let mut position = Point::zero();
|
||||
for c in text.chars().chain(['\0']) {
|
||||
if ix == byte_range.start {
|
||||
point_range.start = position;
|
||||
}
|
||||
if ix == byte_range.end {
|
||||
point_range.end = position;
|
||||
}
|
||||
let len_utf8 = c.len_utf8();
|
||||
ix += len_utf8;
|
||||
if c == '\n' {
|
||||
position.row += 1;
|
||||
position.column = 0;
|
||||
} else {
|
||||
position.column += len_utf8 as u32;
|
||||
}
|
||||
}
|
||||
(text, point_range)
|
||||
let point_ranges = ranges
|
||||
.into_iter()
|
||||
.map(|byte_range| {
|
||||
let mut point_range = Point::zero()..Point::zero();
|
||||
let mut ix = 0;
|
||||
let mut position = Point::zero();
|
||||
for c in text.chars().chain(['\0']) {
|
||||
if ix == byte_range.start {
|
||||
point_range.start = position;
|
||||
}
|
||||
if ix == byte_range.end {
|
||||
point_range.end = position;
|
||||
}
|
||||
let len_utf8 = c.len_utf8();
|
||||
ix += len_utf8;
|
||||
if c == '\n' {
|
||||
position.row += 1;
|
||||
position.column = 0;
|
||||
} else {
|
||||
position.column += len_utf8 as u32;
|
||||
}
|
||||
}
|
||||
point_range
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(text, point_ranges)
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
fn encode_range(text: &str, range: Range<Point>) -> String {
|
||||
let mut byte_range = 0..0;
|
||||
let mut ix = 0;
|
||||
let mut position = Point::zero();
|
||||
for c in text.chars().chain(['\0']) {
|
||||
if position == range.start {
|
||||
byte_range.start = ix;
|
||||
}
|
||||
if position == range.end {
|
||||
byte_range.end = ix;
|
||||
}
|
||||
let len_utf8 = c.len_utf8();
|
||||
ix += len_utf8;
|
||||
if c == '\n' {
|
||||
position.row += 1;
|
||||
position.column = 0;
|
||||
} else {
|
||||
position.column += len_utf8 as u32;
|
||||
}
|
||||
}
|
||||
util::test::generate_marked_text(text, &[byte_range], true)
|
||||
fn encode_ranges(text: &str, point_ranges: &Vec<Range<Point>>) -> String {
|
||||
let byte_ranges = point_ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let mut byte_range = 0..0;
|
||||
let mut ix = 0;
|
||||
let mut position = Point::zero();
|
||||
for c in text.chars().chain(['\0']) {
|
||||
if position == range.start {
|
||||
byte_range.start = ix;
|
||||
}
|
||||
if position == range.end {
|
||||
byte_range.end = ix;
|
||||
}
|
||||
let len_utf8 = c.len_utf8();
|
||||
ix += len_utf8;
|
||||
if c == '\n' {
|
||||
position.row += 1;
|
||||
position.column = 0;
|
||||
} else {
|
||||
position.column += len_utf8 as u32;
|
||||
}
|
||||
}
|
||||
byte_range
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
util::test::generate_marked_text(text, &byte_ranges[..], true)
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::*;
|
||||
|
||||
use super::VimTestContext;
|
||||
|
||||
pub struct VimBindingTestContext<'a, const COUNT: usize> {
|
||||
cx: VimTestContext<'a>,
|
||||
keystrokes_under_test: [&'static str; COUNT],
|
||||
mode_before: Mode,
|
||||
mode_after: Mode,
|
||||
}
|
||||
|
||||
impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
|
||||
pub fn new(
|
||||
keystrokes_under_test: [&'static str; COUNT],
|
||||
mode_before: Mode,
|
||||
mode_after: Mode,
|
||||
cx: VimTestContext<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
keystrokes_under_test,
|
||||
mode_before,
|
||||
mode_after,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binding<const NEW_COUNT: usize>(
|
||||
self,
|
||||
keystrokes_under_test: [&'static str; NEW_COUNT],
|
||||
) -> VimBindingTestContext<'a, NEW_COUNT> {
|
||||
VimBindingTestContext {
|
||||
keystrokes_under_test,
|
||||
cx: self.cx,
|
||||
mode_before: self.mode_before,
|
||||
mode_after: self.mode_after,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert(&mut self, initial_state: &str, state_after: &str) {
|
||||
self.cx.assert_binding(
|
||||
self.keystrokes_under_test,
|
||||
initial_state,
|
||||
self.mode_before,
|
||||
state_after,
|
||||
self.mode_after,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
|
||||
type Target = VimTestContext<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.cx
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const COUNT: usize> DerefMut for VimBindingTestContext<'a, COUNT> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.cx
|
||||
}
|
||||
}
|
|
@ -8,16 +8,24 @@ use search::{BufferSearchBar, ProjectSearchBar};
|
|||
|
||||
use crate::{state::Operator, *};
|
||||
|
||||
use super::VimBindingTestContext;
|
||||
|
||||
pub struct VimTestContext<'a> {
|
||||
cx: EditorLspTestContext<'a>,
|
||||
}
|
||||
|
||||
impl<'a> VimTestContext<'a> {
|
||||
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
|
||||
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
Self::new_with_lsp(lsp, enabled)
|
||||
}
|
||||
|
||||
pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
|
||||
Self::new_with_lsp(
|
||||
EditorLspTestContext::new_typescript(Default::default(), cx).await,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
|
||||
cx.update(|cx| {
|
||||
search::init(cx);
|
||||
crate::init(cx);
|
||||
|
@ -76,12 +84,12 @@ impl<'a> VimTestContext<'a> {
|
|||
}
|
||||
|
||||
pub fn mode(&mut self) -> Mode {
|
||||
self.cx.read(|cx| cx.global::<Vim>().state.mode)
|
||||
self.cx.read(|cx| cx.global::<Vim>().state().mode)
|
||||
}
|
||||
|
||||
pub fn active_operator(&mut self) -> Option<Operator> {
|
||||
self.cx
|
||||
.read(|cx| cx.global::<Vim>().state.operator_stack.last().copied())
|
||||
.read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
|
||||
|
@ -92,6 +100,7 @@ impl<'a> VimTestContext<'a> {
|
|||
vim.switch_mode(mode, true, cx);
|
||||
})
|
||||
});
|
||||
self.cx.foreground().run_until_parked();
|
||||
context_handle
|
||||
}
|
||||
|
||||
|
@ -115,14 +124,6 @@ impl<'a> VimTestContext<'a> {
|
|||
assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
|
||||
assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
|
||||
}
|
||||
|
||||
pub fn binding<const COUNT: usize>(
|
||||
mut self,
|
||||
keystrokes: [&'static str; COUNT],
|
||||
) -> VimBindingTestContext<'a, COUNT> {
|
||||
let mode = self.mode();
|
||||
VimBindingTestContext::new(keystrokes, mode, mode, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for VimTestContext<'a> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use editor::{ClipboardSelection, Editor};
|
||||
use gpui::{AppContext, ClipboardItem};
|
||||
use language::Point;
|
||||
|
||||
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
|
@ -7,13 +8,35 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App
|
|||
let mut text = String::new();
|
||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||
{
|
||||
let mut is_first = true;
|
||||
for selection in selections.iter() {
|
||||
let initial_len = text.len();
|
||||
let start = selection.start;
|
||||
let mut start = selection.start;
|
||||
let end = selection.end;
|
||||
if is_first {
|
||||
is_first = false;
|
||||
} else {
|
||||
text.push_str("\n");
|
||||
}
|
||||
let initial_len = text.len();
|
||||
|
||||
// if the file does not end with \n, and our line-mode selection ends on
|
||||
// that line, we will have expanded the start of the selection to ensure it
|
||||
// contains a newline (so that delete works as expected). We undo that change
|
||||
// here.
|
||||
let is_last_line = linewise
|
||||
&& end.row == buffer.max_buffer_row()
|
||||
&& buffer.max_point().column > 0
|
||||
&& start == Point::new(start.row, buffer.line_len(start.row));
|
||||
|
||||
if is_last_line {
|
||||
start = Point::new(buffer.max_buffer_row(), 0);
|
||||
}
|
||||
for chunk in buffer.text_for_range(start..end) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
if is_last_line {
|
||||
text.push_str("\n");
|
||||
}
|
||||
clipboard_selections.push(ClipboardSelection {
|
||||
len: text.len() - initial_len,
|
||||
is_entire_line: linewise,
|
||||
|
|
|
@ -12,21 +12,21 @@ mod utils;
|
|||
mod visual;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::CommandPaletteFilter;
|
||||
use collections::{CommandPaletteFilter, HashMap};
|
||||
use editor::{movement, Editor, EditorMode, Event};
|
||||
use gpui::{
|
||||
actions, impl_actions, keymap_matcher::KeymapContext, keymap_matcher::MatchResult, AppContext,
|
||||
Subscription, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use language::CursorShape;
|
||||
use language::{CursorShape, Selection, SelectionGoal};
|
||||
pub use mode_indicator::ModeIndicator;
|
||||
use motion::Motion;
|
||||
use normal::normal_replace;
|
||||
use serde::Deserialize;
|
||||
use settings::{Setting, SettingsStore};
|
||||
use state::{Mode, Operator, VimState};
|
||||
use state::{EditorState, Mode, Operator, WorkspaceState};
|
||||
use std::sync::Arc;
|
||||
use visual::visual_replace;
|
||||
use visual::{visual_block_motion, visual_replace};
|
||||
use workspace::{self, Workspace};
|
||||
|
||||
struct VimModeSetting(bool);
|
||||
|
@ -127,7 +127,9 @@ pub struct Vim {
|
|||
active_editor: Option<WeakViewHandle<Editor>>,
|
||||
editor_subscription: Option<Subscription>,
|
||||
enabled: bool,
|
||||
state: VimState,
|
||||
editor_states: HashMap<usize, EditorState>,
|
||||
workspace_state: WorkspaceState,
|
||||
default_state: EditorState,
|
||||
}
|
||||
|
||||
impl Vim {
|
||||
|
@ -143,13 +145,13 @@ impl Vim {
|
|||
}
|
||||
|
||||
fn set_active_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut WindowContext) {
|
||||
self.active_editor = Some(editor.downgrade());
|
||||
self.active_editor = Some(editor.clone().downgrade());
|
||||
self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
|
||||
Event::SelectionsChanged { local: true } => {
|
||||
let editor = editor.read(cx);
|
||||
if editor.leader_replica_id().is_none() {
|
||||
let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
|
||||
local_selections_changed(newest_empty, cx);
|
||||
let newest = editor.selections.newest::<usize>(cx);
|
||||
local_selections_changed(newest, cx);
|
||||
}
|
||||
}
|
||||
Event::InputIgnored { text } => {
|
||||
|
@ -163,8 +165,11 @@ impl Vim {
|
|||
let editor_mode = editor.mode();
|
||||
let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
|
||||
|
||||
if editor_mode == EditorMode::Full && !newest_selection_empty {
|
||||
self.switch_mode(Mode::Visual { line: false }, true, cx);
|
||||
if editor_mode == EditorMode::Full
|
||||
&& !newest_selection_empty
|
||||
&& self.state().mode == Mode::Normal
|
||||
{
|
||||
self.switch_mode(Mode::Visual, true, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,9 +186,14 @@ impl Vim {
|
|||
}
|
||||
|
||||
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.operator_stack.clear();
|
||||
let state = self.state();
|
||||
let last_mode = state.mode;
|
||||
let prior_mode = state.last_mode;
|
||||
self.update_state(|state| {
|
||||
state.last_mode = last_mode;
|
||||
state.mode = mode;
|
||||
state.operator_stack.clear();
|
||||
});
|
||||
|
||||
cx.emit_global(VimEvent::ModeChanged { mode });
|
||||
|
||||
|
@ -196,11 +206,33 @@ impl Vim {
|
|||
|
||||
// Adjust selections
|
||||
self.update_active_editor(cx, |editor, cx| {
|
||||
if last_mode != Mode::VisualBlock && last_mode.is_visual() && mode == Mode::VisualBlock
|
||||
{
|
||||
visual_block_motion(true, editor, cx, |_, point, goal| Some((point, goal)))
|
||||
}
|
||||
|
||||
editor.change_selections(None, cx, |s| {
|
||||
// we cheat with visual block mode and use multiple cursors.
|
||||
// the cost of this cheat is we need to convert back to a single
|
||||
// cursor whenever vim would.
|
||||
if last_mode == Mode::VisualBlock
|
||||
&& (mode != Mode::VisualBlock && mode != Mode::Insert)
|
||||
{
|
||||
let tail = s.oldest_anchor().tail();
|
||||
let head = s.newest_anchor().head();
|
||||
s.select_anchor_ranges(vec![tail..head]);
|
||||
} else if last_mode == Mode::Insert
|
||||
&& prior_mode == Mode::VisualBlock
|
||||
&& mode != Mode::VisualBlock
|
||||
{
|
||||
let pos = s.first_anchor().head();
|
||||
s.select_anchor_ranges(vec![pos..pos])
|
||||
}
|
||||
|
||||
s.move_with(|map, selection| {
|
||||
if last_mode.is_visual() && !mode.is_visual() {
|
||||
let mut point = selection.head();
|
||||
if !selection.reversed {
|
||||
if !selection.reversed && !selection.is_empty() {
|
||||
point = movement::left(map, selection.head());
|
||||
}
|
||||
selection.collapse_to(point, selection.goal)
|
||||
|
@ -215,7 +247,7 @@ impl Vim {
|
|||
}
|
||||
|
||||
fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
|
||||
self.state.operator_stack.push(operator);
|
||||
self.update_state(|state| state.operator_stack.push(operator));
|
||||
self.sync_vim_settings(cx);
|
||||
}
|
||||
|
||||
|
@ -228,9 +260,13 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
fn maybe_pop_operator(&mut self) -> Option<Operator> {
|
||||
self.update_state(|state| state.operator_stack.pop())
|
||||
}
|
||||
|
||||
fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
|
||||
let popped_operator = self.state.operator_stack.pop()
|
||||
.expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
|
||||
let popped_operator = self.update_state( |state| state.operator_stack.pop()
|
||||
) .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
|
||||
self.sync_vim_settings(cx);
|
||||
popped_operator
|
||||
}
|
||||
|
@ -244,12 +280,12 @@ impl Vim {
|
|||
}
|
||||
|
||||
fn clear_operator(&mut self, cx: &mut WindowContext) {
|
||||
self.state.operator_stack.clear();
|
||||
self.update_state(|state| state.operator_stack.clear());
|
||||
self.sync_vim_settings(cx);
|
||||
}
|
||||
|
||||
fn active_operator(&self) -> Option<Operator> {
|
||||
self.state.operator_stack.last().copied()
|
||||
self.state().operator_stack.last().copied()
|
||||
}
|
||||
|
||||
fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
|
||||
|
@ -260,17 +296,21 @@ impl Vim {
|
|||
match Vim::read(cx).active_operator() {
|
||||
Some(Operator::FindForward { before }) => {
|
||||
let find = Motion::FindForward { before, text };
|
||||
Vim::update(cx, |vim, _| vim.state.last_find = Some(find.clone()));
|
||||
Vim::update(cx, |vim, _| {
|
||||
vim.workspace_state.last_find = Some(find.clone())
|
||||
});
|
||||
motion::motion(find, cx)
|
||||
}
|
||||
Some(Operator::FindBackward { after }) => {
|
||||
let find = Motion::FindBackward { after, text };
|
||||
Vim::update(cx, |vim, _| vim.state.last_find = Some(find.clone()));
|
||||
Vim::update(cx, |vim, _| {
|
||||
vim.workspace_state.last_find = Some(find.clone())
|
||||
});
|
||||
motion::motion(find, cx)
|
||||
}
|
||||
Some(Operator::Replace) => match Vim::read(cx).state.mode {
|
||||
Some(Operator::Replace) => match Vim::read(cx).state().mode {
|
||||
Mode::Normal => normal_replace(text, cx),
|
||||
Mode::Visual { .. } => visual_replace(text, cx),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => visual_replace(text, cx),
|
||||
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
||||
},
|
||||
_ => {}
|
||||
|
@ -280,7 +320,6 @@ impl Vim {
|
|||
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
|
||||
if self.enabled != enabled {
|
||||
self.enabled = enabled;
|
||||
self.state = Default::default();
|
||||
|
||||
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
|
||||
if self.enabled {
|
||||
|
@ -307,8 +346,29 @@ impl Vim {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn state(&self) -> &EditorState {
|
||||
if let Some(active_editor) = self.active_editor.as_ref() {
|
||||
if let Some(state) = self.editor_states.get(&active_editor.id()) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
&self.default_state
|
||||
}
|
||||
|
||||
pub fn update_state<T>(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T {
|
||||
let mut state = self.state().clone();
|
||||
let ret = func(&mut state);
|
||||
|
||||
if let Some(active_editor) = self.active_editor.as_ref() {
|
||||
self.editor_states.insert(active_editor.id(), state);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn sync_vim_settings(&self, cx: &mut WindowContext) {
|
||||
let state = &self.state;
|
||||
let state = self.state();
|
||||
let cursor_shape = state.cursor_shape();
|
||||
|
||||
self.update_active_editor(cx, |editor, cx| {
|
||||
|
@ -317,7 +377,8 @@ impl Vim {
|
|||
editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
|
||||
editor.set_collapse_matches(true);
|
||||
editor.set_input_enabled(!state.vim_controlled());
|
||||
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
||||
editor.set_autoindent(state.should_autoindent());
|
||||
editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
|
||||
let context_layer = state.keymap_context_layer();
|
||||
editor.set_keymap_context_layer::<Self>(context_layer, cx);
|
||||
} else {
|
||||
|
@ -333,6 +394,7 @@ impl Vim {
|
|||
editor.set_cursor_shape(CursorShape::Bar, cx);
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.set_input_enabled(true);
|
||||
editor.set_autoindent(true);
|
||||
editor.selections.line_mode = false;
|
||||
|
||||
// we set the VimEnabled context on all editors so that we
|
||||
|
@ -365,10 +427,14 @@ impl Setting for VimModeSetting {
|
|||
}
|
||||
}
|
||||
|
||||
fn local_selections_changed(newest_empty: bool, cx: &mut WindowContext) {
|
||||
fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
|
||||
vim.switch_mode(Mode::Visual { line: false }, false, cx)
|
||||
if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
|
||||
if matches!(newest.goal, SelectionGoal::ColumnRange { .. }) {
|
||||
vim.switch_mode(Mode::VisualBlock, false, cx);
|
||||
} else {
|
||||
vim.switch_mode(Mode::Visual, false, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,15 +1,15 @@
|
|||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":true}}}}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"VisualLine"}}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"fox ˇjumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"a\nˇ\nb"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Get":{"state":"a\n«\nˇ»b","mode":{"Visual":{"line":true}}}}
|
||||
{"Get":{"state":"a\n«\nˇ»b","mode":"VisualLine"}}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"a\nˇb","mode":"Normal"}}
|
||||
{"Put":{"state":"a\nb\nˇ"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Get":{"state":"a\nb\nˇ","mode":{"Visual":{"line":true}}}}
|
||||
{"Get":{"state":"a\nb\nˇ","mode":"VisualLine"}}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"a\nˇb","mode":"Normal"}}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"Visual"}}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"The «quick brown\nfox jumps oˇ»ver\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The «quick brown\nfox jumps oˇ»ver\nthe lazy dog","mode":"Visual"}}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog","mode":"Normal"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"k"}
|
||||
{"Key":"b"}
|
||||
{"Get":{"state":"The «ˇquick brown\nfox jumps o»ver\nthe lazy dog","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The «ˇquick brown\nfox jumps o»ver\nthe lazy dog","mode":"Visual"}}
|
||||
{"Put":{"state":"a\nˇ\nb\n"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"a\n«\nˇ»b\n","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"a\n«\nˇ»b\n","mode":"Visual"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"a\nˇ\nb\n","mode":"Normal"}}
|
||||
{"Put":{"state":"a\nb\nˇ"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"a\nb\nˇ","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"a\nb\nˇ","mode":"Visual"}}
|
||||
|
|
23
crates/vim/test_data/test_folds.json
Normal file
23
crates/vim/test_data/test_folds.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{"SetOption":{"value":"foldmethod=manual"}}
|
||||
{"Put":{"state":"fn boop() {\n ˇbarp()\n bazp()\n}\n"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"j"}
|
||||
{"Key":"z"}
|
||||
{"Key":"f"}
|
||||
{"Key":"escape"}
|
||||
{"Key":"g"}
|
||||
{"Key":"g"}
|
||||
{"Get":{"state":"ˇfn boop() {\n barp()\n bazp()\n}\n","mode":"Normal"}}
|
||||
{"Key":"j"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"fn boop() {\n barp()\n bazp()\nˇ}\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"ˇfn boop() {\n barp()\n bazp()\n}\n","mode":"Normal"}}
|
||||
{"Key":"down"}
|
||||
{"Key":"y"}
|
||||
{"Key":"y"}
|
||||
{"ReadRegister":{"name":"\"","value":" barp()\n bazp()\n"}}
|
||||
{"Key":"z"}
|
||||
{"Key":"o"}
|
||||
{"Get":{"state":"fn boop() {\nˇ barp()\n bazp()\n}\n","mode":"Normal"}}
|
|
@ -2,9 +2,9 @@
|
|||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"{"}
|
||||
{"Get":{"state":"func empty(a string) bool {\n« if a == \"\" {\n return true\n }\n return false\nˇ»}","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"func empty(a string) bool {\n« if a == \"\" {\n return true\n }\n return false\nˇ»}","mode":"Visual"}}
|
||||
{"Put":{"state":"func empty(a string) bool {\n if a == \"\" {\n ˇreturn true\n }\n return false\n}"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"{"}
|
||||
{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":"Visual"}}
|
||||
|
|
3
crates/vim/test_data/test_next_line_start.json
Normal file
3
crates/vim/test_data/test_next_line_start.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{"Put":{"state":"ˇone\n two\nthree"}}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"one\n ˇtwo\nthree","mode":"Normal"}}
|
|
@ -1,13 +0,0 @@
|
|||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"d"}
|
||||
{"Key":"d"}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nthe lazy dog\nˇfox jumps over","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"y"}
|
||||
{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps overjumps ˇo\nthe lazy dog","mode":"Normal"}}
|
31
crates/vim/test_data/test_paste.json
Normal file
31
crates/vim/test_data/test_paste.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{"Put":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"y"}
|
||||
{"ReadRegister":{"name":"\"","value":"jumps o"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps overjumps ˇo\nthe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog"}}
|
||||
{"Key":"shift-p"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps ovejumps ˇor\nthe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"d"}
|
||||
{"Key":"d"}
|
||||
{"ReadRegister":{"name":"\"","value":"fox jumps over\n"}}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nthe lazy dog\nˇfox jumps over","mode":"Normal"}}
|
||||
{"Key":"k"}
|
||||
{"Key":"shift-p"}
|
||||
{"Get":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog\nfox jumps over","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps ˇover\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"j"}
|
||||
{"Key":"y"}
|
||||
{"ReadRegister":{"name":"\"","value":"over\nthe lazy do"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps oˇover\nthe lazy dover\nthe lazy dog","mode":"Normal"}}
|
||||
{"Key":"u"}
|
||||
{"Key":"shift-p"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps ˇover\nthe lazy doover\nthe lazy dog","mode":"Normal"}}
|
42
crates/vim/test_data/test_paste_visual.json
Normal file
42
crates/vim/test_data/test_paste_visual.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"y"}
|
||||
{"Get":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"Key":"w"}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps jumpˇs\nthe lazy dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"over"}}
|
||||
{"Key":"up"}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"shift-p"}
|
||||
{"Get":{"state":"ˇover\nfox jumps jumps\nthe lazy dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"over"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"down"}
|
||||
{"Key":"down"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"oveˇrver\noverox jumps jumps\noverhe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"d"}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nthe \nˇfox jumps over\n dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"lazy"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"d"}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"k"}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"ˇfox jumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"The quick brown\n"}}
|
31
crates/vim/test_data/test_paste_visual_block.json
Normal file
31
crates/vim/test_data/test_paste_visual_block.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"2"}
|
||||
{"Key":"j"}
|
||||
{"Key":"y"}
|
||||
{"ReadRegister":{"name":"\"","value":"q\nj\nl"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The qˇquick brown\nfox jjumps over\nthe llazy dog","mode":"Normal"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"shift-p"}
|
||||
{"Get":{"state":"The ˇq brown\nfox jjjumps over\nthe lllazy dog","mode":"Normal"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"shift-p"}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"j"}
|
||||
{"Key":"y"}
|
||||
{"ReadRegister":{"name":"\"","value":"q\nj"}}
|
||||
{"Key":"l"}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"2"}
|
||||
{"Key":"j"}
|
||||
{"Key":"shift-p"}
|
||||
{"Get":{"state":"The qˇqick brown\nfox jjmps over\nthe lzy dog","mode":"Normal"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"ˇq\nj\nfox jjmps over\nthe lzy dog","mode":"Normal"}}
|
18
crates/vim/test_data/test_visual_block_insert.json
Normal file
18
crates/vim/test_data/test_visual_block_insert.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"9"}
|
||||
{"Key":"down"}
|
||||
{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
|
||||
{"Key":"shift-i"}
|
||||
{"Key":"k"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"ˇkThe quick brown\nkfox jumps over\nkthe lazy dog\nk","mode":"Normal"}}
|
||||
{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"9"}
|
||||
{"Key":"down"}
|
||||
{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}}
|
||||
{"Key":"c"}
|
||||
{"Key":"k"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"ˇkhe quick brown\nkox jumps over\nkhe lazy dog\nk","mode":"Normal"}}
|
38
crates/vim/test_data/test_visual_block_mode.json
Normal file
38
crates/vim/test_data/test_visual_block_mode.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox jumps over\nthe lazy dog","mode":"VisualBlock"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"down"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\nfox «jˇ»umps over\nthe «lˇ»azy dog","mode":"VisualBlock"}}
|
||||
{"Key":"e"}
|
||||
{"Get":{"state":"The «quicˇ»k brown\nfox «jumpˇ»s over\nthe «lazyˇ» dog","mode":"VisualBlock"}}
|
||||
{"Key":"^"}
|
||||
{"Get":{"state":"«ˇThe q»uick brown\n«ˇfox j»umps over\n«ˇthe l»azy dog","mode":"VisualBlock"}}
|
||||
{"Key":"$"}
|
||||
{"Get":{"state":"The «quick brownˇ»\nfox «jumps overˇ»\nthe «lazy dogˇ»","mode":"VisualBlock"}}
|
||||
{"Key":"shift-f"}
|
||||
{"Key":" "}
|
||||
{"Get":{"state":"The «quickˇ» brown\nfox «jumpsˇ» over\nthe «lazy ˇ»dog","mode":"VisualBlock"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"The «quick brown\nfox jumps over\nthe lazy ˇ»dog","mode":"Visual"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Get":{"state":"The «quickˇ» brown\nfox «jumpsˇ» over\nthe «lazy ˇ»dog","mode":"VisualBlock"}}
|
||||
{"Put":{"state":"The ˇquick\nbrown\nfox\njumps over the\n\nlazy dog\n"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"down"}
|
||||
{"Key":"down"}
|
||||
{"Get":{"state":"The«ˇ q»uick\nbro«ˇwn»\nfoxˇ\njumps over the\n\nlazy dog\n","mode":"VisualBlock"}}
|
||||
{"Key":"down"}
|
||||
{"Get":{"state":"The «qˇ»uick\nbrow«nˇ»\nfox\njump«sˇ» over the\n\nlazy dog\n","mode":"VisualBlock"}}
|
||||
{"Key":"left"}
|
||||
{"Get":{"state":"The«ˇ q»uick\nbro«ˇwn»\nfoxˇ\njum«ˇps» over the\n\nlazy dog\n","mode":"VisualBlock"}}
|
||||
{"Key":"s"}
|
||||
{"Key":"o"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"Theˇouick\nbroo\nfoxo\njumo over the\n\nlazy dog\n","mode":"Normal"}}
|
||||
{"Put":{"state":"Theˇ quick brown\n\nfox jumps over\nthe lazy dog\n"}}
|
||||
{"Key":"l"}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"j"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"The «qˇ»uick brown\n\nfox «jˇ»umps over\nthe lazy dog\n","mode":"VisualBlock"}}
|
|
@ -1,7 +1,7 @@
|
|||
{"Put":{"state":"The quick ˇbrown"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «brownˇ»","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «brownˇ»","mode":"Visual"}}
|
||||
{"Put":{"state":"The quick ˇbrown"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
|
|
|
@ -4,14 +4,11 @@
|
|||
{"Get":{"state":"fox juˇmps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"fox jumps over\nˇThe quick brown\nthe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
|
||||
{"Put":{"state":"The quˇick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"j"}
|
||||
|
@ -19,16 +16,6 @@
|
|||
{"Get":{"state":"the laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"the lazy dog\nˇThe quick brown\nfox jumps over","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"j"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"The quˇick brown","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe laˇzy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"j"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"The quick brown\nfox juˇmps over","mode":"Normal"}}
|
||||
{"Put":{"state":"The ˇlong line\nshould not\ncrash\n"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"$"}
|
||||
|
|
19
crates/vim/test_data/test_visual_object.json
Normal file
19
crates/vim/test_data/test_visual_object.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{"Put":{"state":"hello (in [parˇens] o)"}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"l"}
|
||||
{"Key":"a"}
|
||||
{"Key":"]"}
|
||||
{"Get":{"state":"hello (in «[parens]ˇ» o)","mode":"Visual"}}
|
||||
{"Key":"i"}
|
||||
{"Key":"("}
|
||||
{"Get":{"state":"hello («in [parens] oˇ»)","mode":"Visual"}}
|
||||
{"Put":{"state":"hello in a wˇord again."}}
|
||||
{"Key":"ctrl-v"}
|
||||
{"Key":"l"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"hello in a w«ordˇ» again.","mode":"VisualBlock"}}
|
||||
{"Key":"o"}
|
||||
{"Key":"a"}
|
||||
{"Key":"s"}
|
||||
{"Get":{"state":"«ˇhello in a word» again.","mode":"VisualBlock"}}
|
26
crates/vim/test_data/test_visual_paste.json
Normal file
26
crates/vim/test_data/test_visual_paste.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"y"}
|
||||
{"Get":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nfox jjumpˇsumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"d"}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"The quick brown\nthe \nˇfox jumps over\n dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"lazy"}}
|
||||
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"d"}
|
||||
{"Get":{"state":"The quick brown\nthe laˇzy dog","mode":"Normal"}}
|
||||
{"Key":"k"}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"p"}
|
||||
{"Get":{"state":"ˇfox jumps over\nthe lazy dog","mode":"Normal"}}
|
|
@ -1,236 +1,236 @@
|
|||
{"Put":{"state":"The quick ˇbrown\nfox"}}
|
||||
{"Key":"v"}
|
||||
{"Get":{"state":"The quick «bˇ»rown\nfox","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «bˇ»rown\nfox","mode":"Visual"}}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «brownˇ»\nfox","mode":{"Visual":{"line":false}}}}
|
||||
{"Get":{"state":"The quick «brownˇ»\nfox","mode":"Visual"}}
|
||||
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«Theˇ»-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe«-ˇ»quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-«quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-«jumpsˇ» over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick ˇbrown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick browˇn \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick «brownˇ» \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brownˇ \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown« ˇ»\nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox ˇjumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox juˇmps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox «jumpsˇ» over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumpsˇ over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps« ˇ»over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dogˇ \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog« ˇ»\n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \nˇ\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n«\nˇ»\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\nˇ\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n«\nˇ»\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\nˇ\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n«\nˇ»The-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThˇe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nTheˇ-quick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-ˇquick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quˇick brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\n«The-quickˇ» brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quickˇ brown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick« ˇ»brown \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick ˇbrown \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick «brownˇ» \n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brownˇ \n \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown« ˇ»\n \n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \nˇ \n \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n« ˇ»\n \n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \nˇ \n fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n« ˇ»\n fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \nˇ fox-jumps over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n« ˇ»fox-jumps over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumpˇs over\nthe lazy dog \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n «fox-jumpsˇ» over\nthe lazy dog \n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dogˇ \n\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog« ˇ»\n\n","mode":{"Visual":{"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"}}
|
||||
{"Put":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \nˇ\n"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"shift-w"}
|
||||
{"Get":{"state":"The quick brown \nfox jumps over\nthe lazy dog \n\n\n\nThe-quick brown \n \n \n fox-jumps over\nthe lazy dog \n«\nˇ»","mode":{"Visual":{"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"}}
|
||||
|
|
29
crates/vim/test_data/test_visual_yank.json
Normal file
29
crates/vim/test_data/test_visual_yank.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{"Put":{"state":"The quick ˇbrown"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"y"}
|
||||
{"Get":{"state":"The quick ˇbrown","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"brown"}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Key":"y"}
|
||||
{"Get":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"quick brown\nfox jumps o"}}
|
||||
{"Put":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"w"}
|
||||
{"Key":"j"}
|
||||
{"Key":"y"}
|
||||
{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog","mode":"Normal"}}
|
||||
{"ReadRegister":{"name":"\"","value":"lazy d"}}
|
||||
{"Key":"shift-v"}
|
||||
{"Key":"y"}
|
||||
{"ReadRegister":{"name":"\"","value":"the lazy dog\n"}}
|
||||
{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"b"}
|
||||
{"Key":"k"}
|
||||
{"Key":"y"}
|
||||
{"Get":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}}
|
50
crates/vim/test_data/test_wrapped_lines.json
Normal file
50
crates/vim/test_data/test_wrapped_lines.json
Normal file
|
@ -0,0 +1,50 @@
|
|||
{"SetOption":{"value":"wrap"}}
|
||||
{"SetOption":{"value":"columns=12"}}
|
||||
{"Put":{"state":"tˇwelve char twelve char\ntwelve char\n"}}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"twelve char twelve char\ntˇwelve char\n","mode":"Normal"}}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"tˇwelve char twelve char\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"g"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"twelve char tˇwelve char\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"g"}
|
||||
{"Key":"j"}
|
||||
{"Get":{"state":"twelve char twelve char\ntˇwelve char\n","mode":"Normal"}}
|
||||
{"Key":"g"}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"twelve char tˇwelve char\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"g"}
|
||||
{"Key":"^"}
|
||||
{"Get":{"state":"twelve char ˇtwelve char\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"^"}
|
||||
{"Get":{"state":"ˇtwelve char twelve char\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"g"}
|
||||
{"Key":"$"}
|
||||
{"Get":{"state":"twelve charˇ twelve char\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"$"}
|
||||
{"Get":{"state":"twelve char twelve chaˇr\ntwelve char\n","mode":"Normal"}}
|
||||
{"Put":{"state":"tˇwelve char twelve char\ntwelve char\n"}}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"twelve char twelve char\nˇtwelve char\n","mode":"Normal"}}
|
||||
{"Put":{"state":"twelve char\ntˇwelve char twelve char\ntwelve char\n"}}
|
||||
{"Key":"o"}
|
||||
{"Key":"o"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"twelve char\ntwelve char twelve char\nˇo\ntwelve char\n","mode":"Normal"}}
|
||||
{"Put":{"state":"twelve char\ntˇwelve char twelve char\ntwelve char\n"}}
|
||||
{"Key":"shift-a"}
|
||||
{"Key":"a"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"twelve char\ntwelve char twelve charˇa\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"shift-i"}
|
||||
{"Key":"i"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"twelve char\nˇitwelve char twelve chara\ntwelve char\n","mode":"Normal"}}
|
||||
{"Key":"shift-d"}
|
||||
{"Get":{"state":"twelve char\nˇ\ntwelve char\n","mode":"Normal"}}
|
||||
{"Put":{"state":"twelve char\ntwelve char tˇwelve char\ntwelve char\n"}}
|
||||
{"Key":"shift-o"}
|
||||
{"Key":"o"}
|
||||
{"Key":"escape"}
|
||||
{"Get":{"state":"twelve char\nˇo\ntwelve char twelve char\ntwelve char\n","mode":"Normal"}}
|
Loading…
Add table
Add a link
Reference in a new issue