Refactor EditorBuffer and TextBuffer to be immutable
This commit is contained in:
parent
4188301e79
commit
41e9c197cd
|
|
@ -1,11 +1,11 @@
|
||||||
use std::path::PathBuf;
|
use std::{path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use super::{CommandResponse, EditorBuffer};
|
use super::{CommandResult, EditorBuffer, EditorBufferState};
|
||||||
use crate::{Point, TextBuffer, TextBufferReader, TextBufferWriter};
|
use crate::{Point, TextBuffer, TextBufferReader, TextBufferWriter};
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl EditorBuffer {
|
impl EditorBuffer {
|
||||||
pub fn open_file(&mut self, filepath: PathBuf) -> CommandResponse {
|
pub fn open_file(&self, filepath: PathBuf) -> CommandResult {
|
||||||
match std::fs::File::open(&filepath) {
|
match std::fs::File::open(&filepath) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
let mut buffer = TextBuffer::new();
|
let mut buffer = TextBuffer::new();
|
||||||
|
|
@ -15,43 +15,59 @@ impl EditorBuffer {
|
||||||
"Read {bytes_read} bytes from \"{}\"",
|
"Read {bytes_read} bytes from \"{}\"",
|
||||||
filepath.to_string_lossy()
|
filepath.to_string_lossy()
|
||||||
);
|
);
|
||||||
self.filepath = Some(filepath);
|
let state = Rc::new(EditorBufferState {
|
||||||
self.cursor = Point::default();
|
filepath: Some(filepath),
|
||||||
self.buffer = buffer;
|
..*self.state
|
||||||
CommandResponse::Success(msg)
|
});
|
||||||
|
let cursor = Point::default();
|
||||||
|
let buffer = buffer;
|
||||||
|
CommandResult::success_with_message(
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
cursor,
|
||||||
|
buffer,
|
||||||
|
..self.clone()
|
||||||
|
},
|
||||||
|
msg,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Err(err) => CommandResponse::Failure(format!("{}", err)),
|
Err(err) => CommandResult::fail_with_message(self.clone(), format!("{}", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.kind() == std::io::ErrorKind::NotFound {
|
if err.kind() == std::io::ErrorKind::NotFound {
|
||||||
CommandResponse::Failure(format!(
|
CommandResult::fail_with_message(
|
||||||
"File not found: \"{}\"",
|
self.clone(),
|
||||||
filepath.to_string_lossy()
|
format!("File not found: \"{}\"", filepath.to_string_lossy()),
|
||||||
))
|
)
|
||||||
} else {
|
} else {
|
||||||
CommandResponse::Failure(format!("{}", err))
|
CommandResult::fail_with_message(self.clone(), format!("{}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_file(&mut self, filepath: Option<PathBuf>) -> CommandResponse {
|
pub fn save_file(&self, filepath: Option<PathBuf>) -> CommandResult {
|
||||||
if let Some(filepath) = filepath.as_ref().or(self.filepath.as_ref()) {
|
if let Some(filepath) = filepath.as_ref().or(self.state.filepath.as_ref()) {
|
||||||
match std::fs::File::create(filepath) {
|
match std::fs::File::create(filepath) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
match std::io::copy(&mut TextBufferReader::new(&self.buffer), &mut file) {
|
match std::io::copy(&mut TextBufferReader::new(&self.buffer), &mut file) {
|
||||||
Ok(bytes_read) => CommandResponse::Success(format!(
|
Ok(bytes_read) => CommandResult::success_with_message(
|
||||||
|
self.clone(),
|
||||||
|
format!(
|
||||||
"Read {bytes_read} bytes to \"{}\"",
|
"Read {bytes_read} bytes to \"{}\"",
|
||||||
filepath.to_string_lossy()
|
filepath.to_string_lossy()
|
||||||
)),
|
),
|
||||||
Err(err) => CommandResponse::Failure(format!("{}", err)),
|
),
|
||||||
|
Err(err) => {
|
||||||
|
CommandResult::fail_with_message(self.clone(), format!("{}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => CommandResponse::Failure(format!("{}", err)),
|
}
|
||||||
|
Err(err) => CommandResult::fail_with_message(self.clone(), format!("{}", err)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CommandResponse::Failure("Attempting to same file with no name.".into())
|
CommandResult::fail_with_message(self.clone(), "Attempting to same file with no name.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,11 +87,10 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.collect();
|
.collect();
|
||||||
let expected_text = std::fs::read_to_string(&test_file_path).unwrap();
|
let expected_text = std::fs::read_to_string(&test_file_path).unwrap();
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
assert!(matches!(
|
let result = target.execute(Command::OpenFile(test_file_path));
|
||||||
target.execute(Command::OpenFile(test_file_path)),
|
assert!(result.is_ok());
|
||||||
CommandResponse::Success(_)
|
let target = result.buffer;
|
||||||
));
|
|
||||||
|
|
||||||
let mut buffer_bytes = Vec::new();
|
let mut buffer_bytes = Vec::new();
|
||||||
TextBufferReader::new(&target.buffer)
|
TextBufferReader::new(&target.buffer)
|
||||||
|
|
@ -91,9 +106,11 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.collect();
|
.collect();
|
||||||
let expected_message = format!("File not found: \"{}\"", test_file_path.to_string_lossy());
|
let expected_message = format!("File not found: \"{}\"", test_file_path.to_string_lossy());
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
match target.execute(Command::OpenFile(test_file_path)) {
|
let result = target.execute(Command::OpenFile(test_file_path));
|
||||||
CommandResponse::Failure(s) => assert_eq!(expected_message, s),
|
assert!(!result.is_ok());
|
||||||
|
match result.response {
|
||||||
|
CommandResponse::Message(s) => assert_eq!(expected_message, s),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,14 +123,12 @@ mod tests {
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.collect();
|
.collect();
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
target.execute(Command::OpenFile(test_file_path.clone()));
|
let target = target.execute(Command::OpenFile(test_file_path.clone())).buffer;
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let tmp_file_path = temp_dir.path().join(r"Les_Trois_Mousquetaires.txt");
|
let tmp_file_path = temp_dir.path().join(r"Les_Trois_Mousquetaires.txt");
|
||||||
assert!(matches!(
|
let result = target.execute(Command::SaveAs(tmp_file_path.clone()));
|
||||||
target.execute(Command::SaveAs(tmp_file_path.clone())),
|
assert!(result.is_ok());
|
||||||
CommandResponse::Success(_)
|
|
||||||
));
|
|
||||||
|
|
||||||
let read_text = std::fs::read_to_string(&tmp_file_path).unwrap();
|
let read_text = std::fs::read_to_string(&tmp_file_path).unwrap();
|
||||||
let expected_text = std::fs::read_to_string(&test_file_path).unwrap();
|
let expected_text = std::fs::read_to_string(&test_file_path).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::{path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
use crate::{Point, TextBuffer};
|
use crate::{Point, TextBuffer};
|
||||||
|
|
||||||
|
|
@ -6,26 +6,57 @@ mod command;
|
||||||
mod io;
|
mod io;
|
||||||
pub use command::{Command, Movement, Unit};
|
pub use command::{Command, Movement, Unit};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone)]
|
||||||
pub struct EditorBuffer {
|
pub struct EditorBuffer {
|
||||||
buffer: TextBuffer,
|
buffer: TextBuffer,
|
||||||
cursor: Point,
|
cursor: Point,
|
||||||
|
state: Rc<EditorBufferState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct EditorBufferState {
|
||||||
filepath: Option<PathBuf>,
|
filepath: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum CommandResponse {
|
pub enum CommandResponse {
|
||||||
Ok,
|
None,
|
||||||
Success(String),
|
Message(String),
|
||||||
Failure(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandResponse {
|
#[must_use]
|
||||||
|
pub struct CommandResult {
|
||||||
|
success: bool,
|
||||||
|
response: CommandResponse,
|
||||||
|
buffer: EditorBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandResult {
|
||||||
pub fn is_ok(&self) -> bool {
|
pub fn is_ok(&self) -> bool {
|
||||||
match self {
|
self.success
|
||||||
Self::Ok => true,
|
}
|
||||||
Self::Success(_) => true,
|
|
||||||
Self::Failure(_) => false,
|
fn ok(buffer: EditorBuffer) -> Self {
|
||||||
|
Self {
|
||||||
|
success: true,
|
||||||
|
response: CommandResponse::None,
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn success_with_message(buffer: EditorBuffer, msg: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
success: true,
|
||||||
|
response: CommandResponse::Message(msg.into()),
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fail_with_message(buffer: EditorBuffer, msg: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
success: false,
|
||||||
|
response: CommandResponse::Message(msg.into()),
|
||||||
|
buffer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +68,7 @@ impl EditorBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a command on the [EditorBuffer]
|
/// Execute a command on the [EditorBuffer]
|
||||||
pub fn execute(&mut self, command: Command) -> CommandResponse {
|
pub fn execute(&self, command: Command) -> CommandResult {
|
||||||
match command {
|
match command {
|
||||||
Command::OpenFile(filepath) => self.open_file(filepath),
|
Command::OpenFile(filepath) => self.open_file(filepath),
|
||||||
Command::Save => self.save_file(None),
|
Command::Save => self.save_file(None),
|
||||||
|
|
@ -55,29 +86,34 @@ impl EditorBuffer {
|
||||||
self.cursor
|
self.cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_to_point(&mut self, point: Point) -> CommandResponse {
|
fn move_cursor_to_point(&self, point: Point) -> CommandResult {
|
||||||
self.cursor = point;
|
CommandResult::ok(Self {
|
||||||
CommandResponse::Ok
|
cursor: point,
|
||||||
|
..self.clone()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_to_line_number(&mut self, _line_num: usize) -> CommandResponse {
|
fn move_cursor_to_line_number(&self, _line_num: usize) -> CommandResult {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_char(&mut self, c: char) -> CommandResponse {
|
fn insert_char(&self, c: char) -> CommandResult {
|
||||||
self.buffer.insert_char(c, self.cursor);
|
CommandResult::ok(Self {
|
||||||
CommandResponse::Ok
|
buffer: self.buffer.insert_char(c, self.cursor),
|
||||||
|
cursor: self.cursor.advance(),
|
||||||
|
..self.clone()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_string(&mut self, _s: String) -> CommandResponse {
|
fn insert_string(&self, _s: String) -> CommandResult {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor(&mut self, _movement: Movement) -> CommandResponse {
|
fn move_cursor(&self, _movement: Movement) -> CommandResult {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(&mut self, _movement: Movement) -> CommandResponse {
|
fn delete(&self, _movement: Movement) -> CommandResult {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,42 +155,38 @@ mod tests {
|
||||||
test_file
|
test_file
|
||||||
.read_to_string(&mut expected_contents)
|
.read_to_string(&mut expected_contents)
|
||||||
.expect("Reading text file");
|
.expect("Reading text file");
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
target.execute(Command::OpenFile(test_file.path().into()));
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
let found_contents: String = target.buffer.iter_chars().collect();
|
let found_contents: String = result.buffer.buffer.iter_chars().collect();
|
||||||
assert_eq!(expected_contents, found_contents);
|
assert_eq!(expected_contents, found_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cursor_at_beginning_after_file_opened() {
|
fn cursor_at_beginning_after_file_opened() {
|
||||||
let test_file = create_simple_test_file();
|
let test_file = create_simple_test_file();
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
target.execute(Command::OpenFile(test_file.path().into()));
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
assert_eq!(Point::Start, target.get_cursor_position());
|
assert_eq!(Point::Start, result.buffer.get_cursor_position());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_cursor_to_point_in_file() {
|
fn move_cursor_to_point_in_file() {
|
||||||
let test_file = create_simple_test_file();
|
let test_file = create_simple_test_file();
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
target.execute(Command::OpenFile(test_file.path().into()));
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
assert!(
|
assert!(result.is_ok());
|
||||||
target
|
let target = result.buffer;
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(0, 5)))
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(0, 5)));
|
||||||
.is_ok()
|
assert!(result.is_ok());
|
||||||
);
|
let target = result.buffer;
|
||||||
assert_eq!(Point::LineColumn(0, 5), target.get_cursor_position());
|
assert_eq!(Point::LineColumn(0, 5), target.get_cursor_position());
|
||||||
assert!(
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(3, 11)));
|
||||||
target
|
assert!(result.is_ok());
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(3, 11)))
|
let target = result.buffer;
|
||||||
.is_ok()
|
|
||||||
);
|
|
||||||
assert_eq!(Point::LineColumn(3, 11), target.get_cursor_position());
|
assert_eq!(Point::LineColumn(3, 11), target.get_cursor_position());
|
||||||
assert!(
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(3, 0)));
|
||||||
target
|
assert!(result.is_ok());
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(3, 0)))
|
let target = result.buffer;
|
||||||
.is_ok()
|
|
||||||
);
|
|
||||||
assert_eq!(Point::LineColumn(3, 0), target.get_cursor_position());
|
assert_eq!(Point::LineColumn(3, 0), target.get_cursor_position());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,18 +201,16 @@ mod tests {
|
||||||
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
||||||
expected_lines[2] = "Xbíth a menmasam fri seilgg";
|
expected_lines[2] = "Xbíth a menmasam fri seilgg";
|
||||||
let expected_lines = expected_lines;
|
let expected_lines = expected_lines;
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
assert!(
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
target
|
assert!(result.is_ok());
|
||||||
.execute(Command::OpenFile(test_file.path().into()))
|
let target = result.buffer;
|
||||||
.is_ok()
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(3, 0)));
|
||||||
);
|
assert!(result.is_ok());
|
||||||
assert!(
|
let target = result.buffer;
|
||||||
target
|
let result = target.execute(Command::InsertChar('X'));
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(3, 0)))
|
assert!(result.is_ok());
|
||||||
.is_ok()
|
let target = result.buffer;
|
||||||
);
|
|
||||||
assert!(target.execute(Command::InsertChar('X')).is_ok());
|
|
||||||
let found_lines: Vec<String> = target
|
let found_lines: Vec<String> = target
|
||||||
.buffer
|
.buffer
|
||||||
.iter_chars()
|
.iter_chars()
|
||||||
|
|
@ -199,18 +229,16 @@ mod tests {
|
||||||
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
||||||
expected_lines[2] = "bXíth a menmasam fri seilgg";
|
expected_lines[2] = "bXíth a menmasam fri seilgg";
|
||||||
let expected_lines = expected_lines;
|
let expected_lines = expected_lines;
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
assert!(
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
target
|
assert!(result.is_ok());
|
||||||
.execute(Command::OpenFile(test_file.path().into()))
|
let target = result.buffer;
|
||||||
.is_ok()
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(3, 1)));
|
||||||
);
|
assert!(result.is_ok());
|
||||||
assert!(
|
let target = result.buffer;
|
||||||
target
|
let result = target.execute(Command::InsertChar('X'));
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(3, 1)))
|
assert!(result.is_ok());
|
||||||
.is_ok()
|
let target = result.buffer;
|
||||||
);
|
|
||||||
assert!(target.execute(Command::InsertChar('X')).is_ok());
|
|
||||||
let found_lines: Vec<String> = target
|
let found_lines: Vec<String> = target
|
||||||
.buffer
|
.buffer
|
||||||
.iter_chars()
|
.iter_chars()
|
||||||
|
|
@ -229,18 +257,16 @@ mod tests {
|
||||||
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
||||||
expected_lines[2] = "bíXth a menmasam fri seilgg";
|
expected_lines[2] = "bíXth a menmasam fri seilgg";
|
||||||
let expected_lines = expected_lines;
|
let expected_lines = expected_lines;
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
assert!(
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
target
|
assert!(result.is_ok());
|
||||||
.execute(Command::OpenFile(test_file.path().into()))
|
let target = result.buffer;
|
||||||
.is_ok()
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(3, 2)));
|
||||||
);
|
assert!(result.is_ok());
|
||||||
assert!(
|
let target = result.buffer;
|
||||||
target
|
let result = target.execute(Command::InsertChar('X'));
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(3, 2)))
|
assert!(result.is_ok());
|
||||||
.is_ok()
|
let target = result.buffer;
|
||||||
);
|
|
||||||
assert!(target.execute(Command::InsertChar('X')).is_ok());
|
|
||||||
let found_lines: Vec<String> = target
|
let found_lines: Vec<String> = target
|
||||||
.buffer
|
.buffer
|
||||||
.iter_chars()
|
.iter_chars()
|
||||||
|
|
@ -259,18 +285,16 @@ mod tests {
|
||||||
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
let mut expected_lines: Vec<_> = file_contents.lines().collect();
|
||||||
expected_lines[2] = "bíth a menmXasam fri seilgg";
|
expected_lines[2] = "bíth a menmXasam fri seilgg";
|
||||||
let expected_lines = expected_lines;
|
let expected_lines = expected_lines;
|
||||||
let mut target = EditorBuffer::new();
|
let target = EditorBuffer::new();
|
||||||
assert!(
|
let result = target.execute(Command::OpenFile(test_file.path().into()));
|
||||||
target
|
assert!(result.is_ok());
|
||||||
.execute(Command::OpenFile(test_file.path().into()))
|
let target = result.buffer;
|
||||||
.is_ok()
|
let result = target.execute(Command::MoveCursorTo(Point::LineColumn(3, 11)));
|
||||||
);
|
assert!(result.is_ok());
|
||||||
assert!(
|
let target = result.buffer;
|
||||||
target
|
let result = target.execute(Command::InsertChar('X'));
|
||||||
.execute(Command::MoveCursorTo(Point::LineColumn(3, 11)))
|
assert!(result.is_ok());
|
||||||
.is_ok()
|
let target = result.buffer;
|
||||||
);
|
|
||||||
assert!(target.execute(Command::InsertChar('X')).is_ok());
|
|
||||||
let found_lines: Vec<String> = target
|
let found_lines: Vec<String> = target
|
||||||
.buffer
|
.buffer
|
||||||
.iter_chars()
|
.iter_chars()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ mod writer;
|
||||||
pub use writer::TextBufferWriter;
|
pub use writer::TextBufferWriter;
|
||||||
|
|
||||||
/// A block of text, usually containing the contents of a text file.
|
/// A block of text, usually containing the contents of a text file.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct TextBuffer {
|
pub struct TextBuffer {
|
||||||
contents: Rc<Rope>,
|
contents: Rc<Rope>,
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +26,16 @@ pub enum Point {
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
pub fn advance(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Start => Self::LineColumn(0, 1),
|
||||||
|
Self::LineColumn(l, c) => Self::LineColumn(l, c + 1),
|
||||||
|
Self::End => Self::End,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TextBuffer {
|
impl TextBuffer {
|
||||||
/// Create a new empty [TextBuffer]
|
/// Create a new empty [TextBuffer]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
@ -70,8 +81,8 @@ impl TextBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_char(&mut self, c: char, point: Point) {
|
pub fn insert_char(&self, c: char, point: Point) -> Self {
|
||||||
self.contents = match point {
|
let contents = match point {
|
||||||
Point::Start => self.contents.insert_at_char_index(0, c),
|
Point::Start => self.contents.insert_at_char_index(0, c),
|
||||||
Point::LineColumn(line_num, column_num) => {
|
Point::LineColumn(line_num, column_num) => {
|
||||||
self.contents
|
self.contents
|
||||||
|
|
@ -81,6 +92,7 @@ impl TextBuffer {
|
||||||
.contents
|
.contents
|
||||||
.insert_at_char_index(self.contents.total_chars(), c),
|
.insert_at_char_index(self.contents.total_chars(), c),
|
||||||
};
|
};
|
||||||
|
Self { contents }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_at_char_index(&mut self, start: usize, length: usize) -> bool {
|
pub fn delete_at_char_index(&mut self, start: usize, length: usize) -> bool {
|
||||||
|
|
@ -122,68 +134,68 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_char_at_end_increases_counts_as_expected() {
|
fn insert_char_at_end_increases_counts_as_expected() {
|
||||||
let mut target = TextBuffer::new();
|
let target = TextBuffer::new();
|
||||||
target.insert_char('A', Point::End);
|
let target = target.insert_char('A', Point::End);
|
||||||
assert_eq!(1, target.num_bytes());
|
assert_eq!(1, target.num_bytes());
|
||||||
assert_eq!(1, target.num_chars());
|
assert_eq!(1, target.num_chars());
|
||||||
assert_eq!(1, target.num_lines());
|
assert_eq!(1, target.num_lines());
|
||||||
target.insert_char(' ', Point::End);
|
let target = target.insert_char(' ', Point::End);
|
||||||
assert_eq!(2, target.num_bytes());
|
assert_eq!(2, target.num_bytes());
|
||||||
assert_eq!(2, target.num_chars());
|
assert_eq!(2, target.num_chars());
|
||||||
assert_eq!(1, target.num_lines());
|
assert_eq!(1, target.num_lines());
|
||||||
target.insert_char('c', Point::End);
|
let target = target.insert_char('c', Point::End);
|
||||||
assert_eq!(3, target.num_bytes());
|
assert_eq!(3, target.num_bytes());
|
||||||
assert_eq!(3, target.num_chars());
|
assert_eq!(3, target.num_chars());
|
||||||
assert_eq!(1, target.num_lines());
|
assert_eq!(1, target.num_lines());
|
||||||
target.insert_char('a', Point::End);
|
let target = target.insert_char('a', Point::End);
|
||||||
assert_eq!(4, target.num_bytes());
|
assert_eq!(4, target.num_bytes());
|
||||||
assert_eq!(4, target.num_chars());
|
assert_eq!(4, target.num_chars());
|
||||||
assert_eq!(1, target.num_lines());
|
assert_eq!(1, target.num_lines());
|
||||||
target.insert_char('t', Point::End);
|
let target = target.insert_char('t', Point::End);
|
||||||
assert_eq!(5, target.num_bytes());
|
assert_eq!(5, target.num_bytes());
|
||||||
assert_eq!(5, target.num_chars());
|
assert_eq!(5, target.num_chars());
|
||||||
assert_eq!(1, target.num_lines());
|
assert_eq!(1, target.num_lines());
|
||||||
target.insert_char('\n', Point::End);
|
let target = target.insert_char('\n', Point::End);
|
||||||
assert_eq!(6, target.num_bytes());
|
assert_eq!(6, target.num_bytes());
|
||||||
assert_eq!(6, target.num_chars());
|
assert_eq!(6, target.num_chars());
|
||||||
assert_eq!(1, target.num_lines());
|
assert_eq!(1, target.num_lines());
|
||||||
target.insert_char('A', Point::End);
|
let target = target.insert_char('A', Point::End);
|
||||||
assert_eq!(7, target.num_bytes());
|
assert_eq!(7, target.num_bytes());
|
||||||
assert_eq!(7, target.num_chars());
|
assert_eq!(7, target.num_chars());
|
||||||
assert_eq!(2, target.num_lines());
|
assert_eq!(2, target.num_lines());
|
||||||
target.insert_char('\n', Point::End);
|
let target = target.insert_char('\n', Point::End);
|
||||||
assert_eq!(8, target.num_bytes());
|
assert_eq!(8, target.num_bytes());
|
||||||
assert_eq!(8, target.num_chars());
|
assert_eq!(8, target.num_chars());
|
||||||
assert_eq!(2, target.num_lines());
|
assert_eq!(2, target.num_lines());
|
||||||
target.insert_char('\n', Point::End);
|
let target = target.insert_char('\n', Point::End);
|
||||||
assert_eq!(9, target.num_bytes());
|
assert_eq!(9, target.num_bytes());
|
||||||
assert_eq!(9, target.num_chars());
|
assert_eq!(9, target.num_chars());
|
||||||
assert_eq!(3, target.num_lines());
|
assert_eq!(3, target.num_lines());
|
||||||
target.insert_char('*', Point::End);
|
let target = target.insert_char('*', Point::End);
|
||||||
assert_eq!(10, target.num_bytes());
|
assert_eq!(10, target.num_bytes());
|
||||||
assert_eq!(10, target.num_chars());
|
assert_eq!(10, target.num_chars());
|
||||||
assert_eq!(4, target.num_lines());
|
assert_eq!(4, target.num_lines());
|
||||||
target.insert_char('*', Point::End);
|
let target = target.insert_char('*', Point::End);
|
||||||
assert_eq!(11, target.num_bytes());
|
assert_eq!(11, target.num_bytes());
|
||||||
assert_eq!(11, target.num_chars());
|
assert_eq!(11, target.num_chars());
|
||||||
assert_eq!(4, target.num_lines());
|
assert_eq!(4, target.num_lines());
|
||||||
target.insert_char(' ', Point::End);
|
let target = target.insert_char(' ', Point::End);
|
||||||
assert_eq!(12, target.num_bytes());
|
assert_eq!(12, target.num_bytes());
|
||||||
assert_eq!(12, target.num_chars());
|
assert_eq!(12, target.num_chars());
|
||||||
assert_eq!(4, target.num_lines());
|
assert_eq!(4, target.num_lines());
|
||||||
target.insert_char('猫', Point::End);
|
let target = target.insert_char('猫', Point::End);
|
||||||
assert_eq!(15, target.num_bytes());
|
assert_eq!(15, target.num_bytes());
|
||||||
assert_eq!(13, target.num_chars());
|
assert_eq!(13, target.num_chars());
|
||||||
assert_eq!(4, target.num_lines());
|
assert_eq!(4, target.num_lines());
|
||||||
target.insert_char(' ', Point::End);
|
let target = target.insert_char(' ', Point::End);
|
||||||
assert_eq!(16, target.num_bytes());
|
assert_eq!(16, target.num_bytes());
|
||||||
assert_eq!(14, target.num_chars());
|
assert_eq!(14, target.num_chars());
|
||||||
assert_eq!(4, target.num_lines());
|
assert_eq!(4, target.num_lines());
|
||||||
target.insert_char('\n', Point::End);
|
let target = target.insert_char('\n', Point::End);
|
||||||
assert_eq!(17, target.num_bytes());
|
assert_eq!(17, target.num_bytes());
|
||||||
assert_eq!(15, target.num_chars());
|
assert_eq!(15, target.num_chars());
|
||||||
assert_eq!(4, target.num_lines());
|
assert_eq!(4, target.num_lines());
|
||||||
target.insert_char('_', Point::End);
|
let target = target.insert_char('_', Point::End);
|
||||||
assert_eq!(18, target.num_bytes());
|
assert_eq!(18, target.num_bytes());
|
||||||
assert_eq!(16, target.num_chars());
|
assert_eq!(16, target.num_chars());
|
||||||
assert_eq!(5, target.num_lines());
|
assert_eq!(5, target.num_lines());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue