219 lines
7.1 KiB
Rust
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());
|
|
}
|
|
}
|