use crate::helix::{Assoc, ChangeSet, Range, Rope, Selection, Transaction};
use std::num::NonZeroUsize;
use std::time::Duration;
pub type Timestamp = u64;
#[derive(Debug, Clone)]
pub struct State {
pub doc: Rope,
pub selection: Selection,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct History {
revisions: Vec<Revision>,
current: usize,
}
#[derive(Debug, Clone)]
pub struct Rebased {
pub old: usize,
pub remote: ChangeSet,
pub text: Rope,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
struct Revision {
parent: usize,
last_child: Option<NonZeroUsize>,
transaction: Transaction,
inversion: Transaction,
timestamp: Timestamp,
}
impl Default for History {
fn default() -> Self {
Self {
revisions: vec![Revision {
parent: 0,
last_child: None,
transaction: Transaction::from(ChangeSet::new("".into())),
inversion: Transaction::from(ChangeSet::new("".into())),
timestamp: 0,
}],
current: 0,
}
}
}
impl History {
pub fn commit_revision_at_timestamp(
&mut self,
transaction: &Transaction,
original: &State,
timestamp: Timestamp,
) {
let inversion = transaction
.invert(&original.doc)
.with_selection(original.selection.clone());
let new_current = self.revisions.len();
self.revisions[self.current].last_child = NonZeroUsize::new(new_current);
self.revisions.push(Revision {
parent: self.current,
last_child: None,
transaction: transaction.clone(),
inversion,
timestamp,
});
self.current = new_current;
}
pub fn amend_current_revision(
&mut self,
transaction: &Transaction,
original_doc: &Rope,
timestamp: Timestamp,
) -> bool {
if self.current == 0
|| self.current != self.revisions.len() - 1
|| self.revisions[self.current].last_child.is_some()
{
return false;
}
let rev = &mut self.revisions[self.current];
let composed = std::mem::take(&mut rev.transaction).compose(transaction.clone());
rev.transaction = composed;
let inversion = std::mem::take(&mut rev.inversion);
rev.inversion = transaction.invert(original_doc).compose(inversion);
rev.timestamp = timestamp;
true
}
pub fn rebase_over(&mut self, remote: &ChangeSet, doc: &Rope) -> Vec<Rebased> {
let path = self.path_up(self.current, 0);
let mut r = remote.clone();
let mut text = doc.clone();
let mut rebuilt: Vec<(Rebased, Transaction, Transaction, Timestamp)> = Vec::with_capacity(path.len());
for &k in &path {
let rev = &self.revisions[k];
let inv = rev.inversion.changes();
let inv2 = inv.map_ordered(&r, false);
let r_prev = r.map_ordered(inv, true);
let transaction = {
let t = Transaction::from(inv2.invert(&text));
match rev.transaction.selection() {
Some(sel) => t.with_selection(map_selection(sel, &r)),
None => t,
}
};
let mut prev_text = text.clone();
inv2.apply(&mut prev_text);
let inversion = {
let t = Transaction::from(inv2);
match rev.inversion.selection() {
Some(sel) => t.with_selection(map_selection(sel, &r_prev)),
None => t,
}
};
let remote_k = std::mem::replace(&mut r, r_prev);
let text_k = std::mem::replace(&mut text, prev_text);
rebuilt.push((Rebased { old: k, remote: remote_k, text: text_k }, transaction, inversion, rev.timestamp));
}
let mut kept = vec![Rebased { old: 0, remote: r, text }];
let mut revisions = vec![Revision {
parent: 0,
last_child: None,
transaction: std::mem::take(&mut self.revisions[0].transaction),
inversion: std::mem::take(&mut self.revisions[0].inversion),
timestamp: self.revisions[0].timestamp,
}];
for (rebased, transaction, inversion, timestamp) in rebuilt.into_iter().rev() {
let i = revisions.len();
revisions[i - 1].last_child = NonZeroUsize::new(i);
revisions.push(Revision { parent: i - 1, last_child: None, transaction, inversion, timestamp });
kept.push(rebased);
}
self.current = revisions.len() - 1;
self.revisions = revisions;
kept
}
pub fn current_transaction(&self) -> &Transaction {
&self.revisions[self.current].transaction
}
pub fn current_inversion(&self) -> &Transaction {
&self.revisions[self.current].inversion
}
pub fn len(&self) -> usize {
self.revisions.len()
}
pub fn is_empty(&self) -> bool {
self.revisions.len() == 1
}
pub fn can_redo(&self) -> bool {
self.revisions[self.current].last_child.is_some()
}
#[inline]
pub fn current_revision(&self) -> usize {
self.current
}
#[inline]
pub const fn at_root(&self) -> bool {
self.current == 0
}
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))
}
pub fn undo(&mut self) -> Option<&Transaction> {
if self.at_root() {
return None;
}
let current_revision = &self.revisions[self.current];
self.current = current_revision.parent;
Some(¤t_revision.inversion)
}
pub fn redo(&mut self) -> Option<&Transaction> {
let current_revision = &self.revisions[self.current];
let last_child = current_revision.last_child?;
self.current = last_child.get();
Some(&self.revisions[last_child.get()].transaction)
}
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(|(from, to, _fragment)| Range::new(*from, *to).overlaps(&primary_selection))
.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();
let mut b_path_set = HashSet::new();
loop {
a_path_set.insert(a);
b_path_set.insert(b);
if a_path_set.contains(&b) {
return b;
}
if b_path_set.contains(&a) {
return a;
}
a = self.revisions[a].parent; b = self.revisions[b].parent; }
}
fn path_up(&self, mut n: usize, a: usize) -> Vec<usize> {
let mut path = Vec::new();
while n != a {
path.push(n);
n = self.revisions[n].parent;
}
path
}
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);
let down = self.path_up(to, lca);
self.current = to;
let up_txns = up.iter().map(|&n| self.revisions[n].inversion.clone());
let down_txns = down
.iter()
.rev()
.map(|&n| self.revisions[n].transaction.clone());
up_txns.chain(down_txns).collect()
}
fn jump_backward(&mut self, delta: usize) -> Vec<Transaction> {
self.jump_to(self.current.saturating_sub(delta))
}
fn jump_forward(&mut self, delta: usize) -> Vec<Transaction> {
self.jump_to(
self.current
.saturating_add(delta)
.min(self.revisions.len() - 1),
)
}
fn revision_closer_to_instant(&self, i: usize, instant: Timestamp) -> usize {
let dur_im1 = instant.saturating_sub(self.revisions[i - 1].timestamp);
let dur_i = self.revisions[i].timestamp.saturating_sub(instant);
use std::cmp::Ordering::*;
match dur_im1.cmp(&dur_i) {
Less => i - 1,
Equal | Greater => i,
}
}
fn jump_instant(&mut self, instant: Timestamp) -> Vec<Transaction> {
let search_result = self
.revisions
.binary_search_by(|rev| rev.timestamp.cmp(&instant));
let revision = match search_result {
Ok(revision) => revision,
Err(insert_point) => match insert_point {
0 => 0,
n if n == self.revisions.len() => n - 1,
i => self.revision_closer_to_instant(i, instant),
},
};
self.jump_to(revision)
}
fn jump_duration_backward(&mut self, duration: Duration) -> Vec<Transaction> {
match self.revisions[self.current]
.timestamp
.checked_sub(duration.as_millis() as Timestamp) {
Some(instant) => self.jump_instant(instant),
None => self.jump_to(0),
}
}
fn jump_duration_forward(&mut self, duration: Duration) -> Vec<Transaction> {
match self.revisions[self.current]
.timestamp
.checked_add(duration.as_millis() as Timestamp) {
Some(instant) => self.jump_instant(instant),
None => self.jump_to(self.revisions.len() - 1),
}
}
pub fn earlier(&mut self, uk: UndoKind) -> Vec<Transaction> {
use UndoKind::*;
match uk {
Steps(n) => self.jump_backward(n),
TimePeriod(d) => self.jump_duration_backward(d),
}
}
pub fn later(&mut self, uk: UndoKind) -> Vec<Transaction> {
use UndoKind::*;
match uk {
Steps(n) => self.jump_forward(n),
TimePeriod(d) => self.jump_duration_forward(d),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum UndoKind {
Steps(usize),
TimePeriod(std::time::Duration),
}
#[cfg(test)]
mod test {
use super::*;
use crate::helix::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 transaction1 =
Transaction::change(&state.doc, vec![(5, 5, Some(" world!".into()))].into_iter());
history.commit_revision_at_timestamp(&transaction1, &state, 0);
transaction1.apply(&mut state.doc);
assert_eq!("hello world!", state.doc);
let transaction2 =
Transaction::change(&state.doc, vec![(6, 11, Some("世界".into()))].into_iter());
history.commit_revision_at_timestamp(&transaction2, &state, 0);
transaction2.apply(&mut state.doc);
assert_eq!("hello 世界!", state.doc);
fn undo(history: &mut History, state: &mut State) {
if let Some(transaction) = history.undo() {
transaction.apply(&mut state.doc);
}
}
fn redo(history: &mut History, state: &mut State) {
if let Some(transaction) = history.redo() {
transaction.apply(&mut state.doc);
}
}
undo(&mut history, &mut state);
assert_eq!("hello world!", state.doc);
redo(&mut history, &mut state);
assert_eq!("hello 世界!", state.doc);
undo(&mut history, &mut state);
undo(&mut history, &mut state);
assert_eq!("hello", state.doc);
undo(&mut history, &mut state);
assert_eq!("hello", state.doc);
}
#[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),
};
fn undo(history: &mut History, state: &mut State) {
if let Some(transaction) = history.undo() {
transaction.apply(&mut state.doc);
}
}
fn earlier(history: &mut History, state: &mut State, uk: UndoKind) {
let txns = history.earlier(uk);
for txn in txns {
txn.apply(&mut state.doc);
}
}
fn later(history: &mut History, state: &mut State, uk: UndoKind) {
let txns = history.later(uk);
for txn in txns {
txn.apply(&mut state.doc);
}
}
fn commit_change(
history: &mut History,
state: &mut State,
change: crate::helix::transaction::Change,
instant: Timestamp,
) {
let txn = Transaction::change(&state.doc, vec![change].into_iter());
history.commit_revision_at_timestamp(&txn, state, instant);
txn.apply(&mut state.doc);
}
let t = |n: u64| n * 1000;
commit_change(&mut history, &mut state, (1, 1, Some(" b".into())), t(0));
assert_eq!("a b\n", state.doc);
commit_change(&mut history, &mut state, (3, 3, Some(" c".into())), t(10));
assert_eq!("a b c\n", state.doc);
commit_change(&mut history, &mut state, (5, 5, Some(" d".into())), t(20));
assert_eq!("a b c d\n", state.doc);
undo(&mut history, &mut state);
assert_eq!("a b c\n", state.doc);
commit_change(&mut history, &mut state, (5, 5, Some(" e".into())), t(30));
assert_eq!("a b c e\n", state.doc);
undo(&mut history, &mut state);
undo(&mut history, &mut state);
assert_eq!("a b\n", state.doc);
commit_change(&mut history, &mut state, (1, 3, None), t(40));
assert_eq!("a\n", state.doc);
commit_change(&mut history, &mut state, (1, 1, Some(" f".into())), t(50));
assert_eq!("a f\n", state.doc);
use UndoKind::*;
earlier(&mut history, &mut state, Steps(3));
assert_eq!("a b c d\n", state.doc);
later(&mut history, &mut state, TimePeriod(Duration::new(20, 0)));
assert_eq!("a\n", state.doc);
earlier(&mut history, &mut state, TimePeriod(Duration::new(19, 0)));
assert_eq!("a b c d\n", state.doc);
earlier(
&mut history,
&mut state,
TimePeriod(Duration::new(10000, 0)),
);
assert_eq!("a\n", state.doc);
later(&mut history, &mut state, Steps(50));
assert_eq!("a f\n", state.doc);
earlier(&mut history, &mut state, Steps(4));
assert_eq!("a b c\n", state.doc);
later(&mut history, &mut state, TimePeriod(Duration::new(1, 0)));
assert_eq!("a b c\n", state.doc);
later(&mut history, &mut state, TimePeriod(Duration::new(5, 0)));
assert_eq!("a b c d\n", state.doc);
later(&mut history, &mut state, TimePeriod(Duration::new(6, 0)));
assert_eq!("a b c e\n", state.doc);
later(&mut history, &mut state, Steps(1));
assert_eq!("a\n", state.doc);
}
}
fn map_selection(sel: &Selection, changes: &ChangeSet) -> Selection {
let len = changes.len();
sel.clone()
.transform(|r| Range { anchor: r.anchor.min(len), head: r.head.min(len), old_visual_position: None })
.map(changes)
}