#![warn(missing_docs)]
use perl_lexer::{PerlLexer, TokenType};
use perl_parser_core::ast::{Node, NodeKind, SourceLocation};
pub struct FoldingRangeExtractor {
ranges: Vec<FoldingRange>,
}
#[derive(Debug, Clone)]
pub struct FoldingRange {
pub start_offset: usize, pub end_offset: usize, pub kind: Option<FoldingRangeKind>,
}
#[derive(Debug, Clone)]
pub enum FoldingRangeKind {
Comment,
Imports,
Region,
}
impl Default for FoldingRangeExtractor {
fn default() -> Self {
Self::new()
}
}
impl FoldingRangeExtractor {
pub fn new() -> Self {
Self { ranges: Vec::new() }
}
pub fn extract(&mut self, ast: &Node) -> Vec<FoldingRange> {
self.ranges.clear();
self.visit_node(ast);
self.ranges.clone()
}
pub fn extract_heredoc_ranges(text: &str) -> Vec<FoldingRange> {
let mut ranges = Vec::new();
let mut lexer = PerlLexer::new(text);
while let Some(token) = lexer.next_token() {
if matches!(token.token_type, TokenType::HeredocBody(_)) {
ranges.push(FoldingRange {
start_offset: token.start,
end_offset: token.end,
kind: Some(FoldingRangeKind::Region),
});
}
if matches!(token.token_type, TokenType::EOF) {
break;
}
}
ranges
}
fn visit_node(&mut self, node: &Node) {
match &node.kind {
NodeKind::Program { statements } => {
let mut import_start: Option<usize> = None;
let mut import_end: Option<usize> = None;
for (i, stmt) in statements.iter().enumerate() {
match &stmt.kind {
NodeKind::Use { .. } | NodeKind::No { .. } => {
if import_start.is_none() {
import_start = Some(i);
}
import_end = Some(i);
}
_ => {
if let (Some(start_idx), Some(end_idx)) = (import_start, import_end) {
if end_idx > start_idx {
let start_loc = &statements[start_idx].location;
let end_loc = &statements[end_idx].location;
self.add_range_from_locations(
start_loc,
end_loc,
Some(FoldingRangeKind::Imports),
);
}
}
import_start = None;
import_end = None;
}
}
self.visit_node(stmt);
}
if let (Some(start_idx), Some(end_idx)) = (import_start, import_end) {
if end_idx > start_idx {
let start_loc = &statements[start_idx].location;
let end_loc = &statements[end_idx].location;
self.add_range_from_locations(
start_loc,
end_loc,
Some(FoldingRangeKind::Imports),
);
}
}
}
NodeKind::Package { name: _, block, name_span: _ } => {
if let Some(block_node) = block {
self.add_range_from_node(node, None);
self.visit_node(block_node);
} else {
self.add_range_from_node(node, None);
}
}
NodeKind::Subroutine { name: _, prototype: _, signature: _, body, .. }
| NodeKind::Method { name: _, signature: _, body, .. } => {
self.add_range_from_node(node, None);
self.visit_node(body);
}
NodeKind::Block { statements } => {
if !statements.is_empty() {
self.add_range_from_node(node, None);
}
for stmt in statements {
self.visit_node(stmt);
}
}
NodeKind::If { condition: _, then_branch, elsif_branches, else_branch } => {
self.add_range_from_node(node, None);
self.visit_node(then_branch);
for (_, branch) in elsif_branches {
self.visit_node(branch);
}
if let Some(else_br) = else_branch {
self.visit_node(else_br);
}
}
NodeKind::While { condition: _, body, continue_block } => {
self.add_range_from_node(node, None);
self.visit_node(body);
if let Some(cont) = continue_block {
self.visit_node(cont);
}
}
NodeKind::For { init: _, condition: _, update: _, body, continue_block: _ }
| NodeKind::Foreach { variable: _, list: _, body, continue_block: _ } => {
self.add_range_from_node(node, None);
self.visit_node(body);
}
NodeKind::Do { block } | NodeKind::Eval { block } => {
self.add_range_from_node(node, None);
self.visit_node(block);
}
NodeKind::Try { body, catch_blocks, finally_block } => {
self.add_range_from_node(node, None);
self.visit_node(body);
for (_, catch_block) in catch_blocks {
self.visit_node(catch_block);
}
if let Some(finally) = finally_block {
self.visit_node(finally);
}
}
NodeKind::Given { expr: _, body } => {
self.add_range_from_node(node, None);
self.visit_node(body);
}
NodeKind::PhaseBlock { phase: _, phase_span: _, block } => {
self.add_range_from_node(node, None);
self.visit_node(block);
}
NodeKind::Class { name: _, body } => {
self.add_range_from_node(node, None);
self.visit_node(body);
}
NodeKind::Heredoc { .. } => {
self.add_range_from_node(node, Some(FoldingRangeKind::Region));
}
NodeKind::StatementModifier { statement, modifier: _, condition } => {
self.visit_node(statement);
self.visit_node(condition);
}
NodeKind::ArrayLiteral { elements } => {
if !elements.is_empty() {
self.add_range_from_node(node, None);
}
for elem in elements {
self.visit_node(elem);
}
}
NodeKind::HashLiteral { pairs } => {
if !pairs.is_empty() {
self.add_range_from_node(node, None);
}
for (key, value) in pairs {
self.visit_node(key);
self.visit_node(value);
}
}
NodeKind::VariableDeclaration { initializer: Some(init), .. } => {
self.visit_node(init);
}
NodeKind::DataSection { marker: _, body } => {
if body.is_some() {
self.add_range_from_node(node, Some(FoldingRangeKind::Comment));
}
}
NodeKind::LabeledStatement { label: _, statement } => {
self.add_range_from_node(node, None);
self.visit_node(statement);
}
NodeKind::Format { .. } => {
self.add_range_from_node(node, Some(FoldingRangeKind::Region));
}
NodeKind::Tie { variable, package, args } => {
self.add_range_from_node(node, None);
self.visit_node(variable);
self.visit_node(package);
for arg in args {
self.visit_node(arg);
}
}
_ => {}
}
}
fn add_range_from_node(&mut self, node: &Node, kind: Option<FoldingRangeKind>) {
let start_offset = node.location.start;
let end_offset = node.location.end;
if end_offset > start_offset + 1 {
self.ranges.push(FoldingRange { start_offset, end_offset, kind });
}
}
fn add_range_from_locations(
&mut self,
start: &SourceLocation,
end: &SourceLocation,
kind: Option<FoldingRangeKind>,
) {
let start_offset = start.start;
let end_offset = end.end;
if end_offset > start_offset + 1 {
self.ranges.push(FoldingRange { start_offset, end_offset, kind });
}
}
}