vim: Add C preprocessor check in matching function (#55515)

Closes #24820

This PR fixes the bug specified in issue
https://github.com/zed-industries/zed/issues/24820, now the matching
function checks if the cursor is above a comment or a directive before
defaulting to a bracket range as neovim does.

It also fixes fixes the `line_end` calculations so that when `%` is
pressed inside a bracket range


https://github.com/user-attachments/assets/f59daa6f-9769-45e8-bb8c-2d533470b59d

Release Notes:

- `fn matching()` checks for `preprocessor directives` or `comments`
before defaulting to any bracket range.
- In `fn matching()`line_end calculations avoid expanding a blank
current line into start..EOF.
This commit is contained in:
Juan Pablo Briones 2026-05-07 00:25:42 -04:00 committed by GitHub
parent 5dd9082d05
commit 0bcf71f786
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 96 additions and 58 deletions

View file

@ -2452,7 +2452,7 @@ fn find_matching_bracket_text_based(
.find_map(|(ch, char_offset)| get_bracket_pair(ch).map(|info| (info, char_offset)));
if bracket_info.is_none() {
return find_matching_c_preprocessor_directive(map, line_range);
return find_matching_c_preprocessor_directive(map, line_range, offset);
}
let (open, close, is_opening) = bracket_info?.0;
@ -2489,18 +2489,20 @@ fn find_matching_bracket_text_based(
fn find_matching_c_preprocessor_directive(
map: &DisplaySnapshot,
line_range: Range<MultiBufferOffset>,
offset: MultiBufferOffset,
) -> Option<MultiBufferOffset> {
let line_start = map
.buffer_chars_at(line_range.start)
.skip_while(|(c, _)| *c == ' ' || *c == '\t')
.take_while(|(c, char_offset)| *char_offset < line_range.end && !c.is_whitespace())
.map(|(c, _)| c)
.take(6)
.collect::<String>();
if line_start.starts_with("#if")
|| line_start.starts_with("#else")
|| line_start.starts_with("#elif")
{
if line_range.start + line_start.len() < offset {
return None;
}
if line_start.starts_with("#if") || line_start.starts_with("#el") {
let mut depth = 0i32;
for (ch, char_offset) in map.buffer_chars_at(line_range.end) {
if ch != '\n' {
@ -2618,8 +2620,30 @@ fn matching(
// Ensure the range is contained by the current line.
let mut line_end = map.next_line_boundary(point).0;
if line_end == point {
line_end = map.max_point().to_point(map);
let max_point = map.max_point().to_point(map);
// Only widen to EOF when the cursor is actually at EOF.
// This avoids expanding a blank current line into start..EOF.
if line_end == point && point == max_point {
line_end = max_point;
}
let line_range = map.prev_line_boundary(point).0..line_end;
let line_range = line_range.start.to_offset(&map.buffer_snapshot())
..line_range.end.to_offset(&map.buffer_snapshot());
if let Some(preproc_range) = find_matching_c_preprocessor_directive(map, line_range, offset) {
return preproc_range.to_display_point(map);
}
if let Some((open_range, close_range)) = comment_delimiter_pair(map, offset) {
if open_range.contains(&offset) {
return close_range.start.to_display_point(map);
}
if close_range.contains(&offset) {
return open_range.start.to_display_point(map);
}
}
let is_quote_char = |ch: char| matches!(ch, '\'' | '"' | '`');
@ -2729,32 +2753,6 @@ fn matching(
continue;
}
if let Some((open_range, close_range)) = comment_delimiter_pair(map, offset) {
if open_range.contains(&offset) {
return close_range.start.to_display_point(map);
}
if close_range.contains(&offset) {
return open_range.start.to_display_point(map);
}
let open_candidate = (open_range.start >= offset
&& line_range.contains(&open_range.start))
.then_some((open_range.start.saturating_sub(offset), close_range.start));
let close_candidate = (close_range.start >= offset
&& line_range.contains(&close_range.start))
.then_some((close_range.start.saturating_sub(offset), open_range.start));
if let Some((_, destination)) = [open_candidate, close_candidate]
.into_iter()
.flatten()
.min_by_key(|(distance, _)| *distance)
{
return destination.to_display_point(map);
}
}
closest_pair_destination
.map(|destination| destination.to_display_point(map))
.unwrap_or_else(|| {
@ -3663,6 +3661,10 @@ mod test {
cx.shared_state().await.assert_eq(indoc! {r"/*
this is a comment
ˇ*/"});
cx.simulate_shared_keystrokes("k %").await;
cx.shared_state().await.assert_eq(indoc! {r"/*
ˇ this is a comment
*/"});
cx.set_shared_state("ˇ// comment").await;
cx.simulate_shared_keystrokes("%").await;
@ -3673,48 +3675,53 @@ mod test {
async fn test_matching_preprocessor_directives(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {r"#ˇif
cx.set_shared_state(indoc! {r"
#ˇif
#else
#else
#endif
"})
#endif
"})
.await;
cx.simulate_shared_keystrokes("%").await;
cx.shared_state().await.assert_eq(indoc! {r"#if
cx.shared_state().await.assert_eq(indoc! {r"
#if
ˇ#else
#endif
"});
"});
cx.simulate_shared_keystrokes("%").await;
cx.shared_state().await.assert_eq(indoc! {r"#if
cx.shared_state().await.assert_eq(indoc! {r"
#if
#else
ˇ#endif
"});
"});
cx.simulate_shared_keystrokes("%").await;
cx.shared_state().await.assert_eq(indoc! {r"ˇ#if
cx.shared_state().await.assert_eq(indoc! {r"
ˇ#if
#else
#endif
"});
"});
cx.set_shared_state(indoc! {r"
#ˇif
#if
#else
#endif
#ˇif
#if
#else
#endif
"})
#else
#endif
"})
.await;
cx.simulate_shared_keystrokes("%").await;
@ -3727,8 +3734,9 @@ mod test {
#endif
ˇ#else
#endif
"});
"});
cx.simulate_shared_keystrokes("% %").await;
cx.shared_state().await.assert_eq(indoc! {r"
@ -3740,8 +3748,9 @@ mod test {
#endif
#else
#endif
"});
"});
cx.simulate_shared_keystrokes("j % % %").await;
cx.shared_state().await.assert_eq(indoc! {r"
#if
@ -3752,8 +3761,28 @@ mod test {
#endif
#else
#endif
"});
"});
cx.set_shared_state(indoc! {r"
#if definedˇ(something)
#endif
"})
.await;
cx.simulate_shared_keystrokes("%").await;
cx.shared_state().await.assert_eq(indoc! {r"
#if defined(somethingˇ)
#endif
"});
cx.simulate_shared_keystrokes("0 %").await;
cx.shared_state().await.assert_eq(indoc! {r"
#if defined(something)
ˇ#endif
"});
}
#[gpui::test]

View file

@ -5,6 +5,9 @@
{"Get":{"state":"ˇ/*\n this is a comment\n*/","mode":"Normal"}}
{"Key":"%"}
{"Get":{"state":"/*\n this is a comment\nˇ*/","mode":"Normal"}}
{"Key":"k"}
{"Key":"%"}
{"Get":{"state":"/*\nˇ this is a comment\n*/","mode":"Normal"}}
{"Put":{"state":"ˇ// comment"}}
{"Key":"%"}
{"Get":{"state":"ˇ// comment","mode":"Normal"}}

View file

@ -5,14 +5,20 @@
{"Get":{"state":"#if\n\n#else\n\nˇ#endif\n","mode":"Normal"}}
{"Key":"%"}
{"Get":{"state":"ˇ#if\n\n#else\n\n#endif\n","mode":"Normal"}}
{"Put":{"state":"#ˇif\n #if\n\n #else\n\n #endif\n\n#else\n#endif\n"}}
{"Put":{"state":"#ˇif\n #if\n\n #else\n\n #endif\n\n#else\n\n#endif\n"}}
{"Key":"%"}
{"Get":{"state":"#if\n #if\n\n #else\n\n #endif\n\nˇ#else\n#endif\n","mode":"Normal"}}
{"Get":{"state":"#if\n #if\n\n #else\n\n #endif\n\nˇ#else\n\n#endif\n","mode":"Normal"}}
{"Key":"%"}
{"Key":"%"}
{"Get":{"state":"ˇ#if\n #if\n\n #else\n\n #endif\n\n#else\n#endif\n","mode":"Normal"}}
{"Get":{"state":"ˇ#if\n #if\n\n #else\n\n #endif\n\n#else\n\n#endif\n","mode":"Normal"}}
{"Key":"j"}
{"Key":"%"}
{"Key":"%"}
{"Key":"%"}
{"Get":{"state":"#if\n ˇ#if\n\n #else\n\n #endif\n\n#else\n#endif\n","mode":"Normal"}}
{"Get":{"state":"#if\n ˇ#if\n\n #else\n\n #endif\n\n#else\n\n#endif\n","mode":"Normal"}}
{"Put":{"state":"#if definedˇ(something)\n\n#endif\n"}}
{"Key":"%"}
{"Get":{"state":"#if defined(somethingˇ)\n\n#endif\n","mode":"Normal"}}
{"Key":"0"}
{"Key":"%"}
{"Get":{"state":"#if defined(something)\n\nˇ#endif\n","mode":"Normal"}}