use crate::node::{Mark, Node};
use crate::text_diff::{self, DiffGranularity, DiffOptions};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::{HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "camelCase", rename_all_fields = "camelCase")]
pub enum Change {
SetAttr {
path: Vec<usize>,
key: String,
value: Value,
},
RemoveAttr {
path: Vec<usize>,
key: String,
},
SetText {
path: Vec<usize>,
text: Option<String>,
},
SetMarks {
path: Vec<usize>,
marks: Option<Vec<Mark>>,
},
SetExtra {
path: Vec<usize>,
key: String,
value: Value,
},
RemoveExtra {
path: Vec<usize>,
key: String,
},
Insert {
path: Vec<usize>,
index: usize,
node: Node,
},
Remove {
path: Vec<usize>,
index: usize,
},
Replace {
path: Vec<usize>,
node: Node,
},
Move {
path: Vec<usize>,
from: usize,
to: usize,
},
SpliceText {
path: Vec<usize>,
from: usize,
len_del: usize,
insert: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApplyError {
pub path: Vec<usize>,
}
impl fmt::Display for ApplyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "apply: no node at path {:?}", self.path)
}
}
impl std::error::Error for ApplyError {}
impl Node {
pub fn diff(&self, other: &Node) -> Vec<Change> {
self.diff_with(other, &DiffOptions::default())
}
pub fn diff_with(&self, other: &Node, opts: &DiffOptions) -> Vec<Change> {
let mut out = Vec::new();
let mut path = Vec::new();
diff_node(self, other, &mut path, &mut out, opts);
out
}
pub fn apply(&mut self, changes: &[Change]) -> std::result::Result<(), ApplyError> {
apply(self, changes)
}
pub fn invert(&self, changes: &[Change]) -> std::result::Result<Vec<Change>, ApplyError> {
invert(self, changes)
}
}
pub(crate) fn diff_at(a: &Node, b: &Node, prefix: &[usize]) -> Vec<Change> {
let mut out = Vec::new();
let mut path = prefix.to_vec();
diff_node(a, b, &mut path, &mut out, &DiffOptions::default());
out
}
pub fn diff(a: &Node, b: &Node) -> Vec<Change> {
a.diff(b)
}
pub fn apply(root: &mut Node, changes: &[Change]) -> std::result::Result<(), ApplyError> {
for change in changes {
apply_one(root, change)?;
}
Ok(())
}
pub fn invert(base: &Node, changes: &[Change]) -> std::result::Result<Vec<Change>, ApplyError> {
let mut result = base.clone();
apply(&mut result, changes)?;
Ok(result.diff(base))
}
fn diff_node(a: &Node, b: &Node, path: &mut Vec<usize>, out: &mut Vec<Change>, opts: &DiffOptions) {
if a == b {
return; }
if a.node_type != b.node_type
|| empty_shape_mismatch(
a.attrs.as_ref().map(Map::is_empty),
b.attrs.as_ref().map(Map::is_empty),
)
|| empty_shape_mismatch(
a.content.as_ref().map(Vec::is_empty),
b.content.as_ref().map(Vec::is_empty),
)
{
out.push(Change::Replace {
path: path.clone(),
node: b.clone(),
});
return;
}
diff_attrs(a.attrs.as_ref(), b.attrs.as_ref(), path, out);
if a.text != b.text {
diff_text_field(a.text.as_deref(), b.text.as_deref(), path, out, opts);
}
if a.marks != b.marks {
out.push(Change::SetMarks {
path: path.clone(),
marks: b.marks.clone(),
});
}
diff_extra(&a.extra, &b.extra, path, out);
diff_children(a.children(), b.children(), path, out, opts);
}
fn diff_text_field(
a: Option<&str>,
b: Option<&str>,
path: &mut [usize],
out: &mut Vec<Change>,
opts: &DiffOptions,
) {
if let (Some(at), Some(bt)) = (a, b) {
match opts.text {
DiffGranularity::Inline => {
let segs = text_diff::diff_text(at, bt);
text_diff::splices_from_segments(path, &segs, out);
return;
}
DiffGranularity::Smart { replace_threshold } => {
let segs = text_diff::diff_text(at, bt);
let total = at.chars().count() + bt.chars().count();
let changed = text_diff::changed_scalars(&segs);
if total == 0 || (changed as f32 / total as f32) <= replace_threshold {
text_diff::splices_from_segments(path, &segs, out);
return;
}
}
DiffGranularity::Block => {}
}
}
out.push(Change::SetText {
path: path.to_vec(),
text: b.map(str::to_owned),
});
}
fn empty_shape_mismatch(a_is_empty: Option<bool>, b_is_empty: Option<bool>) -> bool {
match b_is_empty {
Some(true) => a_is_empty != Some(true),
None => a_is_empty == Some(true),
Some(false) => false,
}
}
fn diff_attrs(
a: Option<&Map<String, Value>>,
b: Option<&Map<String, Value>>,
path: &mut [usize],
out: &mut Vec<Change>,
) {
let empty = Map::new();
let am = a.unwrap_or(&empty);
let bm = b.unwrap_or(&empty);
for (k, v) in bm {
if am.get(k) != Some(v) {
out.push(Change::SetAttr {
path: path.to_vec(),
key: k.clone(),
value: v.clone(),
});
}
}
for k in am.keys() {
if !bm.contains_key(k) {
out.push(Change::RemoveAttr {
path: path.to_vec(),
key: k.clone(),
});
}
}
}
fn diff_extra(
am: &Map<String, Value>,
bm: &Map<String, Value>,
path: &mut [usize],
out: &mut Vec<Change>,
) {
for (k, v) in bm {
if am.get(k) != Some(v) {
out.push(Change::SetExtra {
path: path.to_vec(),
key: k.clone(),
value: v.clone(),
});
}
}
for k in am.keys() {
if !bm.contains_key(k) {
out.push(Change::RemoveExtra {
path: path.to_vec(),
key: k.clone(),
});
}
}
}
enum Step {
Match,
Del(usize),
Ins(usize),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Key {
Orig(usize),
New(usize),
}
enum Role {
Modify(usize),
Replace(usize),
}
fn diff_children(
a: &[Node],
b: &[Node],
path: &mut Vec<usize>,
out: &mut Vec<Change>,
opts: &DiffOptions,
) {
let mut start = 0;
while start < a.len() && start < b.len() && a[start] == b[start] {
start += 1;
}
let mut ea = a.len();
let mut eb = b.len();
while ea > start && eb > start && a[ea - 1] == b[eb - 1] {
ea -= 1;
eb -= 1;
}
let am = &a[start..ea];
let bm = &b[start..eb];
if am.is_empty() && bm.is_empty() {
return;
}
let (ida, idb) = intern(am, bm);
let steps = lcs_align(&ida, &idb);
let mut gaps: Vec<(Vec<usize>, Vec<usize>)> = Vec::new();
let mut cur_dels: Vec<usize> = Vec::new();
let mut cur_inss: Vec<usize> = Vec::new();
let mut bm_match_src: HashMap<usize, usize> = HashMap::new(); let (mut ai, mut bj) = (0usize, 0usize);
for step in &steps {
match step {
Step::Match => {
gaps.push((std::mem::take(&mut cur_dels), std::mem::take(&mut cur_inss)));
bm_match_src.insert(bj, ai);
ai += 1;
bj += 1;
}
Step::Del(i) => {
cur_dels.push(*i);
ai += 1;
}
Step::Ins(j) => {
cur_inss.push(*j);
bj += 1;
}
}
}
gaps.push((cur_dels, cur_inss));
let mut dels_by_id: HashMap<u32, std::collections::VecDeque<usize>> = HashMap::new();
for (dels, _) in &gaps {
for &d in dels {
dels_by_id.entry(ida[d]).or_default().push_back(d);
}
}
let mut bm_move_src: HashMap<usize, usize> = HashMap::new(); let mut am_moved: HashSet<usize> = HashSet::new();
for (_, inss) in &gaps {
for &j in inss {
if let Some(d) = dels_by_id.get_mut(&idb[j]).and_then(|q| q.pop_front()) {
bm_move_src.insert(j, d);
am_moved.insert(d);
}
}
}
let mut bm_role: HashMap<usize, Role> = HashMap::new();
let mut removed_am: Vec<usize> = Vec::new();
for (dels, inss) in &gaps {
let rem_dels: Vec<usize> = dels
.iter()
.copied()
.filter(|d| !am_moved.contains(d))
.collect();
let rem_inss: Vec<usize> = inss
.iter()
.copied()
.filter(|j| !bm_move_src.contains_key(j))
.collect();
let pairs = rem_dels.len().min(rem_inss.len());
for k in 0..pairs {
let (d, j) = (rem_dels[k], rem_inss[k]);
if am[d].node_type == bm[j].node_type {
bm_role.insert(j, Role::Modify(d)); } else {
bm_role.insert(j, Role::Replace(d)); }
}
removed_am.extend_from_slice(&rem_dels[pairs..]);
}
let n_orig = am.len() as u32;
let mut target: Vec<u32> = Vec::with_capacity(bm.len());
for j in 0..bm.len() {
let key = if let Some(&i) = bm_match_src.get(&j) {
Key::Orig(i)
} else if let Some(&d) = bm_move_src.get(&j) {
Key::Orig(d)
} else {
match bm_role.get(&j) {
Some(Role::Modify(d) | Role::Replace(d)) => Key::Orig(*d),
None => Key::New(j),
}
};
target.push(match key {
Key::Orig(i) => i as u32,
Key::New(j) => n_orig + j as u32,
});
}
let n_ids = am.len() + bm.len();
let mut target_pos = vec![u32::MAX; n_ids];
for (t, &k) in target.iter().enumerate() {
target_pos[k as usize] = t as u32;
}
let mut is_moved = vec![false; n_ids];
for &d in &am_moved {
is_moved[d] = true;
}
for (k, &d) in removed_am.iter().enumerate() {
out.push(Change::Remove {
path: path.clone(),
index: start + d - k,
});
}
let mut is_removed = vec![false; am.len()];
for &d in &removed_am {
is_removed[d] = true;
}
let post_remove = || (0..n_orig).filter(|&i| !is_removed[i as usize]);
let mark = out.len();
if !settle_sorted(
bm,
&target,
&target_pos,
&is_moved,
am_moved.len(),
post_remove(),
start,
path,
out,
) {
out.truncate(mark);
settle_cursor(bm, &target, &is_moved, post_remove(), start, path, out);
}
for (j, target_node) in bm.iter().enumerate() {
match bm_role.get(&j) {
Some(Role::Modify(d)) => {
path.push(start + j);
diff_node(&am[*d], target_node, path, out, opts);
path.pop();
}
Some(Role::Replace(_)) => {
let mut child = path.clone();
child.push(start + j);
out.push(Change::Replace {
path: child,
node: target_node.clone(),
});
}
None => {}
}
}
}
#[allow(clippy::too_many_arguments)]
fn settle_sorted(
bm: &[Node],
target: &[u32],
target_pos: &[u32],
is_moved: &[bool],
budget: usize,
live0: impl Iterator<Item = u32>,
start: usize,
path: &[usize],
out: &mut Vec<Change>,
) -> bool {
let n_orig = is_moved.len() - bm.len();
let mut live = LiveList::new(live0, is_moved.len());
let mut present = Fenwick::new(target.len());
for (t, &k) in target.iter().enumerate() {
if (k as usize) < n_orig {
present.add(t);
}
}
let mut moves = 0;
let mut p = 0;
while p < target.len() {
let want = target[p];
let cur = live.get(p);
if cur == Some(want) {
p += 1;
continue;
}
if want as usize >= n_orig {
out.push(Change::Insert {
path: path.to_vec(),
index: start + p,
node: bm[want as usize - n_orig].clone(),
});
live.insert(p, want);
present.add(p);
p += 1;
continue;
}
if moves == budget {
return false;
}
moves += 1;
let cur = cur.expect("original node missing from live list");
let mover = if is_moved[cur as usize] { cur } else { want };
let from = live.index_of(mover);
let dest = present.prefix(target_pos[mover as usize] as usize);
out.push(Change::Move {
path: path.to_vec(),
from: start + from,
to: start + dest,
});
let k = live.remove(from);
live.insert(dest, k);
}
true
}
fn settle_cursor(
bm: &[Node],
target: &[u32],
is_moved: &[bool],
live0: impl Iterator<Item = u32>,
start: usize,
path: &[usize],
out: &mut Vec<Change>,
) {
let n_orig = is_moved.len() - bm.len();
let mut live = LiveList::new(live0, is_moved.len());
let mut cur = 0;
for &want in target {
if want as usize >= n_orig {
out.push(Change::Insert {
path: path.to_vec(),
index: start + cur,
node: bm[want as usize - n_orig].clone(),
});
live.insert(cur, want);
cur += 1;
continue;
}
let at = live.index_of(want);
if !is_moved[want as usize] {
debug_assert!(at >= cur, "non-moved node behind the cursor");
cur = at + 1;
continue;
}
if at == cur {
cur += 1;
continue;
}
live.remove(at);
if at < cur {
cur -= 1;
}
out.push(Change::Move {
path: path.to_vec(),
from: start + at,
to: start + cur,
});
live.insert(cur, want);
cur += 1;
}
debug_assert_eq!(
live.to_vec(),
target,
"diff move simulation diverged from target"
);
}
struct LiveList {
blocks: Vec<Vec<u32>>,
block_of: Vec<u32>,
cap: usize,
}
impl LiveList {
fn new(keys: impl Iterator<Item = u32>, n_ids: usize) -> Self {
let mut l = LiveList {
blocks: vec![keys.collect()],
block_of: vec![0; n_ids],
cap: 16,
};
l.rebuild();
l
}
fn rebuild(&mut self) {
let all = self.to_vec();
self.cap = (all.len() as f64).sqrt() as usize + 16;
self.blocks = all.chunks(self.cap).map(<[u32]>::to_vec).collect();
if self.blocks.is_empty() {
self.blocks.push(Vec::new());
}
for (b, blk) in self.blocks.iter().enumerate() {
for &k in blk {
self.block_of[k as usize] = b as u32;
}
}
}
fn to_vec(&self) -> Vec<u32> {
self.blocks.concat()
}
fn locate(&self, mut idx: usize) -> (usize, usize) {
let last = self.blocks.len() - 1;
for (b, blk) in self.blocks.iter().enumerate() {
if idx < blk.len() || b == last {
return (b, idx);
}
idx -= blk.len();
}
unreachable!()
}
fn get(&self, idx: usize) -> Option<u32> {
let (b, o) = self.locate(idx);
self.blocks[b].get(o).copied()
}
fn index_of(&self, key: u32) -> usize {
let b = self.block_of[key as usize] as usize;
let before: usize = self.blocks[..b].iter().map(Vec::len).sum();
before
+ self.blocks[b]
.iter()
.position(|&k| k == key)
.expect("key in its block")
}
fn remove(&mut self, idx: usize) -> u32 {
let (b, o) = self.locate(idx);
self.blocks[b].remove(o)
}
fn insert(&mut self, idx: usize, key: u32) {
let (b, o) = self.locate(idx);
self.blocks[b].insert(o, key);
self.block_of[key as usize] = b as u32;
if self.blocks[b].len() > 2 * self.cap {
self.rebuild();
}
}
}
struct Fenwick(Vec<u32>);
impl Fenwick {
fn new(n: usize) -> Self {
Fenwick(vec![0; n])
}
fn add(&mut self, mut i: usize) {
while i < self.0.len() {
self.0[i] += 1;
i |= i + 1;
}
}
fn prefix(&self, mut i: usize) -> usize {
let mut s = 0;
while i > 0 {
s += self.0[i - 1] as usize;
i &= i - 1;
}
s
}
}
const DP_CELLS: usize = 1 << 16;
const MYERS_MAX_D: usize = 4096;
const DP_FALLBACK_CELLS: usize = 1 << 24;
fn lcs_align(a: &[u32], b: &[u32]) -> Vec<Step> {
let (m, n) = (a.len(), b.len());
if m == 0 {
return (0..n).map(Step::Ins).collect();
}
if n == 0 {
return (0..m).map(Step::Del).collect();
}
if m.saturating_mul(n) <= DP_CELLS {
return lcs_table(a, b);
}
if let Some(steps) = myers(a, b, MYERS_MAX_D) {
return steps;
}
if m.saturating_mul(n) <= DP_FALLBACK_CELLS {
return lcs_table(a, b);
}
(0..m).map(Step::Del).chain((0..n).map(Step::Ins)).collect()
}
fn lcs_table(a: &[u32], b: &[u32]) -> Vec<Step> {
let (m, n) = (a.len(), b.len());
let w = n + 1;
let mut dp = vec![0u32; (m + 1) * w];
for i in (0..m).rev() {
for j in (0..n).rev() {
dp[i * w + j] = if a[i] == b[j] {
dp[(i + 1) * w + j + 1] + 1
} else {
dp[(i + 1) * w + j].max(dp[i * w + j + 1])
};
}
}
let mut steps = Vec::with_capacity(m.max(n));
let (mut i, mut j) = (0usize, 0usize);
while i < m && j < n {
if a[i] == b[j] {
steps.push(Step::Match);
i += 1;
j += 1;
} else if dp[(i + 1) * w + j] >= dp[i * w + j + 1] {
steps.push(Step::Del(i));
i += 1;
} else {
steps.push(Step::Ins(j));
j += 1;
}
}
while i < m {
steps.push(Step::Del(i));
i += 1;
}
while j < n {
steps.push(Step::Ins(j));
j += 1;
}
steps
}
fn myers(a: &[u32], b: &[u32], max_d: usize) -> Option<Vec<Step>> {
if a.len().abs_diff(b.len()) > max_d {
return None;
}
let (n, m) = (a.len() as isize, b.len() as isize);
let max_d = max_d.min(a.len() + b.len()) as isize;
let off = max_d + 1;
let mut v = vec![0isize; (2 * off + 1) as usize];
let mut trace: Vec<Vec<isize>> = Vec::new();
let mut end_d = None;
'outer: for d in 0..=max_d {
trace.push(v[(off - d) as usize..=(off + d) as usize].to_vec());
let mut k = -d;
while k <= d {
let down = k == -d || (k != d && v[(off + k - 1) as usize] < v[(off + k + 1) as usize]);
let mut x = if down {
v[(off + k + 1) as usize]
} else {
v[(off + k - 1) as usize] + 1
};
let mut y = x - k;
while x < n && y < m && a[x as usize] == b[y as usize] {
x += 1;
y += 1;
}
v[(off + k) as usize] = x;
if x >= n && y >= m {
end_d = Some(d);
break 'outer;
}
k += 2;
}
}
end_d?;
let mut steps = Vec::with_capacity((n + m) as usize);
let (mut x, mut y) = (n, m);
for d in (0..trace.len() as isize).rev() {
if d == 0 {
while x > 0 && y > 0 {
steps.push(Step::Match);
x -= 1;
y -= 1;
}
break;
}
let vd = &trace[d as usize];
let get = |k: isize| vd[(k + d) as usize];
let k = x - y;
let prev_k = if k == -d || (k != d && get(k - 1) < get(k + 1)) {
k + 1
} else {
k - 1
};
let prev_x = get(prev_k);
let prev_y = prev_x - prev_k;
while x > prev_x && y > prev_y {
steps.push(Step::Match);
x -= 1;
y -= 1;
}
if x == prev_x {
steps.push(Step::Ins((y - 1) as usize));
} else {
steps.push(Step::Del((x - 1) as usize));
}
x = prev_x;
y = prev_y;
}
steps.reverse();
Some(steps)
}
fn intern(a: &[Node], b: &[Node]) -> (Vec<u32>, Vec<u32>) {
let mut buckets: HashMap<u64, Vec<(u32, &Node)>> = HashMap::with_capacity(a.len() + b.len());
let mut next = 0u32;
let mut out_a = Vec::with_capacity(a.len());
let mut out_b = Vec::with_capacity(b.len());
for (side, nodes) in [(0, a), (1, b)] {
for n in nodes {
let bucket = buckets.entry(node_hash(n)).or_default();
let found = bucket.iter().find(|(_, rep)| *rep == n).map(|(i, _)| *i);
let i = found.unwrap_or_else(|| {
let i = next;
next += 1;
bucket.push((i, n));
i
});
if side == 0 {
out_a.push(i);
} else {
out_b.push(i);
}
}
}
(out_a, out_b)
}
fn node_hash(n: &Node) -> u64 {
let mut h = Fx::default();
hash_node(n, &mut h);
h.0
}
#[derive(Default)]
struct Fx(u64);
impl Fx {
#[inline]
fn add(&mut self, x: u64) {
self.0 = (self.0.rotate_left(5) ^ x).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95);
}
#[inline]
fn bytes(&mut self, s: &[u8]) {
let (chunks, rest) = s.as_chunks::<8>();
for c in chunks {
self.add(u64::from_le_bytes(*c));
}
let mut tail = [0u8; 8];
tail[..rest.len()].copy_from_slice(rest);
self.add(u64::from_le_bytes(tail) ^ ((s.len() as u64) << 56));
}
#[inline]
fn opt_str(&mut self, s: Option<&str>) {
match s {
None => self.add(0x9e37),
Some(s) => self.bytes(s.as_bytes()),
}
}
}
fn hash_node(n: &Node, h: &mut Fx) {
h.opt_str(n.node_type.as_deref());
hash_opt_map(n.attrs.as_ref(), h);
match &n.content {
None => h.add(1),
Some(c) => {
h.add(2 ^ ((c.len() as u64) << 8));
for child in c {
hash_node(child, h);
}
}
}
match &n.marks {
None => h.add(3),
Some(ms) => {
h.add(4 ^ ((ms.len() as u64) << 8));
for m in ms {
h.bytes(m.mark_type.as_bytes());
hash_opt_map(m.attrs.as_ref(), h);
hash_map(&m.extra, h);
}
}
}
h.opt_str(n.text.as_deref());
hash_map(&n.extra, h);
}
fn hash_opt_map(m: Option<&Map<String, Value>>, h: &mut Fx) {
match m {
None => h.add(5),
Some(m) => hash_map(m, h),
}
}
fn hash_map(m: &Map<String, Value>, h: &mut Fx) {
let mut sum = 0u64;
for (k, v) in m {
let mut e = Fx::default();
e.bytes(k.as_bytes());
hash_value(v, &mut e);
sum = sum.wrapping_add(e.0);
}
h.add(6 ^ ((m.len() as u64) << 8));
h.add(sum);
}
fn hash_value(v: &Value, h: &mut Fx) {
match v {
Value::Null => h.add(10),
Value::Bool(b) => h.add(11 + *b as u64),
Value::Number(n) => {
if let Some(u) = n.as_u64() {
h.add(13);
h.add(u);
} else if let Some(i) = n.as_i64() {
h.add(14);
h.add(i as u64);
} else {
let f = n.as_f64().unwrap_or(0.0);
h.add(15);
h.add(if f == 0.0 { 0 } else { f.to_bits() });
}
}
Value::String(s) => {
h.add(16);
h.bytes(s.as_bytes());
}
Value::Array(a) => {
h.add(17 ^ ((a.len() as u64) << 8));
for x in a {
hash_value(x, h);
}
}
Value::Object(m) => {
h.add(18);
hash_map(m, h);
}
}
}
fn node_at_mut<'a>(
root: &'a mut Node,
path: &[usize],
) -> std::result::Result<&'a mut Node, ApplyError> {
root.node_at_mut(path).ok_or_else(|| ApplyError {
path: path.to_vec(),
})
}
fn apply_one(root: &mut Node, change: &Change) -> std::result::Result<(), ApplyError> {
match change {
Change::SetAttr { path, key, value } => {
node_at_mut(root, path)?.set_attr(key.clone(), value.clone());
}
Change::RemoveAttr { path, key } => {
node_at_mut(root, path)?.remove_attr(key);
}
Change::SetText { path, text } => {
node_at_mut(root, path)?.text = text.clone();
}
Change::SpliceText {
path,
from,
len_del,
insert,
} => {
let node = node_at_mut(root, path)?;
let (start, end) = {
let cur = node.text.as_deref().unwrap_or("");
let scalars = cur.chars().count();
let end_scalar = match from.checked_add(*len_del) {
Some(e) if *from <= scalars && e <= scalars => e,
_ => return Err(ApplyError { path: path.clone() }),
};
let start = cur.char_indices().nth(*from).map_or(cur.len(), |(b, _)| b);
let end = cur
.char_indices()
.nth(end_scalar)
.map_or(cur.len(), |(b, _)| b);
(start, end)
};
if start != end || !insert.is_empty() {
node.text
.get_or_insert_with(String::new)
.replace_range(start..end, insert);
}
}
Change::SetMarks { path, marks } => {
node_at_mut(root, path)?.marks = marks.clone();
}
Change::SetExtra { path, key, value } => {
node_at_mut(root, path)?
.extra
.insert(key.clone(), value.clone());
}
Change::RemoveExtra { path, key } => {
node_at_mut(root, path)?.extra.remove(key);
}
Change::Insert { path, index, node } => {
let parent = node_at_mut(root, path)?;
if *index > parent.child_count() {
let mut p = path.clone();
p.push(*index);
return Err(ApplyError { path: p });
}
parent.insert_child(*index, node.clone());
}
Change::Remove { path, index } => {
if node_at_mut(root, path)?.remove_child(*index).is_none() {
let mut p = path.clone();
p.push(*index);
return Err(ApplyError { path: p });
}
}
Change::Replace { path, node } => {
if path.is_empty() {
*root = node.clone();
} else {
let (parent_path, last) = path.split_at(path.len() - 1);
let parent = node_at_mut(root, parent_path)?;
if parent.replace_child(last[0], node.clone()).is_none() {
return Err(ApplyError { path: path.clone() });
}
}
}
Change::Move { path, from, to } => {
let parent = node_at_mut(root, path)?;
let len = parent.child_count();
if *from >= len || *to >= len {
let mut p = path.clone();
p.push(if *from >= len { *from } else { *to });
return Err(ApplyError { path: p });
}
let node = parent.remove_child(*from).expect("from bounds-checked");
parent.insert_child(*to, node);
}
}
Ok(())
}