use crate::diff::Change;
use std::collections::HashMap;
pub fn compose(a: &[Change], b: &[Change]) -> Vec<Change> {
let mut all = Vec::with_capacity(a.len() + b.len());
all.extend_from_slice(a);
all.extend_from_slice(b);
compact(&all)
}
#[derive(PartialEq, Eq, Hash)]
enum FieldKey {
Text(Vec<usize>),
Marks(Vec<usize>),
Attr(Vec<usize>, String),
Extra(Vec<usize>, String),
}
fn field_key(change: &Change) -> Option<FieldKey> {
match change {
Change::SetText { path, .. } => Some(FieldKey::Text(path.clone())),
Change::SetMarks { path, .. } => Some(FieldKey::Marks(path.clone())),
Change::SetAttr { path, key, .. } | Change::RemoveAttr { path, key } => {
Some(FieldKey::Attr(path.clone(), key.clone()))
}
Change::SetExtra { path, key, .. } | Change::RemoveExtra { path, key } => {
Some(FieldKey::Extra(path.clone(), key.clone()))
}
_ => None,
}
}
pub fn compact(changes: &[Change]) -> Vec<Change> {
let mut out: Vec<Change> = Vec::with_capacity(changes.len());
let mut last: HashMap<FieldKey, usize> = HashMap::new();
for change in changes {
match field_key(change) {
Some(key) => {
if let Some(&idx) = last.get(&key) {
out[idx] = change.clone(); } else {
last.insert(key, out.len());
out.push(change.clone());
}
}
None => {
if let Change::Remove { path, index } = change {
if let Some(Change::Insert {
path: ip,
index: ii,
..
}) = out.last()
{
if ip == path && ii == index {
out.pop();
last.clear();
continue;
}
}
}
out.push(change.clone());
last.clear(); }
}
}
out
}
fn affected_level(q: &[usize], prefix: &[usize]) -> Option<usize> {
if prefix.len() < q.len() && q[..prefix.len()] == *prefix {
Some(prefix.len())
} else {
None
}
}
pub fn map_path(path: &[usize], changes: &[Change]) -> Option<Vec<usize>> {
let mut q = path.to_vec();
for change in changes {
match change {
Change::Insert { path: p, index, .. } => {
if let Some(d) = affected_level(&q, p) {
if *index <= q[d] {
q[d] += 1;
}
}
}
Change::Remove { path: p, index } => {
if let Some(d) = affected_level(&q, p) {
if *index < q[d] {
q[d] -= 1;
} else if *index == q[d] {
return None; }
}
}
Change::Move { path: p, from, to } if from != to => {
if let Some(d) = affected_level(&q, p) {
let k = q[d];
if *from == k {
q[d] = *to;
} else {
let after_remove = if *from < k { k - 1 } else { k };
q[d] = if *to <= after_remove {
after_remove + 1
} else {
after_remove
};
}
}
}
Change::Replace { path: p, .. } if p.len() <= q.len() && q[..p.len()] == *p => {
return None;
}
_ => {}
}
}
Some(q)
}
pub fn unmap_path(path: &[usize], changes: &[Change]) -> Option<Vec<usize>> {
unmap(path, changes, false)
}
fn unmap(path: &[usize], changes: &[Change], slot: bool) -> Option<Vec<usize>> {
unmap_text(path, changes, slot, None).map(|(p, _)| p)
}
type TextSpan = (usize, usize);
fn unmap_text(
path: &[usize],
changes: &[Change],
slot: bool,
text: Option<TextSpan>,
) -> Option<(Vec<usize>, Option<TextSpan>)> {
let mut q = path.to_vec();
let mut text = text;
let last = q.len().wrapping_sub(1);
for change in changes.iter().rev() {
match change {
Change::Insert { path: p, index, .. } => {
if let Some(d) = affected_level(&q, p) {
if *index == q[d] && !(slot && d == last) {
return None; } else if *index < q[d] {
q[d] -= 1;
}
}
}
Change::Remove { path: p, index } => {
if let Some(d) = affected_level(&q, p) {
if *index <= q[d] {
q[d] += 1;
}
}
}
Change::Move { path: p, from, to } if from != to => {
if let Some(d) = affected_level(&q, p) {
let k = q[d];
if *to == k && !(slot && d == last) {
q[d] = *from;
} else {
let after_remove = if *to < k { k - 1 } else { k };
q[d] = if *from <= after_remove {
after_remove + 1
} else {
after_remove
};
}
}
}
Change::Replace { path: p, .. } if p.len() < q.len() && q[..p.len()] == *p => {
return None;
}
Change::SpliceText {
path: p,
from: pf,
len_del: pdel,
insert,
} if text.is_some() && *p == q => {
let (from, len_del) = text.unwrap();
let ins = insert.chars().count();
if from >= pf + ins {
text = Some((from - ins + pdel, len_del));
} else if from + len_del > *pf {
return None; }
}
Change::SetText { path: p, .. } | Change::Replace { path: p, .. }
if text.is_some() && *p == q =>
{
return None;
}
_ => {}
}
}
Some((q, text))
}
pub fn rebase_change(change: &Change, prior: &[Change]) -> Option<Change> {
fn child(parent: &[usize], i: usize) -> Vec<usize> {
let mut p = parent.to_vec();
p.push(i);
p
}
fn split(mut p: Vec<usize>) -> (Vec<usize>, usize) {
let i = p.pop().unwrap_or(0);
(p, i)
}
let mut out = change.clone();
match &mut out {
Change::Insert { path, index, .. } => {
(*path, *index) = split(unmap(&child(path, *index), prior, true)?);
}
Change::Remove { path, index } => {
(*path, *index) = split(unmap(&child(path, *index), prior, false)?);
}
Change::Move { path, from, to } => {
let slot = if *to >= *from { *to + 1 } else { *to };
let (p, f) = split(unmap(&child(path, *from), prior, false)?);
let (_, s) = split(unmap(&child(path, slot), prior, true)?);
*to = if s > f { s - 1 } else { s };
*path = p;
*from = f;
}
Change::SetAttr { path, .. }
| Change::RemoveAttr { path, .. }
| Change::SetText { path, .. }
| Change::SetMarks { path, .. }
| Change::SetExtra { path, .. }
| Change::RemoveExtra { path, .. }
| Change::Replace { path, .. } => {
*path = unmap(path, prior, false)?;
}
Change::SpliceText {
path,
from,
len_del,
..
} => {
let (p, span) = unmap_text(path, prior, false, Some((*from, *len_del)))?;
*path = p;
(*from, *len_del) = span?;
}
}
Some(out)
}