use rope::{Point, Rope}; /// An extension trait for `Rope` to provide additional utility methods. pub trait RopeExt { /// Get the line at the given row (0-based) index, including the `\r` at the end, but not `\n`. /// /// Return empty rope if the row (0-based) is out of bounds. fn line(&self, row: usize) -> Rope; /// Start offset of the line at the given row (0-based) index. fn line_start_offset(&self, row: usize) -> usize; /// Line the end offset (including `\n`) of the line at the given row (0-based) index. /// /// Return the end of the rope if the row is out of bounds. fn line_end_offset(&self, row: usize) -> usize; /// Return the number of lines in the rope. fn lines_len(&self) -> usize; /// Return the lines iterator. /// /// Each line is including the `\n` at the end, but not `\n`. fn lines(&self) -> RopeLines; /// Check is equal to another rope. fn eq(&self, other: &Rope) -> bool; /// Total number of characters in the rope. fn chars_count(&self) -> usize; /// Get char at the given offset (byte). /// /// If the offset is in the middle of a multi-byte character will panic. /// /// If the offset is out of bounds, return None. fn char_at(&self, offset: usize) -> Option; } /// An iterator over the lines of a `Rope`. pub struct RopeLines { row: usize, end_row: usize, rope: Rope, } impl RopeLines { /// Create a new `RopeLines` iterator. pub fn new(rope: Rope) -> Self { let end_row = rope.lines_len(); Self { row: 0, end_row, rope, } } } impl Iterator for RopeLines { type Item = Rope; #[inline] fn next(&mut self) -> Option { if self.row >= self.end_row { return None; } let line = self.rope.line(self.row); self.row += 1; Some(line) } #[inline] fn nth(&mut self, n: usize) -> Option { self.row = self.row.saturating_add(n); self.next() } #[inline] fn size_hint(&self) -> (usize, Option) { let len = self.end_row - self.row; (len, Some(len)) } } impl std::iter::ExactSizeIterator for RopeLines {} impl std::iter::FusedIterator for RopeLines {} impl RopeExt for Rope { fn line(&self, row: usize) -> Rope { let row = row as u32; let start = self.point_to_offset(Point::new(row, 0)); let end = start + self.line_len(row) as usize; self.slice(start..end) } fn line_start_offset(&self, row: usize) -> usize { let row = row as u32; self.point_to_offset(Point::new(row, 0)) } fn line_end_offset(&self, row: usize) -> usize { if row > self.max_point().row as usize { return self.len(); } self.line_start_offset(row) + self.line_len(row as u32) as usize } fn lines_len(&self) -> usize { self.max_point().row as usize + 1 } fn lines(&self) -> RopeLines { RopeLines::new(self.clone()) } fn eq(&self, other: &Rope) -> bool { self.summary() == other.summary() } fn chars_count(&self) -> usize { self.chars().count() } fn char_at(&self, offset: usize) -> Option { if offset > self.len() { return None; } self.slice(offset..self.len()).chars().next() } } #[cfg(test)] mod tests { use rope::Rope; use crate::input::RopeExt as _; #[test] fn test_line() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope"); assert_eq!(rope.line(0).to_string(), "Hello"); assert_eq!(rope.line(1).to_string(), "World\r"); assert_eq!(rope.line(2).to_string(), "This is a test 中文"); assert_eq!(rope.line(3).to_string(), "Rope"); assert_eq!(rope.line(4).to_string(), ""); } #[test] fn test_lines_len() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope"); assert_eq!(rope.lines_len(), 4); let rope = Rope::from(""); assert_eq!(rope.lines_len(), 1); let rope = Rope::from("Single line"); assert_eq!(rope.lines_len(), 1); } #[test] fn test_eq() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope"); assert!(rope.eq(&Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope"))); assert!(!rope.eq(&Rope::from("Hello\nWorld"))); let rope1 = rope.clone(); assert!(rope.eq(&rope1)); } #[test] fn test_lines() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope"); let lines: Vec<_> = rope.lines().map(|r| r.to_string()).collect(); assert_eq!( lines, vec!["Hello", "World\r", "This is a test 中文", "Rope"] ); } #[test] fn test_line_start_end_offset() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope"); assert_eq!(rope.line_start_offset(0), 0); assert_eq!(rope.line_end_offset(0), 5); assert_eq!(rope.line_start_offset(1), 6); assert_eq!(rope.line_end_offset(1), 12); assert_eq!(rope.line_start_offset(2), 13); assert_eq!(rope.line_end_offset(2), 34); assert_eq!(rope.line_start_offset(3), 35); assert_eq!(rope.line_end_offset(3), 39); assert_eq!(rope.line_start_offset(4), 39); assert_eq!(rope.line_end_offset(4), 39); } #[test] fn test_chars_count() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文🎉\nRope"); assert_eq!(rope.chars_count(), 36); let rope = Rope::from(""); assert_eq!(rope.chars_count(), 0); let rope = Rope::from("Single line"); assert_eq!(rope.chars_count(), 11); } #[test] fn test_char_at() { let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文🎉\nRope"); assert_eq!(rope.char_at(0), Some('H')); assert_eq!(rope.char_at(5), Some('\n')); assert_eq!(rope.char_at(13), Some('T')); assert_eq!(rope.char_at(28), Some('中')); assert_eq!(rope.char_at(34), Some('🎉')); assert_eq!(rope.char_at(38), Some('\n')); assert_eq!(rope.char_at(50), None); } }