vim: Add }/{ for start/end of paragraph

Fixes: zed-industries/community#470
This commit is contained in:
Conrad Irwin 2023-06-29 22:45:54 -06:00
parent f6b64dc67a
commit 9ee2707d43
4 changed files with 149 additions and 9 deletions

View File

@ -37,6 +37,8 @@
"$": "vim::EndOfLine", "$": "vim::EndOfLine",
"shift-g": "vim::EndOfDocument", "shift-g": "vim::EndOfDocument",
"w": "vim::NextWordStart", "w": "vim::NextWordStart",
"{": "vim::StartOfParagraph",
"}": "vim::EndOfParagraph",
"shift-w": [ "shift-w": [
"vim::NextWordStart", "vim::NextWordStart",
{ {

View File

@ -5120,7 +5120,7 @@ impl Editor {
self.change_selections(Some(Autoscroll::fit()), cx, |s| { self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| { s.move_with(|map, selection| {
selection.collapse_to( selection.collapse_to(
movement::start_of_paragraph(map, selection.head()), movement::start_of_paragraph(map, selection.head(), 1),
SelectionGoal::None, SelectionGoal::None,
) )
}); });
@ -5140,7 +5140,7 @@ impl Editor {
self.change_selections(Some(Autoscroll::fit()), cx, |s| { self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| { s.move_with(|map, selection| {
selection.collapse_to( selection.collapse_to(
movement::end_of_paragraph(map, selection.head()), movement::end_of_paragraph(map, selection.head(), 1),
SelectionGoal::None, SelectionGoal::None,
) )
}); });
@ -5159,7 +5159,10 @@ impl Editor {
self.change_selections(Some(Autoscroll::fit()), cx, |s| { self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, _| { s.move_heads_with(|map, head, _| {
(movement::start_of_paragraph(map, head), SelectionGoal::None) (
movement::start_of_paragraph(map, head, 1),
SelectionGoal::None,
)
}); });
}) })
} }
@ -5176,7 +5179,10 @@ impl Editor {
self.change_selections(Some(Autoscroll::fit()), cx, |s| { self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, _| { s.move_heads_with(|map, head, _| {
(movement::end_of_paragraph(map, head), SelectionGoal::None) (
movement::end_of_paragraph(map, head, 1),
SelectionGoal::None,
)
}); });
}) })
} }

View File

@ -193,7 +193,11 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
}) })
} }
pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { pub fn start_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
mut count: usize,
) -> DisplayPoint {
let point = display_point.to_point(map); let point = display_point.to_point(map);
if point.row == 0 { if point.row == 0 {
return map.max_point(); return map.max_point();
@ -203,7 +207,11 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) ->
for row in (0..point.row + 1).rev() { for row in (0..point.row + 1).rev() {
let blank = map.buffer_snapshot.is_line_blank(row); let blank = map.buffer_snapshot.is_line_blank(row);
if found_non_blank_line && blank { if found_non_blank_line && blank {
return Point::new(row, 0).to_display_point(map); if count <= 1 {
return Point::new(row, 0).to_display_point(map);
}
count -= 1;
found_non_blank_line = false;
} }
found_non_blank_line |= !blank; found_non_blank_line |= !blank;
@ -212,7 +220,11 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) ->
DisplayPoint::zero() DisplayPoint::zero()
} }
pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { pub fn end_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
mut count: usize,
) -> DisplayPoint {
let point = display_point.to_point(map); let point = display_point.to_point(map);
if point.row == map.max_buffer_row() { if point.row == map.max_buffer_row() {
return DisplayPoint::zero(); return DisplayPoint::zero();
@ -222,7 +234,11 @@ pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> D
for row in point.row..map.max_buffer_row() + 1 { for row in point.row..map.max_buffer_row() + 1 {
let blank = map.buffer_snapshot.is_line_blank(row); let blank = map.buffer_snapshot.is_line_blank(row);
if found_non_blank_line && blank { if found_non_blank_line && blank {
return Point::new(row, 0).to_display_point(map); if count <= 1 {
return Point::new(row, 0).to_display_point(map);
}
count -= 1;
found_non_blank_line = false;
} }
found_non_blank_line |= !blank; found_non_blank_line |= !blank;

View File

@ -31,6 +31,8 @@ pub enum Motion {
CurrentLine, CurrentLine,
StartOfLine, StartOfLine,
EndOfLine, EndOfLine,
StartOfParagraph,
EndOfParagraph,
StartOfDocument, StartOfDocument,
EndOfDocument, EndOfDocument,
Matching, Matching,
@ -72,6 +74,8 @@ actions!(
StartOfLine, StartOfLine,
EndOfLine, EndOfLine,
CurrentLine, CurrentLine,
StartOfParagraph,
EndOfParagraph,
StartOfDocument, StartOfDocument,
EndOfDocument, EndOfDocument,
Matching, Matching,
@ -92,6 +96,12 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &StartOfLine, cx: _| motion(Motion::StartOfLine, 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, _: &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: _| {
motion(Motion::StartOfParagraph, cx)
});
cx.add_action(|_: &mut Workspace, _: &EndOfParagraph, cx: _| {
motion(Motion::EndOfParagraph, cx)
});
cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| { cx.add_action(|_: &mut Workspace, _: &StartOfDocument, cx: _| {
motion(Motion::StartOfDocument, cx) motion(Motion::StartOfDocument, cx)
}); });
@ -142,7 +152,8 @@ 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 => true, Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart
| StartOfParagraph | EndOfParagraph => true,
EndOfLine EndOfLine
| NextWordEnd { .. } | NextWordEnd { .. }
| Matching | Matching
@ -172,6 +183,8 @@ impl Motion {
| Backspace | Backspace
| Right | Right
| StartOfLine | StartOfLine
| StartOfParagraph
| EndOfParagraph
| NextWordStart { .. } | NextWordStart { .. }
| PreviousWordStart { .. } | PreviousWordStart { .. }
| FirstNonWhitespace | FirstNonWhitespace
@ -197,6 +210,8 @@ impl Motion {
| Backspace | Backspace
| Right | Right
| StartOfLine | StartOfLine
| StartOfParagraph
| EndOfParagraph
| NextWordStart { .. } | NextWordStart { .. }
| PreviousWordStart { .. } | PreviousWordStart { .. }
| FirstNonWhitespace | FirstNonWhitespace
@ -235,6 +250,14 @@ impl Motion {
FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None), FirstNonWhitespace => (first_non_whitespace(map, point), SelectionGoal::None),
StartOfLine => (start_of_line(map, point), SelectionGoal::None), StartOfLine => (start_of_line(map, point), SelectionGoal::None),
EndOfLine => (end_of_line(map, point), SelectionGoal::None), EndOfLine => (end_of_line(map, point), SelectionGoal::None),
StartOfParagraph => (
movement::start_of_paragraph(map, point, times),
SelectionGoal::None,
),
EndOfParagraph => (
movement::end_of_paragraph(map, point, times),
SelectionGoal::None,
),
CurrentLine => (end_of_line(map, point), SelectionGoal::None), CurrentLine => (end_of_line(map, point), SelectionGoal::None),
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None), StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
EndOfDocument => ( EndOfDocument => (
@ -590,3 +613,96 @@ 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());
map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left) map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left)
} }
#[cfg(test)]
mod test {
use crate::{state::Mode, test::VimTestContext};
use indoc::indoc;
#[gpui::test]
async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let initial_state = indoc! {r"ˇabc
def
paragraph
the second
third and
final"};
// goes down once
cx.set_state(initial_state, Mode::Normal);
cx.simulate_keystrokes(["}"]);
cx.assert_state(
indoc! {r"abc
def
ˇ
paragraph
the second
third and
final"},
Mode::Normal,
);
// goes up once
cx.simulate_keystrokes(["{"]);
cx.assert_state(initial_state, Mode::Normal);
// goes down twice
cx.simulate_keystrokes(["2", "}"]);
cx.assert_state(
indoc! {r"abc
def
paragraph
the second
ˇ
third and
final"},
Mode::Normal,
);
// goes down over multiple blanks
cx.simulate_keystrokes(["}"]);
cx.assert_state(
indoc! {r"abc
def
paragraph
the second
third and
finalˇ"},
Mode::Normal,
);
// goes up twice
cx.simulate_keystrokes(["2", "{"]);
cx.assert_state(
indoc! {r"abc
def
ˇ
paragraph
the second
third and
final"},
Mode::Normal,
)
}
}