line_column/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#[cfg(test)]
4mod tests;
5
6const UNINIT_LINE_COL: (u32, u32) = (0, 0);
7
8/// Get multiple sets of lines and columns may be faster
9pub 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
29/// Get multiple of lines and columns may be faster
30///
31/// If the index does not fall on the character boundary,
32/// the unspecified results
33pub 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
65/// Get str index of line and column
66///
67/// If the line or column out the length of the `s`, return `s.len()`
68///
69/// # Panics
70/// - line or column by zero
71///
72/// # Examples
73/// ```
74/// # use line_column::index;
75/// assert_eq!(index("", 1, 1), 0);
76/// assert_eq!(index("a", 1, 1), 0);
77/// assert_eq!(index("a", 1, 2), 1);
78/// assert_eq!(index("a\n", 1, 2), 1);
79/// assert_eq!(index("a\n", 2, 1), 2);
80/// assert_eq!(index("a\nx", 2, 2), 3);
81/// ```
82pub 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/// Get tuple of line and column
99///
100/// Use LF (0x0A) to split newline, also compatible with CRLF (0x0D 0x0A)
101///
102/// # Examples
103/// ```
104/// # use line_column::line_column;
105/// assert_eq!(line_column("", 0),     (1, 1));
106/// assert_eq!(line_column("a", 0),    (1, 1));
107/// assert_eq!(line_column("a", 1),    (1, 2));
108/// assert_eq!(line_column("ab", 1),   (1, 2));
109/// assert_eq!(line_column("a\n", 1),  (1, 2));
110/// assert_eq!(line_column("a\n", 2),  (2, 1));
111/// assert_eq!(line_column("a\nb", 2), (2, 1));
112/// ```
113#[inline]
114pub fn line_column(s: &str, index: usize) -> (u32, u32) {
115    line_columns(s, [index])[0]
116}