tui_canvas/editor/features/
history.rs1use crate::DataProvider;
11use crate::canvas::state::SelectionState;
12use crate::editor::EditorCore;
13
14pub(crate) const DEFAULT_HISTORY_LIMIT: usize = 100;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub(crate) enum EditKind {
21 Insert,
22 Delete,
23 Other,
25}
26
27impl EditKind {
28 fn coalesces(self) -> bool {
29 matches!(self, EditKind::Insert | EditKind::Delete)
30 }
31}
32
33#[derive(Debug, Clone)]
35pub(crate) struct EditSnapshot {
36 content: Vec<String>,
37 current_field: usize,
38 cursor_pos: usize,
39}
40
41impl<D: DataProvider> EditorCore<D> {
42 fn snapshot_now(&self) -> EditSnapshot {
43 EditSnapshot {
44 content: self.data_provider.capture_content(),
45 current_field: self.ui_state.current_field,
46 cursor_pos: self.ui_state.cursor_pos,
47 }
48 }
49
50 pub(crate) fn record_checkpoint(&mut self, kind: EditKind) {
56 if !self.history_enabled {
57 return;
58 }
59
60 if kind.coalesces() && self.history_last_kind == Some(kind) {
63 return;
64 }
65
66 let snapshot = self.snapshot_now();
67 self.undo_stack.push(snapshot);
68 if self.undo_stack.len() > self.history_limit {
69 self.undo_stack.remove(0);
70 }
71 self.redo_stack.clear();
72 self.history_last_kind = Some(kind);
73 }
74
75 pub(crate) fn break_undo_coalescing(&mut self) {
78 self.history_last_kind = None;
79 }
80
81 fn apply_snapshot(&mut self, snapshot: EditSnapshot) {
82 self.data_provider.restore_content(&snapshot.content);
83
84 let field_count = self.data_provider.field_count();
85 self.ui_state.current_field = if field_count == 0 {
86 0
87 } else {
88 snapshot.current_field.min(field_count - 1)
89 };
90
91 let len = self.current_text().chars().count();
92 self.set_cursor_raw(snapshot.cursor_pos.min(len));
93 self.ui_state.selection = SelectionState::None;
94
95 self.after_history_restore();
96 }
97
98 fn after_history_restore(&mut self) {
105 #[cfg(feature = "validation")]
106 {
107 let count = self.data_provider.field_count();
108 for i in 0..count {
109 let text = self.data_provider.field_value(i).to_string();
110 let _ = self.ui_state.validation.validate_field_content(i, &text);
111 }
112 }
113 #[cfg(feature = "suggestions")]
114 self.ui_state.close_suggestions();
115 }
116
117 pub fn undo(&mut self) -> bool {
120 if let Some(previous) = self.undo_stack.pop() {
121 let current = self.snapshot_now();
122 self.redo_stack.push(current);
123 self.apply_snapshot(previous);
124 self.history_last_kind = None;
125 true
126 } else {
127 false
128 }
129 }
130
131 pub fn redo(&mut self) -> bool {
134 if let Some(next) = self.redo_stack.pop() {
135 let current = self.snapshot_now();
136 self.undo_stack.push(current);
137 self.apply_snapshot(next);
138 self.history_last_kind = None;
139 true
140 } else {
141 false
142 }
143 }
144
145 pub fn can_undo(&self) -> bool {
147 !self.undo_stack.is_empty()
148 }
149
150 pub fn can_redo(&self) -> bool {
152 !self.redo_stack.is_empty()
153 }
154
155 pub fn clear_history(&mut self) {
157 self.undo_stack.clear();
158 self.redo_stack.clear();
159 self.history_last_kind = None;
160 }
161
162 pub fn set_history_limit(&mut self, limit: usize) {
164 self.history_limit = limit.max(1);
165 while self.undo_stack.len() > self.history_limit {
166 self.undo_stack.remove(0);
167 }
168 }
169
170 pub fn set_history_enabled(&mut self, enabled: bool) {
176 self.history_enabled = enabled;
177 if !enabled {
178 self.clear_history();
179 }
180 }
181
182 pub fn is_history_enabled(&self) -> bool {
184 self.history_enabled
185 }
186}