diff --git a/src/helpers.rs b/src/helpers.rs index dd5a2c4..48534c3 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -5,7 +5,6 @@ use std::{ ops::Range, }; -use itertools::Either; use rustc_hash::FxHashMap as HashMap; use crate::{ @@ -201,7 +200,7 @@ const EMPTY_ROPE: Rope = Rope::new(); /// Split the string with a needle, each string will contain the needle. /// /// Copied and modified from https://github.com/rust-lang/cargo/blob/30efe860c0e4adc1a6d7057ad223dc6e47d34edf/src/cargo/sources/registry/index.rs#L1048-L1072 -pub fn split<'a>( +fn split<'a>( haystack: &Rope<'a>, needle: u8, ) -> impl Iterator> { @@ -1324,10 +1323,10 @@ pub trait SourceText<'a>: Default + Clone + ToString { impl<'a> SourceText<'a> for Rope<'a> { fn split_into_lines(&self) -> impl Iterator { - if let Some(s) = self.get_simple() { - return Either::Left(split_str(s, b'\n').map(Rope::from)); - } - Either::Right(split(self, b'\n')) + // Split the text into lines, including the line ending character. + // If the text ends with a newline, the last line will be ignored + // For example: "abc\nefg\n" => ["abc\n", "efg\n"] + self.lines_impl(false) } #[inline] diff --git a/src/rope.rs b/src/rope.rs index 04bd65e..403f8e3 100644 --- a/src/rope.rs +++ b/src/rope.rs @@ -421,6 +421,16 @@ impl<'a> Rope<'a> { /// Returns an iterator over the lines of the rope. pub fn lines(&self) -> Lines<'_, 'a> { + self.lines_impl(true) + } + + /// Returns an iterator over the lines of the rope. + /// + /// If `end_line_break_as_newline` is true, the end of the rope with ('\n') is treated as an empty newline + pub(crate) fn lines_impl( + &self, + end_line_break_as_newline: bool, + ) -> Lines<'_, 'a> { Lines { iter: match &self.repr { Repr::Simple(s) => LinesEnum::Simple(s), @@ -431,6 +441,7 @@ impl<'a> Rope<'a> { chunk_idx: 0, ended: false, total_bytes: self.len(), + end_line_break_as_newline, } } @@ -484,9 +495,12 @@ pub struct Lines<'a, 'b> { chunk_idx: usize, ended: bool, total_bytes: usize, + + /// Whether to treat the end of the rope with ('\n') as an empty newline. + end_line_break_as_newline: bool, } -impl<'a, 'b> Iterator for Lines<'a, 'b> { +impl<'a> Iterator for Lines<'_, 'a> { type Item = Rope<'a>; fn next(&mut self) -> Option { @@ -496,13 +510,17 @@ impl<'a, 'b> Iterator for Lines<'a, 'b> { ref mut byte_idx, ref mut ended, ref total_bytes, + end_line_break_as_newline, .. } => { if *ended { return None; } else if byte_idx == total_bytes { - *ended = true; - return Some(Rope::from("")); + if end_line_break_as_newline { + *ended = true; + return Some(Rope::from("")); + } + return None; } else if let Some(idx) = memchr::memchr(b'\n', &s.as_bytes()[*byte_idx..]) { @@ -512,7 +530,7 @@ impl<'a, 'b> Iterator for Lines<'a, 'b> { return Some(rope); } *ended = true; - return Some(Rope::from(&s[*byte_idx..])); + Some(Rope::from(&s[*byte_idx..])) } Lines { iter: LinesEnum::Complex(chunks), @@ -521,12 +539,16 @@ impl<'a, 'b> Iterator for Lines<'a, 'b> { ref mut chunk_idx, ref mut ended, ref total_bytes, + end_line_break_as_newline, } => { if *ended { return None; } else if byte_idx == total_bytes { - *ended = true; - return Some(Rope::from("")); + if end_line_break_as_newline { + *ended = true; + return Some(Rope::from("")); + } + return None; } debug_assert!(*chunk_idx < chunks.len()); @@ -629,9 +651,9 @@ impl<'a, 'b> Iterator for Lines<'a, 'b> { }); // Advance the byte index to the end of the rope. *byte_idx += len; - return Some(Rope { + Some(Rope { repr: Repr::Complex(Rc::new(raw)), - }); + }) } } } @@ -1093,13 +1115,23 @@ mod tests { #[test] fn lines1() { + let rope = Rope::from("abc"); + let lines = rope.lines().collect::>(); + assert_eq!(lines, ["abc"]); + + // empty line at the end if the line before ends with a newline ('\n') let rope = Rope::from("abc\ndef\n"); let lines = rope.lines().collect::>(); assert_eq!(lines, ["abc\n", "def\n", ""]); + // no empty line at the end if the line before does not end with a newline ('\n') let rope = Rope::from("abc\ndef"); let lines = rope.lines().collect::>(); assert_eq!(lines, ["abc\n", "def"]); + + let rope = Rope::from("Test\nTest\nTest\n"); + let lines = rope.lines().collect::>(); + assert_eq!(lines, ["Test\n", "Test\n", "Test\n", ""]); } #[test]