1use std::ops::Range;
2
3use fuzzy::{StringMatch, StringMatchCandidate};
4use gpui::AppContext;
5
6#[derive(Debug)]
7pub struct Outline {
8 pub items: Vec<OutlineItem>,
9 candidates: Vec<StringMatchCandidate>,
10}
11
12#[derive(Clone, Debug)]
13pub struct OutlineItem {
14 pub id: usize,
15 pub depth: usize,
16 pub range: Range<usize>,
17 pub text: String,
18 pub name_range_in_text: Range<usize>,
19}
20
21impl Outline {
22 pub fn new(items: Vec<OutlineItem>) -> Self {
23 Self {
24 candidates: items
25 .iter()
26 .map(|item| {
27 let text = &item.text[item.name_range_in_text.clone()];
28 StringMatchCandidate {
29 string: text.to_string(),
30 char_bag: text.into(),
31 }
32 })
33 .collect(),
34 items,
35 }
36 }
37
38 pub fn search(&self, query: &str, cx: &AppContext) -> Vec<StringMatch> {
39 let mut matches = smol::block_on(fuzzy::match_strings(
40 &self.candidates,
41 query,
42 true,
43 100,
44 &Default::default(),
45 cx.background().clone(),
46 ));
47 matches.sort_unstable_by_key(|m| m.candidate_index);
48
49 let mut tree_matches = Vec::new();
50
51 let mut prev_item_ix = 0;
52 for mut string_match in matches {
53 let outline_match = &self.items[string_match.candidate_index];
54 for position in &mut string_match.positions {
55 *position += outline_match.name_range_in_text.start;
56 }
57
58 for (ix, item) in self.items[prev_item_ix..string_match.candidate_index]
59 .iter()
60 .enumerate()
61 {
62 let candidate_index = ix + prev_item_ix;
63 if item.range.contains(&outline_match.range.start)
64 && item.range.contains(&outline_match.range.end)
65 {
66 tree_matches.push(StringMatch {
67 candidate_index,
68 score: Default::default(),
69 positions: Default::default(),
70 string: Default::default(),
71 });
72 }
73 }
74
75 prev_item_ix = string_match.candidate_index;
76 tree_matches.push(string_match);
77 }
78
79 tree_matches
80 }
81}