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
86    let mut i = 0;
87    for _ in 1..line {
88        let Some(lf) = s[i..].find('\n') else { break };
89        i += lf+1;
90    }
91    let s = &s[i..];
92    let lf = s.find('\n').map_or(s.len(), |l| l+1);
93    let s = &s[..lf];
94    i + s.char_indices()
95        .nth(column as usize-1)
96        .map_or(s.len(), |x| x.0)
97}
98
99/// Get str char index of line and column
100///
101/// If the line or column out the length of the `s`, return `s.chars().count()`
102///
103/// # Panics
104/// - line or column by zero
105///
106/// # Examples
107/// ```
108/// # use line_column::char_index;
109/// assert_eq!(char_index("", 1, 1), 0);
110/// assert_eq!(char_index("a", 1, 1), 0);
111/// assert_eq!(char_index("你好\n世界", 1, 2), 1);
112/// assert_eq!(char_index("你好\n世界", 1, 3), 2);
113/// assert_eq!(char_index("你好\n世界", 2, 1), 3);
114/// ```
115pub fn char_index(s: &str, mut line: u32, mut column: u32) -> usize {
116    assert_ne!(line, 0);
117    assert_ne!(column, 0);
118
119    line -= 1;
120    column -= 1;
121
122    let mut i = 0;
123    let mut eol = false;
124
125    for ch in s.chars() {
126        if line == 0 {
127            if column == 0 || eol { break }
128            column -= 1;
129            eol = ch == '\n';
130        } else {
131            if ch == '\n' { line -= 1 }
132        }
133        i += 1;
134    }
135    i
136}
137
138/// Get tuple of line and column
139///
140/// Use LF (0x0A) to split newline, also compatible with CRLF (0x0D 0x0A)
141///
142/// # Examples
143/// ```
144/// # use line_column::line_column;
145/// assert_eq!(line_column("", 0),     (1, 1));
146/// assert_eq!(line_column("a", 0),    (1, 1));
147/// assert_eq!(line_column("a", 1),    (1, 2));
148/// assert_eq!(line_column("ab", 1),   (1, 2));
149/// assert_eq!(line_column("a\n", 1),  (1, 2));
150/// assert_eq!(line_column("a\n", 2),  (2, 1));
151/// assert_eq!(line_column("a\nb", 2), (2, 1));
152/// ```
153#[inline]
154pub fn line_column(s: &str, index: usize) -> (u32, u32) {
155    line_columns(s, [index])[0]
156}