on master help sign in
Add :earlier and :later commands that can be used to navigate the full edit history. (#194)

* Disable deleting from an empty buffer which can cause a crash. * Improve on the fix for deleting from the end of the buffer. * Clean up leftover log. * Avoid theoretical underflow. * Implement :before which accepts a time interval and moves the editor to the closest history state to the commit of the current time minus that interval. Current time is now by default, or the commit time if :before has just been used. * Add :earlier an :later commands that can move through the edit history and retrieve changes hidded by undoing and commiting new changes. The commands accept a number of steps or a time period relative to the currrent change. * Fix clippy lint error. * Remove the dependency on parse_duration, add a custom parser instead. * Fix clippy errors. * Make helix_core::history a public module. * Use the helper for getting the current document and view. * Handled some PR comments. * Fix the logic in :later n. Co-authored-by: Ivan Tham <pickfire@riseup.net> * Add an alias for :earlier. Co-authored-by: Ivan Tham <pickfire@riseup.net> * Add an alias for later. Co-authored-by: Ivan Tham <pickfire@riseup.net> * Run cargo fmt. * Add some tests for earlier and later. * Add more tests and restore the fix for later that diappeared somehow. * Use ? instead of a match on an option. Co-authored-by: Ivan Tham <pickfire@riseup.net> * Rename to UndoKind. * Remove the leftover match. * Handle a bunch of review comments. * More systemd.time compliant time units and additional description for the new commands. * A more concise rewrite of the time span parser using ideas from PR discussion. * Replace a match with map_err(). Co-authored-by: Ivan Tham <pickfire@riseup.net> Co-authored-by: Jakub Bartodziej <jqb@google.com> Co-authored-by: Ivan Tham <pickfire@riseup.net>

commit69fe46a122f728b4bb6cb218fd410544097b0dcd authorJakub Bartodziej <jbaa@users.noreply.github.com> dateFri 11 Jun 2021, 15:06 (5 years ago) parent86af55c
4 files changed+502−45
helix-core/src/history.rs+446−37 helix-core/src/lib.rs+1−2 helix-term/src/commands.rs+38 helix-view/src/document.rs+17−6
helix-core/src/history.rs+446−37
@@ -1,20 +1,61 @@
11use crate::{ChangeSet, Rope, State, Transaction};
2+use once_cell::sync::Lazy;
3+use regex::Regex;
24use smallvec::{smallvec, SmallVec};
5+use std::num::NonZeroUsize;
6+use std::time::{Duration, Instant};
37
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.
543#[derive(Debug)]
644pub struct History {
745 revisions: Vec<Revision>,
8− cursor: usize,
46+ current: usize,
947}
1048
49+// A single point in history. See [History] for more information.
1150#[derive(Debug)]
1251struct Revision {
1352 parent: usize,
14− children: SmallVec<[(usize, Transaction); 1]>,
15− /// The transaction to revert to previous state.
16− revert: Transaction,
17− // selection before, selection after?
53+ last_child: Option<NonZeroUsize>,
54+ transaction: Transaction,
55+ // We need an inversion for undos because delete transactions don't store
56+ // the deleted text.
57+ inversion: Transaction,
58+ timestamp: Instant,
1859}
1960
2061impl Default for History {
@@ -23,72 +64,253 @@ impl Default for History {
2364 Self {
2465 revisions: vec![Revision {
2566 parent: 0,
26− children: SmallVec::new(),
27− revert: Transaction::from(ChangeSet::new(&Rope::new())),
67+ last_child: None,
68+ transaction: Transaction::from(ChangeSet::new(&Rope::new())),
69+ inversion: Transaction::from(ChangeSet::new(&Rope::new())),
70+ timestamp: Instant::now(),
2871 }],
29− cursor: 0,
72+ current: 0,
3073 }
3174 }
3275}
3376
3477impl History {
3578 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
37− let revert = transaction
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+ ) {
88+ let inversion = transaction
3889 .invert(&original.doc)
3990 // Store the current cursor position
4091 .with_selection(original.selection.clone());
4192
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);
4395 self.revisions.push(Revision {
44− parent: self.cursor,
45− children: SmallVec::new(),
46− revert,
96+ parent: self.current,
97+ last_child: None,
98+ transaction: transaction.clone(),
99+ inversion,
100+ timestamp,
47101 });
48−
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;
102+ self.current = new_current;
57103 }
58104
59105 #[inline]
60106 pub fn current_revision(&self) -> usize {
61− self.cursor
107+ self.current
62108 }
63109
64110 #[inline]
65111 pub const fn at_root(&self) -> bool {
66− self.cursor == 0
112+ self.current == 0
67113 }
68114
69115 pub fn undo(&mut self) -> Option<&Transaction> {
70116 if self.at_root() {
71− // We're at the root of undo, nothing to do.
72117 return None;
73118 }
74119
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+ }
76124
77− self.cursor = current_revision.parent;
125+ pub fn redo(&mut self) -> Option<&Transaction> {
126+ let current_revision = &self.revisions[self.current];
127+ let last_child = current_revision.last_child?;
128+ self.current = last_child.get();
78129
79− Some(&current_revision.revert)
130+ let last_child_revision = &self.revisions[last_child.get()];
131+ Some(&self.revisions[last_child.get()].transaction)
80132 }
81133
82− pub fn redo(&mut self) -> Option<&Transaction> {
83− let current_revision = &self.revisions[self.cursor];
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);
158+ n = self.revisions[n].parent;
159+ }
160+ path
161+ }
84162
85− // for now, simply pick the latest child (linear undo / redo)
86− if let Some((index, transaction)) = current_revision.children.last() {
87− self.cursor = *index;
163+ fn jump_to(&mut self, to: usize) -> Vec<Transaction> {
164+ let lca = self.lowest_common_ancestor(self.current, to);
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+ }
88220
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)?))
90313 }
91− None
92314 }
93315}
94316
@@ -144,4 +366,191 @@ mod test {
144366 undo(&mut history, &mut state);
145367 assert_eq!("hello", state.doc);
146368 }
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+ }
147556}
helix-core/src/lib.rs+1−2
@@ -3,7 +3,7 @@ pub mod auto_pairs;
33pub mod comment;
44pub mod diagnostic;
55pub mod graphemes;
6−mod history;
6+pub mod history;
77pub mod indent;
88pub mod macros;
99pub mod match_brackets;
@@ -89,7 +89,6 @@ pub use smallvec::SmallVec;
8989pub use syntax::Syntax;
9090
9191pub use diagnostic::Diagnostic;
92−pub use history::History;
9392pub use state::State;
9493
9594pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
helix-term/src/commands.rs+38
@@ -980,6 +980,30 @@ mod cmd {
980980 doc.format(view.id)
981981 }
982982
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+
9831007 pub const COMMAND_LIST: &[Command] = &[
9841008 Command {
9851009 name: "quit",
@@ -1023,6 +1047,20 @@ mod cmd {
10231047 fun: format,
10241048 completer: None,
10251049 },
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+ },
10261064 ];
10271065
10281066 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};
55use std::sync::Arc;
66
77use helix_core::{
8+ history::History,
89 syntax::{LanguageConfiguration, LOADER},
9− ChangeSet, Diagnostic, History, Rope, Selection, State, Syntax, Transaction,
10+ ChangeSet, Diagnostic, Rope, Selection, State, Syntax, Transaction,
1011};
1112
1213use crate::{DocumentId, ViewId};
@@ -387,7 +388,7 @@ impl Document {
387388 success
388389 }
389390
390− pub fn undo(&mut self, view_id: ViewId) -> bool {
391+ pub fn undo(&mut self, view_id: ViewId) {
391392 let mut history = self.history.take();
392393 let success = if let Some(transaction) = history.undo() {
393394 self._apply(&transaction, view_id)
@@ -400,11 +401,9 @@ impl Document {
400401 // reset changeset to fix len
401402 self.changes = ChangeSet::new(self.text());
402403 }
403−
404− success
405404 }
406405
407− pub fn redo(&mut self, view_id: ViewId) -> bool {
406+ pub fn redo(&mut self, view_id: ViewId) {
408407 let mut history = self.history.take();
409408 let success = if let Some(transaction) = history.redo() {
410409 self._apply(&transaction, view_id)
@@ -417,8 +416,20 @@ impl Document {
417416 // reset changeset to fix len
418417 self.changes = ChangeSet::new(self.text());
419418 }
419+ }
420420
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+ }
422433 }
423434
424435 pub fn append_changes_to_history(&mut self, view_id: ViewId) {