Skip to main content

code_moniker_cli/
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`) — a slice that ends
3/// just past a `\n` does not yet "touch" the next line. Empty / out-of-bounds
4/// ranges collapse to a single line at the start position. Convention matches
5/// what every IDE shows the user.
6pub fn line_range(source: &str, start: u32, end: u32) -> (u32, u32) {
7	let bytes = source.as_bytes();
8	let s = (start as usize).min(bytes.len());
9	let e = (end as usize).min(bytes.len()).max(s);
10	let start_line = 1 + bytes[..s].iter().filter(|b| **b == b'\n').count() as u32;
11	let last = if e > s { e - 1 } else { s };
12	let end_line = 1 + bytes[..last.min(bytes.len())]
13		.iter()
14		.filter(|b| **b == b'\n')
15		.count() as u32;
16	(start_line, end_line)
17}
18
19#[cfg(test)]
20mod tests {
21	use super::*;
22
23	#[test]
24	fn single_line_def_is_one_line() {
25		let s = "alpha\nbeta\ngamma\n";
26		assert_eq!(line_range(s, 0, 5), (1, 1));
27	}
28
29	#[test]
30	fn multi_line_def_spans_lines_inclusive() {
31		let s = "alpha\nbeta\ngamma\n";
32		assert_eq!(line_range(s, 0, 11), (1, 2));
33	}
34
35	#[test]
36	fn def_starting_on_line_three() {
37		let s = "a\nb\nc\nd\n";
38		assert_eq!(line_range(s, 4, 5), (3, 3));
39	}
40
41	#[test]
42	fn def_ending_at_eof_without_newline() {
43		let s = "a\nb\nc";
44		assert_eq!(line_range(s, 4, 5), (3, 3));
45	}
46
47	#[test]
48	fn out_of_bounds_clamps_safely() {
49		let s = "a\nb\n";
50		assert_eq!(line_range(s, 100, 200), (3, 3));
51	}
52
53	#[test]
54	fn end_before_start_collapses_to_start() {
55		let s = "a\nb\nc\n";
56		assert_eq!(line_range(s, 4, 2), (3, 3));
57	}
58}