use super::*;
#[derive(Clone)]
pub(super) struct PromptSnapshot {
pub(super) visible: String,
pub(super) atoms: Vec<PasteAtom>,
pub(super) next_paste_id: u64,
pub(super) cursor: usize,
}
#[derive(Clone)]
pub(super) struct SemanticHistory {
pub(super) snapshots: Vec<PromptSnapshot>,
pub(super) position: usize,
pub(super) max_edits: usize,
}
impl SemanticHistory {
pub(super) fn new(max_edits: usize, snapshot: PromptSnapshot) -> Self {
Self {
snapshots: vec![snapshot],
position: 0,
max_edits,
}
}
pub(super) fn from_snapshots(max_edits: usize, snapshots: Vec<PromptSnapshot>) -> Self {
let position = snapshots.len().saturating_sub(1);
Self {
snapshots,
position,
max_edits,
}
}
pub(super) fn reset(&mut self, snapshot: PromptSnapshot) {
self.snapshots.clear();
self.snapshots.push(snapshot);
self.position = 0;
}
pub(super) fn update_current_cursor(&mut self, cursor: usize) {
if let Some(snapshot) = self.snapshots.get_mut(self.position) {
snapshot.cursor = cursor;
}
}
pub(super) fn record(&mut self, snapshot: PromptSnapshot) {
self.snapshots.truncate(self.position.saturating_add(1));
self.snapshots.push(snapshot);
self.position = self.snapshots.len().saturating_sub(1);
let max_states = self.max_edits.saturating_add(1);
if self.snapshots.len() > max_states {
let remove = self.snapshots.len() - max_states;
self.snapshots.drain(..remove);
self.position = self.position.saturating_sub(remove);
}
}
pub(super) fn undo_target(&mut self) -> Option<PromptSnapshot> {
if self.position == 0 {
return None;
}
self.position -= 1;
self.snapshots.get(self.position).cloned()
}
pub(super) fn redo_target(&mut self) -> Option<PromptSnapshot> {
if self.position + 1 >= self.snapshots.len() {
return None;
}
self.position += 1;
self.snapshots.get(self.position).cloned()
}
}
impl PromptEditor {
pub(crate) fn undo(&mut self, _visible_rows: u16, _wrap_width: u16) -> PromptEditResult {
if !self.semantic_history_active {
return self.edit_without_atom_tracking(|textarea| textarea.undo());
}
let Some(snapshot) = self.semantic_history.undo_target() else {
return PromptEditResult::Unchanged;
};
let before = self.text();
self.restore_semantic_snapshot(snapshot);
if before != self.text() {
self.bump_content_revision();
}
self.pending_submission = None;
self.cursor_visible = true;
PromptEditResult::Changed
}
pub(crate) fn redo(&mut self, _visible_rows: u16, _wrap_width: u16) -> PromptEditResult {
if !self.semantic_history_active {
return self.edit_without_atom_tracking(|textarea| textarea.redo());
}
let Some(snapshot) = self.semantic_history.redo_target() else {
return PromptEditResult::Unchanged;
};
let before = self.text();
self.restore_semantic_snapshot(snapshot);
if before != self.text() {
self.bump_content_revision();
}
self.pending_submission = None;
self.cursor_visible = true;
PromptEditResult::Changed
}
pub(super) fn semantic_snapshot(&self) -> PromptSnapshot {
PromptSnapshot {
visible: self.visible_text(),
atoms: self.paste_atoms.clone(),
next_paste_id: self.next_paste_id,
cursor: self.visible_cursor(),
}
}
pub(super) fn restore_semantic_snapshot(&mut self, snapshot: PromptSnapshot) {
let cursor = snapshot.cursor.min(snapshot.visible.len());
self.textarea.borrow_mut().set_lines(
split_lines(&snapshot.visible),
cursor_for_byte_offset(&snapshot.visible, cursor),
);
self.paste_atoms = snapshot.atoms;
self.next_paste_id = snapshot.next_paste_id;
self.refresh_atomic_ranges();
}
pub(super) fn activate_semantic_history(&mut self) {
if !self.semantic_history_active {
self.semantic_history = self.semantic_history_from_native();
self.semantic_history_active = true;
}
}
pub(super) fn semantic_history_from_native(&self) -> SemanticHistory {
let (max_edits, mut probe) = {
let textarea = self.textarea.borrow();
(textarea.max_histories(), textarea.clone())
};
let mut snapshots = vec![self.semantic_snapshot()];
while probe.undo() {
snapshots.push(PromptSnapshot {
visible: probe.lines().join("\n"),
atoms: Vec::new(),
next_paste_id: 1,
cursor: byte_offset_for_cursor(probe.lines(), probe.cursor()),
});
}
snapshots.reverse();
SemanticHistory::from_snapshots(max_edits, snapshots)
}
pub(super) fn prepare_semantic_edit(&mut self) {
if self.semantic_history_active {
self.semantic_history
.update_current_cursor(self.visible_cursor());
}
}
pub(super) fn finish_content_edit(&mut self) {
if self.semantic_history_active {
self.semantic_history.record(self.semantic_snapshot());
}
self.bump_content_revision();
self.pending_submission = None;
self.cursor_visible = true;
}
}