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}