xi_core_lib/
line_ending.rs1extern crate xi_rope;
18
19use memchr::memchr2;
20use xi_rope::Rope;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum LineEnding {
25 CrLf, Lf, }
28
29#[derive(Debug)]
31pub struct MixedLineEndingError;
32
33impl LineEnding {
34 pub fn parse(rope: &Rope) -> Result<Option<Self>, MixedLineEndingError> {
36 let mut crlf = false;
37 let mut lf = false;
38
39 for chunk in rope.iter_chunks(..) {
40 match LineEnding::parse_chunk(&chunk) {
41 Ok(Some(LineEnding::CrLf)) => crlf = true,
42 Ok(Some(LineEnding::Lf)) => lf = true,
43 Ok(None) => (),
44 Err(e) => return Err(e),
45 }
46 }
47
48 match (crlf, lf) {
49 (true, false) => Ok(Some(LineEnding::CrLf)),
50 (false, true) => Ok(Some(LineEnding::Lf)),
51 (false, false) => Ok(None),
52 _ => Err(MixedLineEndingError),
53 }
54 }
55
56 pub fn parse_chunk(chunk: &str) -> Result<Option<Self>, MixedLineEndingError> {
58 let bytes = chunk.as_bytes();
59 let newline = memchr2(b'\n', b'\r', bytes);
60 match newline {
61 Some(x) if bytes[x] == b'\r' && bytes.len() > x + 1 && bytes[x + 1] == b'\n' => {
62 Ok(Some(LineEnding::CrLf))
63 }
64 Some(x) if bytes[x] == b'\n' => Ok(Some(LineEnding::Lf)),
65 Some(_) => Err(MixedLineEndingError),
66 _ => Ok(None),
67 }
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn crlf() {
77 let result = LineEnding::parse_chunk("\r\n");
78 assert!(result.is_ok());
79 assert_eq!(result.unwrap(), Some(LineEnding::CrLf));
80 }
81
82 #[test]
83 fn lf() {
84 let result = LineEnding::parse_chunk("\n");
85 assert!(result.is_ok());
86 assert_eq!(result.unwrap(), Some(LineEnding::Lf));
87 }
88
89 #[test]
90 fn legacy_mac_errors() {
91 assert!(LineEnding::parse_chunk("\r").is_err());
92 }
93
94 #[test]
95 fn bad_space() {
96 assert!(LineEnding::parse_chunk("\r \n").is_err());
97 }
98}