undo/redo
This commit is contained in:
+46
-1
@@ -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
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 :/
|
||||||
|
|||||||
Reference in New Issue
Block a user