code_moniker_workspace/
lines.rs1pub fn line_range(source: &str, start: u32, end: u32) -> (u32, u32) {
5 let bytes = source.as_bytes();
6 let s = (start as usize).min(bytes.len());
7 let e = (end as usize).min(bytes.len()).max(s);
8 let start_line = 1 + bytes[..s].iter().filter(|b| **b == b'\n').count() as u32;
9 let last = if e > s { e - 1 } else { s };
10 let end_line = 1 + bytes[..last.min(bytes.len())]
11 .iter()
12 .filter(|b| **b == b'\n')
13 .count() as u32;
14 (start_line, end_line)
15}
16
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct LineIndex {
19 source_len: usize,
20 line_starts: Vec<usize>,
21}
22
23impl LineIndex {
24 pub fn new(source: &str) -> Self {
25 let mut line_starts = vec![0];
26 for (idx, byte) in source.as_bytes().iter().enumerate() {
27 if *byte == b'\n' {
28 line_starts.push(idx + 1);
29 }
30 }
31 Self {
32 source_len: source.len(),
33 line_starts,
34 }
35 }
36
37 pub fn line_range(&self, start: u32, end: u32) -> (u32, u32) {
38 let s = (start as usize).min(self.source_len);
39 let e = (end as usize).min(self.source_len).max(s);
40 let last = if e > s { e - 1 } else { s };
41 (self.line_at(s), self.line_at(last.min(self.source_len)))
42 }
43
44 fn line_at(&self, offset: usize) -> u32 {
45 self.line_starts.partition_point(|start| *start <= offset) as u32
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn single_line_def_is_one_line() {
55 let s = "alpha\nbeta\ngamma\n";
56 assert_eq!(line_range(s, 0, 5), (1, 1));
57 }
58
59 #[test]
60 fn multi_line_def_spans_lines_inclusive() {
61 let s = "alpha\nbeta\ngamma\n";
62 assert_eq!(line_range(s, 0, 11), (1, 2));
63 }
64
65 #[test]
66 fn def_starting_on_line_three() {
67 let s = "a\nb\nc\nd\n";
68 assert_eq!(line_range(s, 4, 5), (3, 3));
69 }
70
71 #[test]
72 fn def_ending_at_eof_without_newline() {
73 let s = "a\nb\nc";
74 assert_eq!(line_range(s, 4, 5), (3, 3));
75 }
76
77 #[test]
78 fn out_of_bounds_clamps_safely() {
79 let s = "a\nb\n";
80 assert_eq!(line_range(s, 100, 200), (3, 3));
81 }
82
83 #[test]
84 fn end_before_start_collapses_to_start() {
85 let s = "a\nb\nc\n";
86 assert_eq!(line_range(s, 4, 2), (3, 3));
87 }
88
89 #[test]
90 fn line_index_matches_line_range() {
91 let s = "alpha\nbeta\ngamma\n";
92 let index = LineIndex::new(s);
93 for start in 0..=s.len() as u32 + 2 {
94 for end in 0..=s.len() as u32 + 2 {
95 assert_eq!(
96 index.line_range(start, end),
97 line_range(s, start, end),
98 "range {start}..{end}"
99 );
100 }
101 }
102 }
103}