emmylua_parser/text/
line_index.rs

1use 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; // 记录每行的字节偏移量
21                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    // get line base 0
49    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    // get col base 0
86    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    // get line and col base 0
97    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    // get offset by line and col
108    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}