Allow multiple disjoint nodes to be captured as matcheable in the outline query
This commit is contained in:
parent
950b06674f
commit
3e1c559b2d
8 changed files with 181 additions and 58 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3132,6 +3132,7 @@ dependencies = [
|
||||||
"language",
|
"language",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"postage",
|
"postage",
|
||||||
|
"smol",
|
||||||
"text",
|
"text",
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1707,12 +1707,11 @@ impl MultiBufferSnapshot {
|
||||||
.items
|
.items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| OutlineItem {
|
.map(|item| OutlineItem {
|
||||||
id: item.id,
|
|
||||||
depth: item.depth,
|
depth: item.depth,
|
||||||
range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start)
|
range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start)
|
||||||
..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end),
|
..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end),
|
||||||
text: item.text,
|
text: item.text,
|
||||||
name_range_in_text: item.name_range_in_text,
|
name_ranges: item.name_ranges,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
))
|
))
|
||||||
|
|
|
@ -1850,52 +1850,45 @@ impl BufferSnapshot {
|
||||||
);
|
);
|
||||||
|
|
||||||
let item_capture_ix = grammar.outline_query.capture_index_for_name("item")?;
|
let item_capture_ix = grammar.outline_query.capture_index_for_name("item")?;
|
||||||
let context_capture_ix = grammar.outline_query.capture_index_for_name("context")?;
|
|
||||||
let name_capture_ix = grammar.outline_query.capture_index_for_name("name")?;
|
let name_capture_ix = grammar.outline_query.capture_index_for_name("name")?;
|
||||||
|
let context_capture_ix = grammar
|
||||||
|
.outline_query
|
||||||
|
.capture_index_for_name("context")
|
||||||
|
.unwrap_or(u32::MAX);
|
||||||
|
|
||||||
let mut stack: Vec<Range<usize>> = Default::default();
|
let mut stack = Vec::<Range<usize>>::new();
|
||||||
let mut id = 0;
|
|
||||||
let items = matches
|
let items = matches
|
||||||
.filter_map(|mat| {
|
.filter_map(|mat| {
|
||||||
let item_node = mat.nodes_for_capture_index(item_capture_ix).next()?;
|
let item_node = mat.nodes_for_capture_index(item_capture_ix).next()?;
|
||||||
let mut name_node = Some(mat.nodes_for_capture_index(name_capture_ix).next()?);
|
|
||||||
let mut context_nodes = mat.nodes_for_capture_index(context_capture_ix).peekable();
|
|
||||||
|
|
||||||
let id = post_inc(&mut id);
|
|
||||||
let range = item_node.start_byte()..item_node.end_byte();
|
let range = item_node.start_byte()..item_node.end_byte();
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut name_range_in_text = 0..0;
|
let mut name_ranges = Vec::new();
|
||||||
loop {
|
|
||||||
let node;
|
for capture in mat.captures {
|
||||||
let node_is_name;
|
let node_is_name;
|
||||||
match (context_nodes.peek(), name_node.as_ref()) {
|
if capture.index == name_capture_ix {
|
||||||
(None, None) => break,
|
node_is_name = true;
|
||||||
(None, Some(_)) => {
|
} else if capture.index == context_capture_ix {
|
||||||
node = name_node.take().unwrap();
|
node_is_name = false;
|
||||||
node_is_name = true;
|
} else {
|
||||||
}
|
continue;
|
||||||
(Some(_), None) => {
|
|
||||||
node = context_nodes.next().unwrap();
|
|
||||||
node_is_name = false;
|
|
||||||
}
|
|
||||||
(Some(context_node), Some(name)) => {
|
|
||||||
if context_node.start_byte() < name.start_byte() {
|
|
||||||
node = context_nodes.next().unwrap();
|
|
||||||
node_is_name = false;
|
|
||||||
} else {
|
|
||||||
node = name_node.take().unwrap();
|
|
||||||
node_is_name = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let range = capture.node.start_byte()..capture.node.end_byte();
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
text.push(' ');
|
text.push(' ');
|
||||||
}
|
}
|
||||||
let range = node.start_byte()..node.end_byte();
|
|
||||||
if node_is_name {
|
if node_is_name {
|
||||||
name_range_in_text = text.len()..(text.len() + range.len())
|
let mut start = text.len() as u32;
|
||||||
|
let end = start + range.len() as u32;
|
||||||
|
|
||||||
|
// When multiple names are captured, then the matcheable text
|
||||||
|
// includes the whitespace in between the names.
|
||||||
|
if !name_ranges.is_empty() {
|
||||||
|
start -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
name_ranges.push(start..end);
|
||||||
}
|
}
|
||||||
text.extend(self.text_for_range(range));
|
text.extend(self.text_for_range(range));
|
||||||
}
|
}
|
||||||
|
@ -1908,11 +1901,10 @@ impl BufferSnapshot {
|
||||||
stack.push(range.clone());
|
stack.push(range.clone());
|
||||||
|
|
||||||
Some(OutlineItem {
|
Some(OutlineItem {
|
||||||
id,
|
|
||||||
depth: stack.len() - 1,
|
depth: stack.len() - 1,
|
||||||
range: self.anchor_after(range.start)..self.anchor_before(range.end),
|
range: self.anchor_after(range.start)..self.anchor_before(range.end),
|
||||||
text,
|
text,
|
||||||
name_range_in_text,
|
name_ranges: name_ranges.into_boxed_slice(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::AppContext;
|
use gpui::executor::Background;
|
||||||
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Outline<T> {
|
pub struct Outline<T> {
|
||||||
|
@ -11,11 +10,10 @@ pub struct Outline<T> {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OutlineItem<T> {
|
pub struct OutlineItem<T> {
|
||||||
pub id: usize,
|
|
||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub range: Range<T>,
|
pub range: Range<T>,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub name_range_in_text: Range<usize>,
|
pub name_ranges: Box<[Range<u32>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Outline<T> {
|
impl<T> Outline<T> {
|
||||||
|
@ -24,10 +22,14 @@ impl<T> Outline<T> {
|
||||||
candidates: items
|
candidates: items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| {
|
.map(|item| {
|
||||||
let text = &item.text[item.name_range_in_text.clone()];
|
let text = item
|
||||||
|
.name_ranges
|
||||||
|
.iter()
|
||||||
|
.map(|range| &item.text[range.start as usize..range.end as usize])
|
||||||
|
.collect::<String>();
|
||||||
StringMatchCandidate {
|
StringMatchCandidate {
|
||||||
string: text.to_string(),
|
char_bag: text.as_str().into(),
|
||||||
char_bag: text.into(),
|
string: text,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -35,15 +37,16 @@ impl<T> Outline<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search(&self, query: &str, cx: &AppContext) -> Vec<StringMatch> {
|
pub async fn search(&self, query: &str, executor: Arc<Background>) -> Vec<StringMatch> {
|
||||||
let mut matches = smol::block_on(fuzzy::match_strings(
|
let mut matches = fuzzy::match_strings(
|
||||||
&self.candidates,
|
&self.candidates,
|
||||||
query,
|
query,
|
||||||
true,
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background().clone(),
|
executor,
|
||||||
));
|
)
|
||||||
|
.await;
|
||||||
matches.sort_unstable_by_key(|m| m.candidate_index);
|
matches.sort_unstable_by_key(|m| m.candidate_index);
|
||||||
|
|
||||||
let mut tree_matches = Vec::new();
|
let mut tree_matches = Vec::new();
|
||||||
|
@ -51,8 +54,16 @@ impl<T> Outline<T> {
|
||||||
let mut prev_item_ix = 0;
|
let mut prev_item_ix = 0;
|
||||||
for mut string_match in matches {
|
for mut string_match in matches {
|
||||||
let outline_match = &self.items[string_match.candidate_index];
|
let outline_match = &self.items[string_match.candidate_index];
|
||||||
|
|
||||||
|
let mut name_ranges = outline_match.name_ranges.iter();
|
||||||
|
let mut name_range = name_ranges.next().unwrap();
|
||||||
|
let mut preceding_ranges_len = 0;
|
||||||
for position in &mut string_match.positions {
|
for position in &mut string_match.positions {
|
||||||
*position += outline_match.name_range_in_text.start;
|
while *position >= preceding_ranges_len + name_range.len() as usize {
|
||||||
|
preceding_ranges_len += name_range.len();
|
||||||
|
name_range = name_ranges.next().unwrap();
|
||||||
|
}
|
||||||
|
*position = name_range.start as usize + (*position - preceding_ranges_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cur_depth = outline_match.depth;
|
let mut cur_depth = outline_match.depth;
|
||||||
|
|
|
@ -278,6 +278,121 @@ async fn test_reparse(mut cx: gpui::TestAppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_outline(mut cx: gpui::TestAppContext) {
|
||||||
|
let language = Some(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
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let text = r#"
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LoginState {
|
||||||
|
LoggedOut,
|
||||||
|
LoggingOn,
|
||||||
|
LoggedIn {
|
||||||
|
person: Person,
|
||||||
|
time: Instant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Person {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("bye");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
|
||||||
|
let outline = buffer
|
||||||
|
.read_with(&cx, |buffer, _| buffer.snapshot().outline())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
outline
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| (item.text.as_str(), item.name_ranges.as_ref(), item.depth))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&[
|
||||||
|
("struct Person", [7..13].as_slice(), 0),
|
||||||
|
("name", &[0..4], 1),
|
||||||
|
("age", &[0..3], 1),
|
||||||
|
("enum LoginState", &[5..15], 0),
|
||||||
|
("LoggedOut", &[0..9], 1),
|
||||||
|
("LoggingOn", &[0..9], 1),
|
||||||
|
("LoggedIn", &[0..8], 1),
|
||||||
|
("person", &[0..6], 2),
|
||||||
|
("time", &[0..4], 2),
|
||||||
|
("impl Drop for Person", &[5..9, 13..20], 0),
|
||||||
|
("fn drop", &[3..7], 1),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
search(&outline, "oon", &cx).await,
|
||||||
|
&[
|
||||||
|
("enum LoginState", vec![]), // included as the parent of a match
|
||||||
|
("LoggingOn", vec![1, 7, 8]), // matches
|
||||||
|
("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
search(&outline, "dp p", &cx).await,
|
||||||
|
&[("impl Drop for Person", vec![5, 8, 13, 14])]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
search(&outline, "dpn", &cx).await,
|
||||||
|
&[("impl Drop for Person", vec![5, 8, 19])]
|
||||||
|
);
|
||||||
|
|
||||||
|
async fn search<'a>(
|
||||||
|
outline: &'a Outline<Anchor>,
|
||||||
|
query: &str,
|
||||||
|
cx: &gpui::TestAppContext,
|
||||||
|
) -> Vec<(&'a str, Vec<usize>)> {
|
||||||
|
let matches = cx
|
||||||
|
.read(|cx| outline.search(query, cx.background().clone()))
|
||||||
|
.await;
|
||||||
|
matches
|
||||||
|
.into_iter()
|
||||||
|
.map(|mat| {
|
||||||
|
(
|
||||||
|
outline.items[mat.candidate_index].text.as_str(),
|
||||||
|
mat.positions,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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| {
|
||||||
|
@ -1017,14 +1132,18 @@ fn rust_lang() -> Language {
|
||||||
)
|
)
|
||||||
.with_indents_query(
|
.with_indents_query(
|
||||||
r#"
|
r#"
|
||||||
(call_expression) @indent
|
(call_expression) @indent
|
||||||
(field_expression) @indent
|
(field_expression) @indent
|
||||||
(_ "(" ")" @end) @indent
|
(_ "(" ")" @end) @indent
|
||||||
(_ "{" "}" @end) @indent
|
(_ "{" "}" @end) @indent
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_brackets_query(r#" ("{" @open "}" @close) "#)
|
.with_brackets_query(
|
||||||
|
r#"
|
||||||
|
("{" @open "}" @close)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,3 +15,4 @@ text = { path = "../text" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
postage = { version = "0.4", features = ["futures-traits"] }
|
postage = { version = "0.4", features = ["futures-traits"] }
|
||||||
|
smol = "1.2"
|
||||||
|
|
|
@ -301,7 +301,7 @@ impl OutlineView {
|
||||||
.0;
|
.0;
|
||||||
navigate_to_selected_index = false;
|
navigate_to_selected_index = false;
|
||||||
} else {
|
} else {
|
||||||
self.matches = self.outline.search(&query, cx);
|
self.matches = smol::block_on(self.outline.search(&query, cx.background().clone()));
|
||||||
selected_index = self
|
selected_index = self
|
||||||
.matches
|
.matches
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -309,7 +309,7 @@ impl OutlineView {
|
||||||
.max_by_key(|(_, m)| OrderedFloat(m.score))
|
.max_by_key(|(_, m)| OrderedFloat(m.score))
|
||||||
.map(|(ix, _)| ix)
|
.map(|(ix, _)| ix)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
navigate_to_selected_index = true;
|
navigate_to_selected_index = !self.matches.is_empty();
|
||||||
}
|
}
|
||||||
self.select(selected_index, navigate_to_selected_index, cx);
|
self.select(selected_index, navigate_to_selected_index, cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
(impl_item
|
(impl_item
|
||||||
"impl" @context
|
"impl" @context
|
||||||
trait: (_)? @context
|
trait: (_)? @name
|
||||||
"for"? @context
|
"for"? @context
|
||||||
type: (_) @name) @item
|
type: (_) @name) @item
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue