Skip to content

Commit

Permalink
Implement rudimentary undo
Browse files Browse the repository at this point in the history
  • Loading branch information
matta committed Jun 18, 2024
1 parent 99979ca commit 9dde8b0
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 36 deletions.
2 changes: 2 additions & 0 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub(crate) enum Command {
MoveDown,
Add,
Delete,
Undo,
Redo,
Quit,
}

Expand Down
2 changes: 2 additions & 0 deletions src/keys.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ a = "Add"
d = "Delete"
Shift-j = "MoveDown"
Shift-k = "MoveUp"
u = "Undo"
Shift-u = "Redo"
145 changes: 109 additions & 36 deletions src/persist/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,19 @@ pub(crate) trait Store {
fn move_task(&mut self, previous: Option<&TaskId>, task: &TaskId) -> anyhow::Result<()>;

fn list_tasks(&mut self) -> anyhow::Result<Vec<Task>>;

fn undo(&mut self) -> anyhow::Result<()>;

fn redo(&mut self) -> anyhow::Result<()>;
}

#[derive(Default)]
pub(crate) struct MemoryStore {
#[derive(Default, Clone)]
struct Record {
tasks: im::HashMap<TaskId, Task>,
order: im::Vector<TaskId>,
}

impl MemoryStore {
#[allow(dead_code)]
pub(crate) fn new() -> Self {
Self::default()
}

pub(crate) fn load(path: &Path) -> Result<MemoryStore, anyhow::Error> {
let tasks = load_tasks(path)?;

let order: im::Vector<TaskId> = tasks.tasks.iter().map(Task::id).collect();
let tasks: im::HashMap<TaskId, Task> = tasks
.tasks
.into_iter()
.map(|task| (task.id(), task))
.collect();

Ok(MemoryStore { tasks, order })
}

pub(crate) fn save(&self, path: &Path) -> Result<(), anyhow::Error> {
let tasks = TaskList {
tasks: self
.order
.iter()
.map(|id| self.tasks.get(id).unwrap())
.cloned()
.collect(),
};
save_tasks(path, &tasks)?;
Ok(())
}
}

impl Store for MemoryStore {
impl Record {
fn get_task(&mut self, id: &TaskId) -> Result<Task, anyhow::Error> {
self.tasks
.get(id)
Expand Down Expand Up @@ -133,3 +104,105 @@ impl Store for MemoryStore {
.collect())
}
}

#[derive(Default)]
pub(crate) struct MemoryStore {
current: Record,
undo_stack: Vec<Record>,
redo_stack: Vec<Record>,
}

impl MemoryStore {
#[allow(dead_code)]
pub(crate) fn new() -> Self {
Self::default()
}

pub(crate) fn load(path: &Path) -> Result<MemoryStore, anyhow::Error> {
let tasks = load_tasks(path)?;

let order: im::Vector<TaskId> = tasks.tasks.iter().map(Task::id).collect();
let tasks: im::HashMap<TaskId, Task> = tasks
.tasks
.into_iter()
.map(|task| (task.id(), task))
.collect();

Ok(MemoryStore {
current: Record { tasks, order },
undo_stack: Vec::new(),
redo_stack: Vec::new(),
})
}

pub(crate) fn save(&self, path: &Path) -> Result<(), anyhow::Error> {
let tasks = TaskList {
tasks: self
.current
.order
.iter()
.map(|id| self.current.tasks.get(id).unwrap())
.cloned()
.collect(),
};
save_tasks(path, &tasks)?;
Ok(())
}
}

impl Store for MemoryStore {
fn get_task(&mut self, id: &TaskId) -> anyhow::Result<Task> {
self.current.get_task(id)
}

fn put_task(&mut self, task: &Task) -> anyhow::Result<()> {
let saved = self.current.clone();
self.current.put_task(task)?;
self.undo_stack.push(saved);
Ok(())
}

fn insert_task(&mut self, previous: Option<&TaskId>, task: &Task) -> anyhow::Result<()> {
let saved = self.current.clone();
self.current.insert_task(previous, task)?;
self.undo_stack.push(saved);
Ok(())
}

fn delete_task(&mut self, id: &TaskId) -> anyhow::Result<()> {
let saved = self.current.clone();
self.current.delete_task(id)?;
self.undo_stack.push(saved);
Ok(())
}

fn move_task(&mut self, previous: Option<&TaskId>, task: &TaskId) -> anyhow::Result<()> {
let saved = self.current.clone();
self.current.move_task(previous, task)?;
self.undo_stack.push(saved);
Ok(())
}

fn list_tasks(&mut self) -> anyhow::Result<Vec<Task>> {
self.current.list_tasks()
}

fn undo(&mut self) -> anyhow::Result<()> {
if let Some(record) = self.undo_stack.pop() {
self.redo_stack.push(record.clone());
self.current = record;
Ok(())
} else {
bail!("undo is not available")
}
}

fn redo(&mut self) -> anyhow::Result<()> {
if let Some(record) = self.redo_stack.pop() {
self.current = record;
Ok(())
} else {
bail!("redo is not available")
}
}
}
4 changes: 4 additions & 0 deletions src/screen/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ impl State {
keys::Command::Delete => {
common_state.delete_selected();
}
keys::Command::Undo => {
common_state.undo();
}
keys::Command::Redo => common_state.redo(),
},
}
None
Expand Down
3 changes: 3 additions & 0 deletions src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub trait Screen {
fn render(self: &Self, conext: &mut CommonState, frame: &mut ratatui::Frame);

// FIXME: replace this with a back channel to the event queue logic?
// ...at which point should handle_key_event return a Box<dyn Screen>
// ...or should it return an enum with a NewScreen variant and another
// ...for the quit case?
fn should_quit(&self, context: &mut CommonState) -> bool {
_ = context;
false
Expand Down
8 changes: 8 additions & 0 deletions src/ui_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ impl CommonState {
.expect("FIXME: handle error here");
}
}

pub(crate) fn undo(&mut self) {
let _ignored = self.store.undo();
}

pub(crate) fn redo(&mut self) {
let _ignored = self.store.redo();
}
}

pub(crate) struct State {
Expand Down

0 comments on commit 9dde8b0

Please sign in to comment.