use std::rc::Rc; mod rope; use rope::Rope; pub use rope::{CharIterator, CharWithPointIterator}; mod reader; pub use reader::TextBufferReader; mod writer; pub use writer::TextBufferWriter; /// A block of text, usually containing the contents of a text file. #[derive(Clone)] pub struct TextBuffer { contents: Rc, } /// A location in a [TextBuffer] #[derive(Clone, Copy, Debug, Default, PartialEq)] pub enum Point { /// The end of the buffer #[default] Start, LineColumn(usize, usize), End, } impl TextBuffer { /// Create a new empty [TextBuffer] pub fn new() -> Self { Self { contents: Rope::empty(), } } /// The size of the contents in bytes pub fn num_bytes(&self) -> usize { self.contents.total_bytes() } /// The size of the contents in characters pub fn num_chars(&self) -> usize { self.contents.total_chars() } /// The total number of lines in the contents pub fn num_lines(&self) -> usize { let num_chars = self.num_chars(); if num_chars == 0 { 0 } else { self.contents.total_lines() } } /// Insert `text` at `point`. pub fn insert_text(&mut self, text: impl Into, point: Point) { match point { Point::Start => { self.contents = self.contents.insert_at_char_index(0, text); } Point::LineColumn(_, _) => { todo!() } Point::End => { self.contents = self .contents .insert_at_char_index(self.contents.total_chars(), text); } } } pub fn insert_char(&self, c: char, point: Point) -> Self { let contents = match point { Point::Start => self.contents.insert_at_char_index(0, c), Point::LineColumn(line_num, column_num) => { self.contents .insert_at_line_and_column(line_num - 1, column_num, c) } Point::End => self .contents .insert_at_char_index(self.contents.total_chars(), c), }; Self { contents } } pub fn delete_at_char_index(&mut self, start: usize, length: usize) -> bool { match self.contents.delete_at_char_index(start, length) { Ok(r) => { self.contents = r; true } Err(_) => false, } } pub fn iter_chars(&self) -> CharIterator { self.contents.iter_chars() } pub fn iter_chars_with_point(&self) -> CharWithPointIterator { self.contents.iter_chars_with_point() } } impl Default for TextBuffer { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn new_creates_empty_buffer() { let target = TextBuffer::new(); assert_eq!(0, target.num_bytes()); assert_eq!(0, target.num_chars()); assert_eq!(0, target.num_lines()); } #[test] fn insert_char_at_end_increases_counts_as_expected() { let target = TextBuffer::new(); let target = target.insert_char('A', Point::End); assert_eq!(1, target.num_bytes()); assert_eq!(1, target.num_chars()); assert_eq!(1, target.num_lines()); let target = target.insert_char(' ', Point::End); assert_eq!(2, target.num_bytes()); assert_eq!(2, target.num_chars()); assert_eq!(1, target.num_lines()); let target = target.insert_char('c', Point::End); assert_eq!(3, target.num_bytes()); assert_eq!(3, target.num_chars()); assert_eq!(1, target.num_lines()); let target = target.insert_char('a', Point::End); assert_eq!(4, target.num_bytes()); assert_eq!(4, target.num_chars()); assert_eq!(1, target.num_lines()); let target = target.insert_char('t', Point::End); assert_eq!(5, target.num_bytes()); assert_eq!(5, target.num_chars()); assert_eq!(1, target.num_lines()); let target = target.insert_char('\n', Point::End); assert_eq!(6, target.num_bytes()); assert_eq!(6, target.num_chars()); assert_eq!(1, target.num_lines()); let target = target.insert_char('A', Point::End); assert_eq!(7, target.num_bytes()); assert_eq!(7, target.num_chars()); assert_eq!(2, target.num_lines()); let target = target.insert_char('\n', Point::End); assert_eq!(8, target.num_bytes()); assert_eq!(8, target.num_chars()); assert_eq!(2, target.num_lines()); let target = target.insert_char('\n', Point::End); assert_eq!(9, target.num_bytes()); assert_eq!(9, target.num_chars()); assert_eq!(3, target.num_lines()); let target = target.insert_char('*', Point::End); assert_eq!(10, target.num_bytes()); assert_eq!(10, target.num_chars()); assert_eq!(4, target.num_lines()); let target = target.insert_char('*', Point::End); assert_eq!(11, target.num_bytes()); assert_eq!(11, target.num_chars()); assert_eq!(4, target.num_lines()); let target = target.insert_char(' ', Point::End); assert_eq!(12, target.num_bytes()); assert_eq!(12, target.num_chars()); assert_eq!(4, target.num_lines()); let target = target.insert_char('猫', Point::End); assert_eq!(15, target.num_bytes()); assert_eq!(13, target.num_chars()); assert_eq!(4, target.num_lines()); let target = target.insert_char(' ', Point::End); assert_eq!(16, target.num_bytes()); assert_eq!(14, target.num_chars()); assert_eq!(4, target.num_lines()); let target = target.insert_char('\n', Point::End); assert_eq!(17, target.num_bytes()); assert_eq!(15, target.num_chars()); assert_eq!(4, target.num_lines()); let target = target.insert_char('_', Point::End); assert_eq!(18, target.num_bytes()); assert_eq!(16, target.num_chars()); assert_eq!(5, target.num_lines()); } #[test] fn insert_text_at_end_increases_counts_as_expected() { let mut target = TextBuffer::new(); target.insert_text("The", Point::End); assert_eq!(3, target.num_bytes()); assert_eq!(3, target.num_chars()); assert_eq!(1, target.num_lines()); target.insert_text(" ", Point::End); assert_eq!(4, target.num_bytes()); assert_eq!(4, target.num_chars()); assert_eq!(1, target.num_lines()); target.insert_text("quick brown", Point::End); assert_eq!(15, target.num_bytes()); assert_eq!(15, target.num_chars()); assert_eq!(1, target.num_lines()); target.insert_text(" fox\n", Point::End); assert_eq!(20, target.num_bytes()); assert_eq!(20, target.num_chars()); assert_eq!(1, target.num_lines()); target.insert_text("jumps over the lazy 猫.", Point::End); assert_eq!(44, target.num_bytes()); assert_eq!(42, target.num_chars()); assert_eq!(2, target.num_lines()); } }