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.rs181
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(&current_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.