ged/core/src/text_buffer/mod.rs

219 lines
7.1 KiB
Rust

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<Rope>,
}
/// 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<String>, 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());
}
}