vim surround (#3152)
- Fix some bugs with vim objects - Add | as a bracket and a motion - Fix neovim tests with angle brackets Release Notes: - vim: Fixed `i` and `a` objects with softwrap, and a few other edge cases - vim: Added support for `ci"` to find the next quoted string on the line - vim: Added support for `|` as a bracket (for languages like ruby and rust) - vim: Added support for `<count>|` to jump to a specific column
This commit is contained in:
commit
67e590202a
16 changed files with 3111 additions and 389 deletions
|
@ -39,6 +39,7 @@
|
||||||
"w": "vim::NextWordStart",
|
"w": "vim::NextWordStart",
|
||||||
"{": "vim::StartOfParagraph",
|
"{": "vim::StartOfParagraph",
|
||||||
"}": "vim::EndOfParagraph",
|
"}": "vim::EndOfParagraph",
|
||||||
|
"|": "vim::GoToColumn",
|
||||||
"shift-w": [
|
"shift-w": [
|
||||||
"vim::NextWordStart",
|
"vim::NextWordStart",
|
||||||
{
|
{
|
||||||
|
@ -97,14 +98,8 @@
|
||||||
"ctrl-o": "pane::GoBack",
|
"ctrl-o": "pane::GoBack",
|
||||||
"ctrl-i": "pane::GoForward",
|
"ctrl-i": "pane::GoForward",
|
||||||
"ctrl-]": "editor::GoToDefinition",
|
"ctrl-]": "editor::GoToDefinition",
|
||||||
"escape": [
|
"escape": ["vim::SwitchMode", "Normal"],
|
||||||
"vim::SwitchMode",
|
"ctrl+[": ["vim::SwitchMode", "Normal"],
|
||||||
"Normal"
|
|
||||||
],
|
|
||||||
"ctrl+[": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Normal"
|
|
||||||
],
|
|
||||||
"v": "vim::ToggleVisual",
|
"v": "vim::ToggleVisual",
|
||||||
"shift-v": "vim::ToggleVisualLine",
|
"shift-v": "vim::ToggleVisualLine",
|
||||||
"ctrl-v": "vim::ToggleVisualBlock",
|
"ctrl-v": "vim::ToggleVisualBlock",
|
||||||
|
@ -233,123 +228,36 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// Count support
|
// Count support
|
||||||
"1": [
|
"1": ["vim::Number", 1],
|
||||||
"vim::Number",
|
"2": ["vim::Number", 2],
|
||||||
1
|
"3": ["vim::Number", 3],
|
||||||
],
|
"4": ["vim::Number", 4],
|
||||||
"2": [
|
"5": ["vim::Number", 5],
|
||||||
"vim::Number",
|
"6": ["vim::Number", 6],
|
||||||
2
|
"7": ["vim::Number", 7],
|
||||||
],
|
"8": ["vim::Number", 8],
|
||||||
"3": [
|
"9": ["vim::Number", 9],
|
||||||
"vim::Number",
|
|
||||||
3
|
|
||||||
],
|
|
||||||
"4": [
|
|
||||||
"vim::Number",
|
|
||||||
4
|
|
||||||
],
|
|
||||||
"5": [
|
|
||||||
"vim::Number",
|
|
||||||
5
|
|
||||||
],
|
|
||||||
"6": [
|
|
||||||
"vim::Number",
|
|
||||||
6
|
|
||||||
],
|
|
||||||
"7": [
|
|
||||||
"vim::Number",
|
|
||||||
7
|
|
||||||
],
|
|
||||||
"8": [
|
|
||||||
"vim::Number",
|
|
||||||
8
|
|
||||||
],
|
|
||||||
"9": [
|
|
||||||
"vim::Number",
|
|
||||||
9
|
|
||||||
],
|
|
||||||
// window related commands (ctrl-w X)
|
// window related commands (ctrl-w X)
|
||||||
"ctrl-w left": [
|
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||||
"workspace::ActivatePaneInDirection",
|
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||||
"Left"
|
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||||
],
|
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||||
"ctrl-w right": [
|
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||||
"workspace::ActivatePaneInDirection",
|
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||||
"Right"
|
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||||
],
|
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||||
"ctrl-w up": [
|
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||||
"workspace::ActivatePaneInDirection",
|
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||||
"Up"
|
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||||
],
|
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||||
"ctrl-w down": [
|
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||||
"workspace::ActivatePaneInDirection",
|
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||||
"Down"
|
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||||
],
|
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||||
"ctrl-w h": [
|
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
|
||||||
"workspace::ActivatePaneInDirection",
|
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
|
||||||
"Left"
|
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
|
||||||
],
|
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
|
||||||
"ctrl-w l": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Right"
|
|
||||||
],
|
|
||||||
"ctrl-w k": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Up"
|
|
||||||
],
|
|
||||||
"ctrl-w j": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Down"
|
|
||||||
],
|
|
||||||
"ctrl-w ctrl-h": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Left"
|
|
||||||
],
|
|
||||||
"ctrl-w ctrl-l": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Right"
|
|
||||||
],
|
|
||||||
"ctrl-w ctrl-k": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Up"
|
|
||||||
],
|
|
||||||
"ctrl-w ctrl-j": [
|
|
||||||
"workspace::ActivatePaneInDirection",
|
|
||||||
"Down"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-left": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Left"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-right": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Right"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-up": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Up"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-down": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Down"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-h": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Left"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-l": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Right"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-k": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Up"
|
|
||||||
],
|
|
||||||
"ctrl-w shift-j": [
|
|
||||||
"workspace::SwapPaneInDirection",
|
|
||||||
"Down"
|
|
||||||
],
|
|
||||||
"ctrl-w g t": "pane::ActivateNextItem",
|
"ctrl-w g t": "pane::ActivateNextItem",
|
||||||
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
||||||
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
||||||
|
@ -371,14 +279,8 @@
|
||||||
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-w n": [
|
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
|
||||||
"workspace::NewFileInDirection",
|
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"]
|
||||||
"Up"
|
|
||||||
],
|
|
||||||
"ctrl-w ctrl-n": [
|
|
||||||
"workspace::NewFileInDirection",
|
|
||||||
"Up"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -393,21 +295,12 @@
|
||||||
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
".": "vim::Repeat",
|
".": "vim::Repeat",
|
||||||
"c": [
|
"c": ["vim::PushOperator", "Change"],
|
||||||
"vim::PushOperator",
|
|
||||||
"Change"
|
|
||||||
],
|
|
||||||
"shift-c": "vim::ChangeToEndOfLine",
|
"shift-c": "vim::ChangeToEndOfLine",
|
||||||
"d": [
|
"d": ["vim::PushOperator", "Delete"],
|
||||||
"vim::PushOperator",
|
|
||||||
"Delete"
|
|
||||||
],
|
|
||||||
"shift-d": "vim::DeleteToEndOfLine",
|
"shift-d": "vim::DeleteToEndOfLine",
|
||||||
"shift-j": "vim::JoinLines",
|
"shift-j": "vim::JoinLines",
|
||||||
"y": [
|
"y": ["vim::PushOperator", "Yank"],
|
||||||
"vim::PushOperator",
|
|
||||||
"Yank"
|
|
||||||
],
|
|
||||||
"shift-y": "vim::YankLine",
|
"shift-y": "vim::YankLine",
|
||||||
"i": "vim::InsertBefore",
|
"i": "vim::InsertBefore",
|
||||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||||
|
@ -443,10 +336,7 @@
|
||||||
"backwards": true
|
"backwards": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"r": [
|
"r": ["vim::PushOperator", "Replace"],
|
||||||
"vim::PushOperator",
|
|
||||||
"Replace"
|
|
||||||
],
|
|
||||||
"s": "vim::Substitute",
|
"s": "vim::Substitute",
|
||||||
"shift-s": "vim::SubstituteLine",
|
"shift-s": "vim::SubstituteLine",
|
||||||
"> >": "editor::Indent",
|
"> >": "editor::Indent",
|
||||||
|
@ -458,10 +348,7 @@
|
||||||
{
|
{
|
||||||
"context": "Editor && VimCount",
|
"context": "Editor && VimCount",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"0": [
|
"0": ["vim::Number", 0]
|
||||||
"vim::Number",
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -497,12 +384,15 @@
|
||||||
"'": "vim::Quotes",
|
"'": "vim::Quotes",
|
||||||
"`": "vim::BackQuotes",
|
"`": "vim::BackQuotes",
|
||||||
"\"": "vim::DoubleQuotes",
|
"\"": "vim::DoubleQuotes",
|
||||||
|
"|": "vim::VerticalBars",
|
||||||
"(": "vim::Parentheses",
|
"(": "vim::Parentheses",
|
||||||
")": "vim::Parentheses",
|
")": "vim::Parentheses",
|
||||||
|
"b": "vim::Parentheses",
|
||||||
"[": "vim::SquareBrackets",
|
"[": "vim::SquareBrackets",
|
||||||
"]": "vim::SquareBrackets",
|
"]": "vim::SquareBrackets",
|
||||||
"{": "vim::CurlyBrackets",
|
"{": "vim::CurlyBrackets",
|
||||||
"}": "vim::CurlyBrackets",
|
"}": "vim::CurlyBrackets",
|
||||||
|
"shift-b": "vim::CurlyBrackets",
|
||||||
"<": "vim::AngleBrackets",
|
"<": "vim::AngleBrackets",
|
||||||
">": "vim::AngleBrackets"
|
">": "vim::AngleBrackets"
|
||||||
}
|
}
|
||||||
|
@ -548,22 +438,10 @@
|
||||||
"shift-i": "vim::InsertBefore",
|
"shift-i": "vim::InsertBefore",
|
||||||
"shift-a": "vim::InsertAfter",
|
"shift-a": "vim::InsertAfter",
|
||||||
"shift-j": "vim::JoinLines",
|
"shift-j": "vim::JoinLines",
|
||||||
"r": [
|
"r": ["vim::PushOperator", "Replace"],
|
||||||
"vim::PushOperator",
|
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||||
"Replace"
|
"escape": ["vim::SwitchMode", "Normal"],
|
||||||
],
|
"ctrl+[": ["vim::SwitchMode", "Normal"],
|
||||||
"ctrl-c": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Normal"
|
|
||||||
],
|
|
||||||
"escape": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Normal"
|
|
||||||
],
|
|
||||||
"ctrl+[": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Normal"
|
|
||||||
],
|
|
||||||
">": "editor::Indent",
|
">": "editor::Indent",
|
||||||
"<": "editor::Outdent",
|
"<": "editor::Outdent",
|
||||||
"i": [
|
"i": [
|
||||||
|
@ -602,14 +480,8 @@
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "vim::Tab",
|
"tab": "vim::Tab",
|
||||||
"enter": "vim::Enter",
|
"enter": "vim::Enter",
|
||||||
"escape": [
|
"escape": ["vim::SwitchMode", "Normal"],
|
||||||
"vim::SwitchMode",
|
"ctrl+[": ["vim::SwitchMode", "Normal"]
|
||||||
"Normal"
|
|
||||||
],
|
|
||||||
"ctrl+[": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Normal"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -369,6 +369,30 @@ pub fn find_boundary(
|
||||||
map.clip_point(offset.to_display_point(map), Bias::Right)
|
map.clip_point(offset.to_display_point(map), Bias::Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn chars_after(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
mut offset: usize,
|
||||||
|
) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
|
||||||
|
map.buffer_snapshot.chars_at(offset).map(move |ch| {
|
||||||
|
let before = offset;
|
||||||
|
offset = offset + ch.len_utf8();
|
||||||
|
(ch, before..offset)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chars_before(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
mut offset: usize,
|
||||||
|
) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
|
||||||
|
map.buffer_snapshot
|
||||||
|
.reversed_chars_at(offset)
|
||||||
|
.map(move |ch| {
|
||||||
|
let after = offset;
|
||||||
|
offset = offset - ch.len_utf8();
|
||||||
|
(ch, offset..after)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
|
pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
|
||||||
let raw_point = point.to_point(map);
|
let raw_point = point.to_point(map);
|
||||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||||
|
|
|
@ -40,6 +40,7 @@ pub enum Motion {
|
||||||
NextLineStart,
|
NextLineStart,
|
||||||
StartOfLineDownward,
|
StartOfLineDownward,
|
||||||
EndOfLineDownward,
|
EndOfLineDownward,
|
||||||
|
GoToColumn,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
|
@ -119,6 +120,7 @@ actions!(
|
||||||
NextLineStart,
|
NextLineStart,
|
||||||
StartOfLineDownward,
|
StartOfLineDownward,
|
||||||
EndOfLineDownward,
|
EndOfLineDownward,
|
||||||
|
GoToColumn,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
|
@ -215,6 +217,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
|
cx.add_action(|_: &mut Workspace, &EndOfLineDownward, cx: _| {
|
||||||
motion(Motion::EndOfLineDownward, cx)
|
motion(Motion::EndOfLineDownward, cx)
|
||||||
});
|
});
|
||||||
|
cx.add_action(|_: &mut Workspace, &GoToColumn, cx: _| motion(Motion::GoToColumn, cx));
|
||||||
cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
|
cx.add_action(|_: &mut Workspace, action: &RepeatFind, cx: _| {
|
||||||
repeat_motion(action.backwards, cx)
|
repeat_motion(action.backwards, cx)
|
||||||
})
|
})
|
||||||
|
@ -292,6 +295,7 @@ impl Motion {
|
||||||
| Right
|
| Right
|
||||||
| StartOfLine { .. }
|
| StartOfLine { .. }
|
||||||
| EndOfLineDownward
|
| EndOfLineDownward
|
||||||
|
| GoToColumn
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
|
@ -317,6 +321,7 @@ impl Motion {
|
||||||
| EndOfParagraph
|
| EndOfParagraph
|
||||||
| StartOfLineDownward
|
| StartOfLineDownward
|
||||||
| EndOfLineDownward
|
| EndOfLineDownward
|
||||||
|
| GoToColumn
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
|
@ -346,6 +351,7 @@ impl Motion {
|
||||||
| StartOfLineDownward
|
| StartOfLineDownward
|
||||||
| StartOfParagraph
|
| StartOfParagraph
|
||||||
| EndOfParagraph
|
| EndOfParagraph
|
||||||
|
| GoToColumn
|
||||||
| NextWordStart { .. }
|
| NextWordStart { .. }
|
||||||
| PreviousWordStart { .. }
|
| PreviousWordStart { .. }
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
|
@ -429,6 +435,7 @@ impl Motion {
|
||||||
NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
|
NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
|
||||||
StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None),
|
StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None),
|
||||||
EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None),
|
EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None),
|
||||||
|
GoToColumn => (go_to_column(map, point, times), SelectionGoal::None),
|
||||||
};
|
};
|
||||||
|
|
||||||
(new_point != point || infallible).then_some((new_point, goal))
|
(new_point != point || infallible).then_some((new_point, goal))
|
||||||
|
@ -919,6 +926,11 @@ fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) ->
|
||||||
first_non_whitespace(map, false, correct_line)
|
first_non_whitespace(map, false, correct_line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn go_to_column(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||||
|
let correct_line = start_of_relative_buffer_row(map, point, 0);
|
||||||
|
right(map, correct_line, times.saturating_sub(1))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn next_line_end(
|
pub(crate) fn next_line_end(
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
mut point: DisplayPoint,
|
mut point: DisplayPoint,
|
||||||
|
|
|
@ -193,10 +193,10 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_delete_e(cx: &mut gpui::TestAppContext) {
|
async fn test_delete_next_word_end(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "e"]);
|
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["d", "e"]);
|
||||||
cx.assert("Teˇst Test").await;
|
// cx.assert("Teˇst Test").await;
|
||||||
cx.assert("Tˇest test").await;
|
// cx.assert("Tˇest test").await;
|
||||||
cx.assert(indoc! {"
|
cx.assert(indoc! {"
|
||||||
Test teˇst
|
Test teˇst
|
||||||
test"})
|
test"})
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::ops::Range;
|
||||||
|
|
||||||
use editor::{
|
use editor::{
|
||||||
char_kind,
|
char_kind,
|
||||||
display_map::DisplaySnapshot,
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
movement::{self, FindRange},
|
movement::{self, FindRange},
|
||||||
Bias, CharKind, DisplayPoint,
|
Bias, CharKind, DisplayPoint,
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,7 @@ pub enum Object {
|
||||||
Quotes,
|
Quotes,
|
||||||
BackQuotes,
|
BackQuotes,
|
||||||
DoubleQuotes,
|
DoubleQuotes,
|
||||||
|
VerticalBars,
|
||||||
Parentheses,
|
Parentheses,
|
||||||
SquareBrackets,
|
SquareBrackets,
|
||||||
CurlyBrackets,
|
CurlyBrackets,
|
||||||
|
@ -40,6 +41,7 @@ actions!(
|
||||||
Quotes,
|
Quotes,
|
||||||
BackQuotes,
|
BackQuotes,
|
||||||
DoubleQuotes,
|
DoubleQuotes,
|
||||||
|
VerticalBars,
|
||||||
Parentheses,
|
Parentheses,
|
||||||
SquareBrackets,
|
SquareBrackets,
|
||||||
CurlyBrackets,
|
CurlyBrackets,
|
||||||
|
@ -64,6 +66,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
});
|
});
|
||||||
cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx));
|
cx.add_action(|_: &mut Workspace, _: &CurlyBrackets, cx: _| object(Object::CurlyBrackets, cx));
|
||||||
cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
|
cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
|
||||||
|
cx.add_action(|_: &mut Workspace, _: &VerticalBars, cx: _| object(Object::VerticalBars, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn object(object: Object, cx: &mut WindowContext) {
|
fn object(object: Object, cx: &mut WindowContext) {
|
||||||
|
@ -79,9 +82,11 @@ fn object(object: Object, cx: &mut WindowContext) {
|
||||||
impl Object {
|
impl Object {
|
||||||
pub fn is_multiline(self) -> bool {
|
pub fn is_multiline(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Object::Word { .. } | Object::Quotes | Object::BackQuotes | Object::DoubleQuotes => {
|
Object::Word { .. }
|
||||||
false
|
| Object::Quotes
|
||||||
}
|
| Object::BackQuotes
|
||||||
|
| Object::VerticalBars
|
||||||
|
| Object::DoubleQuotes => false,
|
||||||
Object::Sentence
|
Object::Sentence
|
||||||
| Object::Parentheses
|
| Object::Parentheses
|
||||||
| Object::AngleBrackets
|
| Object::AngleBrackets
|
||||||
|
@ -96,6 +101,7 @@ impl Object {
|
||||||
Object::Quotes
|
Object::Quotes
|
||||||
| Object::BackQuotes
|
| Object::BackQuotes
|
||||||
| Object::DoubleQuotes
|
| Object::DoubleQuotes
|
||||||
|
| Object::VerticalBars
|
||||||
| Object::Parentheses
|
| Object::Parentheses
|
||||||
| Object::SquareBrackets
|
| Object::SquareBrackets
|
||||||
| Object::CurlyBrackets
|
| Object::CurlyBrackets
|
||||||
|
@ -111,6 +117,7 @@ impl Object {
|
||||||
| Object::Quotes
|
| Object::Quotes
|
||||||
| Object::BackQuotes
|
| Object::BackQuotes
|
||||||
| Object::DoubleQuotes
|
| Object::DoubleQuotes
|
||||||
|
| Object::VerticalBars
|
||||||
| Object::Parentheses
|
| Object::Parentheses
|
||||||
| Object::SquareBrackets
|
| Object::SquareBrackets
|
||||||
| Object::CurlyBrackets
|
| Object::CurlyBrackets
|
||||||
|
@ -142,6 +149,9 @@ impl Object {
|
||||||
Object::DoubleQuotes => {
|
Object::DoubleQuotes => {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '"', '"')
|
||||||
}
|
}
|
||||||
|
Object::VerticalBars => {
|
||||||
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '|', '|')
|
||||||
|
}
|
||||||
Object::Parentheses => {
|
Object::Parentheses => {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
|
||||||
}
|
}
|
||||||
|
@ -427,110 +437,151 @@ fn surrounding_markers(
|
||||||
relative_to: DisplayPoint,
|
relative_to: DisplayPoint,
|
||||||
around: bool,
|
around: bool,
|
||||||
search_across_lines: bool,
|
search_across_lines: bool,
|
||||||
start_marker: char,
|
open_marker: char,
|
||||||
end_marker: char,
|
close_marker: char,
|
||||||
) -> Option<Range<DisplayPoint>> {
|
) -> Option<Range<DisplayPoint>> {
|
||||||
let mut matched_ends = 0;
|
let point = relative_to.to_offset(map, Bias::Left);
|
||||||
let mut start = None;
|
|
||||||
for (char, mut point) in map.reverse_chars_at(relative_to) {
|
let mut matched_closes = 0;
|
||||||
if char == start_marker {
|
let mut opening = None;
|
||||||
if matched_ends > 0 {
|
|
||||||
matched_ends -= 1;
|
if let Some((ch, range)) = movement::chars_after(map, point).next() {
|
||||||
} else {
|
if ch == open_marker {
|
||||||
if around {
|
if open_marker == close_marker {
|
||||||
start = Some(point)
|
let mut total = 0;
|
||||||
} else {
|
for (ch, _) in movement::chars_before(map, point) {
|
||||||
*point.column_mut() += char.len_utf8() as u32;
|
if ch == '\n' {
|
||||||
start = Some(point)
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if char == end_marker {
|
if ch == open_marker {
|
||||||
matched_ends += 1;
|
total += 1;
|
||||||
} else if char == '\n' && !search_across_lines {
|
}
|
||||||
break;
|
}
|
||||||
|
if total % 2 == 0 {
|
||||||
|
opening = Some(range)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opening = Some(range)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut matched_starts = 0;
|
if opening.is_none() {
|
||||||
let mut end = None;
|
for (ch, range) in movement::chars_before(map, point) {
|
||||||
for (char, mut point) in map.chars_at(relative_to) {
|
if ch == '\n' && !search_across_lines {
|
||||||
if char == end_marker {
|
|
||||||
if start.is_none() {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matched_starts > 0 {
|
if ch == open_marker {
|
||||||
matched_starts -= 1;
|
if matched_closes == 0 {
|
||||||
} else {
|
opening = Some(range);
|
||||||
if around {
|
break;
|
||||||
*point.column_mut() += char.len_utf8() as u32;
|
}
|
||||||
end = Some(point);
|
matched_closes -= 1;
|
||||||
} else {
|
} else if ch == close_marker {
|
||||||
end = Some(point);
|
matched_closes += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opening.is_none() {
|
||||||
|
for (ch, range) in movement::chars_after(map, point) {
|
||||||
|
if ch == open_marker {
|
||||||
|
opening = Some(range);
|
||||||
|
break;
|
||||||
|
} else if ch == close_marker {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == start_marker {
|
|
||||||
if start.is_none() {
|
|
||||||
if around {
|
|
||||||
start = Some(point);
|
|
||||||
} else {
|
|
||||||
*point.column_mut() += char.len_utf8() as u32;
|
|
||||||
start = Some(point);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matched_starts += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == '\n' && !search_across_lines {
|
let Some(mut opening) = opening else {
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (Some(mut start), Some(mut end)) = (start, end) else {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !around {
|
let mut matched_opens = 0;
|
||||||
// if a block starts with a newline, move the start to after the newline.
|
let mut closing = None;
|
||||||
let mut was_newline = false;
|
|
||||||
for (char, point) in map.chars_at(start) {
|
for (ch, range) in movement::chars_after(map, opening.end) {
|
||||||
if was_newline {
|
if ch == '\n' && !search_across_lines {
|
||||||
start = point;
|
|
||||||
} else if char == '\n' {
|
|
||||||
was_newline = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// if a block ends with a newline, then whitespace, then the delimeter,
|
|
||||||
// move the end to after the newline.
|
if ch == close_marker {
|
||||||
let mut new_end = end;
|
if matched_opens == 0 {
|
||||||
for (char, point) in map.reverse_chars_at(end) {
|
closing = Some(range);
|
||||||
if char == '\n' {
|
|
||||||
end = new_end;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if !char.is_whitespace() {
|
matched_opens -= 1;
|
||||||
break;
|
} else if ch == open_marker {
|
||||||
}
|
matched_opens += 1;
|
||||||
new_end = point
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(start..end)
|
let Some(mut closing) = closing else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if around && !search_across_lines {
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
for (ch, range) in movement::chars_after(map, closing.end) {
|
||||||
|
if ch.is_whitespace() && ch != '\n' {
|
||||||
|
found = true;
|
||||||
|
closing.end = range.end;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
for (ch, range) in movement::chars_before(map, opening.start) {
|
||||||
|
if ch.is_whitespace() && ch != '\n' {
|
||||||
|
opening.start = range.start
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !around && search_across_lines {
|
||||||
|
if let Some((ch, range)) = movement::chars_after(map, opening.end).next() {
|
||||||
|
if ch == '\n' {
|
||||||
|
opening.end = range.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ch, range) in movement::chars_before(map, closing.start) {
|
||||||
|
if !ch.is_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ch != '\n' {
|
||||||
|
closing.start = range.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if around {
|
||||||
|
opening.start..closing.end
|
||||||
|
} else {
|
||||||
|
opening.end..closing.start
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(
|
||||||
|
map.clip_point(result.start.to_display_point(map), Bias::Left)
|
||||||
|
..map.clip_point(result.end.to_display_point(map), Bias::Right),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
use crate::test::{ExemptionFeatures, NeovimBackedTestContext};
|
use crate::{
|
||||||
|
state::Mode,
|
||||||
|
test::{ExemptionFeatures, NeovimBackedTestContext, VimTestContext},
|
||||||
|
};
|
||||||
|
|
||||||
const WORD_LOCATIONS: &'static str = indoc! {"
|
const WORD_LOCATIONS: &'static str = indoc! {"
|
||||||
The quick ˇbrowˇnˇ•••
|
The quick ˇbrowˇnˇ•••
|
||||||
|
@ -765,13 +816,6 @@ mod test {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
for (start, end) in SURROUNDING_OBJECTS {
|
for (start, end) in SURROUNDING_OBJECTS {
|
||||||
if ((start == &'\'' || start == &'`' || start == &'"')
|
|
||||||
&& !ExemptionFeatures::QuotesSeekForward.supported())
|
|
||||||
|| (start == &'<' && !ExemptionFeatures::AngleBracketsFreezeNeovim.supported())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let marked_string = SURROUNDING_MARKER_STRING
|
let marked_string = SURROUNDING_MARKER_STRING
|
||||||
.replace('`', &start.to_string())
|
.replace('`', &start.to_string())
|
||||||
.replace('\'', &end.to_string());
|
.replace('\'', &end.to_string());
|
||||||
|
@ -786,6 +830,63 @@ mod test {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_singleline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
cx.set_shared_wrap(12).await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"helˇlo \"world\"!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
|
||||||
|
cx.assert_shared_state(indoc! {
|
||||||
|
"hello \"«worldˇ»\"!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"hello \"wˇorld\"!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
|
||||||
|
cx.assert_shared_state(indoc! {
|
||||||
|
"hello \"«worldˇ»\"!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"hello \"wˇorld\"!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
|
||||||
|
cx.assert_shared_state(indoc! {
|
||||||
|
"hello« \"world\"ˇ»!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"hello \"wˇorld\" !"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
|
||||||
|
cx.assert_shared_state(indoc! {
|
||||||
|
"hello «\"world\" ˇ»!"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"hello \"wˇorld\"•
|
||||||
|
goodbye"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "a", "\""]).await;
|
||||||
|
cx.assert_shared_state(indoc! {
|
||||||
|
"hello «\"world\" ˇ»
|
||||||
|
goodbye"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
async fn test_multiline_surrounding_character_objects(cx: &mut gpui::TestAppContext) {
|
||||||
|
@ -827,6 +928,66 @@ mod test {
|
||||||
return false
|
return false
|
||||||
}"})
|
}"})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {
|
||||||
|
"func empty(a string) bool {
|
||||||
|
if a == \"\" ˇ{
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}"
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes(["v", "i", "{"]).await;
|
||||||
|
cx.assert_shared_state(indoc! {"
|
||||||
|
func empty(a string) bool {
|
||||||
|
if a == \"\" {
|
||||||
|
« return true
|
||||||
|
ˇ» }
|
||||||
|
return false
|
||||||
|
}"})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = VimTestContext::new(cx, true).await;
|
||||||
|
cx.set_state(
|
||||||
|
indoc! {"
|
||||||
|
fn boop() {
|
||||||
|
baz(ˇ|a, b| { bar(|j, k| { })})
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes(["c", "i", "|"]);
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {"
|
||||||
|
fn boop() {
|
||||||
|
baz(|ˇ| { bar(|j, k| { })})
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Insert,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes(["escape", "1", "8", "|"]);
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {"
|
||||||
|
fn boop() {
|
||||||
|
baz(|| { bar(ˇ|j, k| { })})
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Normal,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.simulate_keystrokes(["v", "a", "|"]);
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {"
|
||||||
|
fn boop() {
|
||||||
|
baz(|| { bar(«|j, k| ˇ»{ })})
|
||||||
|
}"
|
||||||
|
},
|
||||||
|
Mode::Visual,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -834,12 +995,6 @@ mod test {
|
||||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
for (start, end) in SURROUNDING_OBJECTS {
|
for (start, end) in SURROUNDING_OBJECTS {
|
||||||
if ((start == &'\'' || start == &'`' || start == &'"')
|
|
||||||
&& !ExemptionFeatures::QuotesSeekForward.supported())
|
|
||||||
|| (start == &'<' && !ExemptionFeatures::AngleBracketsFreezeNeovim.supported())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let marked_string = SURROUNDING_MARKER_STRING
|
let marked_string = SURROUNDING_MARKER_STRING
|
||||||
.replace('`', &start.to_string())
|
.replace('`', &start.to_string())
|
||||||
.replace('\'', &end.to_string());
|
.replace('\'', &end.to_string());
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
use editor::scroll::VERTICAL_SCROLL_MARGIN;
|
use editor::scroll::VERTICAL_SCROLL_MARGIN;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::ops::{Deref, DerefMut, Range};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use gpui::{geometry::vector::vec2f, ContextHandle};
|
use gpui::{geometry::vector::vec2f, ContextHandle};
|
||||||
use language::{
|
use language::language_settings::{AllLanguageSettings, SoftWrap};
|
||||||
language_settings::{AllLanguageSettings, SoftWrap},
|
use util::test::marked_text_offsets;
|
||||||
OffsetRangeExt,
|
|
||||||
};
|
|
||||||
use util::test::{generate_marked_text, marked_text_offsets};
|
|
||||||
|
|
||||||
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
|
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
|
||||||
use crate::state::Mode;
|
use crate::state::Mode;
|
||||||
|
@ -37,10 +34,6 @@ pub enum ExemptionFeatures {
|
||||||
AroundSentenceStartingBetweenIncludesWrongWhitespace,
|
AroundSentenceStartingBetweenIncludesWrongWhitespace,
|
||||||
// Non empty selection with text objects in visual mode
|
// Non empty selection with text objects in visual mode
|
||||||
NonEmptyVisualTextObjects,
|
NonEmptyVisualTextObjects,
|
||||||
// Quote style surrounding text objects don't seek forward properly
|
|
||||||
QuotesSeekForward,
|
|
||||||
// Neovim freezes up for some reason with angle brackets
|
|
||||||
AngleBracketsFreezeNeovim,
|
|
||||||
// Sentence Doesn't backtrack when its at the end of the file
|
// Sentence Doesn't backtrack when its at the end of the file
|
||||||
SentenceAfterPunctuationAtEndOfFile,
|
SentenceAfterPunctuationAtEndOfFile,
|
||||||
}
|
}
|
||||||
|
@ -250,25 +243,13 @@ impl<'a> NeovimBackedTestContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn neovim_state(&mut self) -> String {
|
pub async fn neovim_state(&mut self) -> String {
|
||||||
generate_marked_text(
|
self.neovim.marked_text().await
|
||||||
self.neovim.text().await.as_str(),
|
|
||||||
&self.neovim_selections().await[..],
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn neovim_mode(&mut self) -> Mode {
|
pub async fn neovim_mode(&mut self) -> Mode {
|
||||||
self.neovim.mode().await.unwrap()
|
self.neovim.mode().await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
pub async fn assert_state_matches(&mut self) {
|
||||||
self.is_dirty = false;
|
self.is_dirty = false;
|
||||||
let neovim = self.neovim_state().await;
|
let neovim = self.neovim_state().await;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Range},
|
||||||
};
|
};
|
||||||
use std::{ops::Range, path::PathBuf};
|
|
||||||
|
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
use async_compat::Compat;
|
use async_compat::Compat;
|
||||||
|
@ -12,6 +12,7 @@ use async_trait::async_trait;
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
use gpui::keymap_matcher::Keystroke;
|
use gpui::keymap_matcher::Keystroke;
|
||||||
|
|
||||||
|
#[cfg(feature = "neovim")]
|
||||||
use language::Point;
|
use language::Point;
|
||||||
|
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
|
@ -109,7 +110,12 @@ impl NeovimConnection {
|
||||||
// Sends a keystroke to the neovim process.
|
// Sends a keystroke to the neovim process.
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
pub async fn send_keystroke(&mut self, keystroke_text: &str) {
|
pub async fn send_keystroke(&mut self, keystroke_text: &str) {
|
||||||
let keystroke = Keystroke::parse(keystroke_text).unwrap();
|
let mut keystroke = Keystroke::parse(keystroke_text).unwrap();
|
||||||
|
|
||||||
|
if keystroke.key == "<" {
|
||||||
|
keystroke.key = "lt".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
let special = keystroke.shift
|
let special = keystroke.shift
|
||||||
|| keystroke.ctrl
|
|| keystroke.ctrl
|
||||||
|| keystroke.alt
|
|| keystroke.alt
|
||||||
|
@ -296,7 +302,7 @@ impl NeovimConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "neovim")]
|
#[cfg(feature = "neovim")]
|
||||||
pub async fn state(&mut self) -> (Option<Mode>, String, Vec<Range<Point>>) {
|
pub async fn state(&mut self) -> (Option<Mode>, String) {
|
||||||
let nvim_buffer = self
|
let nvim_buffer = self
|
||||||
.nvim
|
.nvim
|
||||||
.get_current_buf()
|
.get_current_buf()
|
||||||
|
@ -405,37 +411,33 @@ impl NeovimConnection {
|
||||||
.push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
|
.push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ranges = encode_ranges(&text, &selections);
|
||||||
let state = NeovimData::Get {
|
let state = NeovimData::Get {
|
||||||
mode,
|
mode,
|
||||||
state: encode_ranges(&text, &selections),
|
state: ranges.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.data.back() != Some(&state) {
|
if self.data.back() != Some(&state) {
|
||||||
self.data.push_back(state.clone());
|
self.data.push_back(state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
(mode, text, selections)
|
(mode, ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "neovim"))]
|
#[cfg(not(feature = "neovim"))]
|
||||||
pub async fn state(&mut self) -> (Option<Mode>, String, Vec<Range<Point>>) {
|
pub async fn state(&mut self) -> (Option<Mode>, String) {
|
||||||
if let Some(NeovimData::Get { state: text, mode }) = self.data.front() {
|
if let Some(NeovimData::Get { state: raw, mode }) = self.data.front() {
|
||||||
let (text, ranges) = parse_state(text);
|
(*mode, raw.to_string())
|
||||||
(*mode, text, ranges)
|
|
||||||
} else {
|
} else {
|
||||||
panic!("operation does not match recorded script. re-record with --features=neovim");
|
panic!("operation does not match recorded script. re-record with --features=neovim");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn selections(&mut self) -> Vec<Range<Point>> {
|
|
||||||
self.state().await.2
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn mode(&mut self) -> Option<Mode> {
|
pub async fn mode(&mut self) -> Option<Mode> {
|
||||||
self.state().await.0
|
self.state().await.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn text(&mut self) -> String {
|
pub async fn marked_text(&mut self) -> String {
|
||||||
self.state().await.1
|
self.state().await.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,6 +529,7 @@ impl Handler for NvimHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "neovim")]
|
||||||
fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) {
|
fn parse_state(marked_text: &str) -> (String, Vec<Range<Point>>) {
|
||||||
let (text, ranges) = util::test::marked_text_ranges(marked_text, true);
|
let (text, ranges) = util::test::marked_text_ranges(marked_text, true);
|
||||||
let point_ranges = ranges
|
let point_ranges = ranges
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::{cmp, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
|
@ -263,21 +263,13 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
|
||||||
|
|
||||||
if let Some(range) = object.range(map, head, around) {
|
if let Some(range) = object.range(map, head, around) {
|
||||||
if !range.is_empty() {
|
if !range.is_empty() {
|
||||||
let expand_both_ways =
|
let expand_both_ways = object.always_expands_both_ways()
|
||||||
if object.always_expands_both_ways() || selection.is_empty() {
|
|| selection.is_empty()
|
||||||
true
|
|| movement::right(map, selection.start) == selection.end;
|
||||||
// contains only one character
|
|
||||||
} else if let Some((_, start)) =
|
|
||||||
map.reverse_chars_at(selection.end).next()
|
|
||||||
{
|
|
||||||
selection.start == start
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if expand_both_ways {
|
if expand_both_ways {
|
||||||
selection.start = cmp::min(selection.start, range.start);
|
selection.start = range.start;
|
||||||
selection.end = cmp::max(selection.end, range.end);
|
selection.end = range.end;
|
||||||
} else if selection.reversed {
|
} else if selection.reversed {
|
||||||
selection.start = range.start;
|
selection.start = range.start;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
[{"Text":"The quick\n\nbrown fox jumps\nover the lazy dog"},{"Mode":"Normal"},{"Selection":{"start":[3,5],"end":[3,5]}}]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +1,3 @@
|
||||||
{"Put":{"state":"Teˇst Test"}}
|
|
||||||
{"Key":"d"}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"Teˇ Test","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"Tˇest test"}}
|
|
||||||
{"Key":"d"}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"Tˇ test","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"Test teˇst\ntest"}}
|
{"Put":{"state":"Test teˇst\ntest"}}
|
||||||
{"Key":"d"}
|
{"Key":"d"}
|
||||||
{"Key":"e"}
|
{"Key":"e"}
|
File diff suppressed because it is too large
Load diff
|
@ -1,32 +0,0 @@
|
||||||
{"Put":{"state":"Thˇe quick-brown\n\n\nfox_jumps over\nthe"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quicˇk-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumpˇs over\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumps oveˇr\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
|
|
||||||
{"Key":"e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"Thˇe quick-brown\n\n\nfox_jumps over\nthe"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"The quicˇk-brown\n\n\nfox_jumps over\nthe"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
|
|
||||||
{"Put":{"state":"The quickˇ-brown\n\n\nfox_jumps over\nthe"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-browˇn\n\n\nfox_jumps over\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumpˇs over\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumps oveˇr\nthe","mode":"Normal"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
|
|
||||||
{"Key":"shift-e"}
|
|
||||||
{"Get":{"state":"The quick-brown\n\n\nfox_jumps over\nthˇe","mode":"Normal"}}
|
|
|
@ -8,3 +8,8 @@
|
||||||
{"Key":"i"}
|
{"Key":"i"}
|
||||||
{"Key":"{"}
|
{"Key":"{"}
|
||||||
{"Get":{"state":"func empty(a string) bool {\n if a == \"\" {\n« return true\nˇ» }\n return false\n}","mode":"Visual"}}
|
{"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"}}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{"SetOption":{"value":"wrap"}}
|
||||||
|
{"SetOption":{"value":"columns=12"}}
|
||||||
|
{"Put":{"state":"helˇlo \"world\"!"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"\""}
|
||||||
|
{"Get":{"state":"hello \"«worldˇ»\"!","mode":"Visual"}}
|
||||||
|
{"Put":{"state":"hello \"wˇorld\"!"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"i"}
|
||||||
|
{"Key":"\""}
|
||||||
|
{"Get":{"state":"hello \"«worldˇ»\"!","mode":"Visual"}}
|
||||||
|
{"Put":{"state":"hello \"wˇorld\"!"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"\""}
|
||||||
|
{"Get":{"state":"hello« \"world\"ˇ»!","mode":"Visual"}}
|
||||||
|
{"Put":{"state":"hello \"wˇorld\" !"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"\""}
|
||||||
|
{"Get":{"state":"hello «\"world\" ˇ»!","mode":"Visual"}}
|
||||||
|
{"Put":{"state":"hello \"wˇorld\"•\ngoodbye"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"a"}
|
||||||
|
{"Key":"\""}
|
||||||
|
{"Get":{"state":"hello «\"world\" ˇ»\ngoodbye","mode":"Visual"}}
|
|
@ -1,26 +0,0 @@
|
||||||
{"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"}}
|
|
Loading…
Add table
Add a link
Reference in a new issue