line_column/lib.rs
1#![no_std]
2#![doc = include_str!("../README.md")]
3
4#[cfg(test)]
5mod tests;
6
7const UNINIT_LINE_COL: (u32, u32) = (0, 0);
8
9/// Get multiple pairs of lines and columns may be faster
10///
11/// Like [`line_column`]
12///
13/// # Panics
14///
15/// - index out of `0..s.len()`
16/// - index not on char boundary
17#[must_use]
18#[track_caller]
19pub fn line_columns<const N: usize>(
20 s: &str,
21 indexs: [usize; N],
22) -> [(u32, u32); N] {
23 let len = s.len();
24
25 for index in indexs {
26 assert!(index <= len,
27 "index {index} out of str length {len} of `{s:?}`");
28 assert!(s.is_char_boundary(index),
29 "byte index {index} is not a char boundary of `{s:?}`");
30 }
31
32 let result = line_columns_unchecked(s, indexs);
33
34 debug_assert!(! result.contains(&UNINIT_LINE_COL),
35 "impl error, report bug issue");
36 result
37}
38
39/// Get multiple pairs of lines and columns may be faster
40///
41/// Like [`char_line_column`]
42///
43/// # Panics
44/// - `indexs` any index greater than `s.chars().count()`
45#[must_use]
46#[track_caller]
47pub fn char_line_columns<const N: usize>(
48 s: &str,
49 indexs: [usize; N],
50) -> [(u32, u32); N] {
51 let mut len = 0;
52 let mut result = [UNINIT_LINE_COL; N];
53
54 let last_loc = s.chars()
55 .enumerate()
56 .inspect(|&(i, _)| len = i+1)
57 .fold((1, 1), |(line, column), (cur, ch)|
58 {
59 for (i, &index) in indexs.iter().enumerate() {
60 if index == cur {
61 result[i] = (line, column);
62 }
63 }
64
65 if ch == '\n' {
66 (line+1, 1)
67 } else {
68 (line, column+1)
69 }
70 });
71
72 for index in indexs {
73 assert!(index <= len,
74 "char index {index} out of str length {len} of `{s:?}`");
75 }
76
77 for (i, &index) in indexs.iter().enumerate() {
78 if index >= len {
79 result[i] = last_loc;
80 }
81 }
82
83 result
84}
85
86/// Get multiple of lines and columns may be faster
87///
88/// Use byte index
89///
90/// If the index does not fall on the character boundary,
91/// the unspecified results
92#[must_use]
93pub fn line_columns_unchecked<const N: usize>(
94 s: &str,
95 indexs: [usize; N],
96) -> [(u32, u32); N] {
97 let len = s.len();
98 let mut result = [UNINIT_LINE_COL; N];
99
100 let last_loc = s.char_indices()
101 .fold((1, 1), |(line, column), (cur, ch)|
102 {
103 for (i, &index) in indexs.iter().enumerate() {
104 if index == cur {
105 result[i] = (line, column);
106 }
107 }
108
109 if ch == '\n' {
110 (line+1, 1)
111 } else {
112 (line, column+1)
113 }
114 });
115
116 for (i, &index) in indexs.iter().enumerate() {
117 if index == len {
118 result[i] = last_loc;
119 }
120 }
121
122 result
123}
124
125/// Get str byte index of line and column
126///
127/// If the line or column out the length of the `s`, return `s.len()`
128///
129/// # Panics
130/// - line or column by zero
131///
132/// # Examples
133/// ```
134/// # use line_column::index;
135/// assert_eq!(index("", 1, 1), 0);
136/// assert_eq!(index("a", 1, 1), 0);
137/// assert_eq!(index("a", 1, 2), 1);
138/// assert_eq!(index("a\n", 1, 2), 1);
139/// assert_eq!(index("a\n", 2, 1), 2);
140/// assert_eq!(index("a\nx", 2, 2), 3);
141/// assert_eq!(index("你好\n世界", 1, 2), 3); // byte index
142/// assert_eq!(index("你好\n世界", 1, 3), 6);
143/// assert_eq!(index("你好\n世界", 2, 1), 7);
144/// ```
145#[must_use]
146#[track_caller]
147pub fn index(s: &str, line: u32, column: u32) -> usize {
148 assert_ne!(line, 0);
149 assert_ne!(column, 0);
150
151 let mut i = 0;
152 for _ in 1..line {
153 let Some(lf) = s[i..].find('\n') else { break };
154 i += lf+1;
155 }
156 let s = &s[i..];
157 let lf = s.find('\n').map_or(s.len(), |l| l+1);
158 let s = &s[..lf];
159 i + s.char_indices()
160 .nth(column as usize-1)
161 .map_or(s.len(), |x| x.0)
162}
163
164/// Get str char index of line and column
165///
166/// If the line or column out the length of the `s`, return `s.chars().count()`
167///
168/// # Panics
169/// - line or column by zero
170///
171/// # Examples
172/// ```
173/// # use line_column::char_index;
174/// assert_eq!(char_index("", 1, 1), 0);
175/// assert_eq!(char_index("a", 1, 1), 0);
176/// assert_eq!(char_index("你好\n世界", 1, 2), 1);
177/// assert_eq!(char_index("你好\n世界", 1, 3), 2);
178/// assert_eq!(char_index("你好\n世界", 2, 1), 3);
179/// ```
180#[must_use]
181#[track_caller]
182pub fn char_index(s: &str, mut line: u32, mut column: u32) -> usize {
183 assert_ne!(line, 0);
184 assert_ne!(column, 0);
185
186 line -= 1;
187 column -= 1;
188
189 let mut i = 0;
190 let mut eol = false;
191
192 for ch in s.chars() {
193 if line == 0 {
194 if column == 0 || eol { break }
195 column -= 1;
196 eol = ch == '\n';
197 } else if ch == '\n' {
198 line -= 1;
199 }
200 i += 1;
201 }
202 i
203}
204
205/// Get tuple of line and column, use byte index
206///
207/// Use LF (0x0A) to split newline, also compatible with CRLF (0x0D 0x0A)
208///
209/// # Examples
210/// ```
211/// # use line_column::line_column;
212/// assert_eq!(line_column("", 0), (1, 1));
213/// assert_eq!(line_column("a", 0), (1, 1));
214/// assert_eq!(line_column("a", 1), (1, 2));
215/// assert_eq!(line_column("ab", 1), (1, 2));
216/// assert_eq!(line_column("a\n", 1), (1, 2));
217/// assert_eq!(line_column("a\n", 2), (2, 1));
218/// assert_eq!(line_column("a\nb", 2), (2, 1));
219/// ```
220#[inline]
221#[must_use]
222#[track_caller]
223pub fn line_column(s: &str, index: usize) -> (u32, u32) {
224 line_columns(s, [index])[0]
225}
226
227/// Get tuple of line and column, use char index
228///
229/// Use LF (0x0A) to split newline, also compatible with CRLF (0x0D 0x0A)
230///
231/// # Panics
232/// - `index > s.chars().count()`
233///
234/// # Examples
235/// ```
236/// # use line_column::char_line_column;
237/// assert_eq!(char_line_column("", 0), (1, 1));
238/// assert_eq!(char_line_column("a", 0), (1, 1));
239/// assert_eq!(char_line_column("a", 1), (1, 2));
240/// assert_eq!(char_line_column("ab", 1), (1, 2));
241/// assert_eq!(char_line_column("😀\n", 1), (1, 2));
242/// assert_eq!(char_line_column("😀\n", 2), (2, 1));
243/// assert_eq!(char_line_column("😀\n❓", 2), (2, 1));
244/// assert_eq!(char_line_column("😀\n❓", 2), (2, 1));
245/// ```
246#[inline]
247#[must_use]
248#[track_caller]
249pub fn char_line_column(s: &str, index: usize) -> (u32, u32) {
250 char_line_columns(s, [index])[0]
251}