lex_analysis/
folding_ranges.rs1use lex_core::lex::ast::{
2 Annotation, AstNode, ContentItem, Definition, Document, List, ListItem, Range, Session,
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::TextLine(_)
62 | ContentItem::VerbatimLine(_)
63 | ContentItem::BlankLineGroup(_) => {}
64 }
65 }
66
67 fn process_list(&mut self, list: &List) {
68 if list.range().start.line < list.range().end.line {
69 self.push_fold(
70 Some(list.range()),
71 list.range(),
72 Some(FoldingRangeKind::Region),
73 );
74 }
75 self.process_annotations(list.annotations());
76 for item in list.items.iter() {
77 if let ContentItem::ListItem(list_item) = item {
78 self.process_list_item(list_item);
79 }
80 }
81 }
82
83 fn process_list_item(&mut self, list_item: &ListItem) {
84 self.process_annotations(list_item.annotations());
85 if let Some(children_range) =
86 Range::bounding_box(list_item.children.iter().map(|child| child.range()))
87 {
88 if list_item.range().start.line < children_range.end.line {
89 self.push_fold(
90 Some(list_item.range()),
91 &children_range,
92 Some(FoldingRangeKind::Region),
93 );
94 }
95 }
96 for child in list_item.children.iter() {
97 self.process_content_item(child);
98 }
99 }
100
101 fn process_definition(&mut self, definition: &Definition) {
102 if definition.range().start.line < definition.range().end.line {
103 let header = definition.header_location();
104 self.push_fold(header, definition.range(), Some(FoldingRangeKind::Region));
105 }
106 self.process_annotations(definition.annotations());
107 for child in definition.children.iter() {
108 self.process_content_item(child);
109 }
110 }
111
112 fn process_annotation(&mut self, annotation: &Annotation) {
113 if annotation.body_location().is_some() {
114 self.push_fold(
115 Some(annotation.header_location()),
116 annotation.range(),
117 Some(FoldingRangeKind::Comment),
118 );
119 }
120 for child in annotation.children.iter() {
121 self.process_content_item(child);
122 }
123 }
124
125 fn process_verbatim(&mut self, verbatim: &Verbatim) {
126 self.process_annotations(verbatim.annotations());
127 if let Some(subject_range) = &verbatim.subject.location {
128 if subject_range.start.line < verbatim.range().end.line {
129 self.push_fold(
130 Some(subject_range),
131 verbatim.range(),
132 Some(FoldingRangeKind::Region),
133 );
134 }
135 }
136 }
137
138 fn process_annotations(&mut self, annotations: &[Annotation]) {
139 for annotation in annotations {
140 self.process_annotation(annotation);
141 }
142 }
143
144 fn push_fold(
145 &mut self,
146 start_range: Option<&Range>,
147 end_range: &Range,
148 kind: Option<FoldingRangeKind>,
149 ) {
150 let start = start_range.unwrap_or(end_range);
151 if start.start.line >= end_range.end.line {
152 return;
153 }
154 self.ranges.push(LexFoldingRange {
155 start_line: start.start.line as u32,
156 start_character: Some(start.start.column as u32),
157 end_line: end_range.end.line as u32,
158 end_character: Some(end_range.end.column as u32),
159 kind,
160 });
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use crate::test_support::sample_document;
168
169 #[test]
170 fn creates_ranges_for_sessions_and_definitions() {
171 let document = sample_document();
172 let ranges = folding_ranges(&document);
173 assert!(ranges
174 .iter()
175 .any(|range| range.kind == Some(FoldingRangeKind::Region) && range.start_line == 2));
176 assert!(ranges
177 .iter()
178 .any(|range| range.kind == Some(FoldingRangeKind::Region) && range.start_line > 2));
179 }
184}