lex_analysis/
folding_ranges.rs1use lex_core::lex::ast::{
2 Annotation, AstNode, ContentItem, Definition, Document, List, ListItem, Range, Session, Table,
3 Verbatim,
4};
5use lsp_types::FoldingRangeKind;
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct LexFoldingRange {
9 pub start_line: u32,
10 pub start_character: Option<u32>,
11 pub end_line: u32,
12 pub end_character: Option<u32>,
13 pub kind: Option<FoldingRangeKind>,
14}
15
16pub fn folding_ranges(document: &Document) -> Vec<LexFoldingRange> {
17 let mut collector = FoldingCollector { ranges: Vec::new() };
18 collector.process_document(document);
19 collector.ranges
20}
21
22struct FoldingCollector {
23 ranges: Vec<LexFoldingRange>,
24}
25
26impl FoldingCollector {
27 fn process_document(&mut self, document: &Document) {
28 self.process_annotations(document.annotations());
29 self.process_session(&document.root, true);
30 }
31
32 fn process_session(&mut self, session: &Session, is_root: bool) {
33 if !is_root {
34 if let Some(body) = session.body_location() {
35 if session.range().start.line < body.end.line {
36 self.push_fold(
37 session.header_location(),
38 session.range(),
39 Some(FoldingRangeKind::Region),
40 );
41 }
42 }
43 }
44 self.process_annotations(session.annotations());
45 for child in session.children.iter() {
46 self.process_content_item(child);
47 }
48 }
49
50 fn process_content_item(&mut self, item: &ContentItem) {
51 match item {
52 ContentItem::Paragraph(paragraph) => {
53 self.process_annotations(paragraph.annotations());
54 }
55 ContentItem::Session(session) => self.process_session(session, false),
56 ContentItem::List(list) => self.process_list(list),
57 ContentItem::ListItem(list_item) => self.process_list_item(list_item),
58 ContentItem::Definition(definition) => self.process_definition(definition),
59 ContentItem::Annotation(annotation) => self.process_annotation(annotation),
60 ContentItem::VerbatimBlock(verbatim) => self.process_verbatim(verbatim),
61 ContentItem::Table(table) => self.process_table(table),
62 ContentItem::TextLine(_)
63 | ContentItem::VerbatimLine(_)
64 | ContentItem::BlankLineGroup(_) => {}
65 }
66 }
67
68 fn process_list(&mut self, list: &List) {
69 if list.range().start.line < list.range().end.line {
70 self.push_fold(
71 Some(list.range()),
72 list.range(),
73 Some(FoldingRangeKind::Region),
74 );
75 }
76 self.process_annotations(list.annotations());
77 for item in list.items.iter() {
78 if let ContentItem::ListItem(list_item) = item {
79 self.process_list_item(list_item);
80 }
81 }
82 }
83
84 fn process_list_item(&mut self, list_item: &ListItem) {
85 self.process_annotations(list_item.annotations());
86 if let Some(children_range) =
87 Range::bounding_box(list_item.children.iter().map(|child| child.range()))
88 {
89 if list_item.range().start.line < children_range.end.line {
90 self.push_fold(
91 Some(list_item.range()),
92 &children_range,
93 Some(FoldingRangeKind::Region),
94 );
95 }
96 }
97 for child in list_item.children.iter() {
98 self.process_content_item(child);
99 }
100 }
101
102 fn process_definition(&mut self, definition: &Definition) {
103 if definition.range().start.line < definition.range().end.line {
104 let header = definition.header_location();
105 self.push_fold(header, definition.range(), Some(FoldingRangeKind::Region));
106 }
107 self.process_annotations(definition.annotations());
108 for child in definition.children.iter() {
109 self.process_content_item(child);
110 }
111 }
112
113 fn process_annotation(&mut self, annotation: &Annotation) {
114 if annotation.body_location().is_some() {
115 self.push_fold(
116 Some(annotation.header_location()),
117 annotation.range(),
118 Some(FoldingRangeKind::Comment),
119 );
120 }
121 for child in annotation.children.iter() {
122 self.process_content_item(child);
123 }
124 }
125
126 fn process_verbatim(&mut self, verbatim: &Verbatim) {
127 self.process_annotations(verbatim.annotations());
128 if let Some(subject_range) = &verbatim.subject.location {
129 if subject_range.start.line < verbatim.range().end.line {
130 self.push_fold(
131 Some(subject_range),
132 verbatim.range(),
133 Some(FoldingRangeKind::Region),
134 );
135 }
136 }
137 }
138
139 fn process_table(&mut self, table: &Table) {
140 self.process_annotations(table.annotations());
141 for row in table.all_rows() {
143 for cell in &row.cells {
144 for child in cell.children.iter() {
145 self.process_content_item(child);
146 }
147 }
148 }
149 if let Some(subject_range) = &table.subject.location {
150 if subject_range.start.line < table.range().end.line {
151 self.push_fold(
152 Some(subject_range),
153 table.range(),
154 Some(FoldingRangeKind::Region),
155 );
156 }
157 }
158 }
159
160 fn process_annotations(&mut self, annotations: &[Annotation]) {
161 for annotation in annotations {
162 self.process_annotation(annotation);
163 }
164 }
165
166 fn push_fold(
167 &mut self,
168 start_range: Option<&Range>,
169 end_range: &Range,
170 kind: Option<FoldingRangeKind>,
171 ) {
172 let start = start_range.unwrap_or(end_range);
173 if start.start.line >= end_range.end.line {
174 return;
175 }
176 self.ranges.push(LexFoldingRange {
177 start_line: start.start.line as u32,
178 start_character: Some(start.start.column as u32),
179 end_line: end_range.end.line as u32,
180 end_character: Some(end_range.end.column as u32),
181 kind,
182 });
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::test_support::sample_document;
190
191 #[test]
192 fn creates_ranges_for_sessions_and_definitions() {
193 let document = sample_document();
194 let ranges = folding_ranges(&document);
195 assert!(ranges
196 .iter()
197 .any(|range| range.kind == Some(FoldingRangeKind::Region) && range.start_line == 2));
198 assert!(ranges
199 .iter()
200 .any(|range| range.kind == Some(FoldingRangeKind::Region) && range.start_line > 2));
201 }
206}