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`); 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#[cfg(test)]
18mod tests {
19	use super::*;
20
21	#[test]
22	fn single_line_def_is_one_line() {
23		let s = "alpha\nbeta\ngamma\n";
24		assert_eq!(line_range(s, 0, 5), (1, 1));
25	}
26
27	#[test]
28	fn multi_line_def_spans_lines_inclusive() {
29		let s = "alpha\nbeta\ngamma\n";
30		assert_eq!(line_range(s, 0, 11), (1, 2));
31	}
32
33	#[test]
34	fn def_starting_on_line_three() {
35		let s = "a\nb\nc\nd\n";
36		assert_eq!(line_range(s, 4, 5), (3, 3));
37	}
38
39	#[test]
40	fn def_ending_at_eof_without_newline() {
41		let s = "a\nb\nc";
42		assert_eq!(line_range(s, 4, 5), (3, 3));
43	}
44
45	#[test]
46	fn out_of_bounds_clamps_safely() {
47		let s = "a\nb\n";
48		assert_eq!(line_range(s, 100, 200), (3, 3));
49	}
50
51	#[test]
52	fn end_before_start_collapses_to_start() {
53		let s = "a\nb\nc\n";
54		assert_eq!(line_range(s, 4, 2), (3, 3));
55	}
56}