use std::{
error::Error,
marker::PhantomData,
sync::{Arc, RwLock},
};
#[derive(Debug)]
pub enum EditableError {
CannotDoAction(String),
CannotUndoAction(String),
Custom(Box<dyn Error>),
}
impl std::fmt::Display for EditableError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CannotDoAction(desc) => write!(f, "Cannot do action: {}", desc),
Self::CannotUndoAction(desc) => write!(f, "Cannot undo action: {}", desc),
Self::Custom(error) => write!(f, "Custom error: {}", error),
}
}
}
impl Error for EditableError {}
pub trait EditAction<T> {
fn execute(&mut self, data: &mut T) -> Result<(), EditableError>;
fn undo(&mut self, data: &mut T) -> Result<(), EditableError>;
}
pub struct SnapshotEditAction<T, E>
where
T: Clone,
E: FnMut(&mut T) -> Result<(), EditableError>,
{
state: Option<T>,
execute: E,
}
impl<T, E> SnapshotEditAction<T, E>
where
T: Clone,
E: FnMut(&mut T) -> Result<(), EditableError>,
{
pub fn new(execute: E) -> Self {
Self {
state: None,
execute,
}
}
}
impl<T, E> EditAction<T> for SnapshotEditAction<T, E>
where
T: Clone,
E: FnMut(&mut T) -> Result<(), EditableError>,
{
fn execute(&mut self, data: &mut T) -> Result<(), EditableError> {
let state = self.state.replace(data.clone());
match (self.execute)(data) {
Ok(_) => Ok(()),
Err(error) => {
self.state = state;
Err(error)
}
}
}
fn undo(&mut self, data: &mut T) -> Result<(), EditableError> {
if let Some(state) = self.state.take() {
*data = state;
Ok(())
} else {
Err(EditableError::Custom(
"Could not undo from snapshot!".into(),
))
}
}
}
pub struct ClosureEditAction<T, S, E, U>
where
E: FnMut(&mut T, &mut S) -> Result<(), EditableError>,
U: FnMut(&mut T, &mut S) -> Result<(), EditableError>,
{
state: S,
execute: E,
undo: U,
_phantom: PhantomData<fn() -> T>,
}
impl<T, S, E, U> ClosureEditAction<T, S, E, U>
where
E: FnMut(&mut T, &mut S) -> Result<(), EditableError>,
U: FnMut(&mut T, &mut S) -> Result<(), EditableError>,
{
pub fn new(execute: E, undo: U, state: S) -> Self {
Self {
state,
execute,
undo,
_phantom: Default::default(),
}
}
}
impl<T, S, E, U> EditAction<T> for ClosureEditAction<T, S, E, U>
where
E: FnMut(&mut T, &mut S) -> Result<(), EditableError>,
U: FnMut(&mut T, &mut S) -> Result<(), EditableError>,
{
fn execute(&mut self, data: &mut T) -> Result<(), EditableError> {
(self.execute)(data, &mut self.state)
}
fn undo(&mut self, data: &mut T) -> Result<(), EditableError> {
(self.undo)(data, &mut self.state)
}
}
#[derive(Clone)]
struct EditRecord<T> {
description: String,
action: Arc<RwLock<dyn EditAction<T>>>,
}
impl<T> EditAction<T> for EditRecord<T> {
fn execute(&mut self, data: &mut T) -> Result<(), EditableError> {
if let Ok(mut action) = self.action.try_write() {
action.execute(data)
} else {
Err(EditableError::CannotDoAction(self.description.to_owned()))
}
}
fn undo(&mut self, data: &mut T) -> Result<(), EditableError> {
if let Ok(mut action) = self.action.try_write() {
action.undo(data)
} else {
Err(EditableError::CannotUndoAction(self.description.to_owned()))
}
}
}
impl<T> std::fmt::Debug for EditRecord<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("EditAction<{}>", std::any::type_name::<T>()))
.field("description", &self.description)
.finish_non_exhaustive()
}
}
impl<T> std::fmt::Display for EditRecord<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.description)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditHistoryKind {
Done,
Undone,
}
#[derive(Debug, Default, Clone)]
pub struct Editable<T> {
data: T,
done: Vec<EditRecord<T>>,
undone: Vec<EditRecord<T>>,
capacity: usize,
}
impl<T> Editable<T> {
pub fn new(data: T) -> Self {
Self {
data,
done: Default::default(),
undone: Default::default(),
capacity: 0,
}
}
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.set_capacity(capacity);
self
}
pub fn commit(self) -> T {
self.data
}
pub fn data(&self) -> &T {
&self.data
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn set_capacity(&mut self, capacity: usize) {
self.capacity = capacity;
if self.capacity > 0 {
self.clear_undone();
while self.done.len() > self.capacity {
self.done.remove(0);
}
}
}
pub fn done_count(&self) -> usize {
self.done.len()
}
pub fn undone_count(&self) -> usize {
self.undone.len()
}
pub fn clear_undone(&mut self) {
self.undone.clear();
}
pub fn edit(
&mut self,
description: impl ToString,
action: impl EditAction<T> + 'static,
) -> Result<(), EditableError> {
let mut record = EditRecord {
description: description.to_string(),
action: Arc::new(RwLock::new(action)),
};
record.execute(&mut self.data)?;
self.undone.clear();
self.done_push(record);
Ok(())
}
pub fn edit_snapshot<E>(
&mut self,
description: impl ToString,
execute: E,
) -> Result<(), EditableError>
where
T: Clone + 'static,
E: FnMut(&mut T) -> Result<(), EditableError> + 'static,
{
self.edit(description, SnapshotEditAction::<T, E>::new(execute))
}
pub fn edit_closure<S, E, U>(
&mut self,
description: impl ToString,
execute: E,
undo: U,
state: S,
) -> Result<(), EditableError>
where
T: 'static,
S: 'static,
E: FnMut(&mut T, &mut S) -> Result<(), EditableError> + 'static,
U: FnMut(&mut T, &mut S) -> Result<(), EditableError> + 'static,
{
self.edit(
description,
ClosureEditAction::<T, S, E, U>::new(execute, undo, state),
)
}
pub fn undo_last(&mut self) -> Result<(), EditableError> {
if let Some(mut record) = self.done.pop() {
if let Err(error) = record.undo(&mut self.data) {
self.done_push(record);
return Err(error);
}
self.undone.push(record);
}
Ok(())
}
pub fn undo_many(&mut self, mut count: usize) -> Result<(), EditableError> {
count = count.min(self.done_count());
while count > 0 {
self.undo_last()?;
count -= 1;
}
Ok(())
}
pub fn undo_all(&mut self) -> Result<(), EditableError> {
while self.done_count() > 0 {
self.undo_last()?;
}
Ok(())
}
pub fn redo_last(&mut self) -> Result<(), EditableError> {
if let Some(mut record) = self.undone.pop() {
if let Err(error) = record.execute(&mut self.data) {
self.undone.push(record);
return Err(error);
}
self.done_push(record);
}
Ok(())
}
pub fn redo_many(&mut self, mut count: usize) -> Result<(), EditableError> {
count = count.min(self.undone_count());
while count > 0 {
self.redo_last()?;
count -= 1;
}
Ok(())
}
pub fn redo_all(&mut self) -> Result<(), EditableError> {
while self.undone_count() > 0 {
self.redo_last()?;
}
Ok(())
}
pub fn history(&self) -> impl Iterator<Item = (&str, EditHistoryKind, usize)> + '_ {
let done_count = self.done.len();
let undone_count = self.undone.len();
self.done
.iter()
.enumerate()
.map(move |(index, action)| {
(
action.description.as_str(),
EditHistoryKind::Done,
done_count - index,
)
})
.chain(
self.undone
.iter()
.enumerate()
.rev()
.map(move |(index, action)| {
(
action.description.as_str(),
EditHistoryKind::Undone,
undone_count - index,
)
}),
)
}
fn done_push(&mut self, record: EditRecord<T>) {
self.done.push(record);
if self.capacity > 0 {
while self.done.len() > self.capacity {
self.done.remove(0);
}
}
}
}
pub enum PossiblyEditable<T> {
Fixed(T),
Editable(Editable<T>),
}
impl<T> PossiblyEditable<T> {
pub fn new(data: T) -> Self {
Self::Fixed(data)
}
pub fn is_fixed(&self) -> bool {
matches!(self, Self::Fixed(_))
}
pub fn is_editable(&self) -> bool {
matches!(self, Self::Editable(_))
}
pub fn into_data(self) -> T {
match self {
Self::Fixed(data) => data,
Self::Editable(editable) => editable.commit(),
}
}
pub fn turn_editable(&mut self) {
unsafe {
let ptr = self as *mut Self;
let data = match ptr.read() {
Self::Fixed(data) => Self::Editable(Editable::new(data)),
data => data,
};
ptr.write(data);
}
}
pub fn turn_fixed(&mut self) {
unsafe {
let ptr = self as *mut Self;
let data = match ptr.read() {
Self::Editable(data) => Self::Fixed(data.commit()),
data => data,
};
ptr.write(data);
}
}
pub fn as_fixed(&self) -> Option<&T> {
match self {
Self::Fixed(data) => Some(data),
_ => None,
}
}
pub fn as_fixed_mut(&mut self) -> Option<&mut T> {
match self {
Self::Fixed(data) => Some(data),
_ => None,
}
}
pub fn as_editable(&self) -> Option<&Editable<T>> {
match self {
Self::Editable(data) => Some(data),
_ => None,
}
}
pub fn as_editable_mut(&mut self) -> Option<&mut Editable<T>> {
match self {
Self::Editable(data) => Some(data),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_editable() {
let mut editable = Editable::new(0usize);
assert_eq!(editable.done_count(), 0);
assert_eq!(editable.undone_count(), 0);
editable
.edit_snapshot("set 42", |data| {
*data = 42;
Ok(())
})
.unwrap();
assert_eq!(editable.done_count(), 1);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 42);
editable.undo_last().unwrap();
assert_eq!(editable.done_count(), 0);
assert_eq!(editable.undone_count(), 1);
assert_eq!(*editable.data(), 0);
editable.redo_last().unwrap();
assert_eq!(editable.done_count(), 1);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 42);
editable
.edit_snapshot("add 8", |data| {
*data += 8;
Ok(())
})
.unwrap();
assert_eq!(editable.done_count(), 2);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 50);
editable
.edit_snapshot("div 5", |data| {
*data /= 5;
Ok(())
})
.unwrap();
assert_eq!(editable.done_count(), 3);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 10);
editable.undo_many(2).unwrap();
assert_eq!(editable.done_count(), 1);
assert_eq!(editable.undone_count(), 2);
assert_eq!(*editable.data(), 42);
editable.redo_all().unwrap();
assert_eq!(editable.done_count(), 3);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 10);
editable.undo_last().unwrap();
assert_eq!(
editable.history().collect::<Vec<_>>(),
vec![
("set 42", EditHistoryKind::Done, 2),
("add 8", EditHistoryKind::Done, 1),
("div 5", EditHistoryKind::Undone, 1)
]
);
editable
.edit_snapshot("mul 2", |data| {
*data *= 2;
Ok(())
})
.unwrap();
assert_eq!(editable.done_count(), 3);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 100);
editable.set_capacity(2);
assert_eq!(editable.done_count(), 2);
assert_eq!(editable.undone_count(), 0);
assert_eq!(*editable.data(), 100);
editable.undo_all().unwrap();
editable.clear_undone();
assert_eq!(editable.done_count(), 0);
assert_eq!(editable.undone_count(), 0);
assert_eq!(editable.commit(), 42);
}
}