mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
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:
parent
757ee0571e
commit
9ecafe1960
3 changed files with 40 additions and 12 deletions
|
|
@ -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"}}
|
||||
Loading…
Reference in a new issue