use std::collections::VecDeque;
pub(crate) const MAX_STEPS: usize = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
Typing,
Backspace,
ForwardDelete,
Other,
}
#[derive(Debug, Clone)]
pub(crate) struct Edit {
pub(crate) at: usize,
pub(crate) removed: String,
pub(crate) inserted: String,
pub(crate) before: (usize, usize),
pub(crate) after: (usize, usize),
pub(crate) kind: Kind,
}
impl Edit {
fn continued_by(&self, next: &Edit) -> bool {
match (self.kind, next.kind) {
(Kind::Typing, Kind::Typing) => {
let word_ended = self.inserted.ends_with(char::is_whitespace)
&& !next.inserted.starts_with(char::is_whitespace);
next.removed.is_empty() && next.at == self.at + self.inserted.len() && !word_ended
}
(Kind::Backspace, Kind::Backspace) => next.at + next.removed.len() == self.at,
(Kind::ForwardDelete, Kind::ForwardDelete) => next.at == self.at,
_ => false,
}
}
fn absorb(&mut self, next: Edit) {
match next.kind {
Kind::Typing => self.inserted.push_str(&next.inserted),
Kind::Backspace => {
self.removed.insert_str(0, &next.removed);
self.at = next.at;
}
_ => self.removed.push_str(&next.removed),
}
self.after = next.after;
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct History {
done: VecDeque<Edit>,
undone: Vec<Edit>,
open: bool,
}
impl History {
pub(crate) fn record(&mut self, edit: Edit) {
self.undone.clear();
match self.done.back_mut() {
Some(last) if self.open && last.continued_by(&edit) => last.absorb(edit),
_ => {
if self.done.len() == MAX_STEPS {
self.done.pop_front();
}
self.done.push_back(edit);
}
}
self.open = true;
}
pub(crate) fn seal(&mut self) {
self.open = false;
}
pub(crate) fn clear(&mut self) {
*self = Self::default();
}
pub(crate) fn can_undo(&self) -> bool {
!self.done.is_empty()
}
pub(crate) fn can_redo(&self) -> bool {
!self.undone.is_empty()
}
pub(crate) fn take_undo(&mut self) -> Option<Edit> {
self.open = false;
self.done.pop_back()
}
pub(crate) fn take_redo(&mut self) -> Option<Edit> {
self.open = false;
self.undone.pop()
}
pub(crate) fn push_undo(&mut self, edit: Edit) {
self.done.push_back(edit);
}
pub(crate) fn push_redo(&mut self, edit: Edit) {
self.undone.push(edit);
}
}