undo/redo

This commit is contained in:
alice pellerin
2026-03-18 16:28:15 -05:00
parent 725e6784ab
commit 7695d23984
5 changed files with 108 additions and 7 deletions
+46 -1
View File
@@ -1,4 +1,4 @@
use std::{cmp::min, mem::swap}; use std::{cmp::min, mem::{replace, swap}};
use crate::{BYTES_PER_LINE, app::{App, Mode, PartialAction}, edit_action::EditAction}; use crate::{BYTES_PER_LINE, app::{App, Mode, PartialAction}, edit_action::EditAction};
@@ -51,6 +51,9 @@ pub enum Action {
ExtendLineAbove, ExtendLineAbove,
Delete, Delete,
Undo,
Redo,
} }
impl App { impl App {
@@ -103,6 +106,9 @@ impl App {
Action::ExtendLineAbove => self.extend_line_above(), Action::ExtendLineAbove => self.extend_line_above(),
Action::Delete => self.delete(), Action::Delete => self.delete(),
Action::Undo => self.undo(),
Action::Redo => self.redo(),
} }
} }
@@ -351,6 +357,45 @@ impl App {
self.mode = Mode::Normal; self.mode = Mode::Normal;
} }
} }
fn undo(&mut self) {
if self.time_traveling == Some(0) { return }
let current_date = self.time_traveling
.map_or(self.edit_history.len() - 1, |date| date - 1);
self.time_traveling = Some(current_date);
let edit_action = replace(
&mut self.edit_history[current_date],
EditAction::Placeholder
);
self.undo_edit(&edit_action);
self.edit_history[current_date] = edit_action;
}
fn redo(&mut self) {
let Some(previous_date) = self.time_traveling else { return };
let current_date = previous_date + 1;
self.time_traveling = if current_date == self.edit_history.len() {
None
} else {
Some(current_date)
};
let edit_action = replace(
&mut self.edit_history[previous_date],
EditAction::Placeholder
);
self.execute_edit(&edit_action);
self.edit_history[previous_date] = edit_action;
}
} }
// helpers // helpers
+3 -1
View File
@@ -23,7 +23,8 @@ pub struct App {
pub partial_replace: Option<u8>, pub partial_replace: Option<u8>,
pub edit_history: Vec<EditAction>, pub edit_history: Vec<EditAction>,
// some index to keep track of where we are? edit_prophecy? // the index *after* the latest edit action
pub time_traveling: Option<usize>,
pub logs: Vec<String>, pub logs: Vec<String>,
} }
@@ -102,6 +103,7 @@ impl App {
partial_replace: None, partial_replace: None,
edit_history: Vec::new(), edit_history: Vec::new(),
time_traveling: None,
logs: Vec::new(), logs: Vec::new(),
} }
+6
View File
@@ -133,6 +133,9 @@ impl Default for Config {
("X".try_into().unwrap(), Action::ExtendLineAbove), ("X".try_into().unwrap(), Action::ExtendLineAbove),
("d".try_into().unwrap(), Action::Delete), ("d".try_into().unwrap(), Action::Delete),
("u".try_into().unwrap(), Action::Undo),
("U".try_into().unwrap(), Action::Redo),
].into()), ].into()),
(Some(PartialAction::Goto), [ (Some(PartialAction::Goto), [
("j".try_into().unwrap(), Action::GotoLineStart), ("j".try_into().unwrap(), Action::GotoLineStart),
@@ -175,6 +178,9 @@ impl Default for Config {
("X".try_into().unwrap(), Action::ExtendLineAbove), ("X".try_into().unwrap(), Action::ExtendLineAbove),
("d".try_into().unwrap(), Action::Delete), ("d".try_into().unwrap(), Action::Delete),
("u".try_into().unwrap(), Action::Undo),
("U".try_into().unwrap(), Action::Redo),
].into()) ].into())
].into()) ].into())
].into() ].into()
+53 -3
View File
@@ -3,6 +3,12 @@ use crate::{app::App, cursor::Cursor};
#[derive(Debug)] #[derive(Debug)]
pub enum EditAction { pub enum EditAction {
// this is a hacky workaround to allow disjoint borrows for
// undoing/redoing edits. because the undo/redo operations need
// to borrow an EditAction from edit_history, but should never touch
// edit_history themselves, we can swap out the action in question,
// then swap it back in once the undo/redo is done
Placeholder,
Delete { Delete {
cursor: Cursor, cursor: Cursor,
old_data: Vec<u8> old_data: Vec<u8>
@@ -11,17 +17,31 @@ pub enum EditAction {
cursor: Cursor, cursor: Cursor,
old_data: Vec<u8>, old_data: Vec<u8>,
new_byte: u8 new_byte: u8
} },
// Insert {
// cursor: Cursor,
// which side of cursor? append/insert
// new_data: Vec<u8>
// }
} }
impl App { impl App {
pub fn execute_and_add(&mut self, edit_action: EditAction) { pub fn execute_and_add(&mut self, edit_action: EditAction) {
assert!(!matches!(edit_action, EditAction::Placeholder));
self.execute_edit(&edit_action); self.execute_edit(&edit_action);
if let Some(date) = self.time_traveling {
self.edit_history.truncate(date);
self.time_traveling = None;
}
self.edit_history.push(edit_action); self.edit_history.push(edit_action);
} }
fn execute_edit(&mut self, edit_action: &EditAction) { pub fn execute_edit(&mut self, edit_action: &EditAction) {
match edit_action { match edit_action {
EditAction::Placeholder => unreachable!(),
EditAction::Delete { cursor, .. } => self.delete_at(*cursor), EditAction::Delete { cursor, .. } => self.delete_at(*cursor),
EditAction::Replace { EditAction::Replace {
cursor, old_data: _, new_byte cursor, old_data: _, new_byte
@@ -29,6 +49,16 @@ impl App {
} }
} }
pub fn undo_edit(&mut self, edit_action: &EditAction) {
match edit_action {
EditAction::Placeholder => unreachable!(),
EditAction::Delete { cursor, old_data } => self.undo_delete_at(*cursor, old_data),
EditAction::Replace {
cursor, old_data, ..
} => self.undo_replace_at_with(*cursor, old_data),
}
}
fn delete_at(&mut self, cursor: Cursor) { fn delete_at(&mut self, cursor: Cursor) {
self.contents.drain(cursor.range()); self.contents.drain(cursor.range());
@@ -36,8 +66,28 @@ impl App {
self.cursor.collapse(); self.cursor.collapse();
} }
fn undo_delete_at(&mut self, cursor: Cursor, old_data: &[u8]) {
let cursor_start = min(cursor.head, cursor.tail);
self.contents.splice(
cursor_start..cursor_start,
old_data.iter().copied()
);
self.cursor = cursor;
}
fn replace_at_with(&mut self, cursor: Cursor, new_byte: u8) { fn replace_at_with(&mut self, cursor: Cursor, new_byte: u8) {
self.contents[self.cursor.range()].fill(new_byte); self.contents[cursor.range()].fill(new_byte);
self.cursor = cursor;
}
fn undo_replace_at_with(&mut self, cursor: Cursor, old_data: &[u8]) {
self.contents.splice(
cursor.range(),
old_data.iter().copied()
);
self.cursor = cursor; self.cursor = cursor;
} }
-2
View File
@@ -19,8 +19,6 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// TODO: // TODO:
// - undo/redo // - undo/redo
// - modifications // - modifications
// - replace
// - partial action(s)
// - insert/append // - insert/append
// - mode // - mode
// - how this works with edit history is strange :/ // - how this works with edit history is strange :/