cosmic_text/
line_ending.rs1use core::ops::Range;
2
3#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
5pub enum LineEnding {
6 #[default]
8 Lf,
9 CrLf,
11 Cr,
13 LfCr,
15 None,
17}
18
19impl LineEnding {
20 pub fn as_str(&self) -> &'static str {
22 match self {
23 Self::Lf => "\n",
24 Self::CrLf => "\r\n",
25 Self::Cr => "\r",
26 Self::LfCr => "\n\r",
27 Self::None => "",
28 }
29 }
30}
31
32#[derive(Debug)]
34pub struct LineIter<'a> {
35 string: &'a str,
36 start: usize,
37 end: usize,
38}
39
40impl<'a> LineIter<'a> {
41 pub fn new(string: &'a str) -> Self {
43 Self {
44 string,
45 start: 0,
46 end: string.len(),
47 }
48 }
49}
50
51impl Iterator for LineIter<'_> {
52 type Item = (Range<usize>, LineEnding);
53 fn next(&mut self) -> Option<Self::Item> {
54 let start = self.start;
55 match self.string[start..self.end].find(['\r', '\n']) {
56 Some(i) => {
57 let end = start + i;
58 self.start = end;
59 let after = &self.string[end..];
60 let ending = if after.starts_with("\r\n") {
61 LineEnding::CrLf
62 } else if after.starts_with("\n\r") {
63 LineEnding::LfCr
64 } else if after.starts_with("\n") {
65 LineEnding::Lf
66 } else if after.starts_with("\r") {
67 LineEnding::Cr
68 } else {
69 LineEnding::None
71 };
72 self.start += ending.as_str().len();
73 Some((start..end, ending))
74 }
75 None => {
76 if self.start < self.end {
77 self.start = self.end;
78 Some((start..self.end, LineEnding::None))
79 } else {
80 None
81 }
82 }
83 }
84 }
85}
86
87#[test]
90fn test_line_iter() {
91 let string = "LF\nCRLF\r\nCR\rLFCR\n\rNONE";
92 let mut iter = LineIter::new(string);
93 assert_eq!(iter.next(), Some((0..2, LineEnding::Lf)));
94 assert_eq!(iter.next(), Some((3..7, LineEnding::CrLf)));
95 assert_eq!(iter.next(), Some((9..11, LineEnding::Cr)));
96 assert_eq!(iter.next(), Some((12..16, LineEnding::LfCr)));
97 assert_eq!(iter.next(), Some((18..22, LineEnding::None)));
98}