on master help sign in
Add :earlier and :later commands that can be used to navigate the full edit history. (#194)
69fe46aJakub Bartodziej
helix-core/src/history.rs+446−37
@@ -1,20 +1,61 @@
1use crate::{ChangeSet, Rope, State, Transaction};
1use crate::{ChangeSet, Rope, State, Transaction};
2+use once_cell::sync::Lazy;
3+use regex::Regex;
2use smallvec::{smallvec, SmallVec};
4use smallvec::{smallvec, SmallVec};
5+use std::num::NonZeroUsize;
6+use std::time::{Duration, Instant};
3
7
4−/// Undo-tree style history store.
8+// Stores the history of changes to a buffer.
9+//
10+// Currently the history is represented as a vector of revisions. The vector
11+// always has at least one element: the empty root revision. Each revision
12+// with the exception of the root has a parent revision, a [Transaction]
13+// that can be applied to its parent to transition from the parent to itself,
14+// and an inversion of that transaction to transition from the parent to its
15+// latest child.
16+//
17+// When using `u` to undo a change, an inverse of the stored transaction will
18+// be applied which will transition the buffer to the parent state.
19+//
20+// Each revision with the exception of the last in the vector also has a
21+// last child revision. When using `U` to redo a change, the last child transaction
22+// will be applied to the current state of the buffer.
23+//
24+// The current revision is the one currently displayed in the buffer.
25+//
26+// Commiting a new revision to the history will update the last child of the
27+// current revision, and push a new revision to the end of the vector.
28+//
29+// Revisions are commited with a timestamp. :earlier and :later can be used
30+// to jump to the closest revision to a moment in time relative to the timestamp
31+// of the current revision plus (:later) or minus (:earlier) the duration
32+// given to the command. If a single integer is given, the editor will instead
33+// jump the given number of revisions in the vector.
34+//
35+// Limitations:
36+// * Changes in selections currently don't commit history changes. The selection
37+// will only be updated to the state after a commited buffer change.
38+// * The vector of history revisions is currently unbounded. This might
39+// cause the memory consumption to grow significantly large during long
40+// editing sessions.
41+// * Because delete transactions currently don't store the text that they
42+// delete, we also store an inversion of the transaction.
5#[derive(Debug)]
43#[derive(Debug)]
6pub struct History {
44pub struct History {
7 revisions: Vec<Revision>,
45 revisions: Vec<Revision>,
8− cursor: usize,
46+ current: usize,
9}
47}
10
48
49+// A single point in history. See [History] for more information.
11#[derive(Debug)]
50#[derive(Debug)]
12struct Revision {
51struct Revision {
13 parent: usize,
52 parent: usize,
14− children: SmallVec<[(usize, Transaction); 1]>,
53+ last_child: Option<NonZeroUsize>,
15− /// The transaction to revert to previous state.
54+ transaction: Transaction,
55+ // We need an inversion for undos because delete transactions don't store
56+ // the deleted text.
16− revert: Transaction,
57+ inversion: Transaction,
17− // selection before, selection after?
58+ timestamp: Instant,
18}
59}
19
60
20impl Default for History {
61impl Default for History {
@@ -23,72 +64,253 @@ impl Default for History {
23 Self {
64 Self {
24 revisions: vec![Revision {
65 revisions: vec![Revision {
25 parent: 0,
66 parent: 0,
26− children: SmallVec::new(),
67+ last_child: None,
68+ transaction: Transaction::from(ChangeSet::new(&Rope::new())),
27− revert: Transaction::from(ChangeSet::new(&Rope::new())),
69+ inversion: Transaction::from(ChangeSet::new(&Rope::new())),
70+ timestamp: Instant::now(),
28 }],
71 }],
29− cursor: 0,
72+ current: 0,
30 }
73 }
31 }
74 }
32}
75}
33
76
34impl History {
77impl History {
35 pub fn commit_revision(&mut self, transaction: &Transaction, original: &State) {
78 pub fn commit_revision(&mut self, transaction: &Transaction, original: &State) {
36− // TODO: could store a single transaction, if deletes also stored the text they delete
79+ self.commit_revision_at_timestamp(transaction, original, Instant::now());
80+ }
81+
82+ pub fn commit_revision_at_timestamp(
83+ &mut self,
84+ transaction: &Transaction,
85+ original: &State,
86+ timestamp: Instant,
87+ ) {
37− let revert = transaction
88+ let inversion = transaction
38 .invert(&original.doc)
89 .invert(&original.doc)
39 // Store the current cursor position
90 // Store the current cursor position
40 .with_selection(original.selection.clone());
91 .with_selection(original.selection.clone());
41
92
42− let new_cursor = self.revisions.len();
93+ let new_current = self.revisions.len();
94+ self.revisions[self.current].last_child = NonZeroUsize::new(new_current);
43 self.revisions.push(Revision {
95 self.revisions.push(Revision {
44− parent: self.cursor,
96+ parent: self.current,
45− children: SmallVec::new(),
97+ last_child: None,
46− revert,
98+ transaction: transaction.clone(),
99+ inversion,
100+ timestamp,
47 });
101 });
48−
102+ self.current = new_current;
49− // add a reference to the parent
50− self.revisions
51− .get_mut(self.cursor)
52− .unwrap() // TODO: get_unchecked_mut
53− .children
54− .push((new_cursor, transaction.clone()));
55−
56− self.cursor = new_cursor;
57 }
103 }
58
104
59 #[inline]
105 #[inline]
60 pub fn current_revision(&self) -> usize {
106 pub fn current_revision(&self) -> usize {
61− self.cursor
107+ self.current
62 }
108 }
63
109
64 #[inline]
110 #[inline]
65 pub const fn at_root(&self) -> bool {
111 pub const fn at_root(&self) -> bool {
66− self.cursor == 0
112+ self.current == 0
67 }
113 }
68
114
69 pub fn undo(&mut self) -> Option<&Transaction> {
115 pub fn undo(&mut self) -> Option<&Transaction> {
70 if self.at_root() {
116 if self.at_root() {
71− // We're at the root of undo, nothing to do.
72 return None;
117 return None;
73 }
118 }
74
119
75− let current_revision = &self.revisions[self.cursor];
120+ let current_revision = &self.revisions[self.current];
121+ self.current = current_revision.parent;
122+ Some(&current_revision.inversion)
123+ }
76
124
125+ pub fn redo(&mut self) -> Option<&Transaction> {
126+ let current_revision = &self.revisions[self.current];
77− self.cursor = current_revision.parent;
127+ let last_child = current_revision.last_child?;
128+ self.current = last_child.get();
78
129
79− Some(&current_revision.revert)
130+ let last_child_revision = &self.revisions[last_child.get()];
131+ Some(&self.revisions[last_child.get()].transaction)
80 }
132 }
81
133
82− pub fn redo(&mut self) -> Option<&Transaction> {
134+ fn lowest_common_ancestor(&self, mut a: usize, mut b: usize) -> usize {
135+ use std::collections::HashSet;
136+ let mut a_path_set = HashSet::new();
137+ let mut b_path_set = HashSet::new();
138+ loop {
139+ a_path_set.insert(a);
140+ b_path_set.insert(b);
141+ if a_path_set.contains(&b) {
142+ return b;
143+ }
144+ if b_path_set.contains(&a) {
145+ return a;
146+ }
147+ a = self.revisions[a].parent; // Relies on the parent of 0 being 0.
148+ b = self.revisions[b].parent; // Same as above.
149+ }
150+ }
151+
152+ // List of nodes on the way from `n` to 'a`. Doesn`t include `a`.
153+ // Includes `n` unless `a == n`. `a` must be an ancestor of `n`.
154+ fn path_up(&self, mut n: usize, a: usize) -> Vec<usize> {
155+ let mut path = Vec::new();
156+ while n != a {
157+ path.push(n);
83− let current_revision = &self.revisions[self.cursor];
158+ n = self.revisions[n].parent;
159+ }
160+ path
161+ }
84
162
85− // for now, simply pick the latest child (linear undo / redo)
163+ fn jump_to(&mut self, to: usize) -> Vec<Transaction> {
86− if let Some((index, transaction)) = current_revision.children.last() {
164+ let lca = self.lowest_common_ancestor(self.current, to);
87− self.cursor = *index;
165+ let up = self.path_up(self.current, lca);
166+ let down = self.path_up(to, lca);
167+ self.current = to;
168+ let up_txns = up.iter().map(|&n| self.revisions[n].inversion.clone());
169+ let down_txns = down
170+ .iter()
171+ .rev()
172+ .map(|&n| self.revisions[n].transaction.clone());
173+ up_txns.chain(down_txns).collect()
174+ }
175+
176+ fn jump_backward(&mut self, delta: usize) -> Vec<Transaction> {
177+ self.jump_to(self.current.saturating_sub(delta))
178+ }
179+
180+ fn jump_forward(&mut self, delta: usize) -> Vec<Transaction> {
181+ self.jump_to(
182+ self.current
183+ .saturating_add(delta)
184+ .min(self.revisions.len() - 1),
185+ )
186+ }
187+
188+ // Helper for a binary search case below.
189+ fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize {
190+ let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp);
191+ let dur_i = self.revisions[i].timestamp.duration_since(instant);
192+ use std::cmp::Ordering::*;
193+ match dur_im1.cmp(&dur_i) {
194+ Less => i - 1,
195+ Equal | Greater => i,
196+ }
197+ }
198+
199+ fn jump_instant(&mut self, instant: Instant) -> Vec<Transaction> {
200+ let search_result = self
201+ .revisions
202+ .binary_search_by(|rev| rev.timestamp.cmp(&instant));
203+ let revision = match search_result {
204+ Ok(revision) => revision,
205+ Err(insert_point) => match insert_point {
206+ 0 => 0,
207+ n if n == self.revisions.len() => n - 1,
208+ i => self.revision_closer_to_instant(i, instant),
209+ },
210+ };
211+ self.jump_to(revision)
212+ }
213+
214+ fn jump_duration_backward(&mut self, duration: Duration) -> Vec<Transaction> {
215+ match self.revisions[self.current].timestamp.checked_sub(duration) {
216+ Some(instant) => self.jump_instant(instant),
217+ None => self.jump_to(0),
218+ }
219+ }
88
220
89− return Some(&transaction);
221+ fn jump_duration_forward(&mut self, duration: Duration) -> Vec<Transaction> {
222+ match self.revisions[self.current].timestamp.checked_add(duration) {
223+ Some(instant) => self.jump_instant(instant),
224+ None => self.jump_to(self.revisions.len() - 1),
225+ }
226+ }
227+
228+ pub fn earlier(&mut self, uk: UndoKind) -> Vec<Transaction> {
229+ use UndoKind::*;
230+ match uk {
231+ Steps(n) => self.jump_backward(n),
232+ TimePeriod(d) => self.jump_duration_backward(d),
233+ }
234+ }
235+
236+ pub fn later(&mut self, uk: UndoKind) -> Vec<Transaction> {
237+ use UndoKind::*;
238+ match uk {
239+ Steps(n) => self.jump_forward(n),
240+ TimePeriod(d) => self.jump_duration_forward(d),
241+ }
242+ }
243+}
244+
245+#[derive(Debug, PartialEq)]
246+pub enum UndoKind {
247+ Steps(usize),
248+ TimePeriod(std::time::Duration),
249+}
250+
251+// A subset of sytemd.time time span syntax units.
252+const TIME_UNITS: &[(&[&str], &str, u64)] = &[
253+ (&["seconds", "second", "sec", "s"], "seconds", 1),
254+ (&["minutes", "minute", "min", "m"], "minutes", 60),
255+ (&["hours", "hour", "hr", "h"], "hours", 60 * 60),
256+ (&["days", "day", "d"], "days", 24 * 60 * 60),
257+];
258+
259+static DURATION_VALIDATION_REGEX: Lazy<Regex> =
260+ Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap());
261+
262+static NUMBER_UNIT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap());
263+
264+fn parse_human_duration(s: &str) -> Result<Duration, String> {
265+ if !DURATION_VALIDATION_REGEX.is_match(s) {
266+ return Err("duration should be composed \
267+ of positive integers followed by time units"
268+ .to_string());
269+ }
270+
271+ let mut specified = [false; TIME_UNITS.len()];
272+ let mut seconds = 0u64;
273+ for cap in NUMBER_UNIT_REGEX.captures_iter(s) {
274+ let (n, unit_str) = (&cap[1], &cap[2]);
275+
276+ let n: u64 = n.parse().map_err(|_| format!("integer too large: {}", n))?;
277+
278+ let time_unit = TIME_UNITS
279+ .iter()
280+ .enumerate()
281+ .find(|(_, (forms, _, _))| forms.iter().any(|f| f == &unit_str));
282+
283+ if let Some((i, (_, unit, mul))) = time_unit {
284+ if specified[i] {
285+ return Err(format!("{} specified more than once", unit));
286+ }
287+ specified[i] = true;
288+
289+ let new_seconds = n.checked_mul(*mul).and_then(|s| seconds.checked_add(s));
290+ match new_seconds {
291+ Some(ns) => seconds = ns,
292+ None => return Err("duration too large".to_string()),
293+ }
294+ } else {
295+ return Err(format!("incorrect time unit: {}", unit_str));
296+ }
297+ }
298+
299+ Ok(Duration::from_secs(seconds))
300+}
301+
302+impl std::str::FromStr for UndoKind {
303+ type Err = String;
304+
305+ fn from_str(s: &str) -> Result<Self, Self::Err> {
306+ let s = s.trim();
307+ if s.is_empty() {
308+ Ok(Self::Steps(1usize))
309+ } else if let Ok(n) = s.parse::<usize>() {
310+ Ok(UndoKind::Steps(n))
311+ } else {
312+ Ok(Self::TimePeriod(parse_human_duration(s)?))
90 }
313 }
91− None
92 }
314 }
93}
315}
94
316
@@ -144,4 +366,191 @@ mod test {
144 undo(&mut history, &mut state);
366 undo(&mut history, &mut state);
145 assert_eq!("hello", state.doc);
367 assert_eq!("hello", state.doc);
146 }
368 }
369+
370+ #[test]
371+ fn test_earlier_later() {
372+ let mut history = History::default();
373+ let doc = Rope::from("a\n");
374+ let mut state = State::new(doc);
375+
376+ fn undo(history: &mut History, state: &mut State) {
377+ if let Some(transaction) = history.undo() {
378+ transaction.apply(&mut state.doc);
379+ }
380+ };
381+
382+ fn earlier(history: &mut History, state: &mut State, uk: UndoKind) {
383+ let txns = history.earlier(uk);
384+ for txn in txns {
385+ txn.apply(&mut state.doc);
386+ }
387+ };
388+
389+ fn later(history: &mut History, state: &mut State, uk: UndoKind) {
390+ let txns = history.later(uk);
391+ for txn in txns {
392+ txn.apply(&mut state.doc);
393+ }
394+ };
395+
396+ fn commit_change(
397+ history: &mut History,
398+ state: &mut State,
399+ change: crate::transaction::Change,
400+ instant: Instant,
401+ ) {
402+ let txn = Transaction::change(&state.doc, vec![change.clone()].into_iter());
403+ history.commit_revision_at_timestamp(&txn, &state, instant);
404+ txn.apply(&mut state.doc);
405+ };
406+
407+ let t0 = Instant::now();
408+ let t = |n| t0.checked_add(Duration::from_secs(n)).unwrap();
409+
410+ commit_change(&mut history, &mut state, (1, 1, Some(" b".into())), t(0));
411+ assert_eq!("a b\n", state.doc);
412+
413+ commit_change(&mut history, &mut state, (3, 3, Some(" c".into())), t(10));
414+ assert_eq!("a b c\n", state.doc);
415+
416+ commit_change(&mut history, &mut state, (5, 5, Some(" d".into())), t(20));
417+ assert_eq!("a b c d\n", state.doc);
418+
419+ undo(&mut history, &mut state);
420+ assert_eq!("a b c\n", state.doc);
421+
422+ commit_change(&mut history, &mut state, (5, 5, Some(" e".into())), t(30));
423+ assert_eq!("a b c e\n", state.doc);
424+
425+ undo(&mut history, &mut state);
426+ undo(&mut history, &mut state);
427+ assert_eq!("a b\n", state.doc);
428+
429+ commit_change(&mut history, &mut state, (1, 3, None), t(40));
430+ assert_eq!("a\n", state.doc);
431+
432+ commit_change(&mut history, &mut state, (1, 1, Some(" f".into())), t(50));
433+ assert_eq!("a f\n", state.doc);
434+
435+ use UndoKind::*;
436+
437+ earlier(&mut history, &mut state, Steps(3));
438+ assert_eq!("a b c d\n", state.doc);
439+
440+ later(&mut history, &mut state, TimePeriod(Duration::new(20, 0)));
441+ assert_eq!("a\n", state.doc);
442+
443+ earlier(&mut history, &mut state, TimePeriod(Duration::new(19, 0)));
444+ assert_eq!("a b c d\n", state.doc);
445+
446+ earlier(
447+ &mut history,
448+ &mut state,
449+ TimePeriod(Duration::new(10000, 0)),
450+ );
451+ assert_eq!("a\n", state.doc);
452+
453+ later(&mut history, &mut state, Steps(50));
454+ assert_eq!("a f\n", state.doc);
455+
456+ earlier(&mut history, &mut state, Steps(4));
457+ assert_eq!("a b c\n", state.doc);
458+
459+ later(&mut history, &mut state, TimePeriod(Duration::new(1, 0)));
460+ assert_eq!("a b c\n", state.doc);
461+
462+ later(&mut history, &mut state, TimePeriod(Duration::new(5, 0)));
463+ assert_eq!("a b c d\n", state.doc);
464+
465+ later(&mut history, &mut state, TimePeriod(Duration::new(6, 0)));
466+ assert_eq!("a b c e\n", state.doc);
467+
468+ later(&mut history, &mut state, Steps(1));
469+ assert_eq!("a\n", state.doc);
470+ }
471+
472+ #[test]
473+ fn test_parse_undo_kind() {
474+ use UndoKind::*;
475+
476+ // Default is one step.
477+ assert_eq!("".parse(), Ok(Steps(1)));
478+
479+ // An integer means the number of steps.
480+ assert_eq!("1".parse(), Ok(Steps(1)));
481+ assert_eq!(" 16 ".parse(), Ok(Steps(16)));
482+
483+ // Duration has a strict format.
484+ let validation_err = Err("duration should be composed \
485+ of positive integers followed by time units"
486+ .to_string());
487+ assert_eq!(" 16 33".parse::<UndoKind>(), validation_err);
488+ assert_eq!(" seconds 22 ".parse::<UndoKind>(), validation_err);
489+ assert_eq!(" -4 m".parse::<UndoKind>(), validation_err);
490+ assert_eq!("5s 3".parse::<UndoKind>(), validation_err);
491+
492+ // Units are u64.
493+ assert_eq!(
494+ "18446744073709551616minutes".parse::<UndoKind>(),
495+ Err("integer too large: 18446744073709551616".to_string())
496+ );
497+
498+ // Units are validated.
499+ assert_eq!(
500+ "1 millenium".parse::<UndoKind>(),
501+ Err("incorrect time unit: millenium".to_string())
502+ );
503+
504+ // Units can't be specified twice.
505+ assert_eq!(
506+ "2 seconds 6s".parse::<UndoKind>(),
507+ Err("seconds specified more than once".to_string())
508+ );
509+
510+ // Various formats are correctly handled.
511+ assert_eq!(
512+ "4s".parse::<UndoKind>(),
513+ Ok(TimePeriod(Duration::from_secs(4)))
514+ );
515+ assert_eq!(
516+ "2m".parse::<UndoKind>(),
517+ Ok(TimePeriod(Duration::from_secs(120)))
518+ );
519+ assert_eq!(
520+ "5h".parse::<UndoKind>(),
521+ Ok(TimePeriod(Duration::from_secs(5 * 60 * 60)))
522+ );
523+ assert_eq!(
524+ "3d".parse::<UndoKind>(),
525+ Ok(TimePeriod(Duration::from_secs(3 * 24 * 60 * 60)))
526+ );
527+ assert_eq!(
528+ "1m30s".parse::<UndoKind>(),
529+ Ok(TimePeriod(Duration::from_secs(90)))
530+ );
531+ assert_eq!(
532+ "1m 20 seconds".parse::<UndoKind>(),
533+ Ok(TimePeriod(Duration::from_secs(80)))
534+ );
535+ assert_eq!(
536+ " 2 minute 1day".parse::<UndoKind>(),
537+ Ok(TimePeriod(Duration::from_secs(24 * 60 * 60 + 2 * 60)))
538+ );
539+ assert_eq!(
540+ "3 d 2hour 5 minutes 30sec".parse::<UndoKind>(),
541+ Ok(TimePeriod(Duration::from_secs(
542+ 3 * 24 * 60 * 60 + 2 * 60 * 60 + 5 * 60 + 30
543+ )))
544+ );
545+
546+ // Sum overflow is handled.
547+ assert_eq!(
548+ "18446744073709551615minutes".parse::<UndoKind>(),
549+ Err("duration too large".to_string())
550+ );
551+ assert_eq!(
552+ "1 minute 18446744073709551615 seconds".parse::<UndoKind>(),
553+ Err("duration too large".to_string())
554+ );
555+ }
147}
556}
helix-core/src/lib.rs+1−2
@@ -3,7 +3,7 @@ pub mod auto_pairs;
3pub mod comment;
3pub mod comment;
4pub mod diagnostic;
4pub mod diagnostic;
5pub mod graphemes;
5pub mod graphemes;
6−mod history;
6+pub mod history;
7pub mod indent;
7pub mod indent;
8pub mod macros;
8pub mod macros;
9pub mod match_brackets;
9pub mod match_brackets;
@@ -89,7 +89,6 @@ pub use smallvec::SmallVec;
89pub use syntax::Syntax;
89pub use syntax::Syntax;
90
90
91pub use diagnostic::Diagnostic;
91pub use diagnostic::Diagnostic;
92−pub use history::History;
93pub use state::State;
92pub use state::State;
94
93
95pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
94pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
helix-term/src/commands.rs+38
@@ -980,6 +980,30 @@ mod cmd {
980 doc.format(view.id)
980 doc.format(view.id)
981 }
981 }
982
982
983+ fn earlier(editor: &mut Editor, args: &[&str], event: PromptEvent) {
984+ let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
985+ Ok(uk) => uk,
986+ Err(msg) => {
987+ editor.set_error(msg);
988+ return;
989+ }
990+ };
991+ let (view, doc) = editor.current();
992+ doc.earlier(view.id, uk)
993+ }
994+
995+ fn later(editor: &mut Editor, args: &[&str], event: PromptEvent) {
996+ let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
997+ Ok(uk) => uk,
998+ Err(msg) => {
999+ editor.set_error(msg);
1000+ return;
1001+ }
1002+ };
1003+ let (view, doc) = editor.current();
1004+ doc.later(view.id, uk)
1005+ }
1006+
983 pub const COMMAND_LIST: &[Command] = &[
1007 pub const COMMAND_LIST: &[Command] = &[
984 Command {
1008 Command {
985 name: "quit",
1009 name: "quit",
@@ -1023,6 +1047,20 @@ mod cmd {
1023 fun: format,
1047 fun: format,
1024 completer: None,
1048 completer: None,
1025 },
1049 },
1050+ Command {
1051+ name: "earlier",
1052+ alias: Some("ear"),
1053+ doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
1054+ fun: earlier,
1055+ completer: None,
1056+ },
1057+ Command {
1058+ name: "later",
1059+ alias: Some("lat"),
1060+ doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
1061+ fun: later,
1062+ completer: None,
1063+ },
1026 ];
1064 ];
1027
1065
1028 pub static COMMANDS: Lazy<HashMap<&'static str, &'static Command>> = Lazy::new(|| {
1066 pub static COMMANDS: Lazy<HashMap<&'static str, &'static Command>> = Lazy::new(|| {
helix-view/src/document.rs+17−6
@@ -5,8 +5,9 @@ use std::path::{Component, Path, PathBuf};
5use std::sync::Arc;
5use std::sync::Arc;
6
6
7use helix_core::{
7use helix_core::{
8+ history::History,
8 syntax::{LanguageConfiguration, LOADER},
9 syntax::{LanguageConfiguration, LOADER},
9− ChangeSet, Diagnostic, History, Rope, Selection, State, Syntax, Transaction,
10+ ChangeSet, Diagnostic, Rope, Selection, State, Syntax, Transaction,
10};
11};
11
12
12use crate::{DocumentId, ViewId};
13use crate::{DocumentId, ViewId};
@@ -387,7 +388,7 @@ impl Document {
387 success
388 success
388 }
389 }
389
390
390− pub fn undo(&mut self, view_id: ViewId) -> bool {
391+ pub fn undo(&mut self, view_id: ViewId) {
391 let mut history = self.history.take();
392 let mut history = self.history.take();
392 let success = if let Some(transaction) = history.undo() {
393 let success = if let Some(transaction) = history.undo() {
393 self._apply(&transaction, view_id)
394 self._apply(&transaction, view_id)
@@ -400,11 +401,9 @@ impl Document {
400 // reset changeset to fix len
401 // reset changeset to fix len
401 self.changes = ChangeSet::new(self.text());
402 self.changes = ChangeSet::new(self.text());
402 }
403 }
403−
404− success
405 }
404 }
406
405
407− pub fn redo(&mut self, view_id: ViewId) -> bool {
406+ pub fn redo(&mut self, view_id: ViewId) {
408 let mut history = self.history.take();
407 let mut history = self.history.take();
409 let success = if let Some(transaction) = history.redo() {
408 let success = if let Some(transaction) = history.redo() {
410 self._apply(&transaction, view_id)
409 self._apply(&transaction, view_id)
@@ -417,8 +416,20 @@ impl Document {
417 // reset changeset to fix len
416 // reset changeset to fix len
418 self.changes = ChangeSet::new(self.text());
417 self.changes = ChangeSet::new(self.text());
419 }
418 }
419+ }
420
420
421− false
421+ pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
422+ let txns = self.history.get_mut().earlier(uk);
423+ for txn in txns {
424+ self._apply(&txn, view_id);
425+ }
426+ }
427+
428+ pub fn later(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
429+ let txns = self.history.get_mut().later(uk);
430+ for txn in txns {
431+ self._apply(&txn, view_id);
432+ }
422 }
433 }
423
434
424 pub fn append_changes_to_history(&mut self, view_id: ViewId) {
435 pub fn append_changes_to_history(&mut self, view_id: ViewId) {