diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index a4fee82805..37dc026abf 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1667,6 +1667,31 @@ impl BufferSnapshot { } pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option> { + self.outline_items_containing(0..self.len(), theme) + .map(Outline::new) + } + + pub fn symbols_containing( + &self, + position: T, + theme: Option<&SyntaxTheme>, + ) -> Option>> { + let position = position.to_offset(&self); + let mut items = self.outline_items_containing(position - 1..position + 1, theme)?; + let mut prev_depth = None; + items.retain(|item| { + let result = prev_depth.map_or(true, |prev_depth| item.depth > prev_depth); + prev_depth = Some(item.depth); + result + }); + Some(items) + } + + fn outline_items_containing( + &self, + range: Range, + theme: Option<&SyntaxTheme>, + ) -> Option>> { let tree = self.tree.as_ref()?; let grammar = self .language @@ -1674,6 +1699,7 @@ impl BufferSnapshot { .and_then(|language| language.grammar.as_ref())?; let mut cursor = QueryCursorHandle::new(); + cursor.set_byte_range(range); let matches = cursor.matches( &grammar.outline_query, tree.root_node(), @@ -1766,12 +1792,7 @@ impl BufferSnapshot { }) }) .collect::>(); - - if items.is_empty() { - None - } else { - Some(Outline::new(items)) - } + Some(items) } pub fn enclosing_bracket_ranges( diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index 0460d122b7..174018b2cf 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -10,7 +10,7 @@ pub struct Outline { path_candidate_prefixes: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct OutlineItem { pub depth: usize, pub range: Range, diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 6c9980b334..1cad180f64 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -282,36 +282,6 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_outline(cx: &mut gpui::TestAppContext) { - let language = Arc::new( - rust_lang() - .with_outline_query( - r#" - (struct_item - "struct" @context - name: (_) @name) @item - (enum_item - "enum" @context - name: (_) @name) @item - (enum_variant - name: (_) @name) @item - (field_declaration - name: (_) @name) @item - (impl_item - "impl" @context - trait: (_) @name - "for" @context - type: (_) @name) @item - (function_item - "fn" @context - name: (_) @name) @item - (mod_item - "mod" @context - name: (_) @name) @item - "#, - ) - .unwrap(), - ); - let text = r#" struct Person { name: String, @@ -339,7 +309,8 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { "# .unindent(); - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); + let buffer = + cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)); let outline = buffer .read_with(cx, |buffer, _| buffer.snapshot().outline(None)) .unwrap(); @@ -413,6 +384,93 @@ async fn test_outline(cx: &mut gpui::TestAppContext) { } } +#[gpui::test] +async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { + let text = r#" + impl Person { + fn one() { + 1 + } + + fn two() { + 2 + }fn three() { + 3 + } + } + "# + .unindent(); + + let buffer = + cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)); + let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); + + // point is at the start of an item + assert_eq!( + symbols_containing(Point::new(1, 4), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) + ] + ); + + // point is in the middle of an item + assert_eq!( + symbols_containing(Point::new(2, 8), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) + ] + ); + + // point is at the end of an item + assert_eq!( + symbols_containing(Point::new(3, 5), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) + ] + ); + + // point is in between two adjacent items + assert_eq!( + symbols_containing(Point::new(7, 5), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5)) + ] + ); + + fn symbols_containing<'a>( + position: Point, + snapshot: &'a BufferSnapshot, + ) -> Vec<(String, Range)> { + snapshot + .symbols_containing(position, None) + .unwrap() + .into_iter() + .map(|item| { + ( + item.text, + item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot), + ) + }) + .collect() + } +} + #[gpui::test] fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { let buffer = cx.add_model(|cx| { @@ -851,6 +909,35 @@ fn rust_lang() -> Language { "#, ) .unwrap() + .with_outline_query( + r#" + (struct_item + "struct" @context + name: (_) @name) @item + (enum_item + "enum" @context + name: (_) @name) @item + (enum_variant + name: (_) @name) @item + (field_declaration + name: (_) @name) @item + (impl_item + "impl" @context + type: (_) @name) @item + (impl_item + "impl" @context + trait: (_) @name + "for" @context + type: (_) @name) @item + (function_item + "fn" @context + name: (_) @name) @item + (mod_item + "mod" @context + name: (_) @name) @item + "#, + ) + .unwrap() } fn empty(point: Point) -> Range {