use crate::Signal;
use incular_text::{TextEditingController, TextEditingValue};
use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct UndoHistoryState {
pub can_undo: bool,
pub can_redo: bool,
}
struct History {
current: TextEditingValue,
undo: Vec<TextEditingValue>,
redo: Vec<TextEditingValue>,
max_entries: usize,
}
const DEFAULT_MAX_ENTRIES: usize = 100;
fn trim_history(entries: &mut Vec<TextEditingValue>, max_entries: usize) {
if entries.len() > max_entries {
let remove = entries.len() - max_entries;
entries.drain(..remove);
}
}
struct Inner {
editor: TextEditingController,
history: RefCell<History>,
applying: Cell<bool>,
restoration_generation: Cell<u64>,
state: Signal<UndoHistoryState>,
listener: usize,
}
impl Inner {
fn availability(&self) -> UndoHistoryState {
let history = self.history.borrow();
UndoHistoryState {
can_undo: !history.undo.is_empty(),
can_redo: !history.redo.is_empty(),
}
}
fn sync_state(&self) {
let _ = self.state.set(self.availability());
}
fn record(&self, value: TextEditingValue) {
let generation = self.editor.restoration_generation();
if generation != self.restoration_generation.get() {
self.restoration_generation.set(generation);
let mut history = self.history.borrow_mut();
history.current = value;
history.undo.clear();
history.redo.clear();
drop(history);
self.sync_state();
return;
}
if self.applying.get() {
self.history.borrow_mut().current = value;
return;
}
let changed = {
let mut history = self.history.borrow_mut();
if history.current == value {
false
} else if history.current.text == value.text {
history.current = value;
false
} else {
let previous = std::mem::replace(&mut history.current, value);
if history.max_entries > 0 {
history.undo.push(previous);
let max_entries = history.max_entries;
trim_history(&mut history.undo, max_entries);
}
history.redo.clear();
true
}
};
if changed {
self.sync_state();
}
}
fn apply_history_value(&self, value: TextEditingValue) {
struct RestorePrevious<'a> {
inner: &'a Inner,
previous: bool,
}
impl Drop for RestorePrevious<'_> {
fn drop(&mut self) {
self.inner.history.borrow_mut().current = self.inner.editor.value();
self.inner.applying.set(self.previous);
if !std::thread::panicking() {
self.inner.sync_state();
}
}
}
let previous = self.applying.replace(true);
let _guard = RestorePrevious {
inner: self,
previous,
};
self.editor.set_value(value);
}
}
impl Drop for Inner {
fn drop(&mut self) {
self.editor.remove_listener(self.listener);
}
}
#[derive(Clone)]
pub struct UndoHistoryController {
inner: Rc<Inner>,
}
impl std::fmt::Debug for UndoHistoryController {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("UndoHistoryController")
.field("editor", &self.inner.editor)
.field("state", &self.status())
.finish()
}
}
impl UndoHistoryController {
#[must_use]
pub fn new(editor: TextEditingController) -> Self {
let initial = editor.value();
let inner = Rc::new_cyclic(|weak: &Weak<Inner>| {
let listener = {
let weak = weak.clone();
editor.add_listener(move |_| {
if let Some(inner) = weak.upgrade() {
inner.record(inner.editor.value());
}
})
};
Inner {
editor: editor.clone(),
history: RefCell::new(History {
current: initial.clone(),
undo: Vec::new(),
redo: Vec::new(),
max_entries: DEFAULT_MAX_ENTRIES,
}),
applying: Cell::new(false),
restoration_generation: Cell::new(editor.restoration_generation()),
state: Signal::new(UndoHistoryState::default()),
listener,
}
});
Self { inner }
}
#[must_use]
pub fn editor(&self) -> TextEditingController {
self.inner.editor.clone()
}
#[must_use]
pub fn state_signal(&self) -> Signal<UndoHistoryState> {
self.inner.state.clone()
}
#[must_use]
pub fn signal(&self) -> Signal<UndoHistoryState> {
self.state_signal()
}
#[must_use]
pub fn status(&self) -> UndoHistoryState {
self.inner.state.get()
}
#[must_use]
pub fn can_undo(&self) -> bool {
self.status().can_undo
}
#[must_use]
pub fn can_redo(&self) -> bool {
self.status().can_redo
}
pub fn set_max_entries(&self, max_entries: usize) {
let mut history = self.inner.history.borrow_mut();
history.max_entries = max_entries;
trim_history(&mut history.undo, max_entries);
trim_history(&mut history.redo, max_entries);
drop(history);
self.inner.sync_state();
}
#[must_use]
pub fn max_entries(&self) -> usize {
self.inner.history.borrow().max_entries
}
#[must_use]
pub fn undo(&self) -> bool {
let target = {
let mut history = self.inner.history.borrow_mut();
let Some(target) = history.undo.pop() else {
return false;
};
let current = std::mem::replace(&mut history.current, target.clone());
if history.max_entries > 0 {
history.redo.push(current);
let max_entries = history.max_entries;
trim_history(&mut history.redo, max_entries);
}
target
};
self.inner.apply_history_value(target);
true
}
#[must_use]
pub fn redo(&self) -> bool {
let target = {
let mut history = self.inner.history.borrow_mut();
let Some(target) = history.redo.pop() else {
return false;
};
let current = std::mem::replace(&mut history.current, target.clone());
if history.max_entries > 0 {
history.undo.push(current);
let max_entries = history.max_entries;
trim_history(&mut history.undo, max_entries);
}
target
};
self.inner.apply_history_value(target);
true
}
pub fn clear(&self) {
let mut history = self.inner.history.borrow_mut();
history.current = self.inner.editor.value();
history.undo.clear();
history.redo.clear();
drop(history);
self.inner.sync_state();
}
}