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}