Beginnings of a Transaction type.
This commit is contained in:
parent
27572c8838
commit
51a86dcfdc
|
@ -7,3 +7,4 @@ pub mod hash;
|
|||
pub mod history;
|
||||
pub mod marks;
|
||||
pub mod project;
|
||||
pub mod transaction;
|
||||
|
|
117
sub_crates/backend/src/transaction.rs
Normal file
117
sub_crates/backend/src/transaction.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use ropey::Rope;
|
||||
|
||||
use crate::marks::MarkSet;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Op {
|
||||
Retain {
|
||||
byte_count: usize,
|
||||
},
|
||||
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: Range<usize>,
|
||||
new: Range<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
/// A reversable set of edits treated as an atomic unit.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Transaction {
|
||||
ops: Vec<Op>,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn new() -> Transaction {
|
||||
Transaction {
|
||||
ops: Vec::new(),
|
||||
buffer: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds another edit to the Transaction.
|
||||
///
|
||||
/// This composes as if the edits already in the Transaction were
|
||||
/// performed first, and then the new edit was performed after.
|
||||
pub fn push_edit(&mut self, byte_idx: usize, old: &str, new: &str) {
|
||||
if self.ops.is_empty() {
|
||||
// The easy case.
|
||||
self.buffer.push_str(old);
|
||||
self.buffer.push_str(new);
|
||||
self.ops.push(Op::Retain {
|
||||
byte_count: byte_idx,
|
||||
});
|
||||
self.ops.push(Op::Replace {
|
||||
old: 0..old.len(),
|
||||
new: old.len()..(old.len() + new.len()),
|
||||
});
|
||||
} else {
|
||||
// The complex case.
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a Transaction that is functionally identical to applying
|
||||
/// first `self` and then `other` sequentially.
|
||||
#[must_use]
|
||||
pub fn compose(&self, _other: &Transaction) -> Transaction {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Build a Transaction that is functionally identical to undoing
|
||||
/// this Transaction.
|
||||
///
|
||||
/// Note: the resulting Transaction will losslessly reverse the
|
||||
/// original Transaction on text content, but will be lossy when
|
||||
/// applied to Marks.
|
||||
#[must_use]
|
||||
pub fn invert(&self) -> Transaction {
|
||||
let mut inverted = self.clone();
|
||||
for op in inverted.ops.iter_mut() {
|
||||
match *op {
|
||||
Op::Retain { .. } => {} // Do nothing.
|
||||
Op::Replace {
|
||||
ref mut old,
|
||||
ref mut new,
|
||||
} => {
|
||||
std::mem::swap(old, new);
|
||||
}
|
||||
}
|
||||
}
|
||||
inverted
|
||||
}
|
||||
|
||||
/// Applies the Transaction to a Rope.
|
||||
pub fn apply_to_text(&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.clone()];
|
||||
let new = &self.buffer[new.clone()];
|
||||
let char_i = text.byte_to_char(i);
|
||||
let old_char_len = old.chars().count();
|
||||
|
||||
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
|
||||
text.remove(char_i..(char_i + old_char_len));
|
||||
text.insert(char_i, new);
|
||||
|
||||
i = i + new.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
debug_assert!(i <= text.len_bytes());
|
||||
}
|
||||
|
||||
/// Applies the Transaction to a set of Marks.
|
||||
pub fn apply_to_marks(&self, _marks: &mut MarkSet) {
|
||||
todo!()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user