bbnf_analysis/
analysis.rs1use ls_types::*;
2
3use crate::state::{DocumentInfo, RuleInfo};
4
5#[derive(Debug, Clone)]
11pub struct LineIndex {
12 line_starts: Vec<usize>,
14 text_len: usize,
16}
17
18impl LineIndex {
19 pub fn new(text: &str) -> Self {
21 let mut line_starts = vec![0];
22 for (i, byte) in text.bytes().enumerate() {
23 if byte == b'\n' {
24 line_starts.push(i + 1);
25 }
26 }
27 Self {
28 line_starts,
29 text_len: text.len(),
30 }
31 }
32
33 pub fn offset_to_position(&self, offset: usize) -> Position {
35 let line = self.line_starts.partition_point(|&s| s <= offset).saturating_sub(1);
36 let col = offset.saturating_sub(self.line_starts[line]);
37 Position::new(line as u32, col as u32)
38 }
39
40 pub fn position_to_offset(&self, pos: Position) -> usize {
42 let line = pos.line as usize;
43 if line < self.line_starts.len() {
44 let line_start = self.line_starts[line];
45 let line_end = if line + 1 < self.line_starts.len() {
46 self.line_starts[line + 1].saturating_sub(1)
48 } else {
49 self.text_len
51 };
52 let requested = line_start + pos.character as usize;
53 requested.min(line_end)
54 } else {
55 panic!(
56 "position_to_offset received out-of-range line {} (line_count={})",
57 line,
58 self.line_starts.len()
59 );
60 }
61 }
62
63 pub fn span_to_range(&self, start: usize, end: usize) -> Range {
65 Range::new(self.offset_to_position(start), self.offset_to_position(end))
66 }
67}
68
69#[derive(Debug)]
75pub enum SymbolAtOffset<'a> {
76 RuleDefinition(&'a RuleInfo),
78 RuleReference {
80 name: String,
82 span: (usize, usize),
84 },
85}
86
87pub fn symbol_at_offset<'a>(info: &'a DocumentInfo, offset: usize) -> Option<SymbolAtOffset<'a>> {
89 for rule in &info.rules {
91 if offset >= rule.name_span.0 && offset <= rule.name_span.1 {
92 return Some(SymbolAtOffset::RuleDefinition(rule));
93 }
94 }
95 for rule in &info.rules {
97 for refinfo in &rule.references {
98 if offset >= refinfo.span.0 && offset <= refinfo.span.1 {
99 return Some(SymbolAtOffset::RuleReference {
100 name: refinfo.name.clone(),
101 span: refinfo.span,
102 });
103 }
104 }
105 }
106 for rec in &info.recovers {
108 if offset >= rec.rule_name_span.0 && offset <= rec.rule_name_span.1 {
109 return Some(SymbolAtOffset::RuleReference {
110 name: rec.rule_name.clone(),
111 span: rec.rule_name_span,
112 });
113 }
114 }
115 for nc in &info.no_collapses {
117 if offset >= nc.rule_name_span.0 && offset <= nc.rule_name_span.1 {
118 return Some(SymbolAtOffset::RuleReference {
119 name: nc.rule_name.clone(),
120 span: nc.rule_name_span,
121 });
122 }
123 }
124 for pretty in &info.pretties {
126 if offset >= pretty.rule_name_span.0 && offset <= pretty.rule_name_span.1 {
127 return Some(SymbolAtOffset::RuleReference {
128 name: pretty.rule_name.clone(),
129 span: pretty.rule_name_span,
130 });
131 }
132 }
133 None
134}