outline.rs

 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}