outline.rs

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