use rdom_core::Selection;
use crate::node::nearest_editable_ancestor;
use crate::runtime::editing::editor_state::EditEntry;
use crate::tui_event::TuiDispatchExt;
use crate::{TuiDom, TuiEvent};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UndoOutcome {
Applied,
Noop,
}
pub fn undo(dom: &mut TuiDom) -> UndoOutcome {
let Some(editable) = focused_editable(dom) else {
return UndoOutcome::Noop;
};
let Some(entry) = pop_entry(dom, editable, StackSide::Undo) else {
return UndoOutcome::Noop;
};
apply_reverse(dom, &entry);
push_entry(dom, editable, entry.clone(), StackSide::Redo);
let _ = entry.old; let mut ev = TuiEvent::input(rdom_core::InputType::HistoryUndo, None);
let _ = dom.dispatch_tui_event(editable, &mut ev);
UndoOutcome::Applied
}
pub fn redo(dom: &mut TuiDom) -> UndoOutcome {
let Some(editable) = focused_editable(dom) else {
return UndoOutcome::Noop;
};
let Some(entry) = pop_entry(dom, editable, StackSide::Redo) else {
return UndoOutcome::Noop;
};
apply_forward(dom, &entry);
push_entry(dom, editable, entry.clone(), StackSide::Undo);
let _ = entry.new; let mut ev = TuiEvent::input(rdom_core::InputType::HistoryRedo, None);
let _ = dom.dispatch_tui_event(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);
dom.set_selection(Some(Selection::caret(entry.caret_after)));
}
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);
dom.set_selection(Some(Selection::caret(entry.caret_before)));
}
#[derive(Debug, Clone, Copy)]
enum StackSide {
Undo,
Redo,
}
fn pop_entry(dom: &mut TuiDom, editable: rdom_core::NodeId, side: StackSide) -> Option<EditEntry> {
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: EditEntry, side: StackSide) {
if let Some(ext) = dom.node_mut(editable).ext_mut()
&& let Some(state) = ext.editor_state.as_mut()
{
match side {
StackSide::Undo => state.push_undo(entry),
StackSide::Redo => state.push_redo(entry),
}
}
}
#[cfg(test)]
mod tests;