use super::DraftBuffer;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Checkpoint {
position: usize,
}
impl Checkpoint {
pub fn from_buffer(buffer: &DraftBuffer) -> Self {
Self {
position: buffer.len(),
}
}
pub fn at(position: usize) -> Self {
Self { position }
}
pub fn position(&self) -> usize {
self.position
}
pub fn restore(&self, buffer: &mut DraftBuffer) {
buffer.truncate(self.position);
}
}
#[derive(Debug, Clone)]
pub struct CheckpointStack {
checkpoints: Vec<Checkpoint>,
}
impl CheckpointStack {
pub fn new() -> Self {
Self {
checkpoints: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
checkpoints: Vec::with_capacity(capacity),
}
}
pub fn push(&mut self, checkpoint: Checkpoint) {
self.checkpoints.push(checkpoint);
}
pub fn push_from_buffer(&mut self, buffer: &DraftBuffer) {
self.push(Checkpoint::from_buffer(buffer));
}
pub fn pop(&mut self) -> Option<Checkpoint> {
self.checkpoints.pop()
}
pub fn peek(&self) -> Option<&Checkpoint> {
self.checkpoints.last()
}
pub fn len(&self) -> usize {
self.checkpoints.len()
}
pub fn is_empty(&self) -> bool {
self.checkpoints.is_empty()
}
pub fn clear(&mut self) {
self.checkpoints.clear();
}
}
impl Default for CheckpointStack {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checkpoint_from_buffer() {
let buffer = DraftBuffer::from_string("test");
let checkpoint = Checkpoint::from_buffer(&buffer);
assert_eq!(checkpoint.position(), 4);
}
#[test]
fn test_checkpoint_at() {
let checkpoint = Checkpoint::at(10);
assert_eq!(checkpoint.position(), 10);
}
#[test]
fn test_checkpoint_restore() {
let mut buffer = DraftBuffer::from_string("hello");
let checkpoint = Checkpoint::at(3);
checkpoint.restore(&mut buffer);
assert_eq!(buffer.as_str(), "hel");
}
#[test]
fn test_checkpoint_stack_new() {
let stack = CheckpointStack::new();
assert!(stack.is_empty());
assert_eq!(stack.len(), 0);
}
#[test]
fn test_checkpoint_stack_push_pop() {
let mut stack = CheckpointStack::new();
let checkpoint = Checkpoint::at(5);
stack.push(checkpoint);
assert_eq!(stack.len(), 1);
let popped = stack.pop();
assert_eq!(
popped
.expect("test fixture: pop on non-empty stack")
.position(),
5
);
assert!(stack.is_empty());
}
#[test]
fn test_checkpoint_stack_peek() {
let mut stack = CheckpointStack::new();
stack.push(Checkpoint::at(5));
assert_eq!(
stack
.peek()
.expect("test/doc fixture: peek on non-empty stack")
.position(),
5
);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_checkpoint_stack_from_buffer() {
let buffer = DraftBuffer::from_string("test");
let mut stack = CheckpointStack::new();
stack.push_from_buffer(&buffer);
assert_eq!(stack.len(), 1);
assert_eq!(
stack
.peek()
.expect("test/doc fixture: peek on non-empty stack")
.position(),
4
);
}
#[test]
fn test_checkpoint_stack_clear() {
let mut stack = CheckpointStack::new();
stack.push(Checkpoint::at(5));
stack.push(Checkpoint::at(10));
stack.clear();
assert!(stack.is_empty());
}
#[test]
fn test_undo_workflow() {
let mut buffer = DraftBuffer::new();
let mut undo_stack = CheckpointStack::new();
undo_stack.push_from_buffer(&buffer);
for ch in "hello".chars() {
buffer.insert(ch);
undo_stack.push_from_buffer(&buffer);
}
assert_eq!(buffer.as_str(), "hello");
assert_eq!(undo_stack.len(), 6);
for _ in 0..2 {
undo_stack.pop(); if let Some(checkpoint) = undo_stack.peek() {
checkpoint.restore(&mut buffer);
}
}
assert_eq!(buffer.as_str(), "hel");
assert_eq!(undo_stack.len(), 4);
undo_stack.pop();
if let Some(checkpoint) = undo_stack.peek() {
checkpoint.restore(&mut buffer);
}
assert_eq!(buffer.as_str(), "he");
assert_eq!(undo_stack.len(), 3);
}
#[test]
fn test_multiple_checkpoints() {
let mut buffer = DraftBuffer::new();
let mut stack = CheckpointStack::new();
stack.push_from_buffer(&buffer);
buffer.insert('a');
stack.push_from_buffer(&buffer);
buffer.insert('b');
stack.push_from_buffer(&buffer);
buffer.insert('c');
stack.push_from_buffer(&buffer);
assert_eq!(buffer.as_str(), "abc");
assert_eq!(stack.len(), 4);
stack.pop();
stack
.peek()
.expect("test fixture: peek on non-empty stack")
.restore(&mut buffer);
assert_eq!(buffer.as_str(), "ab");
stack.pop();
stack
.peek()
.expect("test fixture: peek on non-empty stack")
.restore(&mut buffer);
assert_eq!(buffer.as_str(), "a");
stack.pop();
stack
.peek()
.expect("test fixture: peek on non-empty stack")
.restore(&mut buffer);
assert_eq!(buffer.as_str(), "");
}
}