1#![allow(clippy::cast_possible_truncation)]
2use std::marker::PhantomData;
3
4use super::{ByteOffset, CharOffset, LineCol, OffsetPosition, StringPosition, Utf16Offset};
5
6pub fn char_to_byte(c: CharOffset, source: &str) -> ByteOffset {
7 ByteOffset(source.chars().take(c.0).map(char::len_utf8).sum())
8}
9
10pub fn utf16_to_byte(c: Utf16Offset, source: &str) -> ByteOffset {
11 let mut curr = 0;
12 let mut ret = 0;
13 for u in source.chars() {
14 if curr >= c.0 {
15 break;
16 }
17 curr += u.len_utf16();
18 ret += u.len_utf8();
19 }
20 ByteOffset(ret)
21}
22
23pub fn linecolbyte_to_byte(c: LineCol<ByteOffset>, source: &str) -> ByteOffset {
24 let off = skip_lines(source, c.line as _);
25 ByteOffset(off + (c.col as usize))
26}
27
28pub fn linecolchar_to_byte(c: LineCol<CharOffset>, source: &str) -> ByteOffset {
29 let off = skip_lines(source, c.line as _) as _;
30 let source = &source[off..];
31 ByteOffset(off) + char_to_byte(CharOffset(c.col as usize), source)
32}
33
34pub fn linecolutf16_to_byte(c: LineCol<Utf16Offset>, source: &str) -> ByteOffset {
35 let off = skip_lines(source, c.line as _);
36 let source = &source[off..];
37 ByteOffset(off) + utf16_to_byte(Utf16Offset(c.col as _), source)
38}
39
40pub fn byte_to_char(c: ByteOffset, source: &str) -> CharOffset {
41 CharOffset(source[..c.0].chars().count())
42}
43
44pub fn utf16_to_char(c: Utf16Offset, source: &str) -> CharOffset {
45 let mut curr = 0;
46 let mut ret = 0;
47 for u in source.chars() {
48 if curr >= c.0 {
49 break;
50 }
51 curr += u.len_utf16();
52 ret += 1;
53 }
54 CharOffset(ret)
55}
56
57pub fn linecolbyte_to_char(lc: LineCol<ByteOffset>, source: &str) -> CharOffset {
58 let off = skip_lines(source, lc.line as _);
60 CharOffset(source[..off + (lc.col as usize)].chars().count())
61}
62
63pub fn linecolchar_to_char(lc: LineCol<CharOffset>, source: &str) -> CharOffset {
64 let off = skip_lines(source, lc.line as _);
66 CharOffset(source[..off].chars().count() + (lc.col as usize))
67}
68
69pub fn linecolutf16_to_char(lc: LineCol<Utf16Offset>, source: &str) -> CharOffset {
70 let off = skip_lines(source, lc.line as _);
72 let chars = source[..off].chars().count();
73 let source = &source[off..];
74 utf16_to_char(Utf16Offset(lc.col as _), source) + CharOffset(chars)
75}
76
77pub fn byte_to_utf16(c: ByteOffset, source: &str) -> Utf16Offset {
78 Utf16Offset(source[..c.0].chars().map(char::len_utf16).sum())
79}
80
81pub fn char_to_utf16(c: CharOffset, source: &str) -> Utf16Offset {
82 Utf16Offset(source.chars().take(c.0).map(char::len_utf16).sum())
83}
84
85pub fn linecolbyte_to_utf16(lc: LineCol<ByteOffset>, source: &str) -> Utf16Offset {
86 let off = skip_lines(source, lc.line as _);
88 Utf16Offset(
89 source[..off + (lc.col as usize)]
90 .chars()
91 .map(char::len_utf16)
92 .sum(),
93 )
94}
95
96pub fn linecolchar_to_utf16(lc: LineCol<CharOffset>, source: &str) -> Utf16Offset {
97 let off = skip_lines(source, lc.line as _);
99 Utf16Offset(
100 source[..off].chars().map(char::len_utf16).sum::<usize>()
101 + source[off..]
102 .chars()
103 .take(lc.col as _)
104 .map(char::len_utf16)
105 .sum::<usize>(),
106 )
107}
108
109pub fn linecolutf16_to_utf16(lc: LineCol<Utf16Offset>, source: &str) -> Utf16Offset {
110 let off = skip_lines(source, lc.line as _);
111 Utf16Offset(source[..off].chars().map(char::len_utf16).sum::<usize>() + (lc.col as usize))
112}
113
114pub fn byte_to_linecol_byte(c: ByteOffset, source: &str) -> LineCol<ByteOffset> {
117 let source = &source[..c.0];
118 let (lines, off) = count_lines(source);
119 LineCol {
120 line: lines as _,
121 col: (source.len() - (off as usize)) as _,
122 __phantom: PhantomData,
123 }
124}
125
126pub fn char_to_linecol_byte(mut co: CharOffset, source: &str) -> LineCol<ByteOffset> {
127 let mut bytes = 0;
128 let mut line = 0;
129 let mut char_iter = source.chars().peekable();
130 while co.0 != 0
131 && let Some(c) = char_iter.next()
132 {
133 co.0 -= 1;
134 if c == '\n' {
135 bytes = 0;
136 line += 1;
137 } else if c == '\r' {
138 bytes = 0;
139 line += 1;
140 if char_iter.peek().copied() == Some('\n') {
141 let _ = char_iter.next();
142 co.0 -= 1;
143 }
144 } else {
145 bytes += c.len_utf8();
146 }
147 }
148 LineCol {
149 line,
150 col: bytes as _,
151 __phantom: PhantomData,
152 }
153}
154
155pub fn utf16_to_linecol_byte(mut co: Utf16Offset, source: &str) -> LineCol<ByteOffset> {
156 let mut bytes = 0;
157 let mut line = 0;
158 let mut char_iter = source.chars().peekable();
159 while co.0 != 0
160 && let Some(c) = char_iter.next()
161 {
162 co.0 -= c.len_utf16();
163 if c == '\n' {
164 bytes = 0;
165 line += 1;
166 } else if c == '\r' {
167 bytes = 0;
168 line += 1;
169 if char_iter.peek().copied() == Some('\n') {
170 let _ = char_iter.next();
171 co.0 -= 1;
172 }
173 } else {
174 bytes += c.len_utf8();
175 }
176 }
177 LineCol {
178 line,
179 col: bytes as _,
180 __phantom: PhantomData,
181 }
182}
183
184pub fn byte_to_linecol_char(c: ByteOffset, source: &str) -> LineCol<CharOffset> {
185 let source = &source[..c.0];
186 let (line, off) = count_lines(source);
187 LineCol {
188 line,
189 col: source[off as usize..].chars().count() as _,
190 __phantom: PhantomData,
191 }
192}
193
194pub fn char_to_linecol_char(c: CharOffset, source: &str) -> LineCol<CharOffset> {
195 let (line, bytes, total) = count_lines_chars(source, c.0);
196 LineCol {
197 line,
198 col: source[total - bytes as usize..total].chars().count() as _,
199 __phantom: PhantomData,
200 }
201}
202
203pub fn utf16_to_linecol_char(c: Utf16Offset, source: &str) -> LineCol<CharOffset> {
204 let (line, bytes, total) = count_lines_utf16(source, c.0);
205 LineCol {
206 line,
207 col: source[total - bytes as usize..total].chars().count() as _,
208 __phantom: PhantomData,
209 }
210}
211
212pub fn byte_to_linecol_utf16(c: ByteOffset, source: &str) -> LineCol<Utf16Offset> {
213 let source = &source[..c.0];
214 let (line, offset) = count_lines(source);
215 LineCol {
216 line,
217 col: source[offset as usize..]
218 .chars()
219 .map(|c| c.len_utf16() as u32)
220 .sum(),
221 __phantom: PhantomData,
222 }
223}
224
225pub fn char_to_linecol_utf16(c: CharOffset, source: &str) -> LineCol<Utf16Offset> {
226 let (line, bytes, total) = count_lines_chars(source, c.0);
227 LineCol {
228 line,
229 col: source[total - bytes as usize..total]
230 .chars()
231 .map(|c| c.len_utf16() as u32)
232 .sum(),
233 __phantom: PhantomData,
234 }
235}
236
237pub fn utf16_to_linecol_utf16(c: Utf16Offset, source: &str) -> LineCol<Utf16Offset> {
238 let (line, bytes, total) = count_lines_utf16(source, c.0);
239 LineCol {
240 line,
241 col: source[total - bytes as usize..total]
242 .chars()
243 .map(|c| c.len_utf16() as u32)
244 .sum(),
245
246 __phantom: PhantomData,
247 }
248}
249
250pub fn linecol_to_linecol<P1: OffsetPosition, P2: OffsetPosition>(
253 lc: LineCol<P1>,
254 source: &str,
255) -> LineCol<P2>
256where
257 LineCol<P1>: StringPosition,
258 LineCol<P2>: StringPosition,
259{
260 let zero = LineCol {
261 line: lc.line,
262 col: 0,
263 __phantom: PhantomData,
264 };
265 let ByteOffset(offset) = linecolbyte_to_byte(zero, source);
266 let source = &source[offset..];
267 LineCol {
268 line: lc.line,
269 col: P2::len(source).into() as _,
270 __phantom: PhantomData,
271 }
272}
273
274pub fn count_lines_chars(source: &str, mut chars: usize) -> (u32, u32, usize) {
278 let mut bytes: u32 = 0;
279 let mut line = 0;
280 let mut total: usize = 0;
281 let mut char_iter = source.chars().peekable();
282 while chars != 0
283 && let Some(c) = char_iter.next()
284 {
285 chars -= 1;
286 if c == '\n' {
287 total += (bytes + 1) as usize;
288 bytes = 0;
289 line += 1;
290 } else if c == '\r' {
291 total += (bytes + 1) as usize;
292 bytes = 0;
293 line += 1;
294 if char_iter.peek().copied() == Some('\n') {
295 total += 1;
296 let _ = char_iter.next();
297 chars -= 1;
298 }
299 } else {
300 bytes += c.len_utf8() as u32;
301 }
302 }
303 total += bytes as usize;
304 (line, bytes, total)
305}
306
307pub fn count_lines_utf16(source: &str, mut utfs: usize) -> (u32, u32, usize) {
309 let mut bytes: u32 = 0;
310 let mut line: u32 = 0;
311 let mut total: usize = 0;
312 let mut char_iter = source.chars().peekable();
313 while utfs != 0
314 && let Some(c) = char_iter.next()
315 {
316 utfs -= c.len_utf16();
317 if c == '\n' {
318 total += (bytes + 1) as usize;
319 bytes = 0;
320 line += 1;
321 } else if c == '\r' {
322 total += (bytes + 1) as usize;
323 bytes = 0;
324 line += 1;
325 if char_iter.peek().copied() == Some('\n') {
326 total += 1;
327 let _ = char_iter.next();
328 utfs -= 1;
329 }
330 } else {
331 bytes += c.len_utf8() as u32;
332 }
333 }
334 total += bytes as usize;
335 (line, bytes, total)
336}
337
338pub fn count_lines(source: &str) -> (u32, u32) {
340 let source = source.as_bytes();
341 let mut le_iter = memchr::memchr2_iter(b'\r', b'\n', source);
342 let mut off = 0;
343 let mut lines = 0;
344 while let Some(i) = le_iter.next() {
345 lines += 1;
346 let first = source[i];
347 if first == b'\r' && source.get(i + 1).copied() == Some(b'\n') {
348 off = i + 2;
349 let _ = le_iter.next();
350 } else {
351 off = i + 1;
352 }
353 }
354 (lines, off as _)
355}
356
357pub fn skip_lines(source: &str, mut lines: u32) -> usize {
358 let source = source.as_bytes();
359 let mut le_iter = memchr::memchr2_iter(b'\r', b'\n', source);
360 let mut off = 0;
361 while lines > 0
362 && let Some(i) = le_iter.next()
363 {
364 lines -= 1;
365 let first = source[i];
366 if first == b'\r' && source.get(i + 1).copied() == Some(b'\n') {
367 off = i + 2;
368 let _ = le_iter.next();
369 } else {
370 off = i + 1;
371 }
372 }
373 off
374}