From 0f01568dd8b7a304a7a2ed2d576b100e2d3610cf Mon Sep 17 00:00:00 2001 From: dualfroz Date: Sat, 17 Oct 2026 03:51:18 +0200 Subject: [PATCH] fix: match brackets preceded by an empty node `find_pair` looked up the node under the cursor with an empty byte range. When a zero-width node starts at that position, tree-sitter returns it, and since it is hidden the lookup resolves to its visible parent instead. Typst emits such a token right before the `(` or `[` of a call, so on those brackets `mm` either did nothing or jumped to the closing bracket of an outer pair. `find_matching_bracket` had the same problem. Look up the byte under the cursor instead, like the plaintext fallback at the end of `find_pair` already does. --- helix-core/src/match_brackets.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs index e1e40e0..e0d930c 100644 --- a/helix-core/src/match_brackets.rs +++ b/helix-core/src/match_brackets.rs @@ -83,7 +83,7 @@ fn find_pair( let pos = doc.char_to_byte(pos_) as u32; let root = syntax.tree_for_byte_range(pos, pos).root_node(); - let mut node = root.descendant_for_byte_range(pos, pos)?; + let mut node = root.descendant_for_byte_range(pos, pos + 1)?; loop { if node.is_named() && node.child_count() >= 2 { @@ -350,4 +350,29 @@ mod tests { assert("(prev line\n ) (middle) ( \n next line)", 14, 21); assert("(prev line\n ) (middle) ( \n next line)", 23, 36); } + + #[test] + fn test_find_matching_bracket_after_empty_node() { + // Typst emits a zero-width token right before the `(` or `[` of a call + let loader = crate::config::default_lang_loader(); + let language = loader.language_for_name("typst").unwrap(); + let doc = crate::Rope::from_str( + "#rect(width: 100%, stroke: stroke(dash: \"dash-dotted\", thickness: 2pt), radius: 5mm)[\n\n]\n", + ); + let syntax = Syntax::new(doc.slice(..), language, &loader).unwrap(); + + let assert = |pos, expected| { + let actual = find_matching_bracket(&syntax, doc.slice(..), pos); + assert_eq!(Some(expected), actual); + let actual = find_matching_bracket_fuzzy(&syntax, doc.slice(..), pos); + assert_eq!(Some(expected), actual); + + let actual = find_matching_bracket_fuzzy(&syntax, doc.slice(..), expected); + assert_eq!(Some(pos), actual, "expected symmetrical behaviour"); + }; + + assert(5, 83); + assert(33, 69); + assert(84, 87); + } }