diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index f8d5c1e836..6c29708b5e 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -19,6 +19,7 @@ test-support = [ [dependencies] clock = { path = "../clock" } collections = { path = "../collections" } +fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } lsp = { path = "../lsp" } rpc = { path = "../rpc" } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index a0c9e38349..03f7552e69 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1917,7 +1917,7 @@ impl BufferSnapshot { }) .collect::>(); - Some(Outline(items)) + Some(Outline::new(items)) } pub fn enclosing_bracket_ranges( diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index 6aa559f963..c8ed6cf575 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -1,7 +1,13 @@ use std::ops::Range; +use fuzzy::{StringMatch, StringMatchCandidate}; +use gpui::AppContext; + #[derive(Debug)] -pub struct Outline(pub Vec); +pub struct Outline { + pub items: Vec, + candidates: Vec, +} #[derive(Clone, Debug)] pub struct OutlineItem { @@ -11,3 +17,65 @@ pub struct OutlineItem { pub text: String, pub name_range_in_text: Range, } + +impl Outline { + pub fn new(items: Vec) -> Self { + Self { + candidates: items + .iter() + .map(|item| { + let text = &item.text[item.name_range_in_text.clone()]; + StringMatchCandidate { + string: text.to_string(), + char_bag: text.into(), + } + }) + .collect(), + items, + } + } + + pub fn search(&self, query: &str, cx: &AppContext) -> Vec { + let mut matches = smol::block_on(fuzzy::match_strings( + &self.candidates, + query, + true, + 100, + &Default::default(), + cx.background().clone(), + )); + matches.sort_unstable_by_key(|m| m.candidate_index); + + let mut tree_matches = Vec::new(); + + let mut prev_item_ix = 0; + for mut string_match in matches { + let outline_match = &self.items[string_match.candidate_index]; + for position in &mut string_match.positions { + *position += outline_match.name_range_in_text.start; + } + + for (ix, item) in self.items[prev_item_ix..string_match.candidate_index] + .iter() + .enumerate() + { + let candidate_index = ix + prev_item_ix; + if item.range.contains(&outline_match.range.start) + && item.range.contains(&outline_match.range.end) + { + tree_matches.push(StringMatch { + candidate_index, + score: Default::default(), + positions: Default::default(), + string: Default::default(), + }); + } + } + + prev_item_ix = string_match.candidate_index; + tree_matches.push(string_match); + } + + tree_matches + } +}