vim: Ensure paragraph motions use empty and not blank lines (#47734)
The `}` and `{` paragraph motions now correctly treat only truly empty
lines (zero characters) as paragraph boundaries, matching vim's
documented behavior. Whitespace-only lines are no longer treated as
boundaries.
Changed `start_of_paragraph()` and `end_of_paragraph()` in
`editor/src/movement.rs` to check `line_len() == 0` instead of
`is_line_blank()`.
Note: This change does NOT affect the `ap`/`ip` text objects. Per vim's
`:help ap`, those DO treat whitespace-only lines as boundaries, which is
the existing (correct) behavior in `vim/src/object.rs`.
Closes #36171
Release Notes:
- Fixed vim mode paragraph motions (`}` and `{`) to correctly ignore
whitespace-only lines
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
1 parent
22a9324d89
commit
df3db19d0b
3 files changed
+40
-12
No files matched your search
@@ -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-blank lines.
|
||||
/// is defined as a run of non-empty lines.
|
||||
pub fn start_of_paragraph(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
@@ -534,25 +534,25 @@ pub fn start_of_paragraph(
|
||||
return DisplayPoint::zero();
|
||||
}
|
||||
|
||||
let mut found_non_blank_line = false;
|
||||
let mut found_non_empty_line = false;
|
||||
for row in (0..point.row + 1).rev() {
|
||||
let blank = map.buffer_snapshot().is_line_blank(MultiBufferRow(row));
|
||||
if found_non_blank_line && blank {
|
||||
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_blank_line = false;
|
||||
found_non_empty_line = false;
|
||||
}
|
||||
|
||||
found_non_blank_line |= !blank;
|
||||
found_non_empty_line |= !empty;
|
||||
}
|
||||
|
||||
DisplayPoint::zero()
|
||||
}
|
||||
|
||||
/// Returns a position of the end of the current paragraph, where a paragraph
|
||||
/// is defined as a run of non-blank lines.
|
||||
/// is defined as a run of non-empty lines.
|
||||
pub fn end_of_paragraph(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
@@ -563,18 +563,18 @@ pub fn end_of_paragraph(
|
||||
return map.max_point();
|
||||
}
|
||||
|
||||
let mut found_non_blank_line = false;
|
||||
let mut found_non_empty_line = false;
|
||||
for row in point.row..=map.buffer_snapshot().max_row().0 {
|
||||
let blank = map.buffer_snapshot().is_line_blank(MultiBufferRow(row));
|
||||
if found_non_blank_line && blank {
|
||||
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_blank_line = false;
|
||||
found_non_empty_line = false;
|
||||
}
|
||||
|
||||
found_non_blank_line |= !blank;
|
||||
found_non_empty_line |= !empty;
|
||||
}
|
||||
|
||||
map.max_point()
|
||||
|
||||
@@ -3292,6 +3292,29 @@ mod test {
|
||||
final"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_paragraph_motion_with_whitespace_lines(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
// Test that whitespace-only lines are NOT treated as paragraph boundaries
|
||||
// Per vim's :help paragraph - only truly empty lines are boundaries
|
||||
// Line 2 has 4 spaces (whitespace-only), line 4 is truly empty
|
||||
cx.set_shared_state("ˇfirst\n \nstill first\n\nsecond")
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("}").await;
|
||||
|
||||
// Should skip whitespace-only line and stop at truly empty line
|
||||
let mut shared_state = cx.shared_state().await;
|
||||
shared_state.assert_eq("first\n \nstill first\nˇ\nsecond");
|
||||
shared_state.assert_matches();
|
||||
|
||||
// Should go back to original position
|
||||
cx.simulate_shared_keystrokes("{").await;
|
||||
let mut shared_state = cx.shared_state().await;
|
||||
shared_state.assert_eq("ˇfirst\n \nstill first\n\nsecond");
|
||||
shared_state.assert_matches();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{"Put":{"state":"ˇfirst\n \nstill first\n\nsecond"}}
|
||||
{"Key":"}"}
|
||||
{"Get":{"state":"first\n \nstill first\nˇ\nsecond","mode":"Normal"}}
|
||||
{"Key":"{"}
|
||||
{"Get":{"state":"ˇfirst\n \nstill first\n\nsecond","mode":"Normal"}}
|
||||
Reference in new issue
Block a user