Skip to main content

code_moniker_workspace/
lines.rs

1/// 1-indexed line range for the byte slice `[start, end)` in `source`.
2/// `end_line` is the line of the LAST byte (`end - 1`); empty / out-of-bounds
3/// ranges collapse to a single line at the start position.
4pub 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}