diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 2a0b158a7a..e8bc2bf314 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2225,11 +2225,12 @@ impl BufferSnapshot { range: Range, ) -> Option<(Range, Range)> { // Find bracket pairs that *inclusively* contain the given range. - let range = range.start.to_offset(self).saturating_sub(1) - ..self.len().min(range.end.to_offset(self) + 1); - let mut matches = self.syntax.matches(range, &self.text, |grammar| { - grammar.brackets_config.as_ref().map(|c| &c.query) - }); + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut matches = self.syntax.matches( + range.start.saturating_sub(1)..self.len().min(range.end + 1), + &self.text, + |grammar| grammar.brackets_config.as_ref().map(|c| &c.query), + ); let configs = matches .grammars() .iter() @@ -2252,18 +2253,20 @@ impl BufferSnapshot { matches.advance(); - if let Some((open, close)) = open.zip(close) { - let len = close.end - open.start; - - if let Some((existing_open, existing_close)) = &result { - let existing_len = existing_close.end - existing_open.start; - if len > existing_len { - continue; - } - } - - result = Some((open, close)); + let Some((open, close)) = open.zip(close) else { continue }; + if open.start > range.start || close.end < range.end { + continue; } + let len = close.end - open.start; + + if let Some((existing_open, existing_close)) = &result { + let existing_len = existing_close.end - existing_open.start; + if len > existing_len { + continue; + } + } + + result = Some((open, close)); } result diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 82641dbaa4..5f2fdf6e8e 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -573,14 +573,72 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { )) ); - // Regression test: avoid crash when querying at the end of the buffer. assert_eq!( - buffer.enclosing_bracket_point_ranges(buffer.len() - 1..buffer.len()), + buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(4, 1)), Some(( Point::new(0, 6)..Point::new(0, 7), Point::new(4, 0)..Point::new(4, 1) )) ); + + // Regression test: avoid crash when querying at the end of the buffer. + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(5, 0)), + None + ); +} + +#[gpui::test] +fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children( + cx: &mut MutableAppContext, +) { + let javascript_language = Arc::new( + Language::new( + LanguageConfig { + name: "JavaScript".into(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_brackets_query( + r#" + ("{" @open "}" @close) + ("(" @open ")" @close) + "#, + ) + .unwrap(), + ); + + cx.set_global(Settings::test(cx)); + let buffer = cx.add_model(|cx| { + let text = " + for (const a in b) { + // a comment that's longer than the for-loop header + } + " + .unindent(); + Buffer::new(0, text, cx).with_language(javascript_language, cx) + }); + + let buffer = buffer.read(cx); + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(0, 18)..Point::new(0, 18)), + Some(( + Point::new(0, 4)..Point::new(0, 5), + Point::new(0, 17)..Point::new(0, 18) + )) + ); + + // Regression test: even though the parent node of the parentheses (the for loop) does + // intersect the given range, the parentheses themselves do not contain the range, so + // they should not be returned. Only the curly braces contain the range. + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(0, 20)..Point::new(0, 20)), + Some(( + Point::new(0, 19)..Point::new(0, 20), + Point::new(2, 0)..Point::new(2, 1) + )) + ); } #[gpui::test]