1#![warn(missing_docs)]
2use perl_lexer::{PerlLexer, TokenType};
8use perl_parser_core::ast::{Node, NodeKind, SourceLocation};
9
10pub struct FoldingRangeExtractor {
12 ranges: Vec<FoldingRange>,
14}
15
16#[derive(Debug, Clone)]
27pub struct FoldingRange {
28 pub start_offset: usize, pub end_offset: usize, pub kind: Option<FoldingRangeKind>,
34}
35
36#[derive(Debug, Clone)]
46pub enum FoldingRangeKind {
47 Comment,
49 Imports,
51 Region,
53}
54
55impl Default for FoldingRangeExtractor {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl FoldingRangeExtractor {
62 pub fn new() -> Self {
64 Self { ranges: Vec::new() }
65 }
66
67 pub fn extract(&mut self, ast: &Node) -> Vec<FoldingRange> {
69 self.ranges.clear();
70 self.visit_node(ast);
71 self.ranges.clone()
72 }
73
74 pub fn extract_heredoc_ranges(text: &str) -> Vec<FoldingRange> {
78 let mut ranges = Vec::new();
79 let mut lexer = PerlLexer::new(text);
80
81 while let Some(token) = lexer.next_token() {
82 if matches!(token.token_type, TokenType::HeredocBody(_)) {
83 ranges.push(FoldingRange {
84 start_offset: token.start,
85 end_offset: token.end,
86 kind: Some(FoldingRangeKind::Region),
87 });
88 }
89
90 if matches!(token.token_type, TokenType::EOF) {
92 break;
93 }
94 }
95
96 ranges
97 }
98
99 fn visit_node(&mut self, node: &Node) {
101 match &node.kind {
102 NodeKind::Program { statements } => {
103 let mut import_start: Option<usize> = None;
105 let mut import_end: Option<usize> = None;
106
107 for (i, stmt) in statements.iter().enumerate() {
108 match &stmt.kind {
109 NodeKind::Use { .. } | NodeKind::No { .. } => {
110 if import_start.is_none() {
111 import_start = Some(i);
112 }
113 import_end = Some(i);
114 }
115 _ => {
116 if let (Some(start_idx), Some(end_idx)) = (import_start, import_end) {
118 if end_idx > start_idx {
119 let start_loc = &statements[start_idx].location;
121 let end_loc = &statements[end_idx].location;
122 self.add_range_from_locations(
123 start_loc,
124 end_loc,
125 Some(FoldingRangeKind::Imports),
126 );
127 }
128 }
129 import_start = None;
130 import_end = None;
131 }
132 }
133
134 self.visit_node(stmt);
136 }
137
138 if let (Some(start_idx), Some(end_idx)) = (import_start, import_end) {
140 if end_idx > start_idx {
141 let start_loc = &statements[start_idx].location;
142 let end_loc = &statements[end_idx].location;
143 self.add_range_from_locations(
144 start_loc,
145 end_loc,
146 Some(FoldingRangeKind::Imports),
147 );
148 }
149 }
150 }
151
152 NodeKind::Package { name: _, block, name_span: _ } => {
153 if let Some(block_node) = block {
155 self.add_range_from_node(node, None);
156 self.visit_node(block_node);
157 } else {
158 self.add_range_from_node(node, None);
161 }
162 }
163
164 NodeKind::Subroutine { name: _, prototype: _, signature: _, body, .. }
165 | NodeKind::Method { name: _, signature: _, body, .. } => {
166 self.add_range_from_node(node, None);
168 self.visit_node(body);
169 }
170
171 NodeKind::Block { statements } => {
172 if !statements.is_empty() {
174 self.add_range_from_node(node, None);
175 }
176 for stmt in statements {
177 self.visit_node(stmt);
178 }
179 }
180
181 NodeKind::If { condition: _, then_branch, elsif_branches, else_branch } => {
182 self.add_range_from_node(node, None);
184 self.visit_node(then_branch);
185 for (_, branch) in elsif_branches {
186 self.visit_node(branch);
187 }
188 if let Some(else_br) = else_branch {
189 self.visit_node(else_br);
190 }
191 }
192
193 NodeKind::While { condition: _, body, continue_block } => {
194 self.add_range_from_node(node, None);
195 self.visit_node(body);
196 if let Some(cont) = continue_block {
197 self.visit_node(cont);
198 }
199 }
200
201 NodeKind::For { init: _, condition: _, update: _, body, continue_block: _ }
202 | NodeKind::Foreach { variable: _, list: _, body, continue_block: _ } => {
203 self.add_range_from_node(node, None);
204 self.visit_node(body);
205 }
206
207 NodeKind::Do { block } | NodeKind::Eval { block } => {
208 self.add_range_from_node(node, None);
209 self.visit_node(block);
210 }
211
212 NodeKind::Try { body, catch_blocks, finally_block } => {
213 self.add_range_from_node(node, None);
214 self.visit_node(body);
215 for (_, catch_block) in catch_blocks {
216 self.visit_node(catch_block);
217 }
218 if let Some(finally) = finally_block {
219 self.visit_node(finally);
220 }
221 }
222
223 NodeKind::Given { expr: _, body } => {
224 self.add_range_from_node(node, None);
225 self.visit_node(body);
226 }
227
228 NodeKind::PhaseBlock { phase: _, phase_span: _, block } => {
229 self.add_range_from_node(node, None);
231 self.visit_node(block);
232 }
233
234 NodeKind::Class { name: _, body } => {
235 self.add_range_from_node(node, None);
236 self.visit_node(body);
237 }
238
239 NodeKind::Heredoc { .. } => {
241 self.add_range_from_node(node, Some(FoldingRangeKind::Region));
243 }
244
245 NodeKind::StatementModifier { statement, modifier: _, condition } => {
246 self.visit_node(statement);
247 self.visit_node(condition);
248 }
249
250 NodeKind::ArrayLiteral { elements } => {
251 if !elements.is_empty() {
254 self.add_range_from_node(node, None);
255 }
256 for elem in elements {
257 self.visit_node(elem);
258 }
259 }
260
261 NodeKind::HashLiteral { pairs } => {
262 if !pairs.is_empty() {
264 self.add_range_from_node(node, None);
265 }
266 for (key, value) in pairs {
267 self.visit_node(key);
268 self.visit_node(value);
269 }
270 }
271
272 NodeKind::VariableDeclaration { initializer: Some(init), .. } => {
274 self.visit_node(init);
275 }
276
277 NodeKind::DataSection { marker: _, body } => {
278 if body.is_some() {
280 self.add_range_from_node(node, Some(FoldingRangeKind::Comment));
281 }
282 }
283
284 NodeKind::LabeledStatement { label: _, statement } => {
285 self.add_range_from_node(node, None);
287 self.visit_node(statement);
288 }
289
290 NodeKind::Format { .. } => {
291 self.add_range_from_node(node, Some(FoldingRangeKind::Region));
293 }
294
295 NodeKind::Tie { variable, package, args } => {
296 self.add_range_from_node(node, None);
298 self.visit_node(variable);
299 self.visit_node(package);
300 for arg in args {
301 self.visit_node(arg);
302 }
303 }
304
305 _ => {}
307 }
308 }
309
310 fn add_range_from_node(&mut self, node: &Node, kind: Option<FoldingRangeKind>) {
312 let start_offset = node.location.start;
314 let end_offset = node.location.end;
315
316 if end_offset > start_offset + 1 {
318 self.ranges.push(FoldingRange { start_offset, end_offset, kind });
319 }
320 }
321
322 fn add_range_from_locations(
324 &mut self,
325 start: &SourceLocation,
326 end: &SourceLocation,
327 kind: Option<FoldingRangeKind>,
328 ) {
329 let start_offset = start.start;
330 let end_offset = end.end;
331
332 if end_offset > start_offset + 1 {
333 self.ranges.push(FoldingRange { start_offset, end_offset, kind });
334 }
335 }
336}