use crate::buffer::operation::Operation;
use crate::buffer::{Buffer, Cursor, GapBuffer, Position};
use std::cell::RefCell;
use std::clone::Clone;
use std::convert::Into;
use std::rc::Rc;
#[derive(Clone)]
pub struct Replace {
old_content: String,
new_content: String,
}
impl Operation for Replace {
fn run(&mut self, buffer: &mut Buffer) {
replace_content(self.new_content.clone(), buffer);
}
fn reverse(&mut self, buffer: &mut Buffer) {
replace_content(self.old_content.clone(), buffer);
}
fn clone_operation(&self) -> Box<dyn Operation> {
Box::new(self.clone())
}
}
impl Replace {
pub fn new(old_content: String, new_content: String) -> Replace {
Replace {
old_content,
new_content,
}
}
}
impl Buffer {
pub fn replace<T: Into<String> + AsRef<str>>(&mut self, content: T) {
let old_content = self.data();
if content.as_ref() == old_content {
return;
}
let mut op = Replace::new(self.data(), content.into());
op.run(self);
match self.operation_group {
Some(ref mut group) => group.add(Box::new(op)),
None => self.history.add(Box::new(op)),
};
}
}
fn replace_content(content: String, buffer: &mut Buffer) {
let data = Rc::new(RefCell::new(GapBuffer::new(content)));
let mut cursor = Cursor::new(data.clone(), Position { line: 0, offset: 0 });
if !cursor.move_to(*buffer.cursor) {
cursor.move_to(Position {
line: buffer.cursor.line,
offset: 0,
});
}
buffer.data = data;
buffer.cursor = cursor;
if let Some(ref callback) = buffer.change_callback {
callback(Position::new())
}
}
#[cfg(test)]
mod tests {
use crate::buffer::position::Position;
use crate::buffer::Buffer;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
#[test]
fn replace_retains_full_position_when_possible() {
let mut buffer = Buffer::new();
buffer.insert("amp editor");
buffer.cursor.move_to(Position { line: 0, offset: 3 });
buffer.replace("scribe");
assert_eq!(*buffer.cursor, Position { line: 0, offset: 3 });
}
#[test]
fn replace_retains_position_line_when_possible() {
let mut buffer = Buffer::new();
buffer.insert("amp\neditor");
buffer.cursor.move_to(Position { line: 1, offset: 1 });
buffer.replace("scribe\n");
assert_eq!(*buffer.cursor, Position { line: 1, offset: 0 });
}
#[test]
fn replace_discards_position_when_impossible() {
let mut buffer = Buffer::new();
buffer.insert("\namp\neditor");
buffer.cursor.move_to(Position { line: 2, offset: 1 });
buffer.replace("scribe\n");
assert_eq!(*buffer.cursor, Position::new());
}
#[test]
fn replace_calls_change_callback_with_zero_position() {
let mut buffer = Buffer::new();
buffer.insert("amp\neditor");
let tracked_position = Rc::new(RefCell::new(Position { line: 1, offset: 1 }));
let callback_position = tracked_position.clone();
buffer.change_callback = Some(Box::new(move |change_position| {
*callback_position.borrow_mut() = change_position
}));
buffer.replace("scribe");
assert_eq!(*tracked_position.borrow(), Position::new());
}
#[test]
fn replace_flags_buffer_as_modified() {
let file_path = Path::new("tests/sample/file");
let mut buffer = Buffer::from_file(file_path).unwrap();
buffer.replace("scribe");
assert!(buffer.modified());
}
#[test]
fn replace_is_reversible() {
let file_path = Path::new("tests/sample/file");
let mut buffer = Buffer::from_file(file_path).unwrap();
buffer.replace("scribe");
buffer.undo();
assert_eq!(buffer.data(), "it works!\n");
}
#[test]
fn replace_does_nothing_if_replacement_matches_buffer_contents() {
let file_path = Path::new("tests/sample/file");
let mut buffer = Buffer::from_file(file_path).unwrap();
buffer.replace("it works!\n");
assert!(!buffer.modified());
assert!(buffer.history.previous().is_none());
}
}