#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TextRange {
pub start: usize,
pub end: usize,
}
impl TextRange {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone)]
pub struct DocumentChange {
pub range: TextRange,
pub new_text: String,
pub old_text: String,
}
impl DocumentChange {
pub fn new(range: TextRange, new_text: impl Into<String>, old_text: impl Into<String>) -> Self {
Self {
range,
new_text: new_text.into(),
old_text: old_text.into(),
}
}
pub fn apply(&self, source: &str) -> String {
let mut result =
String::with_capacity(source.len() - self.range.len() + self.new_text.len());
result.push_str(&source[..self.range.start]);
result.push_str(&self.new_text);
result.push_str(&source[self.range.end..]);
result
}
pub fn inverse(&self) -> Self {
Self {
range: TextRange::new(self.range.start, self.range.start + self.new_text.len()),
new_text: self.old_text.clone(),
old_text: self.new_text.clone(),
}
}
}
#[derive(Debug, Default)]
pub struct DocumentHistory {
past: Vec<DocumentChange>,
future: Vec<DocumentChange>,
}
impl DocumentHistory {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, change: DocumentChange) {
self.past.push(change);
self.future.clear();
}
pub fn undo(&mut self) -> Option<&DocumentChange> {
let change = self.past.pop()?;
self.future.push(change.inverse());
self.future.last()
}
pub fn redo(&mut self) -> Option<&DocumentChange> {
let change = self.future.pop()?;
self.past.push(change.inverse());
self.past.last()
}
pub fn can_undo(&self) -> bool {
!self.past.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.future.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_range_len() {
assert_eq!(TextRange::new(2, 7).len(), 5);
assert_eq!(TextRange::new(3, 3).len(), 0);
}
#[test]
fn test_text_range_is_empty() {
assert!(TextRange::new(5, 5).is_empty());
assert!(!TextRange::new(5, 6).is_empty());
}
#[test]
fn test_apply_replaces_range() {
let source = "Hello, world!";
let change = DocumentChange::new(TextRange::new(7, 12), "Rust", "world");
assert_eq!(change.apply(source), "Hello, Rust!");
}
#[test]
fn test_apply_insert_at_start() {
let source = "world";
let change = DocumentChange::new(TextRange::new(0, 0), "Hello ", "");
assert_eq!(change.apply(source), "Hello world");
}
#[test]
fn test_apply_delete_range() {
let source = "Hello, world!";
let change = DocumentChange::new(TextRange::new(5, 12), "", ", world");
assert_eq!(change.apply(source), "Hello!");
}
#[test]
fn test_inverse_undoes_change() {
let source = "Hello, world!";
let change = DocumentChange::new(TextRange::new(7, 12), "Rust", "world");
let modified = change.apply(source);
assert_eq!(modified, "Hello, Rust!");
let undo = change.inverse();
let restored = undo.apply(&modified);
assert_eq!(restored, source);
}
#[test]
fn test_inverse_of_inverse_is_original() {
let c = DocumentChange::new(TextRange::new(0, 5), "new", "old_t");
let inv = c.inverse();
let inv_inv = inv.inverse();
assert_eq!(inv_inv.range.start, c.range.start);
assert_eq!(inv_inv.new_text, c.new_text);
assert_eq!(inv_inv.old_text, c.old_text);
}
#[test]
fn test_history_can_undo_after_push() {
let mut h = DocumentHistory::new();
assert!(!h.can_undo());
h.push(DocumentChange::new(TextRange::new(0, 0), "x", ""));
assert!(h.can_undo());
}
#[test]
fn test_history_undo_returns_inverse() {
let source = "Hello, world!";
let change = DocumentChange::new(TextRange::new(7, 12), "Rust", "world");
let modified = change.apply(source);
let mut h = DocumentHistory::new();
h.push(change);
let undo_change = h.undo().expect("should have undo");
let restored = undo_change.apply(&modified);
assert_eq!(restored, source);
}
#[test]
fn test_history_undo_enables_redo() {
let mut h = DocumentHistory::new();
h.push(DocumentChange::new(TextRange::new(0, 1), "b", "a"));
assert!(!h.can_redo());
h.undo();
assert!(h.can_redo());
assert!(!h.can_undo());
}
#[test]
fn test_history_redo_re_applies() {
let source = "a";
let change = DocumentChange::new(TextRange::new(0, 1), "b", "a");
let modified = change.apply(source);
let mut h = DocumentHistory::new();
h.push(change);
let undo = h.undo().expect("undo").clone();
let after_undo = undo.apply(&modified); assert_eq!(after_undo, "a");
let redo = h.redo().expect("redo").clone();
let after_redo = redo.apply(&after_undo);
assert_eq!(after_redo, "b");
}
#[test]
fn test_push_clears_redo_stack() {
let mut h = DocumentHistory::new();
h.push(DocumentChange::new(TextRange::new(0, 1), "b", "a"));
h.undo();
assert!(h.can_redo());
h.push(DocumentChange::new(TextRange::new(0, 1), "c", "a"));
assert!(!h.can_redo());
}
#[test]
fn test_multi_step_undo_redo() {
let mut source = String::from("a");
let mut h = DocumentHistory::new();
let c1 = DocumentChange::new(TextRange::new(1, 1), "b", "");
source = c1.apply(&source); h.push(c1);
let c2 = DocumentChange::new(TextRange::new(2, 2), "c", "");
source = c2.apply(&source); h.push(c2);
let u2 = h.undo().expect("undo c2").clone();
source = u2.apply(&source); assert_eq!(source, "ab");
let u1 = h.undo().expect("undo c1").clone();
source = u1.apply(&source); assert_eq!(source, "a");
let r1 = h.redo().expect("redo c1").clone();
source = r1.apply(&source); assert_eq!(source, "ab");
let r2 = h.redo().expect("redo c2").clone();
source = r2.apply(&source); assert_eq!(source, "abc");
}
}