mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 01:34:02 +00:00
Add inclusive vs exclusive motions to vim mode
This commit is contained in:
parent
0c587ae73c
commit
5ea782de21
11 changed files with 1350 additions and 750 deletions
|
@ -75,37 +75,13 @@
|
|||
{
|
||||
"context": "Editor && vim_operator == c",
|
||||
"bindings": {
|
||||
"w": [
|
||||
"vim::NextWordEnd",
|
||||
{
|
||||
"ignorePunctuation": false
|
||||
}
|
||||
],
|
||||
"w": "vim::ChangeWord",
|
||||
"shift-W": [
|
||||
"vim::NextWordEnd",
|
||||
"vim::ChangeWord",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == d",
|
||||
"bindings": {
|
||||
"w": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
"ignorePunctuation": false,
|
||||
"stopAtNewline": true
|
||||
}
|
||||
],
|
||||
"shift-W": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
"ignorePunctuation": true,
|
||||
"stopAtNewline": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
|
@ -814,14 +814,20 @@ pub mod tests {
|
|||
DisplayPoint::new(0, 7)
|
||||
);
|
||||
assert_eq!(
|
||||
movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None),
|
||||
movement::up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(1, 10),
|
||||
SelectionGoal::None,
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(0, 7), SelectionGoal::Column(10))
|
||||
);
|
||||
assert_eq!(
|
||||
movement::down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(0, 7),
|
||||
SelectionGoal::Column(10)
|
||||
SelectionGoal::Column(10),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(1, 10), SelectionGoal::Column(10))
|
||||
);
|
||||
|
@ -829,7 +835,8 @@ pub mod tests {
|
|||
movement::down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(1, 10),
|
||||
SelectionGoal::Column(10)
|
||||
SelectionGoal::Column(10),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(2, 4), SelectionGoal::Column(10))
|
||||
);
|
||||
|
|
|
@ -1134,8 +1134,10 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
||||
self.display_map
|
||||
.update(cx, |map, _| map.clip_at_line_ends = clip);
|
||||
if self.display_map.read(cx).clip_at_line_ends != clip {
|
||||
self.display_map
|
||||
.update(cx, |map, _| map.clip_at_line_ends = clip);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_keymap_context_layer<Tag: 'static>(&mut self, context: gpui::keymap::Context) {
|
||||
|
@ -3579,13 +3581,13 @@ impl Editor {
|
|||
if !selection.is_empty() {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
let (cursor, goal) = movement::up(&map, selection.start, selection.goal);
|
||||
let (cursor, goal) = movement::up(&map, selection.start, selection.goal, false);
|
||||
selection.collapse_to(cursor, goal);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
|
||||
self.move_selection_heads(cx, movement::up)
|
||||
self.move_selection_heads(cx, |map, head, goal| movement::up(map, head, goal, false))
|
||||
}
|
||||
|
||||
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
|
||||
|
@ -3606,13 +3608,13 @@ impl Editor {
|
|||
if !selection.is_empty() {
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
let (cursor, goal) = movement::down(&map, selection.end, selection.goal);
|
||||
let (cursor, goal) = movement::down(&map, selection.end, selection.goal, false);
|
||||
selection.collapse_to(cursor, goal);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
|
||||
self.move_selection_heads(cx, movement::down)
|
||||
self.move_selection_heads(cx, |map, head, goal| movement::down(map, head, goal, false))
|
||||
}
|
||||
|
||||
pub fn move_to_previous_word_start(
|
||||
|
|
|
@ -28,6 +28,7 @@ pub fn up(
|
|||
map: &DisplaySnapshot,
|
||||
start: DisplayPoint,
|
||||
goal: SelectionGoal,
|
||||
preserve_column_at_start: bool,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
let mut goal_column = if let SelectionGoal::Column(column) = goal {
|
||||
column
|
||||
|
@ -42,6 +43,8 @@ pub fn up(
|
|||
);
|
||||
if point.row() < start.row() {
|
||||
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
|
||||
} else if preserve_column_at_start {
|
||||
return (start, goal);
|
||||
} else {
|
||||
point = DisplayPoint::new(0, 0);
|
||||
goal_column = 0;
|
||||
|
@ -63,6 +66,7 @@ pub fn down(
|
|||
map: &DisplaySnapshot,
|
||||
start: DisplayPoint,
|
||||
goal: SelectionGoal,
|
||||
preserve_column_at_end: bool,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
let mut goal_column = if let SelectionGoal::Column(column) = goal {
|
||||
column
|
||||
|
@ -74,6 +78,8 @@ pub fn down(
|
|||
let mut point = map.clip_point(DisplayPoint::new(next_row, 0), Bias::Right);
|
||||
if point.row() > start.row() {
|
||||
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
|
||||
} else if preserve_column_at_end {
|
||||
return (start, goal);
|
||||
} else {
|
||||
point = map.max_point();
|
||||
goal_column = map.column_to_chars(point.row(), point.column())
|
||||
|
@ -503,41 +509,81 @@ mod tests {
|
|||
|
||||
// Can't move up into the first excerpt's header
|
||||
assert_eq!(
|
||||
up(&snapshot, DisplayPoint::new(2, 2), SelectionGoal::Column(2)),
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(2, 2),
|
||||
SelectionGoal::Column(2),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(2, 0), SelectionGoal::Column(0)),
|
||||
);
|
||||
assert_eq!(
|
||||
up(&snapshot, DisplayPoint::new(2, 0), SelectionGoal::None),
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(2, 0),
|
||||
SelectionGoal::None,
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(2, 0), SelectionGoal::Column(0)),
|
||||
);
|
||||
|
||||
// Move up and down within first excerpt
|
||||
assert_eq!(
|
||||
up(&snapshot, DisplayPoint::new(3, 4), SelectionGoal::Column(4)),
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(3, 4),
|
||||
SelectionGoal::Column(4),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(2, 3), SelectionGoal::Column(4)),
|
||||
);
|
||||
assert_eq!(
|
||||
down(&snapshot, DisplayPoint::new(2, 3), SelectionGoal::Column(4)),
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(2, 3),
|
||||
SelectionGoal::Column(4),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(3, 4), SelectionGoal::Column(4)),
|
||||
);
|
||||
|
||||
// Move up and down across second excerpt's header
|
||||
assert_eq!(
|
||||
up(&snapshot, DisplayPoint::new(6, 5), SelectionGoal::Column(5)),
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(6, 5),
|
||||
SelectionGoal::Column(5),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(3, 4), SelectionGoal::Column(5)),
|
||||
);
|
||||
assert_eq!(
|
||||
down(&snapshot, DisplayPoint::new(3, 4), SelectionGoal::Column(5)),
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(3, 4),
|
||||
SelectionGoal::Column(5),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(6, 5), SelectionGoal::Column(5)),
|
||||
);
|
||||
|
||||
// Can't move down off the end
|
||||
assert_eq!(
|
||||
down(&snapshot, DisplayPoint::new(7, 0), SelectionGoal::Column(0)),
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(7, 0),
|
||||
SelectionGoal::Column(0),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
|
||||
);
|
||||
assert_eq!(
|
||||
down(&snapshot, DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(7, 2),
|
||||
SelectionGoal::Column(2),
|
||||
false
|
||||
),
|
||||
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ mod test {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true, "").await;
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
cx.simulate_keystroke("i");
|
||||
assert_eq!(cx.mode(), Mode::Insert);
|
||||
cx.simulate_keystrokes(["T", "e", "s", "t"]);
|
||||
|
|
|
@ -4,7 +4,7 @@ use editor::{
|
|||
movement, Bias, DisplayPoint,
|
||||
};
|
||||
use gpui::{actions, impl_actions, MutableAppContext};
|
||||
use language::SelectionGoal;
|
||||
use language::{Selection, SelectionGoal};
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
|
@ -14,22 +14,15 @@ use crate::{
|
|||
Vim,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Motion {
|
||||
Left,
|
||||
Down,
|
||||
Up,
|
||||
Right,
|
||||
NextWordStart {
|
||||
ignore_punctuation: bool,
|
||||
stop_at_newline: bool,
|
||||
},
|
||||
NextWordEnd {
|
||||
ignore_punctuation: bool,
|
||||
},
|
||||
PreviousWordStart {
|
||||
ignore_punctuation: bool,
|
||||
},
|
||||
NextWordStart { ignore_punctuation: bool },
|
||||
NextWordEnd { ignore_punctuation: bool },
|
||||
PreviousWordStart { ignore_punctuation: bool },
|
||||
StartOfLine,
|
||||
EndOfLine,
|
||||
StartOfDocument,
|
||||
|
@ -41,8 +34,6 @@ pub enum Motion {
|
|||
struct NextWordStart {
|
||||
#[serde(default)]
|
||||
ignore_punctuation: bool,
|
||||
#[serde(default)]
|
||||
stop_at_newline: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
|
@ -87,19 +78,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));
|
||||
|
||||
cx.add_action(
|
||||
|_: &mut Workspace,
|
||||
&NextWordStart {
|
||||
ignore_punctuation,
|
||||
stop_at_newline,
|
||||
}: &NextWordStart,
|
||||
cx: _| {
|
||||
motion(
|
||||
Motion::NextWordStart {
|
||||
ignore_punctuation,
|
||||
stop_at_newline,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
|
||||
motion(Motion::NextWordStart { ignore_punctuation }, cx)
|
||||
},
|
||||
);
|
||||
cx.add_action(
|
||||
|
@ -128,29 +108,48 @@ fn motion(motion: Motion, cx: &mut MutableAppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
// Motion handling is specified here:
|
||||
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
|
||||
impl Motion {
|
||||
pub fn linewise(self) -> bool {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Down | Up | StartOfDocument | EndOfDocument => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inclusive(self) -> bool {
|
||||
use Motion::*;
|
||||
if self.linewise() {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self {
|
||||
EndOfLine | NextWordEnd { .. } => true,
|
||||
Left | Right | StartOfLine | NextWordStart { .. } | PreviousWordStart { .. } => false,
|
||||
_ => panic!("Exclusivity not defined for {self:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_point(
|
||||
self,
|
||||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
goal: SelectionGoal,
|
||||
block_cursor_positioning: bool,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Left => (left(map, point), SelectionGoal::None),
|
||||
Down => movement::down(map, point, goal),
|
||||
Up => movement::up(map, point, goal),
|
||||
Down => movement::down(map, point, goal, true),
|
||||
Up => movement::up(map, point, goal, true),
|
||||
Right => (right(map, point), SelectionGoal::None),
|
||||
NextWordStart {
|
||||
ignore_punctuation,
|
||||
stop_at_newline,
|
||||
} => (
|
||||
next_word_start(map, point, ignore_punctuation, stop_at_newline),
|
||||
NextWordStart { ignore_punctuation } => (
|
||||
next_word_start(map, point, ignore_punctuation),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
NextWordEnd { ignore_punctuation } => (
|
||||
next_word_end(map, point, ignore_punctuation, block_cursor_positioning),
|
||||
next_word_end(map, point, ignore_punctuation),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousWordStart { ignore_punctuation } => (
|
||||
|
@ -164,11 +163,55 @@ impl Motion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn line_wise(self) -> bool {
|
||||
use Motion::*;
|
||||
match self {
|
||||
Down | Up | StartOfDocument | EndOfDocument => true,
|
||||
_ => false,
|
||||
// Expands a selection using self motion for an operator
|
||||
pub fn expand_selection(
|
||||
self,
|
||||
map: &DisplaySnapshot,
|
||||
selection: &mut Selection<DisplayPoint>,
|
||||
expand_to_surrounding_newline: bool,
|
||||
) {
|
||||
let (head, goal) = self.move_point(map, selection.head(), selection.goal);
|
||||
selection.set_head(head, goal);
|
||||
|
||||
if self.linewise() {
|
||||
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
|
||||
|
||||
if expand_to_surrounding_newline {
|
||||
if selection.end.row() < map.max_point().row() {
|
||||
*selection.end.row_mut() += 1;
|
||||
*selection.end.column_mut() = 0;
|
||||
// Don't reset the end here
|
||||
return;
|
||||
} else if selection.start.row() > 0 {
|
||||
*selection.start.row_mut() -= 1;
|
||||
*selection.start.column_mut() = map.line_len(selection.start.row());
|
||||
}
|
||||
}
|
||||
|
||||
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
||||
} else {
|
||||
// If the motion is exclusive and the end of the motion is in column 1, the
|
||||
// end of the motion is moved to the end of the previous line and the motion
|
||||
// becomes inclusive. Example: "}" moves to the first line after a paragraph,
|
||||
// but "d}" will not include that line.
|
||||
let mut inclusive = self.inclusive();
|
||||
if !inclusive
|
||||
&& selection.end.row() > selection.start.row()
|
||||
&& selection.end.column() == 0
|
||||
&& selection.end.row() > 0
|
||||
{
|
||||
inclusive = true;
|
||||
*selection.end.row_mut() -= 1;
|
||||
*selection.end.column_mut() = 0;
|
||||
selection.end = map.clip_point(
|
||||
map.next_line_boundary(selection.end.to_point(map)).1,
|
||||
Bias::Left,
|
||||
);
|
||||
}
|
||||
|
||||
if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
|
||||
*selection.end.column_mut() += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +230,6 @@ fn next_word_start(
|
|||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
stop_at_newline: bool,
|
||||
) -> DisplayPoint {
|
||||
let mut crossed_newline = false;
|
||||
movement::find_boundary(map, point, |left, right| {
|
||||
|
@ -196,8 +238,8 @@ fn next_word_start(
|
|||
let at_newline = right == '\n';
|
||||
|
||||
let found = (left_kind != right_kind && !right.is_whitespace())
|
||||
|| (at_newline && (crossed_newline || stop_at_newline))
|
||||
|| (at_newline && left == '\n'); // Prevents skipping repeated empty lines
|
||||
|| at_newline && crossed_newline
|
||||
|| at_newline && left == '\n'; // Prevents skipping repeated empty lines
|
||||
|
||||
if at_newline {
|
||||
crossed_newline = true;
|
||||
|
@ -210,7 +252,6 @@ fn next_word_end(
|
|||
map: &DisplaySnapshot,
|
||||
mut point: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
before_end_character: bool,
|
||||
) -> DisplayPoint {
|
||||
*point.column_mut() += 1;
|
||||
point = movement::find_boundary(map, point, |left, right| {
|
||||
|
@ -221,13 +262,12 @@ fn next_word_end(
|
|||
});
|
||||
// find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know
|
||||
// we have backtraced already
|
||||
if before_end_character
|
||||
&& !map
|
||||
.chars_at(point)
|
||||
.skip(1)
|
||||
.next()
|
||||
.map(|c| c == '\n')
|
||||
.unwrap_or(true)
|
||||
if !map
|
||||
.chars_at(point)
|
||||
.skip(1)
|
||||
.next()
|
||||
.map(|c| c == '\n')
|
||||
.unwrap_or(true)
|
||||
{
|
||||
*point.column_mut() = point.column().saturating_sub(1);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
436
crates/vim/src/normal/change.rs
Normal file
436
crates/vim/src/normal/change.rs
Normal file
|
@ -0,0 +1,436 @@
|
|||
use crate::{motion::Motion, state::Mode, Vim};
|
||||
use editor::{char_kind, movement};
|
||||
use gpui::{impl_actions, MutableAppContext, ViewContext};
|
||||
use serde::Deserialize;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ChangeWord {
|
||||
#[serde(default)]
|
||||
ignore_punctuation: bool,
|
||||
}
|
||||
|
||||
impl_actions!(vim, [ChangeWord]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(change_word);
|
||||
}
|
||||
|
||||
pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.move_selections(cx, |map, selection| {
|
||||
motion.expand_selection(map, selection, false);
|
||||
});
|
||||
editor.insert(&"", cx);
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Insert, cx)
|
||||
}
|
||||
|
||||
// From the docs https://vimhelp.org/change.txt.html#cw
|
||||
// Special case: When the cursor is in a word, "cw" and "cW" do not include the
|
||||
// white space after a word, they only change up to the end of the word. This is
|
||||
// because Vim interprets "cw" as change-word, and a word does not include the
|
||||
// following white space.
|
||||
fn change_word(
|
||||
_: &mut Workspace,
|
||||
&ChangeWord { ignore_punctuation }: &ChangeWord,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.move_selections(cx, |map, selection| {
|
||||
if selection.end.column() == map.line_len(selection.end.row()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selection.end = movement::find_boundary(map, selection.end, |left, right| {
|
||||
let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation);
|
||||
let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation);
|
||||
|
||||
left_kind != right_kind || left == '\n' || right == '\n'
|
||||
});
|
||||
});
|
||||
editor.insert(&"", cx);
|
||||
});
|
||||
});
|
||||
vim.switch_mode(Mode::Insert, cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::{state::Mode, vim_test_context::VimTestContext};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_h(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "h"]).mode_after(Mode::Insert);
|
||||
cx.assert("Te|st", "T|st");
|
||||
cx.assert("T|est", "|est");
|
||||
cx.assert("|Test", "|Test");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test
|
||||
|test"},
|
||||
indoc! {"
|
||||
Test
|
||||
|test"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_l(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "l"]).mode_after(Mode::Insert);
|
||||
cx.assert("Te|st", "Te|t");
|
||||
cx.assert("Tes|t", "Tes|");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_w(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "w"]).mode_after(Mode::Insert);
|
||||
cx.assert("Te|st", "Te|");
|
||||
cx.assert("T|est test", "T| test");
|
||||
cx.assert("Test| test", "Test|test");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test te|st
|
||||
test"},
|
||||
indoc! {"
|
||||
Test te|
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test tes|t
|
||||
test"},
|
||||
indoc! {"
|
||||
Test tes|
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
);
|
||||
|
||||
let mut cx = cx.binding(["c", "shift-W"]);
|
||||
cx.assert("Test te|st-test test", "Test te| test");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_e(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "e"]).mode_after(Mode::Insert);
|
||||
cx.assert("Te|st Test", "Te| Test");
|
||||
cx.assert("T|est test", "T| test");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test te|st
|
||||
test"},
|
||||
indoc! {"
|
||||
Test te|
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test tes|t
|
||||
test"},
|
||||
"Test tes|",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
);
|
||||
|
||||
let mut cx = cx.binding(["c", "shift-E"]);
|
||||
cx.assert("Test te|st-test test", "Test te| test");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_b(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "b"]).mode_after(Mode::Insert);
|
||||
cx.assert("Te|st Test", "|st Test");
|
||||
cx.assert("Test |test", "|test");
|
||||
cx.assert("Test1 test2 |test3", "Test1 |test3");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|test"},
|
||||
indoc! {"
|
||||
Test |
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
indoc! {"
|
||||
Test |
|
||||
|
||||
test"},
|
||||
);
|
||||
|
||||
let mut cx = cx.binding(["c", "shift-B"]);
|
||||
cx.assert("Test test-test |test", "Test |test");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "shift-$"]).mode_after(Mode::Insert);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
The q|
|
||||
brown fox"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_0(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "0"]).mode_after(Mode::Insert);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
|uick
|
||||
brown fox"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_k(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "k"]).mode_after(Mode::Insert);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown |fox
|
||||
jumps over"},
|
||||
indoc! {"
|
||||
|
|
||||
jumps over"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps |over"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox
|
||||
jumps over"},
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over"},
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_j(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "j"]).mode_after(Mode::Insert);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown |fox
|
||||
jumps over"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps |over"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
|"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox
|
||||
jumps over"},
|
||||
indoc! {"
|
||||
|
|
||||
jumps over"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
|"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
|"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "shift-G"]).mode_after(Mode::Insert);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
the l|azy"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
|"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
|"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
|"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_change_gg(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["c", "g", "g"]).mode_after(Mode::Insert);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
|
|
||||
jumps over
|
||||
the lazy"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
the l|azy"},
|
||||
"|",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
);
|
||||
}
|
||||
}
|
386
crates/vim/src/normal/delete.rs
Normal file
386
crates/vim/src/normal/delete.rs
Normal file
|
@ -0,0 +1,386 @@
|
|||
use crate::{motion::Motion, Vim};
|
||||
use editor::Bias;
|
||||
use gpui::MutableAppContext;
|
||||
use language::SelectionGoal;
|
||||
|
||||
pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||
vim.update_active_editor(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.move_selections(cx, |map, selection| {
|
||||
let original_head = selection.head();
|
||||
motion.expand_selection(map, selection, true);
|
||||
selection.goal = SelectionGoal::Column(original_head.column());
|
||||
});
|
||||
editor.insert(&"", cx);
|
||||
|
||||
// Fixup cursor position after the deletion
|
||||
editor.set_clip_at_line_ends(true, cx);
|
||||
editor.move_cursors(cx, |map, mut cursor, goal| {
|
||||
if motion.linewise() {
|
||||
if let SelectionGoal::Column(column) = goal {
|
||||
*cursor.column_mut() = column
|
||||
}
|
||||
}
|
||||
|
||||
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::vim_test_context::VimTestContext;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_h(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "h"]);
|
||||
cx.assert("Te|st", "T|st");
|
||||
cx.assert("T|est", "|est");
|
||||
cx.assert("|Test", "|Test");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test
|
||||
|test"},
|
||||
indoc! {"
|
||||
Test
|
||||
|test"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_l(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "l"]);
|
||||
cx.assert("|Test", "|est");
|
||||
cx.assert("Te|st", "Te|t");
|
||||
cx.assert("Tes|t", "Te|s");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Tes|t
|
||||
test"},
|
||||
indoc! {"
|
||||
Te|s
|
||||
test"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_w(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "w"]);
|
||||
cx.assert("Te|st", "T|e");
|
||||
cx.assert("T|est test", "T|test");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test te|st
|
||||
test"},
|
||||
indoc! {"
|
||||
Test t|e
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test tes|t
|
||||
test"},
|
||||
indoc! {"
|
||||
Test te|s
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
);
|
||||
|
||||
let mut cx = cx.binding(["d", "shift-W"]);
|
||||
cx.assert("Test te|st-test test", "Test te|test");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_e(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "e"]);
|
||||
cx.assert("Te|st Test", "Te| Test");
|
||||
cx.assert("T|est test", "T| test");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test te|st
|
||||
test"},
|
||||
indoc! {"
|
||||
Test t|e
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test tes|t
|
||||
test"},
|
||||
"Test te|s",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
);
|
||||
|
||||
let mut cx = cx.binding(["d", "shift-E"]);
|
||||
cx.assert("Test te|st-test test", "Test te| test");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_b(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "b"]);
|
||||
cx.assert("Te|st Test", "|st Test");
|
||||
cx.assert("Test |test", "|test");
|
||||
cx.assert("Test1 test2 |test3", "Test1 |test3");
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|test"},
|
||||
// Trailing whitespace after cursor
|
||||
indoc! {"
|
||||
Test|
|
||||
test"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
Test test
|
||||
|
|
||||
test"},
|
||||
// Trailing whitespace after cursor
|
||||
indoc! {"
|
||||
Test|
|
||||
|
||||
test"},
|
||||
);
|
||||
|
||||
let mut cx = cx.binding(["d", "shift-B"]);
|
||||
cx.assert("Test test-test |test", "Test |test");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "shift-$"]);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
The |q
|
||||
brown fox"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_0(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "0"]);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
|uick
|
||||
brown fox"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|
|
||||
brown fox"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_k(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "k"]);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown |fox
|
||||
jumps over"},
|
||||
"jumps |over",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps |over"},
|
||||
"The qu|ick",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox
|
||||
jumps over"},
|
||||
indoc! {"
|
||||
brown| fox
|
||||
jumps over"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
|brown fox
|
||||
jumps over"},
|
||||
"|jumps over",
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_j(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "j"]);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown |fox
|
||||
jumps over"},
|
||||
"The qu|ick",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps |over"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown |fox"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox
|
||||
jumps over"},
|
||||
"jumps| over",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
|"},
|
||||
indoc! {"
|
||||
The quick
|
||||
|brown fox"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "shift-G"]);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
"The q|uick",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
"The q|uick",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
the l|azy"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps| over"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
|"},
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
|jumps over"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_gg(cx: &mut gpui::TestAppContext) {
|
||||
let cx = VimTestContext::new(cx, true).await;
|
||||
let mut cx = cx.binding(["d", "g", "g"]);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
jumps| over
|
||||
the lazy"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
jumps over
|
||||
the l|azy"},
|
||||
"|",
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
The q|uick
|
||||
brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
brown| fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
);
|
||||
cx.assert(
|
||||
indoc! {"
|
||||
|
|
||||
brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
indoc! {"
|
||||
|brown fox
|
||||
jumps over
|
||||
the lazy"},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
#[cfg(test)]
|
||||
mod vim_test_context;
|
||||
|
||||
mod editor_events;
|
||||
mod insert;
|
||||
mod motion;
|
||||
mod normal;
|
||||
mod state;
|
||||
#[cfg(test)]
|
||||
mod vim_test_context;
|
||||
|
||||
use collections::HashMap;
|
||||
use editor::{CursorShape, Editor};
|
||||
|
@ -25,6 +26,7 @@ impl_actions!(vim, [SwitchMode, PushOperator]);
|
|||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
editor_events::init(cx);
|
||||
normal::init(cx);
|
||||
insert::init(cx);
|
||||
motion::init(cx);
|
||||
|
||||
|
@ -142,14 +144,14 @@ mod test {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, false, "").await;
|
||||
let mut cx = VimTestContext::new(cx, false).await;
|
||||
cx.simulate_keystrokes(["h", "j", "k", "l"]);
|
||||
cx.assert_editor_state("hjkl|");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true, "").await;
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
cx.simulate_keystroke("i");
|
||||
assert_eq!(cx.mode(), Mode::Insert);
|
||||
|
|
|
@ -15,11 +15,7 @@ pub struct VimTestContext<'a> {
|
|||
}
|
||||
|
||||
impl<'a> VimTestContext<'a> {
|
||||
pub async fn new(
|
||||
cx: &'a mut gpui::TestAppContext,
|
||||
enabled: bool,
|
||||
initial_editor_text: &str,
|
||||
) -> VimTestContext<'a> {
|
||||
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
|
||||
cx.update(|cx| {
|
||||
editor::init(cx);
|
||||
crate::init(cx);
|
||||
|
@ -38,10 +34,7 @@ impl<'a> VimTestContext<'a> {
|
|||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/root",
|
||||
json!({ "dir": { "test.txt": initial_editor_text } }),
|
||||
)
|
||||
.insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
|
||||
.await;
|
||||
|
||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
|
@ -202,6 +195,14 @@ impl<'a> VimTestContext<'a> {
|
|||
assert_eq!(self.mode(), mode_after);
|
||||
assert_eq!(self.active_operator(), None);
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -211,3 +212,61 @@ impl<'a> Deref for VimTestContext<'a> {
|
|||
self.cx
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VimBindingTestContext<'a, const COUNT: usize> {
|
||||
cx: VimTestContext<'a>,
|
||||
keystrokes_under_test: [&'static str; COUNT],
|
||||
initial_mode: Mode,
|
||||
mode_after: Mode,
|
||||
}
|
||||
|
||||
impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
|
||||
pub fn new(
|
||||
keystrokes_under_test: [&'static str; COUNT],
|
||||
initial_mode: Mode,
|
||||
mode_after: Mode,
|
||||
cx: VimTestContext<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
keystrokes_under_test,
|
||||
initial_mode,
|
||||
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,
|
||||
initial_mode: self.initial_mode,
|
||||
mode_after: self.mode_after,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode_after(mut self, mode_after: Mode) -> Self {
|
||||
self.mode_after = mode_after;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn assert(&mut self, initial_state: &str, state_after: &str) {
|
||||
self.cx.assert_binding(
|
||||
self.keystrokes_under_test,
|
||||
initial_state,
|
||||
self.initial_mode,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue