emmylua_parser/text/
line_index.rs1use rowan::TextSize;
2
3#[derive(Debug, Clone)]
4pub struct LineIndex {
5 line_offsets: Vec<TextSize>,
6 line_only_ascii_vec: Vec<bool>,
7}
8
9impl LineIndex {
10 pub fn parse(text: &str) -> LineIndex {
11 let mut line_offsets = Vec::new();
12 let mut line_only_ascii_vec = Vec::new();
13 let mut offset = 0;
14
15 line_offsets.push(TextSize::from(offset as u32));
16
17 let mut is_line_only_ascii = true;
18 for (i, c) in text.char_indices() {
19 if c == '\n' {
20 offset = i + 1; line_offsets.push(TextSize::from(offset as u32));
22 line_only_ascii_vec.push(is_line_only_ascii);
23 is_line_only_ascii = true;
24 } else if !c.is_ascii() {
25 is_line_only_ascii = false;
26 }
27 }
28
29 line_only_ascii_vec.push(is_line_only_ascii);
30
31 assert_eq!(line_offsets.len(), line_only_ascii_vec.len());
32 LineIndex {
33 line_offsets,
34 line_only_ascii_vec,
35 }
36 }
37
38 pub fn get_line_offset(&self, line: usize) -> Option<TextSize> {
39 let line_index = line;
40 if line_index < self.line_offsets.len() {
41 let line_offset = self.line_offsets[line_index];
42 Some(line_offset)
43 } else {
44 None
45 }
46 }
47
48 pub fn get_line(&self, offset: TextSize) -> Option<usize> {
50 let offset_value = usize::from(offset);
51 match self
52 .line_offsets
53 .binary_search(&TextSize::from(offset_value as u32))
54 {
55 Ok(line) => Some(line),
56 Err(line) => {
57 if line > 0 {
58 Some(line - 1)
59 } else {
60 None
61 }
62 }
63 }
64 }
65
66 pub fn get_line_with_start_offset(&self, offset: TextSize) -> Option<(usize, TextSize)> {
67 let line = self.get_line(offset)?;
68 let start_offset = self.line_offsets[line];
69 Some((line, start_offset))
70 }
71
72 pub fn is_line_only_ascii(&self, line: TextSize) -> bool {
73 let line_index = usize::from(line);
74 if line_index < self.line_only_ascii_vec.len() {
75 self.line_only_ascii_vec[line_index]
76 } else {
77 false
78 }
79 }
80
81 pub fn line_count(&self) -> usize {
82 self.line_offsets.len()
83 }
84
85 pub fn get_col(&self, offset: TextSize, source_text: &str) -> Option<usize> {
87 let (line, start_offset) = self.get_line_with_start_offset(offset)?;
88 if self.is_line_only_ascii(line.try_into().unwrap()) {
89 Some(usize::from(offset - start_offset))
90 } else {
91 let text = &source_text[usize::from(start_offset)..usize::from(offset)];
92 Some(text.chars().count())
93 }
94 }
95
96 pub fn get_line_col(&self, offset: TextSize, source_text: &str) -> Option<(usize, usize)> {
98 let (line, start_offset) = self.get_line_with_start_offset(offset)?;
99 if self.is_line_only_ascii(line.try_into().unwrap()) {
100 Some((line, usize::from(offset - start_offset)))
101 } else {
102 let text = &source_text[usize::from(start_offset)..usize::from(offset)];
103 Some((line, text.chars().count()))
104 }
105 }
106
107 pub fn get_offset(&self, line: usize, col: usize, source_text: &str) -> Option<TextSize> {
109 let start_offset = self.get_line_offset(line)?;
110 if col == 0 {
111 return Some(start_offset);
112 }
113
114 if self.is_line_only_ascii(line.try_into().unwrap()) {
115 let col = col.min(source_text.len());
116 Some(start_offset + TextSize::from(col as u32))
117 } else {
118 let mut offset = 0;
119 let mut col = col;
120 for c in source_text[usize::from(start_offset)..].chars() {
121 if col == 0 {
122 break;
123 }
124
125 offset += c.len_utf8();
126 col -= 1;
127 }
128 Some(start_offset + TextSize::from(offset as u32))
129 }
130 }
131
132 pub fn get_col_offset_at_line(
133 &self,
134 line: usize,
135 col: usize,
136 source_text: &str,
137 ) -> Option<TextSize> {
138 let start_offset = self.get_line_offset(line)?;
139 if col == 0 {
140 return Some(0.into());
141 }
142
143 if self.is_line_only_ascii(line.try_into().unwrap()) {
144 let col = col.min(source_text.len());
145 Some(TextSize::from(col as u32))
146 } else {
147 let mut offset = 0;
148 let mut col = col;
149 for c in source_text[usize::from(start_offset)..].chars() {
150 if col == 0 {
151 break;
152 }
153
154 offset += c.len_utf8();
155 col -= 1;
156 }
157 Some(TextSize::from(offset as u32))
158 }
159 }
160}