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 out the length of the `s`, return `s.len()`
128///
129/// # Panics
130/// - line 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
150 let mut i = 0;
151 for _ in 1..line {
152 let Some(lf) = s[i..].find('\n') else { return s.len() };
153 i += lf+1;
154 }
155 if column == 0 {
156 return i.saturating_sub(1)
157 }
158 s[i..].chars()
159 .take_while(|ch| *ch != '\n')
160 .take(column as usize - 1)
161 .fold(i, |acc, ch| acc + ch.len_utf8())
162}
163
164/// Get str char index of line and column
165///
166/// If the line out the length of the `s`, return `s.chars().count()`
167///
168/// # Panics
169/// - line 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
185 let mut back_style = column == 0;
186 line -= 1;
187 column = column.saturating_sub(1);
188
189 let mut i = 0usize;
190 let mut chars = s.chars();
191 loop {
192 let Some(ch) = chars.next() else {
193 back_style &= line == 0;
194 break
195 };
196 if line == 0 {
197 if column == 0 || ch == '\n' { break }
198 column -= 1;
199 } else if ch == '\n' {
200 line -= 1;
201 }
202 i += 1;
203 }
204 i.saturating_sub(back_style.into())
205}
206
207/// Get tuple of line and column, use byte index
208///
209/// Use LF (0x0A) to split newline, also compatible with CRLF (0x0D 0x0A)
210///
211/// # Examples
212/// ```
213/// # use line_column::line_column;
214/// assert_eq!(line_column("", 0), (1, 1));
215/// assert_eq!(line_column("a", 0), (1, 1));
216/// assert_eq!(line_column("a", 1), (1, 2));
217/// assert_eq!(line_column("ab", 1), (1, 2));
218/// assert_eq!(line_column("a\n", 1), (1, 2));
219/// assert_eq!(line_column("a\n", 2), (2, 1));
220/// assert_eq!(line_column("a\nb", 2), (2, 1));
221/// ```
222#[inline]
223#[must_use]
224#[track_caller]
225pub fn line_column(s: &str, index: usize) -> (u32, u32) {
226 line_columns(s, [index])[0]
227}
228
229/// Get tuple of line and column, use char index
230///
231/// Use LF (0x0A) to split newline, also compatible with CRLF (0x0D 0x0A)
232///
233/// # Panics
234/// - `index > s.chars().count()`
235///
236/// # Examples
237/// ```
238/// # use line_column::char_line_column;
239/// assert_eq!(char_line_column("", 0), (1, 1));
240/// assert_eq!(char_line_column("a", 0), (1, 1));
241/// assert_eq!(char_line_column("a", 1), (1, 2));
242/// assert_eq!(char_line_column("ab", 1), (1, 2));
243/// assert_eq!(char_line_column("😀\n", 1), (1, 2));
244/// assert_eq!(char_line_column("😀\n", 2), (2, 1));
245/// assert_eq!(char_line_column("😀\n❓", 2), (2, 1));
246/// assert_eq!(char_line_column("😀\n❓", 2), (2, 1));
247/// ```
248#[inline]
249#[must_use]
250#[track_caller]
251pub fn char_line_column(s: &str, index: usize) -> (u32, u32) {
252 char_line_columns(s, [index])[0]
253}