vim: Fix relative line motion
Before this change up and down were in display co-ordinates, after this change they are in fold coordinates (which matches the vim behaviour). To make this work without causing usabliity problems, a bunch of extra keyboard shortcuts now work: - vim: `z {o,c}` to open,close a fold - vim: `z f` to fold current visual selection - vim: `g {j,k,up,down}` to move up/down a display line - vim: `g {0,^,$,home,end}` to get to start/end of a display line Fixes: zed-industries/community#1562
This commit is contained in:
parent
0280d5d010
commit
20aa2a4c54
13 changed files with 580 additions and 67 deletions
|
@ -137,10 +137,67 @@
|
||||||
"partialWord": true
|
"partialWord": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"g j": [
|
||||||
|
"vim::Down",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g down": [
|
||||||
|
"vim::Down",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g k": [
|
||||||
|
"vim::Up",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g up": [
|
||||||
|
"vim::Up",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g $": [
|
||||||
|
"vim::EndOfLine",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g end": [
|
||||||
|
"vim::EndOfLine",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g 0": [
|
||||||
|
"vim::StartOfLine",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g home": [
|
||||||
|
"vim::StartOfLine",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"g ^": [
|
||||||
|
"vim::FirstNonWhitespace",
|
||||||
|
{
|
||||||
|
"displayLines": true
|
||||||
|
}
|
||||||
|
],
|
||||||
// z commands
|
// z commands
|
||||||
"z t": "editor::ScrollCursorTop",
|
"z t": "editor::ScrollCursorTop",
|
||||||
"z z": "editor::ScrollCursorCenter",
|
"z z": "editor::ScrollCursorCenter",
|
||||||
"z b": "editor::ScrollCursorBottom",
|
"z b": "editor::ScrollCursorBottom",
|
||||||
|
"z c": "editor::Fold",
|
||||||
|
"z o": "editor::UnfoldLines",
|
||||||
|
"z f": "editor::FoldSelectedRanges",
|
||||||
// Count support
|
// Count support
|
||||||
"1": [
|
"1": [
|
||||||
"vim::Number",
|
"vim::Number",
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub use block_map::{
|
||||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use self::fold_map::FoldPoint;
|
||||||
pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint};
|
pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -310,7 +311,7 @@ impl DisplayMap {
|
||||||
|
|
||||||
pub struct DisplaySnapshot {
|
pub struct DisplaySnapshot {
|
||||||
pub buffer_snapshot: MultiBufferSnapshot,
|
pub buffer_snapshot: MultiBufferSnapshot,
|
||||||
fold_snapshot: fold_map::FoldSnapshot,
|
pub fold_snapshot: fold_map::FoldSnapshot,
|
||||||
inlay_snapshot: inlay_map::InlaySnapshot,
|
inlay_snapshot: inlay_map::InlaySnapshot,
|
||||||
tab_snapshot: tab_map::TabSnapshot,
|
tab_snapshot: tab_map::TabSnapshot,
|
||||||
wrap_snapshot: wrap_map::WrapSnapshot,
|
wrap_snapshot: wrap_map::WrapSnapshot,
|
||||||
|
@ -438,6 +439,20 @@ impl DisplaySnapshot {
|
||||||
fold_point.to_inlay_point(&self.fold_snapshot)
|
fold_point.to_inlay_point(&self.fold_snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
|
||||||
|
let block_point = point.0;
|
||||||
|
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
|
||||||
|
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
|
||||||
|
self.tab_snapshot.to_fold_point(tab_point, bias).0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
|
||||||
|
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||||
|
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
|
||||||
|
let block_point = self.block_snapshot.to_block_point(wrap_point);
|
||||||
|
DisplayPoint(block_point)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn max_point(&self) -> DisplayPoint {
|
pub fn max_point(&self) -> DisplayPoint {
|
||||||
DisplayPoint(self.block_snapshot.max_point())
|
DisplayPoint(self.block_snapshot.max_point())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7198,7 +7198,7 @@ impl Editor {
|
||||||
|
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all_adjusted(cx);
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
let range = selection.range().sorted();
|
let range = selection.range().sorted();
|
||||||
let buffer_start_row = range.start.row;
|
let buffer_start_row = range.start.row;
|
||||||
|
@ -7274,7 +7274,17 @@ impl Editor {
|
||||||
|
|
||||||
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
|
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
let ranges = selections.into_iter().map(|s| s.start..s.end);
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
let line_mode = self.selections.line_mode;
|
||||||
|
let ranges = selections.into_iter().map(|s| {
|
||||||
|
if line_mode {
|
||||||
|
let start = Point::new(s.start.row, 0);
|
||||||
|
let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row));
|
||||||
|
start..end
|
||||||
|
} else {
|
||||||
|
s.start..s.end
|
||||||
|
}
|
||||||
|
});
|
||||||
self.fold_ranges(ranges, true, cx);
|
self.fold_ranges(ranges, true, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{cmp, sync::Arc};
|
||||||
|
|
||||||
use editor::{
|
use editor::{
|
||||||
char_kind,
|
char_kind,
|
||||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
|
||||||
movement, Bias, CharKind, DisplayPoint, ToOffset,
|
movement, Bias, CharKind, DisplayPoint, ToOffset,
|
||||||
};
|
};
|
||||||
use gpui::{actions, impl_actions, AppContext, WindowContext};
|
use gpui::{actions, impl_actions, AppContext, WindowContext};
|
||||||
|
@ -21,16 +21,16 @@ use crate::{
|
||||||
pub enum Motion {
|
pub enum Motion {
|
||||||
Left,
|
Left,
|
||||||
Backspace,
|
Backspace,
|
||||||
Down,
|
Down { display_lines: bool },
|
||||||
Up,
|
Up { display_lines: bool },
|
||||||
Right,
|
Right,
|
||||||
NextWordStart { ignore_punctuation: bool },
|
NextWordStart { ignore_punctuation: bool },
|
||||||
NextWordEnd { ignore_punctuation: bool },
|
NextWordEnd { ignore_punctuation: bool },
|
||||||
PreviousWordStart { ignore_punctuation: bool },
|
PreviousWordStart { ignore_punctuation: bool },
|
||||||
FirstNonWhitespace,
|
FirstNonWhitespace { display_lines: bool },
|
||||||
CurrentLine,
|
CurrentLine,
|
||||||
StartOfLine,
|
StartOfLine { display_lines: bool },
|
||||||
EndOfLine,
|
EndOfLine { display_lines: bool },
|
||||||
StartOfParagraph,
|
StartOfParagraph,
|
||||||
EndOfParagraph,
|
EndOfParagraph,
|
||||||
StartOfDocument,
|
StartOfDocument,
|
||||||
|
@ -62,6 +62,41 @@ struct PreviousWordStart {
|
||||||
ignore_punctuation: bool,
|
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)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
struct RepeatFind {
|
struct RepeatFind {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -73,12 +108,7 @@ actions!(
|
||||||
[
|
[
|
||||||
Left,
|
Left,
|
||||||
Backspace,
|
Backspace,
|
||||||
Down,
|
|
||||||
Up,
|
|
||||||
Right,
|
Right,
|
||||||
FirstNonWhitespace,
|
|
||||||
StartOfLine,
|
|
||||||
EndOfLine,
|
|
||||||
CurrentLine,
|
CurrentLine,
|
||||||
StartOfParagraph,
|
StartOfParagraph,
|
||||||
EndOfParagraph,
|
EndOfParagraph,
|
||||||
|
@ -90,20 +120,63 @@ actions!(
|
||||||
);
|
);
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
vim,
|
vim,
|
||||||
[NextWordStart, NextWordEnd, PreviousWordStart, RepeatFind]
|
[
|
||||||
|
NextWordStart,
|
||||||
|
NextWordEnd,
|
||||||
|
PreviousWordStart,
|
||||||
|
RepeatFind,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
FirstNonWhitespace,
|
||||||
|
EndOfLine,
|
||||||
|
StartOfLine,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(|_: &mut Workspace, _: &Left, cx: _| motion(Motion::Left, cx));
|
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, _: &Backspace, cx: _| motion(Motion::Backspace, cx));
|
||||||
cx.add_action(|_: &mut Workspace, _: &Down, cx: _| motion(Motion::Down, cx));
|
cx.add_action(|_: &mut Workspace, action: &Down, cx: _| {
|
||||||
cx.add_action(|_: &mut Workspace, _: &Up, cx: _| motion(Motion::Up, cx));
|
motion(
|
||||||
cx.add_action(|_: &mut Workspace, _: &Right, cx: _| motion(Motion::Right, cx));
|
Motion::Down {
|
||||||
cx.add_action(|_: &mut Workspace, _: &FirstNonWhitespace, cx: _| {
|
display_lines: action.display_lines,
|
||||||
motion(Motion::FirstNonWhitespace, cx)
|
},
|
||||||
|
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, _: &CurrentLine, cx: _| motion(Motion::CurrentLine, cx));
|
||||||
cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
|
cx.add_action(|_: &mut Workspace, _: &StartOfParagraph, cx: _| {
|
||||||
motion(Motion::StartOfParagraph, cx)
|
motion(Motion::StartOfParagraph, cx)
|
||||||
|
@ -192,19 +265,25 @@ impl Motion {
|
||||||
pub fn linewise(&self) -> bool {
|
pub fn linewise(&self) -> bool {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
match self {
|
match self {
|
||||||
Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart
|
Down { .. }
|
||||||
| StartOfParagraph | EndOfParagraph => true,
|
| Up { .. }
|
||||||
EndOfLine
|
| StartOfDocument
|
||||||
|
| EndOfDocument
|
||||||
|
| CurrentLine
|
||||||
|
| NextLineStart
|
||||||
|
| StartOfParagraph
|
||||||
|
| EndOfParagraph => true,
|
||||||
|
EndOfLine { .. }
|
||||||
| NextWordEnd { .. }
|
| NextWordEnd { .. }
|
||||||
| Matching
|
| Matching
|
||||||
| FindForward { .. }
|
| FindForward { .. }
|
||||||
| Left
|
| Left
|
||||||
| Backspace
|
| Backspace
|
||||||
| Right
|
| Right
|
||||||
| StartOfLine
|
| StartOfLine { .. }
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| FirstNonWhitespace
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. } => false,
|
| FindBackward { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,21 +292,21 @@ impl Motion {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
match self {
|
match self {
|
||||||
StartOfDocument | EndOfDocument | CurrentLine => true,
|
StartOfDocument | EndOfDocument | CurrentLine => true,
|
||||||
Down
|
Down { .. }
|
||||||
| Up
|
| Up { .. }
|
||||||
| EndOfLine
|
| EndOfLine { .. }
|
||||||
| NextWordEnd { .. }
|
| NextWordEnd { .. }
|
||||||
| Matching
|
| Matching
|
||||||
| FindForward { .. }
|
| FindForward { .. }
|
||||||
| Left
|
| Left
|
||||||
| Backspace
|
| Backspace
|
||||||
| Right
|
| Right
|
||||||
| StartOfLine
|
| StartOfLine { .. }
|
||||||
| StartOfParagraph
|
| StartOfParagraph
|
||||||
| EndOfParagraph
|
| EndOfParagraph
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| FirstNonWhitespace
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. }
|
| FindBackward { .. }
|
||||||
| NextLineStart => false,
|
| NextLineStart => false,
|
||||||
}
|
}
|
||||||
|
@ -236,12 +315,12 @@ impl Motion {
|
||||||
pub fn inclusive(&self) -> bool {
|
pub fn inclusive(&self) -> bool {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
match self {
|
match self {
|
||||||
Down
|
Down { .. }
|
||||||
| Up
|
| Up { .. }
|
||||||
| StartOfDocument
|
| StartOfDocument
|
||||||
| EndOfDocument
|
| EndOfDocument
|
||||||
| CurrentLine
|
| CurrentLine
|
||||||
| EndOfLine
|
| EndOfLine { .. }
|
||||||
| NextWordEnd { .. }
|
| NextWordEnd { .. }
|
||||||
| Matching
|
| Matching
|
||||||
| FindForward { .. }
|
| FindForward { .. }
|
||||||
|
@ -249,12 +328,12 @@ impl Motion {
|
||||||
Left
|
Left
|
||||||
| Backspace
|
| Backspace
|
||||||
| Right
|
| Right
|
||||||
| StartOfLine
|
| StartOfLine { .. }
|
||||||
| StartOfParagraph
|
| StartOfParagraph
|
||||||
| EndOfParagraph
|
| EndOfParagraph
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| FirstNonWhitespace
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. } => false,
|
| FindBackward { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,8 +351,18 @@ impl Motion {
|
||||||
let (new_point, goal) = match self {
|
let (new_point, goal) = match self {
|
||||||
Left => (left(map, point, times), SelectionGoal::None),
|
Left => (left(map, point, times), SelectionGoal::None),
|
||||||
Backspace => (backspace(map, point, times), SelectionGoal::None),
|
Backspace => (backspace(map, point, times), SelectionGoal::None),
|
||||||
Down => down(map, point, goal, times),
|
Down {
|
||||||
Up => up(map, point, goal, times),
|
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),
|
Right => (right(map, point, times), SelectionGoal::None),
|
||||||
NextWordStart { ignore_punctuation } => (
|
NextWordStart { ignore_punctuation } => (
|
||||||
next_word_start(map, point, *ignore_punctuation, times),
|
next_word_start(map, point, *ignore_punctuation, times),
|
||||||
|
@ -287,9 +376,17 @@ impl Motion {
|
||||||
previous_word_start(map, point, *ignore_punctuation, times),
|
previous_word_start(map, point, *ignore_punctuation, times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
),
|
),
|
||||||
FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None),
|
FirstNonWhitespace { display_lines } => (
|
||||||
StartOfLine => (start_of_line(map, point), SelectionGoal::None),
|
first_non_whitespace(map, *display_lines, point),
|
||||||
EndOfLine => (end_of_line(map, point), SelectionGoal::None),
|
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 => (
|
StartOfParagraph => (
|
||||||
movement::start_of_paragraph(map, point, times),
|
movement::start_of_paragraph(map, point, times),
|
||||||
SelectionGoal::None,
|
SelectionGoal::None,
|
||||||
|
@ -298,7 +395,7 @@ impl Motion {
|
||||||
map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
|
map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
|
||||||
SelectionGoal::None,
|
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),
|
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
|
||||||
EndOfDocument => (
|
EndOfDocument => (
|
||||||
end_of_document(map, point, maybe_times),
|
end_of_document(map, point, maybe_times),
|
||||||
|
@ -399,15 +496,39 @@ fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Di
|
||||||
}
|
}
|
||||||
|
|
||||||
fn down(
|
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,
|
map: &DisplaySnapshot,
|
||||||
mut point: DisplayPoint,
|
mut point: DisplayPoint,
|
||||||
mut goal: SelectionGoal,
|
mut goal: SelectionGoal,
|
||||||
times: usize,
|
times: usize,
|
||||||
) -> (DisplayPoint, SelectionGoal) {
|
) -> (DisplayPoint, SelectionGoal) {
|
||||||
let start_row = point.to_point(map).row;
|
for _ in 0..times {
|
||||||
let target = cmp::min(map.max_buffer_row(), start_row + times as u32);
|
|
||||||
|
|
||||||
while point.to_point(map).row < target {
|
|
||||||
(point, goal) = movement::down(map, point, goal, true);
|
(point, goal) = movement::down(map, point, goal, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,17 +536,39 @@ fn down(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(
|
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,
|
map: &DisplaySnapshot,
|
||||||
mut point: DisplayPoint,
|
mut point: DisplayPoint,
|
||||||
mut goal: SelectionGoal,
|
mut goal: SelectionGoal,
|
||||||
times: usize,
|
times: usize,
|
||||||
) -> (DisplayPoint, SelectionGoal) {
|
) -> (DisplayPoint, SelectionGoal) {
|
||||||
let start_row = point.to_point(map).row;
|
for _ in 0..times {
|
||||||
let target = start_row.saturating_sub(times as u32);
|
|
||||||
|
|
||||||
while point.to_point(map).row > target {
|
|
||||||
(point, goal) = movement::up(map, point, goal, true);
|
(point, goal) = movement::up(map, point, goal, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
(point, goal)
|
(point, goal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,8 +659,12 @@ fn previous_word_start(
|
||||||
point
|
point
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_non_whitespace(map: &DisplaySnapshot, from: DisplayPoint) -> DisplayPoint {
|
fn first_non_whitespace(
|
||||||
let mut last_point = DisplayPoint::new(from.row(), 0);
|
map: &DisplaySnapshot,
|
||||||
|
display_lines: bool,
|
||||||
|
from: DisplayPoint,
|
||||||
|
) -> DisplayPoint {
|
||||||
|
let mut last_point = start_of_line(map, display_lines, from);
|
||||||
let language = map.buffer_snapshot.language_at(from.to_point(map));
|
let language = map.buffer_snapshot.language_at(from.to_point(map));
|
||||||
for (ch, point) in map.chars_at(last_point) {
|
for (ch, point) in map.chars_at(last_point) {
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
|
@ -534,12 +681,23 @@ fn first_non_whitespace(map: &DisplaySnapshot, from: DisplayPoint) -> DisplayPoi
|
||||||
map.clip_point(last_point, Bias::Left)
|
map.clip_point(last_point, Bias::Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_of_line(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
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
|
map.prev_line_boundary(point.to_point(map)).1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_of_line(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
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)
|
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 {
|
fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
|
||||||
|
@ -664,6 +822,7 @@ fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) ->
|
||||||
let new_row = (point.row() + times as u32).min(map.max_buffer_row());
|
let new_row = (point.row() + times as u32).min(map.max_buffer_row());
|
||||||
first_non_whitespace(
|
first_non_whitespace(
|
||||||
map,
|
map,
|
||||||
|
false,
|
||||||
map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left),
|
map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,27 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
|
cx.add_action(|_: &mut Workspace, _: &ChangeToEndOfLine, cx| {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let times = vim.pop_number_operator(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| {
|
cx.add_action(|_: &mut Workspace, _: &DeleteToEndOfLine, cx| {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
let times = vim.pop_number_operator(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,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
scroll::init(cx);
|
scroll::init(cx);
|
||||||
|
@ -165,7 +179,10 @@ fn insert_first_non_whitespace(
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.maybe_move_cursors_with(|map, cursor, goal| {
|
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)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -178,7 +195,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.maybe_move_cursors_with(|map, cursor, goal| {
|
s.maybe_move_cursors_with(|map, cursor, goal| {
|
||||||
Motion::EndOfLine.move_point(map, cursor, goal, None)
|
Motion::CurrentLine.move_point(map, cursor, goal, None)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -238,7 +255,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
|
||||||
});
|
});
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.maybe_move_cursors_with(|map, cursor, goal| {
|
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);
|
editor.edit_with_autoindent(edits, cx);
|
||||||
|
|
|
@ -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
|
// Some motions ignore failure when switching to normal mode
|
||||||
let mut motion_succeeded = matches!(
|
let mut motion_succeeded = matches!(
|
||||||
motion,
|
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| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
|
|
|
@ -15,7 +15,10 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
|
||||||
}
|
}
|
||||||
if line_mode {
|
if line_mode {
|
||||||
Motion::CurrentLine.expand_selection(map, selection, None, false);
|
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,
|
map,
|
||||||
selection.start,
|
selection.start,
|
||||||
selection.goal,
|
selection.goal,
|
||||||
|
|
|
@ -285,3 +285,145 @@ async fn test_word_characters(cx: &mut gpui::TestAppContext) {
|
||||||
Mode::Visual,
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,14 @@
|
||||||
|
use editor::EditorSettings;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use settings::SettingsStore;
|
||||||
use std::ops::{Deref, DerefMut, Range};
|
use std::ops::{Deref, DerefMut, Range};
|
||||||
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use gpui::ContextHandle;
|
use gpui::ContextHandle;
|
||||||
use language::OffsetRangeExt;
|
use language::{
|
||||||
|
language_settings::{AllLanguageSettings, LanguageSettings, SoftWrap},
|
||||||
|
OffsetRangeExt,
|
||||||
|
};
|
||||||
use util::test::{generate_marked_text, marked_text_offsets};
|
use util::test::{generate_marked_text, marked_text_offsets};
|
||||||
|
|
||||||
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
|
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
|
||||||
|
@ -127,6 +132,27 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
context_handle
|
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) {
|
pub async fn assert_shared_state(&mut self, marked_text: &str) {
|
||||||
let neovim = self.neovim_state().await;
|
let neovim = self.neovim_state().await;
|
||||||
let editor = self.editor_state();
|
let editor = self.editor_state();
|
||||||
|
|
|
@ -41,6 +41,7 @@ pub enum NeovimData {
|
||||||
Key(String),
|
Key(String),
|
||||||
Get { state: String, mode: Option<Mode> },
|
Get { state: String, mode: Option<Mode> },
|
||||||
ReadRegister { name: char, value: String },
|
ReadRegister { name: char, value: String },
|
||||||
|
SetOption { value: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NeovimConnection {
|
pub struct NeovimConnection {
|
||||||
|
@ -222,6 +223,29 @@ 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"))]
|
#[cfg(not(feature = "neovim"))]
|
||||||
pub async fn read_register(&mut self, register: char) -> String {
|
pub async fn read_register(&mut self, register: char) -> String {
|
||||||
if let Some(NeovimData::Get { .. }) = self.data.front() {
|
if let Some(NeovimData::Get { .. }) = self.data.front() {
|
||||||
|
|
|
@ -51,8 +51,15 @@ pub fn init(cx: &mut AppContext) {
|
||||||
pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
pub fn visual_motion(motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
if vim.state().mode == Mode::VisualBlock && !matches!(motion, Motion::EndOfLine) {
|
if vim.state().mode == Mode::VisualBlock
|
||||||
let is_up_or_down = matches!(motion, Motion::Up | Motion::Down);
|
&& !matches!(
|
||||||
|
motion,
|
||||||
|
Motion::EndOfLine {
|
||||||
|
display_lines: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let is_up_or_down = matches!(motion, Motion::Up { .. } | Motion::Down { .. });
|
||||||
visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
|
visual_block_motion(is_up_or_down, editor, cx, |map, point, goal| {
|
||||||
motion.move_point(map, point, goal, times)
|
motion.move_point(map, point, goal, times)
|
||||||
})
|
})
|
||||||
|
|
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"}}
|
26
crates/vim/test_data/test_wrapped_lines.json
Normal file
26
crates/vim/test_data/test_wrapped_lines.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{"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"}}
|
Loading…
Add table
Add a link
Reference in a new issue