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 {
25 if !c.is_ascii() {
26 is_line_only_ascii = false;
27 }
28 }
29 }
30
31 line_only_ascii_vec.push(is_line_only_ascii);
32
33 assert_eq!(line_offsets.len(), line_only_ascii_vec.len());
34 LineIndex {
35 line_offsets,
36 line_only_ascii_vec,
37 }
38 }
39
40 pub fn get_line_offset(&self, line: usize) -> Option<TextSize> {
41 let line_index = line;
42 if line_index < self.line_offsets.len() {
43 let line_offset = self.line_offsets[line_index];
44 Some(line_offset)
45 } else {
46 None
47 }
48 }
49
50 pub fn get_line(&self, offset: TextSize) -> Option<usize> {
52 let offset_value = usize::from(offset);
53 match self
54 .line_offsets
55 .binary_search(&TextSize::from(offset_value as u32))
56 {
57 Ok(line) => Some(line),
58 Err(line) => {
59 if line > 0 {
60 Some(line - 1)
61 } else {
62 None
63 }
64 }
65 }
66 }
67
68 pub fn get_line_with_start_offset(&self, offset: TextSize) -> Option<(usize, TextSize)> {
69 let line = self.get_line(offset)?;
70 let start_offset = self.line_offsets[line];
71 Some((line, start_offset))
72 }
73
74 pub fn is_line_only_ascii(&self, line: TextSize) -> bool {
75 let line_index = usize::from(line);
76 if line_index < self.line_only_ascii_vec.len() {
77 self.line_only_ascii_vec[line_index]
78 } else {
79 false
80 }
81 }
82
83 pub fn line_count(&self) -> usize {
84 self.line_offsets.len()
85 }
86
87 pub fn get_col(&self, offset: TextSize, source_text: &str) -> Option<usize> {
89 let (line, start_offset) = self.get_line_with_start_offset(offset)?;
90 if self.is_line_only_ascii(line.try_into().unwrap()) {
91 Some(usize::from(offset - start_offset))
92 } else {
93 let text = &source_text[usize::from(start_offset)..usize::from(offset)];
94 Some(text.chars().count())
95 }
96 }
97
98 pub fn get_line_col(&self, offset: TextSize, source_text: &str) -> Option<(usize, usize)> {
100 let (line, start_offset) = self.get_line_with_start_offset(offset)?;
101 if self.is_line_only_ascii(line.try_into().unwrap()) {
102 Some((line, usize::from(offset - start_offset)))
103 } else {
104 let text = &source_text[usize::from(start_offset)..usize::from(offset)];
105 Some((line, text.chars().count()))
106 }
107 }
108
109 pub fn get_offset(&self, line: usize, col: usize, source_text: &str) -> Option<TextSize> {
111 let start_offset = self.get_line_offset(line)?;
112 if col == 0 {
113 return Some(start_offset);
114 }
115
116 if self.is_line_only_ascii(line.try_into().unwrap()) {
117 let col = col.min(source_text.len());
118 Some(start_offset + TextSize::from(col as u32))
119 } else {
120 let mut offset = 0;
121 let mut col = col;
122 for c in source_text[usize::from(start_offset)..].chars() {
123 if col == 0 {
124 break;
125 }
126
127 offset += c.len_utf8();
128 col -= 1;
129 }
130 Some(start_offset + TextSize::from(offset as u32))
131 }
132 }
133}