Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 10 additions & 76 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2243,107 +2243,41 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext)
});
cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));

cx.set_state(
&r#"藝one
two

three
four藝
five

six"#
.unindent(),
);
// The third line only contains a single space so we can later assert that the
// editor's paragraph movement considers a non-blank line as a paragraph
// boundary.
cx.set_state(&"藝one\ntwo\n \nthree\nfour藝\nfive\n\nsix");

cx.update_editor(|editor, window, cx| {
editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
});
cx.assert_editor_state(
&r#"one
two
three
four
five
six"#
.unindent(),
);
cx.assert_editor_state(&"one\ntwo\n藝 \nthree\nfour\nfive\n藝\nsix");

cx.update_editor(|editor, window, cx| {
editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
});
cx.assert_editor_state(
&r#"one
two

three
four
five
six藝"#
.unindent(),
);
cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\n藝\nsix藝");

cx.update_editor(|editor, window, cx| {
editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
});
cx.assert_editor_state(
&r#"one
two

three
four
five

six藝"#
.unindent(),
);
cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\n\nsix藝");

cx.update_editor(|editor, window, cx| {
editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
});
cx.assert_editor_state(
&r#"one
two

three
four
five
six"#
.unindent(),
);
cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\n藝\nsix");

cx.update_editor(|editor, window, cx| {
editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
});
cx.assert_editor_state(
&r#"one
two
three
four
five

six"#
.unindent(),
);
cx.assert_editor_state(&"one\ntwo\n藝 \nthree\nfour\nfive\n\nsix");

cx.update_editor(|editor, window, cx| {
editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
});
cx.assert_editor_state(
&r#"藝one
two

three
four
five

six"#
.unindent(),
);
cx.assert_editor_state(&"藝one\ntwo\n \nthree\nfour\nfive\n\nsix");
}

#[gpui::test]
Expand Down
24 changes: 12 additions & 12 deletions crates/editor/src/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ fn is_subword_boundary_end(left: char, right: char, classifier: &CharClassifier)
}

/// Returns a position of the start of the current paragraph, where a paragraph
/// is defined as a run of non-empty lines.
/// is defined as a run of non-blank lines.
pub fn start_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
Expand All @@ -534,25 +534,25 @@ pub fn start_of_paragraph(
return DisplayPoint::zero();
}

let mut found_non_empty_line = false;
let mut found_non_blank_line = false;
for row in (0..point.row + 1).rev() {
let empty = map.buffer_snapshot().line_len(MultiBufferRow(row)) == 0;
if found_non_empty_line && empty {
let blank = map.buffer_snapshot().is_line_blank(MultiBufferRow(row));
if found_non_blank_line && blank {
if count <= 1 {
return Point::new(row, 0).to_display_point(map);
}
count -= 1;
found_non_empty_line = false;
found_non_blank_line = false;
}

found_non_empty_line |= !empty;
found_non_blank_line |= !blank;
}

DisplayPoint::zero()
}

/// Returns a position of the end of the current paragraph, where a paragraph
/// is defined as a run of non-empty lines.
/// is defined as a run of non-blank lines.
pub fn end_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
Expand All @@ -563,18 +563,18 @@ pub fn end_of_paragraph(
return map.max_point();
}

let mut found_non_empty_line = false;
let mut found_non_blank_line = false;
for row in point.row..=map.buffer_snapshot().max_row().0 {
let empty = map.buffer_snapshot().line_len(MultiBufferRow(row)) == 0;
if found_non_empty_line && empty {
let blank = map.buffer_snapshot().is_line_blank(MultiBufferRow(row));
if found_non_blank_line && blank {
if count <= 1 {
return Point::new(row, 0).to_display_point(map);
}
count -= 1;
found_non_empty_line = false;
found_non_blank_line = false;
}

found_non_empty_line |= !empty;
found_non_blank_line |= !blank;
}

map.max_point()
Expand Down
69 changes: 64 additions & 5 deletions crates/vim/src/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,12 +1038,9 @@ impl Motion {
),
SentenceBackward => (sentence_backwards(map, point, times), SelectionGoal::None),
SentenceForward => (sentence_forwards(map, point, times), SelectionGoal::None),
StartOfParagraph => (
movement::start_of_paragraph(map, point, times),
SelectionGoal::None,
),
StartOfParagraph => (start_of_paragraph(map, point, times), SelectionGoal::None),
EndOfParagraph => (
map.clip_at_line_end(movement::end_of_paragraph(map, point, times)),
map.clip_at_line_end(end_of_paragraph(map, point, times)),
SelectionGoal::None,
),
CurrentLine => (next_line_end(map, point, times), SelectionGoal::None),
Expand Down Expand Up @@ -2238,6 +2235,68 @@ pub(crate) fn sentence_forwards(
map.max_point()
}

/// Returns a position of the start of the current paragraph for vim motions,
/// where a paragraph is defined as a run of non-empty lines. Lines containing
/// only whitespace are not considered empty and do not act as paragraph
/// boundaries.
pub(crate) fn start_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
mut count: usize,
) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == 0 {
return DisplayPoint::zero();
}

let mut found_non_empty_line = false;
for row in (0..point.row + 1).rev() {
let empty = map.buffer_snapshot().line_len(MultiBufferRow(row)) == 0;
if found_non_empty_line && empty {
if count <= 1 {
return Point::new(row, 0).to_display_point(map);
}
count -= 1;
found_non_empty_line = false;
}

found_non_empty_line |= !empty;
}

DisplayPoint::zero()
}

/// Returns a position of the end of the current paragraph for vim motions,
/// where a paragraph is defined as a run of non-empty lines. Lines containing
/// only whitespace are not considered empty and do not act as paragraph
/// boundaries.
pub(crate) fn end_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
mut count: usize,
) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == map.buffer_snapshot().max_row().0 {
return map.max_point();
}

let mut found_non_empty_line = false;
for row in point.row..=map.buffer_snapshot().max_row().0 {
let empty = map.buffer_snapshot().line_len(MultiBufferRow(row)) == 0;
if found_non_empty_line && empty {
if count <= 1 {
return Point::new(row, 0).to_display_point(map);
}
count -= 1;
found_non_empty_line = false;
}

found_non_empty_line |= !empty;
}

map.max_point()
}

fn next_non_blank(map: &DisplaySnapshot, start: MultiBufferOffset) -> MultiBufferOffset {
for (c, o) in map.buffer_chars_at(start) {
if c == '\n' || !c.is_whitespace() {
Expand Down