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};
@@ -51,6 +51,9 @@ pub enum Action {
ExtendLineAbove,
Delete,
Undo,
Redo,
}
impl App {
@@ -103,6 +106,9 @@ impl App {
Action::ExtendLineAbove => self.extend_line_above(),
Action::Delete => self.delete(),
Action::Undo => self.undo(),
Action::Redo => self.redo(),
}
}
@@ -351,6 +357,45 @@ impl App {
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
+3 -1
View File
@@ -23,7 +23,8 @@ pub struct App {
pub partial_replace: Option<u8>,
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>,
}
@@ -102,6 +103,7 @@ impl App {
partial_replace: None,
edit_history: Vec::new(),
time_traveling: None,
logs: Vec::new(),
}
+6
View File
@@ -133,6 +133,9 @@ impl Default for Config {
("X".try_into().unwrap(), Action::ExtendLineAbove),
("d".try_into().unwrap(), Action::Delete),
("u".try_into().unwrap(), Action::Undo),
("U".try_into().unwrap(), Action::Redo),
].into()),
(Some(PartialAction::Goto), [
("j".try_into().unwrap(), Action::GotoLineStart),
@@ -175,6 +178,9 @@ impl Default for Config {
("X".try_into().unwrap(), Action::ExtendLineAbove),
("d".try_into().unwrap(), Action::Delete),
("u".try_into().unwrap(), Action::Undo),
("U".try_into().unwrap(), Action::Redo),
].into())
].into())
].into()
+53 -3
View File
@@ -3,6 +3,12 @@ use crate::{app::App, cursor::Cursor};
#[derive(Debug)]
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 {
cursor: Cursor,
old_data: Vec<u8>
@@ -11,17 +17,31 @@ pub enum EditAction {
cursor: Cursor,
old_data: Vec<u8>,
new_byte: u8
}
},
// Insert {
// cursor: Cursor,
// which side of cursor? append/insert
// new_data: Vec<u8>
// }
}
impl App {
pub fn execute_and_add(&mut self, edit_action: EditAction) {
assert!(!matches!(edit_action, EditAction::Placeholder));
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);
}
fn execute_edit(&mut self, edit_action: &EditAction) {
pub fn execute_edit(&mut self, edit_action: &EditAction) {
match edit_action {
EditAction::Placeholder => unreachable!(),
EditAction::Delete { cursor, .. } => self.delete_at(*cursor),
EditAction::Replace {
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) {
self.contents.drain(cursor.range());
@@ -36,8 +66,28 @@ impl App {
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) {
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;
}
-2
View File
@@ -19,8 +19,6 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
// TODO:
// - undo/redo
// - modifications
// - replace
// - partial action(s)
// - insert/append
// - mode
// - how this works with edit history is strange :/