oak_vfs/
line_map.rs

1use oak_core::source::Source;
2
3#[derive(Debug, Clone)]
4pub struct LineMap {
5    line_starts: Vec<usize>,
6    len: usize,
7}
8
9impl LineMap {
10    pub fn from_source(source: &dyn Source) -> Self {
11        let len = source.length();
12        let mut line_starts = Vec::new();
13        line_starts.push(0);
14
15        let mut offset = 0usize;
16        while offset < len {
17            let chunk = source.chunk_at(offset);
18            let text = chunk.slice_from(offset);
19            for (i, b) in text.as_bytes().iter().enumerate() {
20                if *b == b'\n' {
21                    let next = offset + i + 1;
22                    if next <= len {
23                        line_starts.push(next);
24                    }
25                }
26            }
27            offset = chunk.end();
28        }
29
30        Self { line_starts, len }
31    }
32
33    pub fn line_count(&self) -> usize {
34        self.line_starts.len()
35    }
36
37    pub fn line_start(&self, line: u32) -> Option<usize> {
38        self.line_starts.get(line as usize).copied()
39    }
40
41    pub fn line_end(&self, line: u32) -> Option<usize> {
42        let idx = line as usize;
43        let start = *self.line_starts.get(idx)?;
44        let next = self.line_starts.get(idx + 1).copied().unwrap_or(self.len);
45        Some(next.max(start))
46    }
47
48    pub fn offset_to_line_col_utf16(&self, source: &dyn Source, offset: usize) -> (u32, u32) {
49        let offset = offset.min(self.len);
50        let line_idx = match self.line_starts.binary_search(&offset) {
51            Ok(i) => i,
52            Err(0) => 0,
53            Err(i) => i - 1,
54        };
55        let line_start = self.line_starts[line_idx];
56        let slice = source.get_text_in(core::range::Range { start: line_start, end: offset });
57        let col = slice.as_ref().encode_utf16().count() as u32;
58        (line_idx as u32, col)
59    }
60
61    pub fn line_col_utf16_to_offset(&self, source: &dyn Source, line: u32, col_utf16: u32) -> usize {
62        let Some(line_start) = self.line_start(line)
63        else {
64            return self.len;
65        };
66        let line_end = self.line_end(line).unwrap_or(self.len);
67        let slice = source.get_text_in(core::range::Range { start: line_start, end: line_end });
68        let text = slice.as_ref();
69        let target = col_utf16 as usize;
70
71        let mut utf16 = 0usize;
72        for (byte_idx, ch) in text.char_indices() {
73            if utf16 >= target {
74                return (line_start + byte_idx).min(self.len);
75            }
76            utf16 += ch.len_utf16();
77        }
78        line_end.min(self.len)
79    }
80}