1use std::iter::FusedIterator;
25use std::ops::Deref;
26
27use memchr::{memchr2, memrchr2};
28use text_size::{TextLen, TextRange, TextSize};
29
30pub trait UniversalNewlines {
32 fn universal_newlines(&self) -> UniversalNewlineIterator<'_>;
33}
34
35impl UniversalNewlines for str {
36 fn universal_newlines(&self) -> UniversalNewlineIterator<'_> {
37 UniversalNewlineIterator::from(self)
38 }
39}
40
41#[derive(Clone)]
44pub struct UniversalNewlineIterator<'a> {
45 text: &'a str,
46 offset: TextSize,
47 offset_back: TextSize,
48}
49
50impl<'a> UniversalNewlineIterator<'a> {
51 pub fn with_offset(text: &'a str, offset: TextSize) -> UniversalNewlineIterator<'a> {
52 UniversalNewlineIterator {
53 text,
54 offset,
55 offset_back: offset + text.text_len(),
56 }
57 }
58
59 pub fn from(text: &'a str) -> UniversalNewlineIterator<'a> {
60 Self::with_offset(text, TextSize::default())
61 }
62}
63
64#[inline]
66pub fn find_newline(text: &str) -> Option<(usize, LineEnding)> {
67 let bytes = text.as_bytes();
68 if let Some(position) = memchr2(b'\n', b'\r', bytes) {
69 let line_ending = match bytes[position] {
70 b'\n' => LineEnding::Lf,
72 b'\r' if bytes.get(position.saturating_add(1)) == Some(&b'\n') => LineEnding::CrLf,
74 _ => LineEnding::Cr,
76 };
77
78 Some((position, line_ending))
79 } else {
80 None
81 }
82}
83
84impl<'a> Iterator for UniversalNewlineIterator<'a> {
85 type Item = Line<'a>;
86
87 #[inline]
88 fn next(&mut self) -> Option<Line<'a>> {
89 if self.text.is_empty() {
90 return None;
91 }
92
93 let line = if let Some((newline_position, line_ending)) = find_newline(self.text) {
94 let (text, remainder) = self.text.split_at(newline_position + line_ending.len());
95
96 let line = Line {
97 offset: self.offset,
98 text,
99 };
100
101 self.text = remainder;
102 self.offset += text.text_len();
103
104 line
105 }
106 else {
108 Line {
109 offset: self.offset,
110 text: std::mem::take(&mut self.text),
111 }
112 };
113
114 Some(line)
115 }
116
117 fn last(mut self) -> Option<Self::Item> {
118 self.next_back()
119 }
120}
121
122impl DoubleEndedIterator for UniversalNewlineIterator<'_> {
123 #[inline]
124 fn next_back(&mut self) -> Option<Self::Item> {
125 if self.text.is_empty() {
126 return None;
127 }
128
129 let len = self.text.len();
130
131 let haystack = match self.text.as_bytes()[len - 1] {
133 b'\n' if len > 1 && self.text.as_bytes()[len - 2] == b'\r' => &self.text[..len - 2],
134 b'\n' | b'\r' => &self.text[..len - 1],
135 _ => self.text,
136 };
137
138 let line = if let Some(line_end) = memrchr2(b'\n', b'\r', haystack.as_bytes()) {
141 let (remainder, line) = self.text.split_at(line_end + 1);
143 self.text = remainder;
144 self.offset_back -= line.text_len();
145
146 Line {
147 text: line,
148 offset: self.offset_back,
149 }
150 } else {
151 let offset = self.offset_back - self.text.text_len();
153 Line {
154 text: std::mem::take(&mut self.text),
155 offset,
156 }
157 };
158
159 Some(line)
160 }
161}
162
163impl FusedIterator for UniversalNewlineIterator<'_> {}
164
165pub struct NewlineWithTrailingNewline<'a> {
167 trailing: Option<Line<'a>>,
168 underlying: UniversalNewlineIterator<'a>,
169}
170
171impl<'a> NewlineWithTrailingNewline<'a> {
172 pub fn from(input: &'a str) -> NewlineWithTrailingNewline<'a> {
173 Self::with_offset(input, TextSize::default())
174 }
175
176 pub fn with_offset(input: &'a str, offset: TextSize) -> Self {
177 NewlineWithTrailingNewline {
178 underlying: UniversalNewlineIterator::with_offset(input, offset),
179 trailing: if input.ends_with(['\r', '\n']) {
180 Some(Line {
181 text: "",
182 offset: offset + input.text_len(),
183 })
184 } else {
185 None
186 },
187 }
188 }
189}
190
191impl<'a> Iterator for NewlineWithTrailingNewline<'a> {
192 type Item = Line<'a>;
193
194 #[inline]
195 fn next(&mut self) -> Option<Self::Item> {
196 self.underlying.next().or_else(|| self.trailing.take())
197 }
198}
199
200impl DoubleEndedIterator for NewlineWithTrailingNewline<'_> {
201 #[inline]
202 fn next_back(&mut self) -> Option<Self::Item> {
203 self.trailing.take().or_else(|| self.underlying.next_back())
204 }
205}
206
207#[derive(Debug, Clone, Eq, PartialEq)]
208pub struct Line<'a> {
209 text: &'a str,
210 offset: TextSize,
211}
212
213impl<'a> Line<'a> {
214 pub fn new(text: &'a str, offset: TextSize) -> Self {
215 Self { text, offset }
216 }
217
218 #[inline]
219 pub const fn start(&self) -> TextSize {
220 self.offset
221 }
222
223 #[inline]
225 pub fn full_end(&self) -> TextSize {
226 self.offset + self.full_text_len()
227 }
228
229 #[inline]
231 pub fn end(&self) -> TextSize {
232 self.offset + self.as_str().text_len()
233 }
234
235 #[inline]
237 pub fn full_range(&self) -> TextRange {
238 TextRange::at(self.offset, self.text.text_len())
239 }
240
241 #[inline]
243 pub fn range(&self) -> TextRange {
244 TextRange::new(self.start(), self.end())
245 }
246
247 #[inline]
249 pub fn line_ending(&self) -> Option<LineEnding> {
250 let mut bytes = self.text.bytes().rev();
251 match bytes.next() {
252 Some(b'\n') => {
253 if bytes.next() == Some(b'\r') {
254 Some(LineEnding::CrLf)
255 } else {
256 Some(LineEnding::Lf)
257 }
258 }
259 Some(b'\r') => Some(LineEnding::Cr),
260 _ => None,
261 }
262 }
263
264 #[inline]
266 pub fn as_str(&self) -> &'a str {
267 let newline_len = self
268 .line_ending()
269 .map_or(0, |line_ending| line_ending.len());
270 &self.text[..self.text.len() - newline_len]
271 }
272
273 #[inline]
275 pub fn as_full_str(&self) -> &'a str {
276 self.text
277 }
278
279 #[inline]
280 fn full_text_len(&self) -> TextSize {
281 self.text.text_len()
282 }
283}
284
285impl Deref for Line<'_> {
286 type Target = str;
287
288 fn deref(&self) -> &Self::Target {
289 self.as_str()
290 }
291}
292
293impl PartialEq<&str> for Line<'_> {
294 fn eq(&self, other: &&str) -> bool {
295 self.as_str() == *other
296 }
297}
298
299impl PartialEq<Line<'_>> for &str {
300 fn eq(&self, other: &Line<'_>) -> bool {
301 *self == other.as_str()
302 }
303}
304
305#[derive(Debug, PartialEq, Eq, Copy, Clone)]
308pub enum LineEnding {
309 Lf,
310 Cr,
311 CrLf,
312}
313
314impl Default for LineEnding {
315 fn default() -> Self {
316 if cfg!(windows) {
317 LineEnding::CrLf
318 } else {
319 LineEnding::Lf
320 }
321 }
322}
323
324impl LineEnding {
325 pub const fn as_str(&self) -> &'static str {
326 match self {
327 LineEnding::Lf => "\n",
328 LineEnding::CrLf => "\r\n",
329 LineEnding::Cr => "\r",
330 }
331 }
332
333 #[expect(clippy::len_without_is_empty)]
334 pub const fn len(&self) -> usize {
335 match self {
336 LineEnding::Lf | LineEnding::Cr => 1,
337 LineEnding::CrLf => 2,
338 }
339 }
340
341 pub const fn text_len(&self) -> TextSize {
342 match self {
343 LineEnding::Lf | LineEnding::Cr => TextSize::new(1),
344 LineEnding::CrLf => TextSize::new(2),
345 }
346 }
347}
348
349impl Deref for LineEnding {
350 type Target = str;
351
352 fn deref(&self) -> &Self::Target {
353 self.as_str()
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use text_size::TextSize;
360
361 use super::{Line, UniversalNewlineIterator};
362
363 #[test]
364 fn universal_newlines_empty_str() {
365 let lines: Vec<_> = UniversalNewlineIterator::from("").collect();
366 assert_eq!(lines, Vec::<Line<'_>>::new());
367
368 let lines: Vec<_> = UniversalNewlineIterator::from("").rev().collect();
369 assert_eq!(lines, Vec::<Line<'_>>::new());
370 }
371
372 #[test]
373 fn universal_newlines_forward() {
374 let lines: Vec<_> = UniversalNewlineIterator::from("foo\nbar\n\r\nbaz\rbop").collect();
375 assert_eq!(
376 lines,
377 vec![
378 Line::new("foo\n", TextSize::from(0)),
379 Line::new("bar\n", TextSize::from(4)),
380 Line::new("\r\n", TextSize::from(8)),
381 Line::new("baz\r", TextSize::from(10)),
382 Line::new("bop", TextSize::from(14)),
383 ]
384 );
385
386 let lines: Vec<_> = UniversalNewlineIterator::from("foo\nbar\n\r\nbaz\rbop\n").collect();
387 assert_eq!(
388 lines,
389 vec![
390 Line::new("foo\n", TextSize::from(0)),
391 Line::new("bar\n", TextSize::from(4)),
392 Line::new("\r\n", TextSize::from(8)),
393 Line::new("baz\r", TextSize::from(10)),
394 Line::new("bop\n", TextSize::from(14)),
395 ]
396 );
397
398 let lines: Vec<_> = UniversalNewlineIterator::from("foo\nbar\n\r\nbaz\rbop\n\n").collect();
399 assert_eq!(
400 lines,
401 vec![
402 Line::new("foo\n", TextSize::from(0)),
403 Line::new("bar\n", TextSize::from(4)),
404 Line::new("\r\n", TextSize::from(8)),
405 Line::new("baz\r", TextSize::from(10)),
406 Line::new("bop\n", TextSize::from(14)),
407 Line::new("\n", TextSize::from(18)),
408 ]
409 );
410 }
411
412 #[test]
413 fn universal_newlines_backwards() {
414 let lines: Vec<_> = UniversalNewlineIterator::from("foo\nbar\n\r\nbaz\rbop")
415 .rev()
416 .collect();
417 assert_eq!(
418 lines,
419 vec![
420 Line::new("bop", TextSize::from(14)),
421 Line::new("baz\r", TextSize::from(10)),
422 Line::new("\r\n", TextSize::from(8)),
423 Line::new("bar\n", TextSize::from(4)),
424 Line::new("foo\n", TextSize::from(0)),
425 ]
426 );
427
428 let lines: Vec<_> = UniversalNewlineIterator::from("foo\nbar\n\nbaz\rbop\n")
429 .rev()
430 .map(|line| line.as_str())
431 .collect();
432
433 assert_eq!(
434 lines,
435 vec![
436 Line::new("bop\n", TextSize::from(13)),
437 Line::new("baz\r", TextSize::from(9)),
438 Line::new("\n", TextSize::from(8)),
439 Line::new("bar\n", TextSize::from(4)),
440 Line::new("foo\n", TextSize::from(0)),
441 ]
442 );
443 }
444
445 #[test]
446 fn universal_newlines_mixed() {
447 let mut lines = UniversalNewlineIterator::from("foo\nbar\n\r\nbaz\rbop");
448
449 assert_eq!(
450 lines.next_back(),
451 Some(Line::new("bop", TextSize::from(14)))
452 );
453 assert_eq!(lines.next(), Some(Line::new("foo\n", TextSize::from(0))));
454 assert_eq!(
455 lines.next_back(),
456 Some(Line::new("baz\r", TextSize::from(10)))
457 );
458 assert_eq!(lines.next(), Some(Line::new("bar\n", TextSize::from(4))));
459 assert_eq!(
460 lines.next_back(),
461 Some(Line::new("\r\n", TextSize::from(8)))
462 );
463 assert_eq!(lines.next(), None);
464 }
465}