Implemented Transaction composition.
This commit is contained in:
parent
8a9388e816
commit
696ecb732d
121
Cargo.lock
generated
121
Cargo.lock
generated
|
@ -1,5 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Led"
|
||||
version = "0.0.2"
|
||||
|
@ -35,10 +37,17 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proptest",
|
||||
"ropey",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
@ -49,6 +58,21 @@ version = "0.2.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
|
@ -61,6 +85,12 @@ version = "3.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -130,6 +160,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "gag"
|
||||
version = "1.0.0"
|
||||
|
@ -153,9 +189,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -236,6 +272,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
|
@ -282,6 +327,38 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"quick-error 2.0.1",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
|
@ -331,6 +408,15 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.9"
|
||||
|
@ -340,6 +426,12 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
|
@ -351,9 +443,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4b1996ad82fb6b8ac0711329432d80aa1ad5d70a0fe6edd1a9cf6fd2375fbb2"
|
||||
checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
@ -367,6 +459,18 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error 1.2.3",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
|
@ -650,6 +754,15 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
|
|
|
@ -13,3 +13,6 @@ path = "src/lib.rs"
|
|||
ropey = "1"
|
||||
# ropey = { git = "https://github.com/cessen/ropey", branch = "master" }
|
||||
unicode-segmentation = "1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.0"
|
||||
|
|
|
@ -4,9 +4,7 @@ use crate::marks::MarkSet;
|
|||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Op {
|
||||
Retain {
|
||||
byte_count: usize,
|
||||
},
|
||||
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
|
||||
|
@ -16,8 +14,28 @@ enum Op {
|
|||
},
|
||||
}
|
||||
|
||||
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(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Transaction {
|
||||
ops: Vec<Op>,
|
||||
buffer: String,
|
||||
|
@ -38,9 +56,7 @@ impl Transaction {
|
|||
buffer.push_str(new);
|
||||
|
||||
let ops = vec![
|
||||
Op::Retain {
|
||||
byte_count: byte_idx,
|
||||
},
|
||||
Op::Retain(byte_idx),
|
||||
Op::Replace {
|
||||
old: (0, old.len()),
|
||||
new: (old.len(), old.len() + new.len()),
|
||||
|
@ -79,9 +95,7 @@ impl Transaction {
|
|||
trans.buffer.push_str(new);
|
||||
|
||||
if retained > 0 {
|
||||
trans.ops.push(Op::Retain {
|
||||
byte_count: retained,
|
||||
});
|
||||
trans.ops.push(Op::Retain(retained));
|
||||
}
|
||||
trans.ops.push(Op::Replace {
|
||||
old: old_range,
|
||||
|
@ -101,30 +115,119 @@ impl Transaction {
|
|||
use Op::*;
|
||||
|
||||
let mut trans = Transaction::new();
|
||||
let mut ops1 = self.ops.iter();
|
||||
let mut ops2 = other.ops.iter();
|
||||
let mut op1 = ops1.next();
|
||||
let mut op2 = ops2.next();
|
||||
let mut range1 = (0, 0);
|
||||
let mut range2 = (0, 0);
|
||||
|
||||
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 (op1, op2) {
|
||||
//-------------------------------
|
||||
// Move past the unchanged bits.
|
||||
(Some(Retain { byte_count: bc }), _) => {
|
||||
range1.1 += bc;
|
||||
op1 = ops1.next();
|
||||
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();
|
||||
}
|
||||
|
||||
(_, Some(Retain { byte_count: bc })) => {
|
||||
range2.1 += bc;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
// Handle changes when there are still changes in both
|
||||
// source transactions.
|
||||
//-----------------
|
||||
// Replace, Retain
|
||||
(Some(Replace { .. }), Some(Retain(bc2))) => {
|
||||
let op1 = next_op1.unwrap();
|
||||
|
||||
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, op1b) = match op1 {
|
||||
Op::Retain(bc1) => (Op::Retain(bc2), Op::Retain(bc1 - bc2)),
|
||||
Op::Replace { old, new } => (
|
||||
Op::Replace {
|
||||
old: old,
|
||||
new: (new.0, new.0 + bc2),
|
||||
},
|
||||
Op::Replace {
|
||||
old: (0, 0),
|
||||
new: (new.0 + bc2, new.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,
|
||||
|
@ -135,65 +238,54 @@ impl Transaction {
|
|||
new: new2,
|
||||
}),
|
||||
) => {
|
||||
// This is the complex case.
|
||||
todo!()
|
||||
}
|
||||
let op1 = next_op1.unwrap();
|
||||
let op2 = next_op2.unwrap();
|
||||
|
||||
//------------------------------------------------------
|
||||
// Handle changes when there are only changes remaining
|
||||
// in one of the source transactions.
|
||||
(Some(Replace { old, new }), None) => {
|
||||
if range1.0 < range1.1 {
|
||||
trans.ops.push(Retain {
|
||||
byte_count: range1.1 - range1.0,
|
||||
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,
|
||||
});
|
||||
range1.0 = range1.1;
|
||||
}
|
||||
|
||||
let ti = trans.buffer.len();
|
||||
trans.buffer.push_str(&self.buffer[old.0..old.1]);
|
||||
let old_range = (ti, ti + (old.1 - old.0));
|
||||
|
||||
let ti = trans.buffer.len();
|
||||
trans.buffer.push_str(&self.buffer[new.0..new.1]);
|
||||
let new_range = (ti, ti + (new.1 - new.0));
|
||||
|
||||
trans.ops.push(Replace {
|
||||
old: old_range,
|
||||
new: new_range,
|
||||
} 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),
|
||||
});
|
||||
|
||||
op1 = ops1.next();
|
||||
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();
|
||||
}
|
||||
|
||||
(None, Some(Replace { old, new })) => {
|
||||
if range2.0 < range2.1 {
|
||||
trans.ops.push(Retain {
|
||||
byte_count: range2.1 - range2.0,
|
||||
});
|
||||
range2.0 = range2.1;
|
||||
}
|
||||
|
||||
let ti = trans.buffer.len();
|
||||
trans.buffer.push_str(&other.buffer[old.0..old.1]);
|
||||
let old_range = (ti, ti + (old.1 - old.0));
|
||||
|
||||
let ti = trans.buffer.len();
|
||||
trans.buffer.push_str(&other.buffer[new.0..new.1]);
|
||||
let new_range = (ti, ti + (new.1 - new.0));
|
||||
|
||||
trans.ops.push(Replace {
|
||||
old: old_range,
|
||||
new: new_range,
|
||||
});
|
||||
|
||||
op2 = ops2.next();
|
||||
}
|
||||
|
||||
//-------
|
||||
// Done.
|
||||
(None, None) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +304,7 @@ impl Transaction {
|
|||
let mut inverted = self.clone();
|
||||
for op in inverted.ops.iter_mut() {
|
||||
match *op {
|
||||
Op::Retain { .. } => {} // Do nothing.
|
||||
Op::Retain(_) => {} // Do nothing.
|
||||
Op::Replace {
|
||||
ref mut old,
|
||||
ref mut new,
|
||||
|
@ -225,11 +317,11 @@ impl Transaction {
|
|||
}
|
||||
|
||||
/// Applies the Transaction to a Rope.
|
||||
pub fn apply_to_text(&self, text: &mut Rope) {
|
||||
pub fn apply(&self, text: &mut Rope) {
|
||||
let mut i = 0;
|
||||
for op in self.ops.iter() {
|
||||
match op {
|
||||
Op::Retain { byte_count } => {
|
||||
Op::Retain(byte_count) => {
|
||||
i += byte_count;
|
||||
}
|
||||
Op::Replace { old, new } => {
|
||||
|
@ -257,4 +349,87 @@ impl Transaction {
|
|||
pub fn apply_to_marks(&self, _marks: &mut MarkSet) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
|
|
12
sub_crates/backend/tests/transaction.proptest-regressions
Normal file
12
sub_crates/backend/tests/transaction.proptest-regressions
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 9a4e25ad28ca0e4c6282ebe1e7de4d2f021a698e7c36691e99aad7e62af967f7 # shrinks to ref start_text = "🄀𐀨", mut edits1 = [], mut edits2 = [(0, 0, "®")]
|
||||
cc cc0cfd1ee5c07fa5555bc31a889474ee30c835b17df1a72deb6392ba59ac567c # shrinks to ref start_text = "ਓ A🉠A𐊠០a\u{3099}𖩮 aA\u{1e130}0aA¡🌀﷏a00 \u{b3c}aa®A \u{1da9b} 𝑖 ㇰ\u{1e130}0Σ ", mut edits1 = [(0, 0, "A")], mut edits2 = []
|
||||
cc f3e118eb2c12051b9272965ede18bedae8d80c8b20deb204ea2ddcac0167ea7c # shrinks to ref start_text = " a¡a®\u{abc}ﹶAa ὐAA®🌀ⶠ¡0a\u{c3c}a0aa0𖿠0AA 𞹂 ዀ𐼀𖭛", mut edits1 = [(0, 0, "𐖗")], mut edits2 = [(0, 1, "")]
|
||||
cc 91517cacb2a3f87865f9c3cc2679187e1c766c87c3bbf677fb45ce1a336f2156 # shrinks to ref start_text = "᭐𞥞 𝕒𞹛\u{20d0}A 0𝋠0𐐀AaA𐕼 𞅎\u{10eab}Aኊ࡞𑌵יִA0𞺡00𛅤מּ 𐝀", mut edits1 = [(1, 1, ""), (1, 6, "¡𑊏a"), (69, 0, " 0\u{1b80}0A\u{16f8f}𞸻\u{11366}")], mut edits2 = [(71, 5, "")]
|
||||
cc 4aeb2caebb202397370ec1021309a8f10e05336b2841a3d357d6164081038219 # shrinks to ref start_text = "㆐વ\u{3000}𐔀0ஒஜ 𞹝 𐒰0\u{ec8}ዘഒa𞺀𑰊a0AaaA 𐧒0A", mut edits1 = [(0, 0, "🌀"), (52, 0, "®0ก𑵧 A a")], mut edits2 = [(60, 9, "")]
|
||||
cc 8955ab9f00a8484dceb51acb72d612c8720c227a5f57fd87a18acfe1958dfd66 # shrinks to ref start_text = "ΣὙ®aAΣ 0 ኲ 0 𘠀ኸ𑊟0ਓ AAﬓ𐴰𑶠A🂱¡ \u{2de0}A𝟎ⴭ 𝕊", mut edits1 = [(25, 1, "¡Aa ")], mut edits2 = [(25, 1, ""), (25, 4, "")]
|
65
sub_crates/backend/tests/transaction.rs
Normal file
65
sub_crates/backend/tests/transaction.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
#[macro_use]
|
||||
extern crate proptest;
|
||||
|
||||
use proptest::collection::vec;
|
||||
use proptest::test_runner::Config;
|
||||
|
||||
use ropey::Rope;
|
||||
|
||||
use backend::transaction::Transaction;
|
||||
|
||||
fn make_edit_list_valid(edits: &mut Vec<(usize, usize, String)>, start_text: &str) {
|
||||
edits.sort_by_key(|e| e.0);
|
||||
let mut tail = 0;
|
||||
for e in edits.iter_mut() {
|
||||
e.0 = e.0.max(tail);
|
||||
while e.0 < start_text.len() && !start_text.is_char_boundary(e.0) {
|
||||
e.0 += 1;
|
||||
}
|
||||
while (e.0 + e.1) < start_text.len() && !start_text.is_char_boundary(e.0 + e.1) {
|
||||
e.1 += 1;
|
||||
}
|
||||
tail = e.0 + e.1;
|
||||
}
|
||||
for i in (0..edits.len()).rev() {
|
||||
if edits[i].0 > start_text.len() {
|
||||
edits.remove(i);
|
||||
} else if (edits[i].0 + edits[i].1) > start_text.len() {
|
||||
edits[i].1 = start_text.len() - edits[i].0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(Config::with_cases(512))]
|
||||
|
||||
#[test]
|
||||
fn compose(
|
||||
ref start_text in "\\PC{0, 200}",
|
||||
mut edits1 in vec((0usize..100, 0usize..10, "\\PC{0, 10}"), 0..5),
|
||||
mut edits2 in vec((0usize..100, 0usize..10, "\\PC{0, 10}"), 0..5),
|
||||
) {
|
||||
// Make edits1 valid and compute the post-edits1 string.
|
||||
make_edit_list_valid(&mut edits1, start_text);
|
||||
let trans1 = Transaction::from_ordered_edit_set(edits1.iter().map(|e| {
|
||||
(e.0, &start_text[e.0..(e.0+e.1)], &e.2[..])
|
||||
}));
|
||||
let mut r1 = Rope::from_str(start_text);
|
||||
trans1.apply(&mut r1);
|
||||
let text1: String = r1.clone().into();
|
||||
|
||||
// Make edits2 valid and compute the post-edits2 string.
|
||||
make_edit_list_valid(&mut edits2, &text1);
|
||||
let trans2 = Transaction::from_ordered_edit_set(edits2.iter().map(|e| {
|
||||
(e.0, &text1[e.0..(e.0+e.1)], &e.2[..])
|
||||
}));
|
||||
trans2.apply(&mut r1);
|
||||
|
||||
// Do the test!
|
||||
let trans3 = trans1.compose(&trans2);
|
||||
let mut r2 = Rope::from_str(start_text);
|
||||
trans3.apply(&mut r2);
|
||||
|
||||
assert_eq!(r1, r2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user