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}