1#![no_std]
2#![doc = include_str!("../README.md")]
3#[cfg(test)]
4mod tests;
5
6const UNINIT_LINE_COL: (u32, u32) = (0, 0);
7
8pub fn line_columns<const N: usize>(
10 s: &str,
11 indexs: [usize; N],
12) -> [(u32, u32); N] {
13 let len = s.len();
14
15 for index in indexs {
16 assert!(index <= len,
17 "index {index} out of str length {len} of `{s:?}`");
18 assert!(s.is_char_boundary(index),
19 "byte index {index} is not a char boundary of `{s:?}`");
20 }
21
22 let result = line_columns_unchecked(s, indexs);
23
24 debug_assert!(! result.contains(&UNINIT_LINE_COL),
25 "impl error, report bug issue");
26 result
27}
28
29pub fn line_columns_unchecked<const N: usize>(
34 s: &str,
35 indexs: [usize; N],
36) -> [(u32, u32); N] {
37 let len = s.len();
38 let mut result = [UNINIT_LINE_COL; N];
39
40 let last_loc = s.char_indices()
41 .fold((1, 1), |(line, column), (cur, ch)|
42 {
43 for (i, &index) in indexs.iter().enumerate() {
44 if index == cur {
45 result[i] = (line, column);
46 }
47 }
48
49 if ch == '\n' {
50 (line+1, 1)
51 } else {
52 (line, column+1)
53 }
54 });
55
56 for (i, &index) in indexs.iter().enumerate() {
57 if index == len {
58 result[i] = last_loc;
59 }
60 }
61
62 result
63}
64
65pub fn index(s: &str, line: u32, column: u32) -> usize {
83 assert_ne!(line, 0);
84 assert_ne!(column, 0);
85 let mut i = 0;
86 for _ in 1..line {
87 let Some(lf) = s[i..].find('\n') else { break };
88 i += lf+1;
89 }
90 let s = &s[i..];
91 let lf = s.find('\n').map_or(s.len(), |l| l+1);
92 let s = &s[..lf];
93 i + s.char_indices()
94 .nth(column as usize-1)
95 .map_or(s.len(), |x| x.0)
96}
97
98#[inline]
114pub fn line_column(s: &str, index: usize) -> (u32, u32) {
115 line_columns(s, [index])[0]
116}