use rdom_core::Selection;
use crate::node::nearest_editable_ancestor;
use crate::runtime::editing::editor_state::{EditEntry, HistoryItem};
use crate::{TuiDom, TuiEvent};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UndoOutcome {
Applied,
Noop,
}
pub fn undo(dom: &mut TuiDom) -> UndoOutcome {
let Some(editable) = focused_editable(dom) else {
return UndoOutcome::Noop;
};
let Some(item) = pop_entry(dom, editable, StackSide::Undo) else {
return UndoOutcome::Noop;
};
for part in item.iter().rev() {
apply_reverse(dom, part);
}
if let Some(first) = item.first() {
dom.set_selection(Some(Selection::caret(first.caret_before)));
}
push_entry(dom, editable, item, StackSide::Redo);
let mut ev = TuiEvent::input(rdom_core::InputType::HistoryUndo, None);
crate::tui_event::dispatch_to_live(dom, editable, &mut ev);
UndoOutcome::Applied
}
pub fn redo(dom: &mut TuiDom) -> UndoOutcome {
let Some(editable) = focused_editable(dom) else {
return UndoOutcome::Noop;
};
let Some(item) = pop_entry(dom, editable, StackSide::Redo) else {
return UndoOutcome::Noop;
};
for part in &item {
apply_forward(dom, part);
}
if let Some(last) = item.last() {
dom.set_selection(Some(Selection::caret(last.caret_after)));
}
push_entry(dom, editable, item, StackSide::Undo);
let mut ev = TuiEvent::input(rdom_core::InputType::HistoryRedo, None);
crate::tui_event::dispatch_to_live(dom, editable, &mut ev);
UndoOutcome::Applied
}
fn focused_editable(dom: &TuiDom) -> Option<rdom_core::NodeId> {
let focused = dom.focused()?;
nearest_editable_ancestor(dom, focused)
}
fn apply_forward(dom: &mut TuiDom, entry: &EditEntry) {
let end = entry.range.start + entry.old.len();
let _ = dom
.node_mut(entry.node)
.edit_text(entry.range.start, end, &entry.new);
}
fn apply_reverse(dom: &mut TuiDom, entry: &EditEntry) {
let end = entry.range.start + entry.new.len();
let _ = dom
.node_mut(entry.node)
.edit_text(entry.range.start, end, &entry.old);
}
#[derive(Debug, Clone, Copy)]
enum StackSide {
Undo,
Redo,
}
fn pop_entry(
dom: &mut TuiDom,
editable: rdom_core::NodeId,
side: StackSide,
) -> Option<HistoryItem> {
let mut node = dom.node_mut(editable);
let state = node.ext_mut()?.editor_state.as_mut()?;
match side {
StackSide::Undo => state.pop_undo(),
StackSide::Redo => state.pop_redo(),
}
}
fn push_entry(dom: &mut TuiDom, editable: rdom_core::NodeId, entry: HistoryItem, side: StackSide) {
if let Some(ext) = dom.node_mut(editable).ext_mut()
&& let Some(state) = ext.editor_state.as_mut()
{
ext.form_state.get_mut().value_user_edited = true;
match side {
StackSide::Undo => state.push_undo(entry),
StackSide::Redo => state.push_redo(entry),
}
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod coalescing_tests;