Add buffer method for getting the symbols containing a position

This commit is contained in:
Max Brunsfeld 2022-03-15 14:04:58 -07:00
parent a0224cbe71
commit 257601b3c1
3 changed files with 146 additions and 38 deletions

View file

@ -1667,6 +1667,31 @@ impl BufferSnapshot {
} }
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> { pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
self.outline_items_containing(0..self.len(), theme)
.map(Outline::new)
}
pub fn symbols_containing<T: ToOffset>(
&self,
position: T,
theme: Option<&SyntaxTheme>,
) -> Option<Vec<OutlineItem<Anchor>>> {
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<usize>,
theme: Option<&SyntaxTheme>,
) -> Option<Vec<OutlineItem<Anchor>>> {
let tree = self.tree.as_ref()?; let tree = self.tree.as_ref()?;
let grammar = self let grammar = self
.language .language
@ -1674,6 +1699,7 @@ impl BufferSnapshot {
.and_then(|language| language.grammar.as_ref())?; .and_then(|language| language.grammar.as_ref())?;
let mut cursor = QueryCursorHandle::new(); let mut cursor = QueryCursorHandle::new();
cursor.set_byte_range(range);
let matches = cursor.matches( let matches = cursor.matches(
&grammar.outline_query, &grammar.outline_query,
tree.root_node(), tree.root_node(),
@ -1766,12 +1792,7 @@ impl BufferSnapshot {
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Some(items)
if items.is_empty() {
None
} else {
Some(Outline::new(items))
}
} }
pub fn enclosing_bracket_ranges<T: ToOffset>( pub fn enclosing_bracket_ranges<T: ToOffset>(

View file

@ -10,7 +10,7 @@ pub struct Outline<T> {
path_candidate_prefixes: Vec<usize>, path_candidate_prefixes: Vec<usize>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct OutlineItem<T> { pub struct OutlineItem<T> {
pub depth: usize, pub depth: usize,
pub range: Range<T>, pub range: Range<T>,

View file

@ -282,36 +282,6 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
#[gpui::test] #[gpui::test]
async fn test_outline(cx: &mut gpui::TestAppContext) { 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#" let text = r#"
struct Person { struct Person {
name: String, name: String,
@ -339,7 +309,8 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
"# "#
.unindent(); .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 let outline = buffer
.read_with(cx, |buffer, _| buffer.snapshot().outline(None)) .read_with(cx, |buffer, _| buffer.snapshot().outline(None))
.unwrap(); .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<Point>)> {
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] #[gpui::test]
fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
@ -851,6 +909,35 @@ fn rust_lang() -> Language {
"#, "#,
) )
.unwrap() .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<Point> { fn empty(point: Point) -> Range<Point> {