use crate::block::{BlockError, BlockRange};
use crate::diff::{apply, diff_at, ApplyError, Change};
use crate::node::{Mark, Node};
use crate::range::{Position, Range};
use serde_json::{Map, Value};
pub struct Transform<'a> {
root: &'a mut Node,
changes: Vec<Change>,
undo: Vec<Change>,
}
impl Node {
pub fn transform(&mut self) -> Transform<'_> {
Transform {
root: self,
changes: Vec::new(),
undo: Vec::new(),
}
}
pub fn apply_inverting(&mut self, changes: Vec<Change>) -> Result<Vec<Change>, ApplyError> {
let mut undo = Vec::with_capacity(changes.len());
for change in changes {
match apply_inverting_one(self, change) {
Ok(inv) => undo.extend(inv),
Err(e) => {
rollback(self, undo);
return Err(e);
}
}
}
undo.reverse();
Ok(undo)
}
}
fn rollback(root: &mut Node, undo: Vec<Change>) {
for change in undo.into_iter().rev() {
let _ = apply_inverting_one(root, change);
}
}
fn at_mut<'n>(root: &'n mut Node, path: &[usize]) -> Result<&'n mut Node, ApplyError> {
root.node_at_mut(path).ok_or_else(|| ApplyError {
path: path.to_vec(),
})
}
fn child_err(path: &[usize], index: usize) -> ApplyError {
let mut p = path.to_vec();
p.push(index);
ApplyError { path: p }
}
fn apply_inverting_one(root: &mut Node, change: Change) -> Result<Option<Change>, ApplyError> {
let empty_attrs = |n: &Node| matches!(&n.attrs, Some(a) if a.is_empty());
Ok(match change {
Change::SetAttr { path, key, value } => {
let node = at_mut(root, &path)?;
if empty_attrs(node) {
let before = node.clone();
node.set_attr(key, value);
Some(Change::Replace { path, node: before })
} else {
Some(match node.set_attr(key.clone(), value) {
Some(prev) => Change::SetAttr {
path,
key,
value: prev,
},
None => Change::RemoveAttr { path, key },
})
}
}
Change::RemoveAttr { path, key } => {
let node = at_mut(root, &path)?;
if empty_attrs(node) {
let before = node.clone();
node.remove_attr(&key);
Some(Change::Replace { path, node: before })
} else {
node.remove_attr(&key)
.map(|value| Change::SetAttr { path, key, value })
}
}
Change::SetText { path, text } => {
let node = at_mut(root, &path)?;
let prev = std::mem::replace(&mut node.text, text);
Some(Change::SetText { path, text: prev })
}
Change::SpliceText {
path,
from,
len_del,
insert,
} => {
let prev = at_mut(root, &path)?.text.clone();
let splice = Change::SpliceText {
path,
from,
len_del,
insert,
};
apply(root, std::slice::from_ref(&splice))?;
let Change::SpliceText { path, insert, .. } = splice else {
unreachable!()
};
match prev {
None if root.node_at(&path).is_some_and(|n| n.text.is_none()) => None,
None => Some(Change::SetText { path, text: None }),
Some(t) => Some(Change::SpliceText {
path,
from,
len_del: insert.chars().count(),
insert: t.chars().skip(from).take(len_del).collect(),
}),
}
}
Change::SetMarks { path, marks } => {
let node = at_mut(root, &path)?;
let prev = std::mem::replace(&mut node.marks, marks);
Some(Change::SetMarks { path, marks: prev })
}
Change::SetExtra { path, key, value } => {
let node = at_mut(root, &path)?;
Some(match node.extra.insert(key.clone(), value) {
Some(prev) => Change::SetExtra {
path,
key,
value: prev,
},
None => Change::RemoveExtra { path, key },
})
}
Change::RemoveExtra { path, key } => {
let node = at_mut(root, &path)?;
node.extra
.remove(&key)
.map(|value| Change::SetExtra { path, key, value })
}
Change::Insert { path, index, node } => {
let parent = at_mut(root, &path)?;
if index > parent.child_count() {
return Err(child_err(&path, index));
}
if matches!(&parent.content, Some(c) if c.is_empty()) {
let before = parent.clone();
parent.insert_child(index, node);
Some(Change::Replace { path, node: before })
} else {
parent.insert_child(index, node);
Some(Change::Remove { path, index })
}
}
Change::Remove { path, index } => {
let parent = at_mut(root, &path)?;
let node = parent
.remove_child(index)
.ok_or_else(|| child_err(&path, index))?;
Some(Change::Insert { path, index, node })
}
Change::Replace { path, node } => {
let old = match path.split_last() {
None => std::mem::replace(root, node),
Some((&last, parent)) => at_mut(root, parent)?
.replace_child(last, node)
.ok_or_else(|| ApplyError { path: path.clone() })?,
};
Some(Change::Replace { path, node: old })
}
Change::Move { path, from, to } => {
let mv = Change::Move { path, from, to };
apply(root, std::slice::from_ref(&mv))?;
let Change::Move { path, .. } = mv else {
unreachable!()
};
Some(Change::Move {
path,
from: to,
to: from,
})
}
})
}
impl<'a> Transform<'a> {
fn push(&mut self, change: Change) -> Result<&mut Self, ApplyError> {
let inv = apply_inverting_one(self.root, change.clone())?;
self.changes.push(change);
self.undo.extend(inv);
Ok(self)
}
pub fn set_attr(
&mut self,
path: Vec<usize>,
key: impl Into<String>,
value: impl Into<Value>,
) -> Result<&mut Self, ApplyError> {
self.push(Change::SetAttr {
path,
key: key.into(),
value: value.into(),
})
}
pub fn remove_attr(
&mut self,
path: Vec<usize>,
key: impl Into<String>,
) -> Result<&mut Self, ApplyError> {
self.push(Change::RemoveAttr {
path,
key: key.into(),
})
}
pub fn set_text(
&mut self,
path: Vec<usize>,
text: Option<String>,
) -> Result<&mut Self, ApplyError> {
self.push(Change::SetText { path, text })
}
pub fn set_marks(
&mut self,
path: Vec<usize>,
marks: Option<Vec<Mark>>,
) -> Result<&mut Self, ApplyError> {
self.push(Change::SetMarks { path, marks })
}
pub fn set_extra(
&mut self,
path: Vec<usize>,
key: impl Into<String>,
value: impl Into<Value>,
) -> Result<&mut Self, ApplyError> {
self.push(Change::SetExtra {
path,
key: key.into(),
value: value.into(),
})
}
pub fn remove_extra(
&mut self,
path: Vec<usize>,
key: impl Into<String>,
) -> Result<&mut Self, ApplyError> {
self.push(Change::RemoveExtra {
path,
key: key.into(),
})
}
pub fn insert(
&mut self,
path: Vec<usize>,
index: usize,
node: Node,
) -> Result<&mut Self, ApplyError> {
self.push(Change::Insert { path, index, node })
}
pub fn remove(&mut self, path: Vec<usize>, index: usize) -> Result<&mut Self, ApplyError> {
self.push(Change::Remove { path, index })
}
pub fn replace(&mut self, path: Vec<usize>, node: Node) -> Result<&mut Self, ApplyError> {
self.push(Change::Replace { path, node })
}
pub fn move_child(
&mut self,
path: Vec<usize>,
from: usize,
to: usize,
) -> Result<&mut Self, ApplyError> {
self.push(Change::Move { path, from, to })
}
pub fn apply_change(&mut self, change: Change) -> Result<&mut Self, ApplyError> {
self.push(change)
}
pub fn changes(&self) -> &[Change] {
&self.changes
}
pub fn finish(self) -> Vec<Change> {
self.changes
}
pub fn inverse(&self) -> Vec<Change> {
self.undo.iter().rev().cloned().collect()
}
pub fn finish_with_inverse(self) -> (Vec<Change>, Vec<Change>) {
let mut undo = self.undo;
undo.reverse();
(self.changes, undo)
}
pub fn rollback(self) {
rollback(self.root, self.undo);
}
}
impl Transform<'_> {
fn record_structural<F>(&mut self, scope: &[usize], f: F) -> Result<&mut Self, BlockError>
where
F: FnOnce(&mut Node) -> Result<(), BlockError>,
{
let scope = if self.root.node_at(scope).is_some() {
scope
} else {
&[]
};
let before = self.root.node_at(scope).cloned().unwrap_or_default();
if let Err(e) = f(self.root) {
if let Some(n) = self.root.node_at_mut(scope) {
*n = before;
}
return Err(e);
}
let after = self
.root
.node_at(scope)
.ok_or_else(|| BlockError::PathNotFound {
path: scope.to_vec(),
})?;
let patch = diff_at(&before, after, scope);
if !patch.is_empty() {
self.changes.extend(patch);
self.undo.push(Change::Replace {
path: scope.to_vec(),
node: before,
});
}
Ok(self)
}
pub fn set_block_type(
&mut self,
path: Vec<usize>,
new_type: impl Into<String>,
attrs: Option<Map<String, Value>>,
) -> Result<&mut Self, BlockError> {
self.record_structural(&path.clone(), |root| {
root.set_block_type(&path, new_type, attrs)
})
}
pub fn split_block(
&mut self,
path: Vec<usize>,
at: usize,
depth: usize,
) -> Result<&mut Self, BlockError> {
let scope = split_scope(&path, depth).to_vec();
self.record_structural(&scope, |root| root.split_block(&path, at, depth))
}
pub fn split_block_at(
&mut self,
path: Vec<usize>,
pos: Position,
depth: usize,
) -> Result<&mut Self, BlockError> {
let scope = split_scope(&path, depth).to_vec();
self.record_structural(&scope, |root| root.split_block_at(&path, pos, depth))
}
pub fn join_blocks(
&mut self,
parent: Vec<usize>,
index: usize,
) -> Result<&mut Self, BlockError> {
self.record_structural(&parent.clone(), |root| root.join_blocks(&parent, index))
}
pub fn wrap(
&mut self,
path: Vec<usize>,
wrapper_type: impl Into<String>,
attrs: Option<Map<String, Value>>,
) -> Result<&mut Self, BlockError> {
self.record_structural(&path.clone(), |root| root.wrap(&path, wrapper_type, attrs))
}
pub fn wrap_range(
&mut self,
range: BlockRange,
wrapper_type: impl Into<String>,
attrs: Option<Map<String, Value>>,
) -> Result<&mut Self, BlockError> {
self.record_structural(&range.parent.clone(), |root| {
root.wrap_range(&range, wrapper_type, attrs)
})
}
pub fn lift(&mut self, path: Vec<usize>) -> Result<&mut Self, BlockError> {
let scope = path[..path.len().saturating_sub(2)].to_vec();
self.record_structural(&scope, |root| root.lift(&path))
}
}
fn split_scope(path: &[usize], depth: usize) -> &[usize] {
let k = depth.min(path.len().saturating_sub(1));
&path[..path.len().saturating_sub(1 + k)]
}
impl Transform<'_> {
fn at_block<F>(&mut self, block_path: Vec<usize>, f: F) -> Result<&mut Self, BlockError>
where
F: FnOnce(&mut Node) -> Result<(), BlockError>,
{
self.record_structural(&block_path.clone(), move |root| {
let block = root
.node_at_mut(&block_path)
.ok_or_else(|| BlockError::PathNotFound {
path: block_path.clone(),
})?;
f(block)
})
}
pub fn insert_text_at(
&mut self,
block_path: Vec<usize>,
pos: Position,
text: impl Into<String>,
marks: Option<Vec<Mark>>,
) -> Result<&mut Self, BlockError> {
let text = text.into();
self.at_block(block_path, move |block| {
block.insert_text(pos, &text, marks.as_deref())?;
Ok(())
})
}
pub fn delete_range_in(
&mut self,
block_path: Vec<usize>,
range: Range,
) -> Result<&mut Self, BlockError> {
self.at_block(block_path, move |block| {
block.delete_range(range)?;
Ok(())
})
}
pub fn replace_range_in(
&mut self,
block_path: Vec<usize>,
range: Range,
text: impl Into<String>,
marks: Option<Vec<Mark>>,
) -> Result<&mut Self, BlockError> {
let text = text.into();
self.at_block(block_path, move |block| {
block.replace_range(range, &text, marks.as_deref())?;
Ok(())
})
}
pub fn add_mark_range_in(
&mut self,
block_path: Vec<usize>,
range: Range,
mark: Mark,
) -> Result<&mut Self, BlockError> {
self.at_block(block_path, move |block| {
block.add_mark_range(range, mark)?;
Ok(())
})
}
pub fn remove_mark_range_in(
&mut self,
block_path: Vec<usize>,
range: Range,
mark_type: impl Into<String>,
) -> Result<&mut Self, BlockError> {
let mark_type = mark_type.into();
self.at_block(block_path, move |block| {
block.remove_mark_range(range, &mark_type)?;
Ok(())
})
}
}