Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/history.rs')
| -rw-r--r-- | helix-core/src/history.rs | 181 |
1 files changed, 51 insertions, 130 deletions
diff --git a/helix-core/src/history.rs b/helix-core/src/history.rs index 28d6dd6e..67ded166 100644 --- a/helix-core/src/history.rs +++ b/helix-core/src/history.rs @@ -1,60 +1,52 @@ -use crate::{Assoc, ChangeSet, Range, Rope, Selection, Transaction}; +use crate::{ChangeSet, Rope, State, Transaction}; use once_cell::sync::Lazy; use regex::Regex; use std::num::NonZeroUsize; use std::time::{Duration, Instant}; -#[derive(Debug, Clone)] -pub struct State { - pub doc: Rope, - pub selection: Selection, -} - -/// Stores the history of changes to a buffer. -/// -/// Currently the history is represented as a vector of revisions. The vector -/// always has at least one element: the empty root revision. Each revision -/// with the exception of the root has a parent revision, a [Transaction] -/// that can be applied to its parent to transition from the parent to itself, -/// and an inversion of that transaction to transition from the parent to its -/// latest child. -/// -/// When using `u` to undo a change, an inverse of the stored transaction will -/// be applied which will transition the buffer to the parent state. -/// -/// Each revision with the exception of the last in the vector also has a -/// last child revision. When using `U` to redo a change, the last child transaction -/// will be applied to the current state of the buffer. -/// -/// The current revision is the one currently displayed in the buffer. -/// -/// Committing a new revision to the history will update the last child of the -/// current revision, and push a new revision to the end of the vector. -/// -/// Revisions are committed with a timestamp. :earlier and :later can be used -/// to jump to the closest revision to a moment in time relative to the timestamp -/// of the current revision plus (:later) or minus (:earlier) the duration -/// given to the command. If a single integer is given, the editor will instead -/// jump the given number of revisions in the vector. -/// -/// Limitations: -/// * Changes in selections currently don't commit history changes. The selection -/// will only be updated to the state after a committed buffer change. -/// * The vector of history revisions is currently unbounded. This might -/// cause the memory consumption to grow significantly large during long -/// editing sessions. -/// * Because delete transactions currently don't store the text that they -/// delete, we also store an inversion of the transaction. -/// -/// Using time to navigate the history: <https://github.com/helix-editor/helix/pull/194> +// Stores the history of changes to a buffer. +// +// Currently the history is represented as a vector of revisions. The vector +// always has at least one element: the empty root revision. Each revision +// with the exception of the root has a parent revision, a [Transaction] +// that can be applied to its parent to transition from the parent to itself, +// and an inversion of that transaction to transition from the parent to its +// latest child. +// +// When using `u` to undo a change, an inverse of the stored transaction will +// be applied which will transition the buffer to the parent state. +// +// Each revision with the exception of the last in the vector also has a +// last child revision. When using `U` to redo a change, the last child transaction +// will be applied to the current state of the buffer. +// +// The current revision is the one currently displayed in the buffer. +// +// Commiting a new revision to the history will update the last child of the +// current revision, and push a new revision to the end of the vector. +// +// Revisions are commited with a timestamp. :earlier and :later can be used +// to jump to the closest revision to a moment in time relative to the timestamp +// of the current revision plus (:later) or minus (:earlier) the duration +// given to the command. If a single integer is given, the editor will instead +// jump the given number of revisions in the vector. +// +// Limitations: +// * Changes in selections currently don't commit history changes. The selection +// will only be updated to the state after a commited buffer change. +// * The vector of history revisions is currently unbounded. This might +// cause the memory consumption to grow significantly large during long +// editing sessions. +// * Because delete transactions currently don't store the text that they +// delete, we also store an inversion of the transaction. #[derive(Debug)] pub struct History { revisions: Vec<Revision>, current: usize, } -/// A single point in history. See [History] for more information. -#[derive(Debug, Clone)] +// A single point in history. See [History] for more information. +#[derive(Debug)] struct Revision { parent: usize, last_child: Option<NonZeroUsize>, @@ -72,8 +64,8 @@ impl Default for History { revisions: vec![Revision { parent: 0, last_child: None, - transaction: Transaction::from(ChangeSet::new("".into())), - inversion: Transaction::from(ChangeSet::new("".into())), + transaction: Transaction::from(ChangeSet::new(&Rope::new())), + inversion: Transaction::from(ChangeSet::new(&Rope::new())), timestamp: Instant::now(), }], current: 0, @@ -119,22 +111,6 @@ impl History { self.current == 0 } - /// Returns the changes since the given revision composed into a transaction. - /// Returns None if there are no changes between the current and given revisions. - pub fn changes_since(&self, revision: usize) -> Option<Transaction> { - let lca = self.lowest_common_ancestor(revision, self.current); - let up = self.path_up(revision, lca); - let down = self.path_up(self.current, lca); - let up_txns = up - .iter() - .rev() - .map(|&n| self.revisions[n].inversion.clone()); - let down_txns = down.iter().map(|&n| self.revisions[n].transaction.clone()); - - down_txns.chain(up_txns).reduce(|acc, tx| tx.compose(acc)) - } - - /// Undo the last edit. pub fn undo(&mut self) -> Option<&Transaction> { if self.at_root() { return None; @@ -145,7 +121,6 @@ impl History { Some(¤t_revision.inversion) } - /// Redo the last edit. pub fn redo(&mut self) -> Option<&Transaction> { let current_revision = &self.revisions[self.current]; let last_child = current_revision.last_child?; @@ -154,32 +129,6 @@ impl History { Some(&self.revisions[last_child.get()].transaction) } - // Get the position of last change - pub fn last_edit_pos(&self) -> Option<usize> { - if self.current == 0 { - return None; - } - let current_revision = &self.revisions[self.current]; - let primary_selection = current_revision - .inversion - .selection() - .expect("inversion always contains a selection") - .primary(); - let (_from, to, _fragment) = current_revision - .transaction - .changes_iter() - // find a change that matches the primary selection - .find(|(from, to, _fragment)| Range::new(*from, *to).overlaps(&primary_selection)) - // or use the first change - .or_else(|| current_revision.transaction.changes_iter().next()) - .unwrap(); - let pos = current_revision - .transaction - .changes() - .map_pos(to, Assoc::After); - Some(pos) - } - fn lowest_common_ancestor(&self, mut a: usize, mut b: usize) -> usize { use std::collections::HashSet; let mut a_path_set = HashSet::new(); @@ -198,8 +147,8 @@ impl History { } } - /// List of nodes on the way from `n` to 'a`. Doesn't include `a`. - /// Includes `n` unless `a == n`. `a` must be an ancestor of `n`. + // List of nodes on the way from `n` to 'a`. Doesn`t include `a`. + // Includes `n` unless `a == n`. `a` must be an ancestor of `n`. fn path_up(&self, mut n: usize, a: usize) -> Vec<usize> { let mut path = Vec::new(); while n != a { @@ -209,7 +158,6 @@ impl History { path } - /// Create a [`Transaction`] that will jump to a specific revision in the history. fn jump_to(&mut self, to: usize) -> Vec<Transaction> { let lca = self.lowest_common_ancestor(self.current, to); let up = self.path_up(self.current, lca); @@ -223,12 +171,10 @@ impl History { up_txns.chain(down_txns).collect() } - /// Creates a [`Transaction`] that will undo `delta` revisions. fn jump_backward(&mut self, delta: usize) -> Vec<Transaction> { self.jump_to(self.current.saturating_sub(delta)) } - /// Creates a [`Transaction`] that will redo `delta` revisions. fn jump_forward(&mut self, delta: usize) -> Vec<Transaction> { self.jump_to( self.current @@ -237,7 +183,7 @@ impl History { ) } - /// Helper for a binary search case below. + // Helper for a binary search case below. fn revision_closer_to_instant(&self, i: usize, instant: Instant) -> usize { let dur_im1 = instant.duration_since(self.revisions[i - 1].timestamp); let dur_i = self.revisions[i].timestamp.duration_since(instant); @@ -248,8 +194,6 @@ impl History { } } - /// Creates a [`Transaction`] that will match a revision created at around - /// `instant`. fn jump_instant(&mut self, instant: Instant) -> Vec<Transaction> { let search_result = self .revisions @@ -265,8 +209,6 @@ impl History { self.jump_to(revision) } - /// Creates a [`Transaction`] that will match a revision created `duration` ago - /// from the timestamp of current revision. fn jump_duration_backward(&mut self, duration: Duration) -> Vec<Transaction> { match self.revisions[self.current].timestamp.checked_sub(duration) { Some(instant) => self.jump_instant(instant), @@ -274,8 +216,6 @@ impl History { } } - /// Creates a [`Transaction`] that will match a revision created `duration` in - /// the future from the timestamp of the current revision. fn jump_duration_forward(&mut self, duration: Duration) -> Vec<Transaction> { match self.revisions[self.current].timestamp.checked_add(duration) { Some(instant) => self.jump_instant(instant), @@ -283,7 +223,6 @@ impl History { } } - /// Creates an undo [`Transaction`]. pub fn earlier(&mut self, uk: UndoKind) -> Vec<Transaction> { use UndoKind::*; match uk { @@ -292,7 +231,6 @@ impl History { } } - /// Creates a redo [`Transaction`]. pub fn later(&mut self, uk: UndoKind) -> Vec<Transaction> { use UndoKind::*; match uk { @@ -302,14 +240,13 @@ impl History { } } -/// Whether to undo by a number of edits or a duration of time. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq)] pub enum UndoKind { Steps(usize), TimePeriod(std::time::Duration), } -/// A subset of systemd.time time span syntax units. +// A subset of sytemd.time time span syntax units. const TIME_UNITS: &[(&[&str], &str, u64)] = &[ (&["seconds", "second", "sec", "s"], "seconds", 1), (&["minutes", "minute", "min", "m"], "minutes", 60), @@ -317,20 +254,11 @@ const TIME_UNITS: &[(&[&str], &str, u64)] = &[ (&["days", "day", "d"], "days", 24 * 60 * 60), ]; -/// Checks if the duration input can be turned into a valid duration. It must be a -/// positive integer and denote the [unit of time.](`TIME_UNITS`) -/// Examples of valid durations: -/// * `5 sec` -/// * `5 min` -/// * `5 hr` -/// * `5 days` static DURATION_VALIDATION_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(?:\d+\s*[a-z]+\s*)+$").unwrap()); -/// Captures both the number and unit as separate capture groups. static NUMBER_UNIT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s*([a-z]+)").unwrap()); -/// Parse a string (e.g. "5 sec") and try to convert it into a [`Duration`]. fn parse_human_duration(s: &str) -> Result<Duration, String> { if !DURATION_VALIDATION_REGEX.is_match(s) { return Err("duration should be composed \ @@ -387,16 +315,12 @@ impl std::str::FromStr for UndoKind { #[cfg(test)] mod test { use super::*; - use crate::Selection; #[test] fn test_undo_redo() { let mut history = History::default(); let doc = Rope::from("hello"); - let mut state = State { - doc, - selection: Selection::point(0), - }; + let mut state = State::new(doc); let transaction1 = Transaction::change(&state.doc, vec![(5, 5, Some(" world!".into()))].into_iter()); @@ -445,10 +369,7 @@ mod test { fn test_earlier_later() { let mut history = History::default(); let doc = Rope::from("a\n"); - let mut state = State { - doc, - selection: Selection::point(0), - }; + let mut state = State::new(doc); fn undo(history: &mut History, state: &mut State) { if let Some(transaction) = history.undo() { @@ -476,8 +397,8 @@ mod test { change: crate::transaction::Change, instant: Instant, ) { - let txn = Transaction::change(&state.doc, vec![change].into_iter()); - history.commit_revision_at_timestamp(&txn, state, instant); + let txn = Transaction::change(&state.doc, vec![change.clone()].into_iter()); + history.commit_revision_at_timestamp(&txn, &state, instant); txn.apply(&mut state.doc); } @@ -574,8 +495,8 @@ mod test { // Units are validated. assert_eq!( - "1 millennium".parse::<UndoKind>(), - Err("incorrect time unit: millennium".to_string()) + "1 millenium".parse::<UndoKind>(), + Err("incorrect time unit: millenium".to_string()) ); // Units can't be specified twice. |