Skip to main content

bbnf_analysis/features/
selection_range.rs

1use ls_types::*;
2
3use bbnf::types::{Expression, Token};
4
5use crate::state::DocumentState;
6
7/// Extract the inner value from a TokenExpression.
8fn get_inner_expression<'a, T>(tok: &'a Token<'a, T>) -> &'a T {
9    &tok.value
10}
11
12/// Compute selection ranges for each requested position.
13///
14/// Builds a chain of nested ranges from innermost (token) to outermost (full rule),
15/// enabling "Expand/Shrink Selection" in the editor.
16pub fn selection_ranges(state: &DocumentState, positions: Vec<Position>) -> Vec<SelectionRange> {
17    // Use the cached AST (no re-parsing needed).
18    let ast = state.ast().unwrap_or_else(|| panic!(
19        "selection_ranges requested for document with no parsed AST"
20    ));
21
22    positions
23        .iter()
24        .map(|&pos| {
25            let offset = state.line_index.position_to_offset(pos);
26            compute_selection_range(&state.line_index, ast, offset).unwrap_or_else(|| {
27                panic!(
28                    "selection_ranges could not resolve a span chain for position {}:{}",
29                    pos.line, pos.character
30                )
31            })
32        })
33        .collect()
34}
35
36/// Walk the AST to find all spans containing the offset, ordered innermost-first.
37fn compute_selection_range(
38    line_index: &crate::analysis::LineIndex,
39    ast: &bbnf::types::AST<'_>,
40    offset: usize,
41) -> Option<SelectionRange> {
42    // Find which rule contains this offset.
43    for (lhs, rhs) in ast.iter() {
44        if let Expression::Nonterminal(Token { span: name_span, .. }) = lhs {
45            let rule_start = name_span.start;
46            let rule_end = crate::state::compute_expression_end_pub(rhs).unwrap_or_else(|| {
47                panic!(
48                    "compute_selection_range could not compute expression end for rule at {}",
49                    name_span.start
50                )
51            });
52
53            if offset < rule_start || offset > rule_end {
54                continue;
55            }
56
57            // Collect nested spans from the RHS expression tree.
58            let mut spans = Vec::new();
59            collect_spans(rhs, offset, &mut spans);
60
61            // Add the full rule span as the outermost.
62            spans.push((rule_start, rule_end));
63
64            // Sort spans innermost-first (smallest to largest).
65            spans.sort_by_key(|(start, end)| *end - *start);
66            spans.dedup();
67
68            // Build the chain from innermost to outermost.
69            let mut result: Option<SelectionRange> = None;
70            for (start, end) in spans.into_iter().rev() {
71                let range = line_index.span_to_range(start, end);
72                result = Some(SelectionRange {
73                    range,
74                    parent: result.map(Box::new),
75                });
76            }
77
78            return result;
79        }
80    }
81    None
82}
83
84/// Recursively collect all expression spans that contain the given offset.
85fn collect_spans(expr: &Expression<'_>, offset: usize, spans: &mut Vec<(usize, usize)>) {
86    match expr {
87        Expression::Literal(tok) | Expression::Nonterminal(tok) | Expression::Regex(tok) => {
88            if offset >= tok.span.start && offset <= tok.span.end {
89                spans.push((tok.span.start, tok.span.end));
90            }
91        }
92        Expression::Epsilon(tok) => {
93            if offset >= tok.span.start && offset <= tok.span.end {
94                spans.push((tok.span.start, tok.span.end));
95            }
96        }
97        Expression::Alternation(inner) | Expression::Concatenation(inner) => {
98            if offset >= inner.span.start && offset <= inner.span.end {
99                spans.push((inner.span.start, inner.span.end));
100            }
101            for child in get_inner_expression(inner) {
102                collect_spans(child, offset, spans);
103            }
104        }
105        Expression::Group(inner)
106        | Expression::Optional(inner)
107        | Expression::Many(inner)
108        | Expression::Many1(inner)
109        | Expression::OptionalWhitespace(inner) => {
110            if offset >= inner.span.start && offset <= inner.span.end {
111                spans.push((inner.span.start, inner.span.end));
112            }
113            collect_spans(get_inner_expression(inner), offset, spans);
114        }
115        Expression::Skip(l, r) | Expression::Next(l, r) | Expression::Minus(l, r) => {
116            // Use the combined span of both operands.
117            let start = l.span.start;
118            let end = r.span.end;
119            if offset >= start && offset <= end {
120                spans.push((start, end));
121            }
122            collect_spans(get_inner_expression(l), offset, spans);
123            collect_spans(get_inner_expression(r), offset, spans);
124        }
125        Expression::Rule(rhs, _) => {
126            collect_spans(rhs, offset, spans);
127        }
128        Expression::MappedExpression((expr_tok, _)) => {
129            collect_spans(get_inner_expression(expr_tok), offset, spans);
130        }
131        Expression::DebugExpression((expr_tok, _)) => {
132            collect_spans(get_inner_expression(expr_tok), offset, spans);
133        }
134        _ => {}
135    }
136}