outline.rs

  1use language::{BufferSnapshot, LanguageId, SyntaxMapMatches};
  2use std::{cmp::Reverse, ops::Range, sync::Arc};
  3
  4// TODO:
  5//
  6// * how to handle multiple name captures? for now last one wins
  7//
  8// * annotation ranges
  9//
 10// * new "signature" capture for outline queries
 11//
 12// * Check parent behavior of "int x, y = 0" declarations in a test
 13
 14pub struct OutlineDeclaration {
 15    pub parent_index: Option<usize>,
 16    pub identifier: Identifier,
 17    pub item_range: Range<usize>,
 18    pub signature_range: Range<usize>,
 19}
 20
 21#[derive(Debug, Clone, Eq, PartialEq, Hash)]
 22pub struct Identifier {
 23    pub name: Arc<str>,
 24    pub language_id: LanguageId,
 25}
 26
 27pub fn declarations_in_buffer(buffer: &BufferSnapshot) -> Vec<OutlineDeclaration> {
 28    declarations_overlapping_range(0..buffer.len(), buffer)
 29}
 30
 31pub fn declarations_overlapping_range(
 32    range: Range<usize>,
 33    buffer: &BufferSnapshot,
 34) -> Vec<OutlineDeclaration> {
 35    let mut declarations = OutlineIterator::new(range, buffer).collect::<Vec<_>>();
 36    declarations.sort_unstable_by_key(|item| (item.item_range.start, Reverse(item.item_range.end)));
 37
 38    let mut parent_stack: Vec<(usize, Range<usize>)> = Vec::new();
 39    for (index, declaration) in declarations.iter_mut().enumerate() {
 40        while let Some((top_parent_index, top_parent_range)) = parent_stack.last() {
 41            if declaration.item_range.start >= top_parent_range.end {
 42                parent_stack.pop();
 43            } else {
 44                declaration.parent_index = Some(*top_parent_index);
 45                break;
 46            }
 47        }
 48        parent_stack.push((index, declaration.item_range.clone()));
 49    }
 50    declarations
 51}
 52
 53/// Iterates outline items without being ordered w.r.t. nested items and without populating
 54/// `parent`.
 55pub struct OutlineIterator<'a> {
 56    buffer: &'a BufferSnapshot,
 57    matches: SyntaxMapMatches<'a>,
 58}
 59
 60impl<'a> OutlineIterator<'a> {
 61    pub fn new(range: Range<usize>, buffer: &'a BufferSnapshot) -> Self {
 62        let matches = buffer.syntax.matches(range, &buffer.text, |grammar| {
 63            grammar.outline_config.as_ref().map(|c| &c.query)
 64        });
 65
 66        Self { buffer, matches }
 67    }
 68}
 69
 70impl<'a> Iterator for OutlineIterator<'a> {
 71    type Item = OutlineDeclaration;
 72
 73    fn next(&mut self) -> Option<Self::Item> {
 74        while let Some(mat) = self.matches.peek() {
 75            let config = self.matches.grammars()[mat.grammar_index]
 76                .outline_config
 77                .as_ref()
 78                .unwrap();
 79
 80            let mut name_range = None;
 81            let mut item_range = None;
 82            let mut signature_start = None;
 83            let mut signature_end = None;
 84
 85            let mut add_to_signature = |range: Range<usize>| {
 86                if signature_start.is_none() {
 87                    signature_start = Some(range.start);
 88                }
 89                signature_end = Some(range.end);
 90            };
 91
 92            for capture in mat.captures {
 93                let range = capture.node.byte_range();
 94                if capture.index == config.name_capture_ix {
 95                    name_range = Some(range.clone());
 96                    add_to_signature(range);
 97                } else if Some(capture.index) == config.context_capture_ix
 98                    || Some(capture.index) == config.extra_context_capture_ix
 99                {
100                    add_to_signature(range);
101                } else if capture.index == config.item_capture_ix {
102                    item_range = Some(range.clone());
103                }
104            }
105
106            let language_id = mat.language.id();
107            self.matches.advance();
108
109            if let Some(name_range) = name_range
110                && let Some(item_range) = item_range
111                && let Some(signature_start) = signature_start
112                && let Some(signature_end) = signature_end
113            {
114                let name = self
115                    .buffer
116                    .text_for_range(name_range)
117                    .collect::<String>()
118                    .into();
119
120                return Some(OutlineDeclaration {
121                    identifier: Identifier { name, language_id },
122                    item_range: item_range,
123                    signature_range: signature_start..signature_end,
124                    parent_index: None,
125                });
126            }
127        }
128        None
129    }
130}