led/sub_crates/backend/src/transaction.rs

682 lines
22 KiB
Rust

use ropey::Rope;
use crate::marks::MarkSet;
#[derive(Debug, Clone, Copy)]
enum Op {
Retain(usize), // In bytes.
Replace {
// These both represent strings, and are byte-index ranges into
// the Transaction's `buffer` where their actual string data is
// stored. `(0, 0)` can be used to indicate no string data.
old: (usize, usize),
new: (usize, usize),
},
}
impl Op {
/// The length of the string segment this Op represents *before*
/// its application. In bytes.
fn len_pre(&self) -> usize {
match self {
Op::Retain(byte_count) => *byte_count,
Op::Replace { old, .. } => old.1 - old.0,
}
}
/// The length of the string segment this Op represents *after*
/// its application. In bytes.
fn len_post(&self) -> usize {
match self {
Op::Retain(byte_count) => *byte_count,
Op::Replace { new, .. } => new.1 - new.0,
}
}
}
/// A reversable set of edits treated as an atomic unit.
#[derive(Clone)]
pub struct Transaction {
ops: Vec<Op>,
buffer: String,
}
impl Transaction {
pub fn new() -> Transaction {
Transaction {
ops: Vec::new(),
buffer: String::new(),
}
}
/// Creates a Transaction from a single edit.
pub fn from_edit(byte_idx: usize, old: &str, new: &str) -> Transaction {
let mut buffer = String::new();
buffer.push_str(old);
buffer.push_str(new);
let ops = vec![
Op::Retain(byte_idx),
Op::Replace {
old: (0, old.len()),
new: (old.len(), old.len() + new.len()),
},
];
Transaction {
ops: ops,
buffer: buffer,
}
}
/// Creates a Transaction from a sorted, non-overlapping set of
/// simultaneous edits.
///
/// Takes an iterator that yields `(byte_index, old_text, replacement_text)`
/// tuples, representing the edits. The items are expected to be
/// yielded in byte-index order, and the byte indices are relative to
/// the original text--this function does _not_ treat them as a
/// sequence of progressively applied edits, but rather as a set of
/// edits applied simultaneously.
pub fn from_ordered_edit_set<'a, I>(edit_iter: I) -> Transaction
where
I: Iterator<Item = (usize, &'a str, &'a str)> + 'a,
{
let mut trans = Transaction::new();
let mut i = 0;
let mut len_delta = 0isize;
for (byte_idx, old, new) in edit_iter {
let adjusted_byte_idx = (byte_idx as isize + len_delta) as usize;
let retained = adjusted_byte_idx - i;
let old_range = (trans.buffer.len(), trans.buffer.len() + old.len());
trans.buffer.push_str(old);
let new_range = (trans.buffer.len(), trans.buffer.len() + new.len());
trans.buffer.push_str(new);
if retained > 0 {
trans.ops.push(Op::Retain(retained));
}
trans.ops.push(Op::Replace {
old: old_range,
new: new_range,
});
i += retained + new.len();
len_delta += new.len() as isize - old.len() as isize;
}
trans
}
/// Build a Transaction that is functionally identical to applying
/// first `self` and then `other` sequentially.
#[must_use]
pub fn compose(&self, other: &Transaction) -> Transaction {
use Op::*;
let mut trans = Transaction::new();
let mut ops1 = self.ops.iter().copied();
let mut ops2 = other.ops.iter().copied();
let mut next_op1 = ops1.next();
let mut next_op2 = ops2.next();
// We progress through both transaction's ops such that their
// heads stay in sync. To do this, we have to split ops
// sometimes, fragmenting the transaction.
loop {
match (next_op1, next_op2) {
// Done!
(None, None) => break,
// One of the transactions is empty, so just append the
// remaining ops from the other one.
(Some(op), None) => {
trans.push_op(op, &self.buffer);
next_op1 = ops1.next();
}
(None, Some(op)) => {
trans.push_op(op, &other.buffer);
next_op2 = ops2.next();
}
//----------------
// Retain, Retain
(Some(Retain(bc1)), Some(Retain(bc2))) => {
if bc1 < bc2 {
trans.retain(bc1);
next_op1 = ops1.next();
next_op2 = Some(Retain(bc2 - bc1));
} else if bc2 < bc1 {
trans.retain(bc2);
next_op1 = Some(Retain(bc1 - bc2));
next_op2 = ops2.next();
} else {
trans.retain(bc1);
next_op1 = ops1.next();
next_op2 = ops2.next();
}
}
//-----------------
// Replace, Retain
(
Some(
op1 @ Replace {
old: old1,
new: new1,
},
),
Some(Retain(bc2)),
) => {
if op1.len_post() < bc2 {
trans.push_op(op1, &self.buffer);
next_op1 = ops1.next();
next_op2 = Some(Retain(bc2 - op1.len_post()));
} else if op1.len_post() > bc2 {
let op1a = Op::Replace {
old: old1,
new: (new1.0, new1.0 + bc2),
};
let op1b = Op::Replace {
old: (0, 0),
new: (new1.0 + bc2, new1.1),
};
trans.push_op(op1a, &self.buffer);
next_op1 = Some(op1b);
next_op2 = ops2.next();
} else {
trans.push_op(op1, &self.buffer);
next_op1 = ops1.next();
next_op2 = ops2.next();
}
}
//-----------------
// Retain, Replace
(
Some(Retain(bc1)),
Some(Replace {
old: old2,
new: new2,
}),
) => {
let op2 = next_op2.unwrap();
if bc1 < op2.len_pre() {
trans.push_op(
Replace {
old: (old2.0, old2.0 + bc1),
new: (0, 0),
},
&other.buffer,
);
next_op1 = ops1.next();
next_op2 = Some(Replace {
old: (old2.0 + bc1, old2.1),
new: new2,
});
} else if bc1 > op2.len_pre() {
trans.push_op(op2, &other.buffer);
next_op1 = Some(Retain(bc1 - op2.len_pre()));
next_op2 = ops2.next();
} else {
trans.push_op(op2, &other.buffer);
next_op1 = ops1.next();
next_op2 = ops2.next();
}
}
//------------------
// Replace, Replace
(
Some(Replace {
old: old1,
new: new1,
}),
Some(Replace {
old: old2,
new: new2,
}),
) => {
let op1 = next_op1.unwrap();
let op2 = next_op2.unwrap();
if op1.len_post() < op2.len_pre() {
trans.push_op(
Replace {
old: old1,
new: (0, 0),
},
&self.buffer,
);
next_op1 = ops1.next();
next_op2 = Some(Replace {
old: (old2.0 + op1.len_post(), old2.1),
new: new2,
});
} else if op1.len_post() > op2.len_pre() {
trans.push_op(
Replace {
old: old1,
new: (0, 0),
},
&self.buffer,
);
trans.push_op(
Replace {
old: (0, 0),
new: new2,
},
&other.buffer,
);
next_op1 = Some(Replace {
old: (0, 0),
new: (new1.0 + op2.len_pre(), new1.1),
});
next_op2 = ops2.next();
} else {
trans.push_op_separate_buf(
Replace {
old: old1,
new: new2,
},
&self.buffer,
&other.buffer,
);
next_op1 = ops1.next();
next_op2 = ops2.next();
}
}
}
}
trans
}
/// Applies the Transaction to a Rope.
pub fn apply(&self, text: &mut Rope) {
let mut i = 0;
for op in self.ops.iter() {
match op {
Op::Retain(byte_count) => {
i += byte_count;
}
Op::Replace { old, new } => {
let old = &self.buffer[old.0..old.1];
let new = &self.buffer[new.0..new.1];
if !old.is_empty() {
debug_assert_eq!(text.slice(i..(i + old.len())), old);
text.remove(i..(i + old.len()));
}
if !new.is_empty() {
text.insert(i, new);
}
i = i + new.len();
}
}
}
debug_assert!(i <= text.len());
}
/// Applies the inverse of the Transaction to a Rope.
///
/// This is an "undo" of the Transaction.
pub fn apply_inverse(&self, text: &mut Rope) {
let mut i = 0;
for op in self.ops.iter() {
match op {
Op::Retain(byte_count) => {
i += byte_count;
}
Op::Replace {
old: old_range,
new: new_range,
} => {
// Swap old and new, to do the inverse.
let old = &self.buffer[new_range.0..new_range.1];
let new = &self.buffer[old_range.0..old_range.1];
if !old.is_empty() {
debug_assert_eq!(text.slice(i..(i + old.len())), old);
text.remove(i..(i + old.len()));
}
if !new.is_empty() {
text.insert(i, new);
}
i = i + new.len();
}
}
}
debug_assert!(i <= text.len());
}
/// Applies the Transaction to a set of Marks.
pub fn apply_to_marks(&self, marks: &mut MarkSet) {
marks.make_consistent();
// We do this in two passes: first heads, then tails.
for tail in 0..=1 {
let mut mark_idx = 0;
let mut byte_idx_old = 0;
let mut byte_idx_new = 0;
for op in self.ops.iter() {
let old_len = op.len_pre();
let new_len = op.len_post();
while mark_idx < marks.len() {
let idx = if tail == 1 {
&mut marks[mark_idx].tail
} else {
&mut marks[mark_idx].head
};
if *idx < byte_idx_old {
mark_idx += 1;
} else if *idx < (byte_idx_old + old_len) {
*idx = *idx + byte_idx_new - byte_idx_old;
*idx = (*idx).min(byte_idx_new + new_len);
mark_idx += 1;
} else {
break;
}
}
byte_idx_old += old_len;
byte_idx_new += new_len;
}
while mark_idx < marks.len() {
let idx = if tail == 1 {
&mut marks[mark_idx].tail
} else {
&mut marks[mark_idx].head
};
*idx = *idx + byte_idx_new - byte_idx_old;
mark_idx += 1;
}
}
marks.make_consistent();
}
/// Applies the inverse of the Transaction to a set of Marks.
///
/// This is essentially an "undo" of the Transaction. However, on
/// marks this is a lossy operation and is not guaranteed to return
/// the marks to their original count and position.
pub fn apply_inverse_to_marks(&self, marks: &mut MarkSet) {
marks.make_consistent();
// We do this in two passes: first heads, then tails.
for tail in 0..=1 {
let mut mark_idx = 0;
let mut byte_idx_old = 0;
let mut byte_idx_new = 0;
for op in self.ops.iter() {
let old_len = op.len_post();
let new_len = op.len_pre();
while mark_idx < marks.len() {
let idx = if tail == 1 {
&mut marks[mark_idx].tail
} else {
&mut marks[mark_idx].head
};
if *idx < byte_idx_old {
mark_idx += 1;
} else if *idx < (byte_idx_old + old_len) {
*idx = *idx + byte_idx_new - byte_idx_old;
*idx = (*idx).min(byte_idx_new + new_len);
mark_idx += 1;
} else {
break;
}
}
byte_idx_old += old_len;
byte_idx_new += new_len;
}
while mark_idx < marks.len() {
let idx = if tail == 1 {
&mut marks[mark_idx].tail
} else {
&mut marks[mark_idx].head
};
*idx = *idx + byte_idx_new - byte_idx_old;
mark_idx += 1;
}
}
marks.make_consistent();
}
//---------------------------------------------------------
/// Pushes a retain op onto the transaction operations.
#[inline(always)]
fn retain(&mut self, byte_count: usize) {
if let Some(Op::Retain(ref mut last_byte_count)) = self.ops.last_mut() {
*last_byte_count += byte_count;
} else if byte_count > 0 {
self.ops.push(Op::Retain(byte_count));
}
}
/// Pushes a replace op onto the transaction operations.
#[inline(always)]
fn replace(&mut self, old: &str, new: &str) {
if !old.is_empty() || !new.is_empty() {
let ti = self.buffer.len();
self.buffer.push_str(old);
let old_range = (ti, ti + old.len());
let ti = self.buffer.len();
self.buffer.push_str(new);
let new_range = (ti, ti + new.len());
self.ops.push(Op::Replace {
old: old_range,
new: new_range,
});
}
}
/// Pushes an op onto the transaction operations.
///
/// - op: the operation.
/// - buffer: the string buffer the op points into if it's a `Replace`.
#[inline(always)]
fn push_op(&mut self, op: Op, buffer: &str) {
self.push_op_separate_buf(op, buffer, buffer);
}
/// Pushes an op onto the transaction operations.
///
/// - op: the operation.
/// - buf_old: the string buffer that `Replace { old }` points into.
/// - buf_new: the string buffer that `Replace { new }` points into.
#[inline(always)]
fn push_op_separate_buf(&mut self, op: Op, buf_old: &str, buf_new: &str) {
match op {
Op::Retain(byte_count) => self.retain(byte_count),
Op::Replace { old, new } => {
self.replace(&buf_old[old.0..old.1], &buf_new[new.0..new.1])
}
}
}
}
impl std::fmt::Debug for Transaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("Transaction {\n")?;
f.write_str(" ops: [\n")?;
for op in self.ops.iter() {
match op {
Op::Retain(byte_count) => f.write_str(&format!(" Retain({}),\n", byte_count))?,
Op::Replace { old, new } => f.write_str(&format!(
" Replace(\n old({}): \"{}\",\n new({}): \"{}\"\n ),\n",
old.1 - old.0,
&self.buffer[old.0..old.1],
new.1 - new.0,
&self.buffer[new.0..new.1],
))?,
}
}
f.write_str(" ],\n")?;
f.write_str(&format!(
" buffer({}): \"{}\",\n",
self.buffer.len(),
&self.buffer
))?;
f.write_str("}\n")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::marks::{Mark, MarkSet};
#[test]
fn apply_to_marks_01() {
let trans = Transaction::from_edit(5, "", "hi");
let mut mark_set = MarkSet::new();
mark_set.add_mark(Mark::new(4, 4));
mark_set.add_mark(Mark::new(5, 5));
mark_set.add_mark(Mark::new(6, 6));
trans.apply_to_marks(&mut mark_set);
assert_eq!(mark_set.marks[0].head, 4);
assert_eq!(mark_set.marks[0].tail, 4);
assert_eq!(mark_set.marks[1].head, 7);
assert_eq!(mark_set.marks[1].tail, 7);
assert_eq!(mark_set.marks[2].head, 8);
assert_eq!(mark_set.marks[2].tail, 8);
}
#[test]
fn apply_to_marks_02() {
let trans = Transaction {
ops: vec![
Op::Retain(5),
Op::Replace {
old: (0, 0),
new: (0, 1),
},
Op::Replace {
old: (0, 0),
new: (1, 2),
},
],
buffer: "hi".into(),
};
let mut mark_set = MarkSet::new();
mark_set.add_mark(Mark::new(4, 4));
mark_set.add_mark(Mark::new(5, 5));
mark_set.add_mark(Mark::new(6, 6));
trans.apply_to_marks(&mut mark_set);
assert_eq!(mark_set.marks[0].head, 4);
assert_eq!(mark_set.marks[0].tail, 4);
assert_eq!(mark_set.marks[1].head, 7);
assert_eq!(mark_set.marks[1].tail, 7);
assert_eq!(mark_set.marks[2].head, 8);
assert_eq!(mark_set.marks[2].tail, 8);
}
#[test]
fn apply_to_marks_03() {
let trans = Transaction::from_edit(5, "hi", "");
let mut mark_set = MarkSet::new();
mark_set.add_mark(Mark::new(4, 4));
mark_set.add_mark(Mark::new(6, 6));
mark_set.add_mark(Mark::new(8, 8));
trans.apply_to_marks(&mut mark_set);
assert_eq!(mark_set.marks[0].head, 4);
assert_eq!(mark_set.marks[0].tail, 4);
assert_eq!(mark_set.marks[1].head, 5);
assert_eq!(mark_set.marks[1].tail, 5);
assert_eq!(mark_set.marks[2].head, 6);
assert_eq!(mark_set.marks[2].tail, 6);
}
#[test]
fn apply_to_marks_04() {
let trans = Transaction {
ops: vec![
Op::Retain(5),
Op::Replace {
old: (0, 1),
new: (0, 0),
},
Op::Replace {
old: (1, 2),
new: (0, 0),
},
],
buffer: "hi".into(),
};
let mut mark_set = MarkSet::new();
mark_set.add_mark(Mark::new(4, 4));
mark_set.add_mark(Mark::new(6, 6));
mark_set.add_mark(Mark::new(8, 8));
trans.apply_to_marks(&mut mark_set);
assert_eq!(mark_set.marks[0].head, 4);
assert_eq!(mark_set.marks[0].tail, 4);
assert_eq!(mark_set.marks[1].head, 5);
assert_eq!(mark_set.marks[1].tail, 5);
assert_eq!(mark_set.marks[2].head, 6);
assert_eq!(mark_set.marks[2].tail, 6);
}
#[test]
fn apply_inverse_to_marks_01() {
let trans = Transaction::from_edit(5, "hi", "");
let mut mark_set = MarkSet::new();
mark_set.add_mark(Mark::new(4, 4));
mark_set.add_mark(Mark::new(5, 5));
mark_set.add_mark(Mark::new(6, 6));
trans.apply_inverse_to_marks(&mut mark_set);
assert_eq!(mark_set.marks[0].head, 4);
assert_eq!(mark_set.marks[0].tail, 4);
assert_eq!(mark_set.marks[1].head, 7);
assert_eq!(mark_set.marks[1].tail, 7);
assert_eq!(mark_set.marks[2].head, 8);
assert_eq!(mark_set.marks[2].tail, 8);
}
#[test]
fn apply_inverse_to_marks_02() {
let trans = Transaction::from_edit(5, "", "hi");
let mut mark_set = MarkSet::new();
mark_set.add_mark(Mark::new(4, 4));
mark_set.add_mark(Mark::new(6, 6));
mark_set.add_mark(Mark::new(8, 8));
trans.apply_inverse_to_marks(&mut mark_set);
assert_eq!(mark_set.marks[0].head, 4);
assert_eq!(mark_set.marks[0].tail, 4);
assert_eq!(mark_set.marks[1].head, 5);
assert_eq!(mark_set.marks[1].tail, 5);
assert_eq!(mark_set.marks[2].head, 6);
assert_eq!(mark_set.marks[2].tail, 6);
}
}