on master help sign in
Undo tree draft.
fd311fbBlaž Hrastnik
helix-core/src/history.rsnew file+127
@@ -0,0 +1,127 @@
1+use crate::{ChangeSet, Rope, State, Transaction};
2+
3+/// Undo-tree style history store.
4+pub struct History {
5+ revisions: Vec<Revision>,
6+ cursor: usize,
7+ //
8+}
9+
10+#[derive(Debug)]
11+struct Revision {
12+ // prev: usize,
13+ parent: usize,
14+ /// The transaction to revert to previous state.
15+ revert: Transaction,
16+ // selection before, selection after?
17+}
18+
19+impl Default for History {
20+ fn default() -> Self {
21+ // Add a dummy root revision with empty transaction
22+ Self {
23+ revisions: vec![Revision {
24+ parent: 0,
25+ revert: Transaction::from(ChangeSet::new(&Rope::new())),
26+ }],
27+ cursor: 0,
28+ }
29+ }
30+}
31+
32+impl History {
33+ pub fn commit_revision(&mut self, transaction: &Transaction, original: &State) {
34+ // TODO: store both directions
35+ // TODO: could store a single set if deletes also stored the text they delete
36+ let revert = transaction.invert(original);
37+
38+ let new_cursor = self.revisions.len();
39+ self.revisions.push(Revision {
40+ parent: self.cursor,
41+ revert,
42+ });
43+ self.cursor = new_cursor;
44+
45+ // TODO: child tracking too?
46+ }
47+
48+ #[inline]
49+ pub fn at_root(&self) -> bool {
50+ self.cursor == 0
51+ }
52+
53+ pub fn undo(&mut self, state: &mut State) {
54+ if self.at_root() {
55+ // We're at the root of undo, nothing to do.
56+ return;
57+ }
58+
59+ let current_revision = &self.revisions[self.cursor];
60+ // unimplemented!("{:?}", revision);
61+ // unimplemented!("{:?}", state.doc().len_chars());
62+ // TODO: pass the return value through?
63+ let success = current_revision.revert.apply(state);
64+
65+ if !success {
66+ panic!("Failed to apply undo!");
67+ }
68+
69+ self.cursor = current_revision.parent;
70+ }
71+
72+ pub fn redo(&mut self, state: &mut State) {
73+ let current_revision = &self.revisions[self.cursor];
74+
75+ // TODO: pick the latest child
76+
77+ // if !success {
78+ // panic!("Failed to apply undo!");
79+ // }
80+
81+ unimplemented!()
82+ }
83+}
84+
85+#[cfg(test)]
86+mod test {
87+ use super::*;
88+
89+ #[test]
90+ fn test_undo_redo() {
91+ let mut history = History::default();
92+ let doc = Rope::from("hello");
93+ let mut state = State::new(doc);
94+
95+ let transaction1 =
96+ Transaction::change(&state, vec![(5, 5, Some(" world!".into()))].into_iter());
97+
98+ // Need to commit before applying!
99+ history.commit_revision(&transaction1, &state);
100+ transaction1.apply(&mut state);
101+ assert_eq!("hello world!", state.doc());
102+
103+ // ---
104+
105+ let transaction2 =
106+ Transaction::change(&state, vec![(6, 11, Some("世界".into()))].into_iter());
107+
108+ // Need to commit before applying!
109+ history.commit_revision(&transaction2, &state);
110+ transaction2.apply(&mut state);
111+ assert_eq!("hello 世界!", state.doc());
112+
113+ // ---
114+
115+ history.undo(&mut state);
116+ assert_eq!("hello world!", state.doc());
117+ history.redo(&mut state);
118+ assert_eq!("hello 世界!", state.doc());
119+ history.undo(&mut state);
120+ history.undo(&mut state);
121+ assert_eq!("hello", state.doc());
122+
123+ // undo at root is a no-op
124+ history.undo(&mut state);
125+ assert_eq!("hello", state.doc());
126+ }
127+}
helix-core/src/lib.rs+2
@@ -1,5 +1,6 @@
1#![allow(unused)]
1#![allow(unused)]
2pub mod graphemes;
2pub mod graphemes;
3+mod history;
3pub mod macros;
4pub mod macros;
4mod position;
5mod position;
5pub mod selection;
6pub mod selection;
@@ -19,6 +20,7 @@ pub use selection::Range;
19pub use selection::Selection;
20pub use selection::Selection;
20pub use syntax::Syntax;
21pub use syntax::Syntax;
21
22
23+pub use history::History;
22pub use state::State;
24pub use state::State;
23
25
24pub use transaction::{Assoc, Change, ChangeSet, Transaction};
26pub use transaction::{Assoc, Change, ChangeSet, Transaction};
helix-core/src/state.rs+1−1
@@ -48,8 +48,8 @@ impl State {
48 doc,
48 doc,
49 selection: Selection::single(0, 0),
49 selection: Selection::single(0, 0),
50 mode: Mode::Normal,
50 mode: Mode::Normal,
51− syntax: None,
52 restore_cursor: false,
51 restore_cursor: false,
52+ syntax: None,
53 }
53 }
54 }
54 }
55
55
helix-core/src/transaction.rs+1−1
@@ -338,7 +338,7 @@ impl ChangeSet {
338
338
339/// Transaction represents a single undoable unit of changes. Several changes can be grouped into
339/// Transaction represents a single undoable unit of changes. Several changes can be grouped into
340/// a single transaction.
340/// a single transaction.
341−#[derive(Clone)]
341+#[derive(Debug, Clone)]
342pub struct Transaction {
342pub struct Transaction {
343 /// Changes made to the buffer.
343 /// Changes made to the buffer.
344 pub(crate) changes: ChangeSet,
344 pub(crate) changes: ChangeSet,
helix-view/src/commands.rs+12
@@ -417,3 +417,15 @@ pub fn delete_char_forward(view: &mut View, count: usize) {
417 transaction.apply(&mut view.state);
417 transaction.apply(&mut view.state);
418 // TODO: need to store into history if successful
418 // TODO: need to store into history if successful
419}
419}
420+
421+// Undo / Redo
422+
423+pub fn undo(view: &mut View, _count: usize) {
424+ view.history.undo(&mut view.state);
425+
426+ // TODO: each command should simply return a Option<transaction>, then the higher level handles storing it?
427+}
428+
429+pub fn redo(view: &mut View, _count: usize) {
430+ view.history.redo(&mut view.state);
431+}
helix-view/src/keymap.rs+2
@@ -145,6 +145,8 @@ pub fn default() -> Keymaps {
145 vec![key!('c')] => commands::change_selection,
145 vec![key!('c')] => commands::change_selection,
146 vec![key!('s')] => commands::split_selection_on_newline,
146 vec![key!('s')] => commands::split_selection_on_newline,
147 vec![key!(';')] => commands::collapse_selection,
147 vec![key!(';')] => commands::collapse_selection,
148+ vec![key!('u')] => commands::undo,
149+ vec![shift!('U')] => commands::redo,
148 vec![Key {
150 vec![Key {
149 code: KeyCode::Esc,
151 code: KeyCode::Esc,
150 modifiers: Modifiers::NONE
152 modifiers: Modifiers::NONE
helix-view/src/view.rs+3−1
@@ -5,12 +5,13 @@ use std::{borrow::Cow, path::PathBuf};
5use crate::theme::Theme;
5use crate::theme::Theme;
6use helix_core::{
6use helix_core::{
7 graphemes::{grapheme_width, RopeGraphemes},
7 graphemes::{grapheme_width, RopeGraphemes},
8− Position, RopeSlice, State,
8+ History, Position, RopeSlice, State,
9};
9};
10use tui::layout::Rect;
10use tui::layout::Rect;
11
11
12pub struct View {
12pub struct View {
13 pub state: State,
13 pub state: State,
14+ pub history: History,
14 pub first_line: usize,
15 pub first_line: usize,
15 pub size: (u16, u16),
16 pub size: (u16, u16),
16 pub theme: Theme, // TODO: share one instance
17 pub theme: Theme, // TODO: share one instance
@@ -26,6 +27,7 @@ impl View {
26 first_line: 0,
27 first_line: 0,
27 size, // TODO: pass in from term
28 size, // TODO: pass in from term
28 theme,
29 theme,
30+ history: History::default(),
29 };
31 };
30
32
31 Ok(view)
33 Ok(view)