diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 6e992704f54bf7aba3cc775d906a90281234dbd0..28669d4890a2e764e02d1383114a3fdf342e7ad8 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -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, + offset: MultiBufferOffset, ) -> Option { 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::(); - 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] diff --git a/crates/vim/test_data/test_matching_comments.json b/crates/vim/test_data/test_matching_comments.json index 7fcf5e46e1ea16f2be794ff76b583242b33aabc0..8d130621913356898123e3243a13c89e9146db2e 100644 --- a/crates/vim/test_data/test_matching_comments.json +++ b/crates/vim/test_data/test_matching_comments.json @@ -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"}} diff --git a/crates/vim/test_data/test_matching_preprocessor_directives.json b/crates/vim/test_data/test_matching_preprocessor_directives.json index 9f0bd9792ee8dad5029f4ecaf325c231755530e1..7a55ac7995f5bd3c9b1460505b13345a6a44f7a0 100644 --- a/crates/vim/test_data/test_matching_preprocessor_directives.json +++ b/crates/vim/test_data/test_matching_preprocessor_directives.json @@ -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"}}