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 {
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    // get line base 0
51    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    // get col base 0
88    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    // get line and col base 0
99    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    // get offset by line and col
110    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}