Skip to main content

tiptap_rusty_parser/
transform.rs

1//! Transactions: mutate a [`Node`] tree while recording a replayable,
2//! invertible [`Change`] log in the same pass.
3//!
4//! A [`Transform`] borrows the tree mutably; each builder method both applies an
5//! edit in place (via the same engine as [`apply`](crate::apply)) **and** records
6//! the corresponding [`Change`]. Calling [`finish`](Transform::finish) returns
7//! the recorded list, which — applied to a clone of the *original* tree —
8//! reproduces the transformed tree, and whose [`invert`](crate::invert) is its
9//! undo. This unifies the mutation API with [`diff`](crate::diff): instead of
10//! editing and then diffing to recover a patch, you get the patch for free.
11//!
12//! The undo is recorded too: each change captures the pre-change state it
13//! overwrites (old attr/text/marks value, the removed or replaced subtree, …)
14//! as it applies, so [`finish_with_inverse`](Transform::finish_with_inverse)
15//! returns the inverse without cloning or re-diffing the tree. Cost is
16//! proportional to the edit, not the document.
17//!
18//! ```
19//! use tiptap_rusty_parser::{apply, Node};
20//!
21//! let mut doc = Node::element("doc").with_child(Node::element("paragraph"));
22//! let original = doc.clone();
23//!
24//! let changes = {
25//!     let mut tx = doc.transform();
26//!     tx.set_attr(vec![0], "level", 1).unwrap();
27//!     tx.insert(vec![], 1, Node::element("paragraph")).unwrap();
28//!     tx.finish()
29//! };
30//!
31//! // The recorded log reproduces `doc` from a clone of the original.
32//! let mut replay = original.clone();
33//! apply(&mut replay, &changes).unwrap();
34//! assert_eq!(replay, doc);
35//!
36//! // …and inverts to an undo that restores the original.
37//! let undo = original.invert(&changes).unwrap();
38//! let mut back = doc.clone();
39//! apply(&mut back, &undo).unwrap();
40//! assert_eq!(back, original);
41//!
42//! // Same undo, recorded incrementally (no clone of `original` needed).
43//! let mut doc2 = original.clone();
44//! let (_, undo2) = {
45//!     let mut tx = doc2.transform();
46//!     tx.set_attr(vec![0], "level", 1).unwrap();
47//!     tx.insert(vec![], 1, Node::element("paragraph")).unwrap();
48//!     tx.finish_with_inverse()
49//! };
50//! apply(&mut doc2, &undo2).unwrap();
51//! assert_eq!(doc2, original);
52//! ```
53
54use crate::block::{BlockError, BlockRange};
55use crate::diff::{apply, diff_at, ApplyError, Change};
56use crate::node::{Mark, Node};
57use crate::range::{Position, Range};
58use serde_json::{Map, Value};
59
60/// A mutation transaction over a [`Node`] tree: edits apply in place and are
61/// recorded as a [`Change`] log. Create with [`Node::transform`].
62///
63/// Each builder returns `Result<&mut Self, ApplyError>` so calls chain with `?`;
64/// an error (e.g. a path that doesn't resolve) leaves the tree as mutated by the
65/// changes recorded so far.
66pub struct Transform<'a> {
67    root: &'a mut Node,
68    changes: Vec<Change>,
69    /// Inverse of each recorded step, in application order (undo = reversed).
70    undo: Vec<Change>,
71}
72
73impl Node {
74    /// Begin a [`Transform`] over this tree.
75    pub fn transform(&mut self) -> Transform<'_> {
76        Transform {
77            root: self,
78            changes: Vec::new(),
79            undo: Vec::new(),
80        }
81    }
82
83    /// Apply `changes` in order, consuming them, and return their inverse
84    /// (applied to the result it restores `self`). The inverse is captured
85    /// while applying, from the state each change overwrites: no tree clone,
86    /// no diff, and moved-in nodes are not cloned. Atomic: on error the
87    /// already-applied prefix is rolled back and the tree is unchanged.
88    ///
89    /// ```
90    /// use tiptap_rusty_parser::{Change, Node};
91    /// let mut doc = Node::element("doc").with_child(Node::element("paragraph"));
92    /// let original = doc.clone();
93    /// let undo = doc
94    ///     .apply_inverting(vec![Change::Remove { path: vec![], index: 0 }])
95    ///     .unwrap();
96    /// assert_eq!(doc.child_count(), 0);
97    /// let redo = doc.apply_inverting(undo).unwrap();
98    /// assert_eq!(doc, original);
99    /// assert_eq!(redo, vec![Change::Remove { path: vec![], index: 0 }]);
100    /// ```
101    pub fn apply_inverting(&mut self, changes: Vec<Change>) -> Result<Vec<Change>, ApplyError> {
102        let mut undo = Vec::with_capacity(changes.len());
103        for change in changes {
104            match apply_inverting_one(self, change) {
105                Ok(inv) => undo.extend(inv),
106                Err(e) => {
107                    rollback(self, undo);
108                    return Err(e);
109                }
110            }
111        }
112        undo.reverse();
113        Ok(undo)
114    }
115}
116
117/// Undo `undo` (inverse steps in application order) on `root`, newest first.
118fn rollback(root: &mut Node, undo: Vec<Change>) {
119    for change in undo.into_iter().rev() {
120        // Inverses of applied steps always resolve.
121        let _ = apply_inverting_one(root, change);
122    }
123}
124
125fn at_mut<'n>(root: &'n mut Node, path: &[usize]) -> Result<&'n mut Node, ApplyError> {
126    root.node_at_mut(path).ok_or_else(|| ApplyError {
127        path: path.to_vec(),
128    })
129}
130
131fn child_err(path: &[usize], index: usize) -> ApplyError {
132    let mut p = path.to_vec();
133    p.push(index);
134    ApplyError { path: p }
135}
136
137/// Apply one change in place and return the change that undoes it (`None`
138/// for a no-op). Same semantics and errors as [`apply`]; on error the tree is
139/// untouched.
140///
141/// Container shapes round-trip exactly: where the inverse op would normalize a
142/// present-but-empty `attrs: {}` / `content: []` to absent, the pre-change node
143/// is restored with a [`Change::Replace`] instead (a clone of that one node,
144/// only in that rare shape).
145fn apply_inverting_one(root: &mut Node, change: Change) -> Result<Option<Change>, ApplyError> {
146    let empty_attrs = |n: &Node| matches!(&n.attrs, Some(a) if a.is_empty());
147    Ok(match change {
148        Change::SetAttr { path, key, value } => {
149            let node = at_mut(root, &path)?;
150            if empty_attrs(node) {
151                let before = node.clone();
152                node.set_attr(key, value);
153                Some(Change::Replace { path, node: before })
154            } else {
155                Some(match node.set_attr(key.clone(), value) {
156                    Some(prev) => Change::SetAttr {
157                        path,
158                        key,
159                        value: prev,
160                    },
161                    None => Change::RemoveAttr { path, key },
162                })
163            }
164        }
165        Change::RemoveAttr { path, key } => {
166            let node = at_mut(root, &path)?;
167            if empty_attrs(node) {
168                // `remove_attr` normalizes `{}` to absent.
169                let before = node.clone();
170                node.remove_attr(&key);
171                Some(Change::Replace { path, node: before })
172            } else {
173                node.remove_attr(&key)
174                    .map(|value| Change::SetAttr { path, key, value })
175            }
176        }
177        Change::SetText { path, text } => {
178            let node = at_mut(root, &path)?;
179            let prev = std::mem::replace(&mut node.text, text);
180            Some(Change::SetText { path, text: prev })
181        }
182        Change::SpliceText {
183            path,
184            from,
185            len_del,
186            insert,
187        } => {
188            let prev = at_mut(root, &path)?.text.clone();
189            let splice = Change::SpliceText {
190                path,
191                from,
192                len_del,
193                insert,
194            };
195            apply(root, std::slice::from_ref(&splice))?;
196            let Change::SpliceText { path, insert, .. } = splice else {
197                unreachable!()
198            };
199            match prev {
200                // A no-op splice on absent text keeps it absent.
201                None if root.node_at(&path).is_some_and(|n| n.text.is_none()) => None,
202                None => Some(Change::SetText { path, text: None }),
203                Some(t) => Some(Change::SpliceText {
204                    path,
205                    from,
206                    len_del: insert.chars().count(),
207                    insert: t.chars().skip(from).take(len_del).collect(),
208                }),
209            }
210        }
211        Change::SetMarks { path, marks } => {
212            let node = at_mut(root, &path)?;
213            let prev = std::mem::replace(&mut node.marks, marks);
214            Some(Change::SetMarks { path, marks: prev })
215        }
216        Change::SetExtra { path, key, value } => {
217            let node = at_mut(root, &path)?;
218            Some(match node.extra.insert(key.clone(), value) {
219                Some(prev) => Change::SetExtra {
220                    path,
221                    key,
222                    value: prev,
223                },
224                None => Change::RemoveExtra { path, key },
225            })
226        }
227        Change::RemoveExtra { path, key } => {
228            let node = at_mut(root, &path)?;
229            node.extra
230                .remove(&key)
231                .map(|value| Change::SetExtra { path, key, value })
232        }
233        Change::Insert { path, index, node } => {
234            let parent = at_mut(root, &path)?;
235            if index > parent.child_count() {
236                return Err(child_err(&path, index));
237            }
238            if matches!(&parent.content, Some(c) if c.is_empty()) {
239                // `Remove` would normalize `[]` to absent; the parent is
240                // childless, so this clone is shallow.
241                let before = parent.clone();
242                parent.insert_child(index, node);
243                Some(Change::Replace { path, node: before })
244            } else {
245                parent.insert_child(index, node);
246                Some(Change::Remove { path, index })
247            }
248        }
249        Change::Remove { path, index } => {
250            let parent = at_mut(root, &path)?;
251            let node = parent
252                .remove_child(index)
253                .ok_or_else(|| child_err(&path, index))?;
254            Some(Change::Insert { path, index, node })
255        }
256        Change::Replace { path, node } => {
257            let old = match path.split_last() {
258                None => std::mem::replace(root, node),
259                Some((&last, parent)) => at_mut(root, parent)?
260                    .replace_child(last, node)
261                    .ok_or_else(|| ApplyError { path: path.clone() })?,
262            };
263            Some(Change::Replace { path, node: old })
264        }
265        Change::Move { path, from, to } => {
266            let mv = Change::Move { path, from, to };
267            apply(root, std::slice::from_ref(&mv))?;
268            let Change::Move { path, .. } = mv else {
269                unreachable!()
270            };
271            Some(Change::Move {
272                path,
273                from: to,
274                to: from,
275            })
276        }
277    })
278}
279
280impl<'a> Transform<'a> {
281    /// Apply `change` in place, record it and its inverse.
282    fn push(&mut self, change: Change) -> Result<&mut Self, ApplyError> {
283        let inv = apply_inverting_one(self.root, change.clone())?;
284        self.changes.push(change);
285        self.undo.extend(inv);
286        Ok(self)
287    }
288
289    /// Set (insert or overwrite) attribute `key` on the node at `path`.
290    pub fn set_attr(
291        &mut self,
292        path: Vec<usize>,
293        key: impl Into<String>,
294        value: impl Into<Value>,
295    ) -> Result<&mut Self, ApplyError> {
296        self.push(Change::SetAttr {
297            path,
298            key: key.into(),
299            value: value.into(),
300        })
301    }
302
303    /// Remove attribute `key` from the node at `path`.
304    pub fn remove_attr(
305        &mut self,
306        path: Vec<usize>,
307        key: impl Into<String>,
308    ) -> Result<&mut Self, ApplyError> {
309        self.push(Change::RemoveAttr {
310            path,
311            key: key.into(),
312        })
313    }
314
315    /// Set the text payload of the node at `path` (`None` clears it).
316    pub fn set_text(
317        &mut self,
318        path: Vec<usize>,
319        text: Option<String>,
320    ) -> Result<&mut Self, ApplyError> {
321        self.push(Change::SetText { path, text })
322    }
323
324    /// Replace the whole mark list of the node at `path` (`None` clears it).
325    pub fn set_marks(
326        &mut self,
327        path: Vec<usize>,
328        marks: Option<Vec<Mark>>,
329    ) -> Result<&mut Self, ApplyError> {
330        self.push(Change::SetMarks { path, marks })
331    }
332
333    /// Set (insert or overwrite) unknown top-level field `key` on the node at `path`.
334    pub fn set_extra(
335        &mut self,
336        path: Vec<usize>,
337        key: impl Into<String>,
338        value: impl Into<Value>,
339    ) -> Result<&mut Self, ApplyError> {
340        self.push(Change::SetExtra {
341            path,
342            key: key.into(),
343            value: value.into(),
344        })
345    }
346
347    /// Remove unknown top-level field `key` from the node at `path`.
348    pub fn remove_extra(
349        &mut self,
350        path: Vec<usize>,
351        key: impl Into<String>,
352    ) -> Result<&mut Self, ApplyError> {
353        self.push(Change::RemoveExtra {
354            path,
355            key: key.into(),
356        })
357    }
358
359    /// Insert `node` as a child of the node at `path` (the parent), at `index`.
360    pub fn insert(
361        &mut self,
362        path: Vec<usize>,
363        index: usize,
364        node: Node,
365    ) -> Result<&mut Self, ApplyError> {
366        self.push(Change::Insert { path, index, node })
367    }
368
369    /// Remove the child at `index` of the node at `path` (the parent).
370    pub fn remove(&mut self, path: Vec<usize>, index: usize) -> Result<&mut Self, ApplyError> {
371        self.push(Change::Remove { path, index })
372    }
373
374    /// Replace the node at `path` wholesale.
375    pub fn replace(&mut self, path: Vec<usize>, node: Node) -> Result<&mut Self, ApplyError> {
376        self.push(Change::Replace { path, node })
377    }
378
379    /// Relocate the child at `from` to `to` within the parent at `path`, without
380    /// cloning its subtree. See [`Change::Move`].
381    pub fn move_child(
382        &mut self,
383        path: Vec<usize>,
384        from: usize,
385        to: usize,
386    ) -> Result<&mut Self, ApplyError> {
387        self.push(Change::Move { path, from, to })
388    }
389
390    /// Apply an already-built [`Change`] (e.g. one picked from a
391    /// [`diff`](crate::diff) list) in place and record it. Lets callers
392    /// cherry-pick changes from a diff into an invertible transaction.
393    pub fn apply_change(&mut self, change: Change) -> Result<&mut Self, ApplyError> {
394        self.push(change)
395    }
396
397    /// The changes recorded so far, without consuming the transaction.
398    pub fn changes(&self) -> &[Change] {
399        &self.changes
400    }
401
402    /// Finish the transaction, returning the recorded [`Change`] log.
403    pub fn finish(self) -> Vec<Change> {
404        self.changes
405    }
406
407    /// The undo of everything recorded so far: applied to the current tree it
408    /// restores the tree as it was when the transaction began. Equivalent to
409    /// [`invert`](crate::invert) against the original, without needing it.
410    pub fn inverse(&self) -> Vec<Change> {
411        self.undo.iter().rev().cloned().collect()
412    }
413
414    /// Finish the transaction, returning `(changes, inverse)`. See
415    /// [`inverse`](Transform::inverse).
416    pub fn finish_with_inverse(self) -> (Vec<Change>, Vec<Change>) {
417        let mut undo = self.undo;
418        undo.reverse();
419        (self.changes, undo)
420    }
421
422    /// Abandon the transaction: undo every recorded change, restoring the
423    /// tree as it was when the transaction began (e.g. after a builder error).
424    pub fn rollback(self) {
425        rollback(self.root, self.undo);
426    }
427}
428
429/// Block-structural builders. Unlike the field/child ops above (which map to a
430/// single [`Change`]), these restructure the tree and are recorded by running
431/// the in-place edit and recovering the patch via [`diff`](crate::diff). Only
432/// the smallest subtree the op can touch (its *scope*: the block, its parent or
433/// grandparent) is cloned and diffed, so the cost is proportional to that
434/// subtree, not the document; the direct [`Node`] methods stay clone-free.
435/// The inverse is a [`Change::Replace`] of the scope with its pre-op clone.
436impl Transform<'_> {
437    /// Run `f` on the root, recording the patch of the subtree at `scope`
438    /// (which must contain every node `f` changes and keep its own path).
439    /// On error the scope is restored, so a failed op leaves no trace.
440    fn record_structural<F>(&mut self, scope: &[usize], f: F) -> Result<&mut Self, BlockError>
441    where
442        F: FnOnce(&mut Node) -> Result<(), BlockError>,
443    {
444        // An unresolvable scope means `f` will fail its own path check; fall
445        // back to the root so the error comes from `f`.
446        let scope = if self.root.node_at(scope).is_some() {
447            scope
448        } else {
449            &[]
450        };
451        let before = self.root.node_at(scope).cloned().unwrap_or_default();
452        if let Err(e) = f(self.root) {
453            if let Some(n) = self.root.node_at_mut(scope) {
454                *n = before;
455            }
456            return Err(e);
457        }
458        let after = self
459            .root
460            .node_at(scope)
461            .ok_or_else(|| BlockError::PathNotFound {
462                path: scope.to_vec(),
463            })?;
464        let patch = diff_at(&before, after, scope);
465        if !patch.is_empty() {
466            self.changes.extend(patch);
467            self.undo.push(Change::Replace {
468                path: scope.to_vec(),
469                node: before,
470            });
471        }
472        Ok(self)
473    }
474
475    /// Record [`Node::set_block_type`].
476    pub fn set_block_type(
477        &mut self,
478        path: Vec<usize>,
479        new_type: impl Into<String>,
480        attrs: Option<Map<String, Value>>,
481    ) -> Result<&mut Self, BlockError> {
482        self.record_structural(&path.clone(), |root| {
483            root.set_block_type(&path, new_type, attrs)
484        })
485    }
486
487    /// Record [`Node::split_block`].
488    pub fn split_block(
489        &mut self,
490        path: Vec<usize>,
491        at: usize,
492        depth: usize,
493    ) -> Result<&mut Self, BlockError> {
494        let scope = split_scope(&path, depth).to_vec();
495        self.record_structural(&scope, |root| root.split_block(&path, at, depth))
496    }
497
498    /// Record [`Node::split_block_at`].
499    pub fn split_block_at(
500        &mut self,
501        path: Vec<usize>,
502        pos: Position,
503        depth: usize,
504    ) -> Result<&mut Self, BlockError> {
505        let scope = split_scope(&path, depth).to_vec();
506        self.record_structural(&scope, |root| root.split_block_at(&path, pos, depth))
507    }
508
509    /// Record [`Node::join_blocks`].
510    pub fn join_blocks(
511        &mut self,
512        parent: Vec<usize>,
513        index: usize,
514    ) -> Result<&mut Self, BlockError> {
515        self.record_structural(&parent.clone(), |root| root.join_blocks(&parent, index))
516    }
517
518    /// Record [`Node::wrap`].
519    pub fn wrap(
520        &mut self,
521        path: Vec<usize>,
522        wrapper_type: impl Into<String>,
523        attrs: Option<Map<String, Value>>,
524    ) -> Result<&mut Self, BlockError> {
525        // The wrapper takes the block's place: same path, same parent count.
526        self.record_structural(&path.clone(), |root| root.wrap(&path, wrapper_type, attrs))
527    }
528
529    /// Record [`Node::wrap_range`].
530    pub fn wrap_range(
531        &mut self,
532        range: BlockRange,
533        wrapper_type: impl Into<String>,
534        attrs: Option<Map<String, Value>>,
535    ) -> Result<&mut Self, BlockError> {
536        self.record_structural(&range.parent.clone(), |root| {
537            root.wrap_range(&range, wrapper_type, attrs)
538        })
539    }
540
541    /// Record [`Node::lift`].
542    pub fn lift(&mut self, path: Vec<usize>) -> Result<&mut Self, BlockError> {
543        // Splits the parent inside the grandparent.
544        let scope = path[..path.len().saturating_sub(2)].to_vec();
545        self.record_structural(&scope, |root| root.lift(&path))
546    }
547}
548
549/// Host of the new sibling [`Node::split_block`] inserts: the parent of the
550/// topmost split block (`depth` ancestors up, clamped below the root).
551fn split_scope(path: &[usize], depth: usize) -> &[usize] {
552    let k = depth.min(path.len().saturating_sub(1));
553    &path[..path.len().saturating_sub(1 + k)]
554}
555
556/// Inline range builders: edit the inline content of the block at `block_path`
557/// (recorded, like the block builders, by running the edit and recovering the
558/// patch via [`diff`](crate::diff)). They wrap the [`range`](crate::range) API
559/// so range edits join the recorded/invertible transaction.
560impl Transform<'_> {
561    fn at_block<F>(&mut self, block_path: Vec<usize>, f: F) -> Result<&mut Self, BlockError>
562    where
563        F: FnOnce(&mut Node) -> Result<(), BlockError>,
564    {
565        self.record_structural(&block_path.clone(), move |root| {
566            let block = root
567                .node_at_mut(&block_path)
568                .ok_or_else(|| BlockError::PathNotFound {
569                    path: block_path.clone(),
570                })?;
571            f(block)
572        })
573    }
574
575    /// Record [`Node::insert_text`] at `pos` in the block at `block_path`.
576    pub fn insert_text_at(
577        &mut self,
578        block_path: Vec<usize>,
579        pos: Position,
580        text: impl Into<String>,
581        marks: Option<Vec<Mark>>,
582    ) -> Result<&mut Self, BlockError> {
583        let text = text.into();
584        self.at_block(block_path, move |block| {
585            block.insert_text(pos, &text, marks.as_deref())?;
586            Ok(())
587        })
588    }
589
590    /// Record [`Node::delete_range`] over `range` in the block at `block_path`.
591    pub fn delete_range_in(
592        &mut self,
593        block_path: Vec<usize>,
594        range: Range,
595    ) -> Result<&mut Self, BlockError> {
596        self.at_block(block_path, move |block| {
597            block.delete_range(range)?;
598            Ok(())
599        })
600    }
601
602    /// Record [`Node::replace_range`] over `range` in the block at `block_path`.
603    pub fn replace_range_in(
604        &mut self,
605        block_path: Vec<usize>,
606        range: Range,
607        text: impl Into<String>,
608        marks: Option<Vec<Mark>>,
609    ) -> Result<&mut Self, BlockError> {
610        let text = text.into();
611        self.at_block(block_path, move |block| {
612            block.replace_range(range, &text, marks.as_deref())?;
613            Ok(())
614        })
615    }
616
617    /// Record [`Node::add_mark_range`] over `range` in the block at `block_path`.
618    pub fn add_mark_range_in(
619        &mut self,
620        block_path: Vec<usize>,
621        range: Range,
622        mark: Mark,
623    ) -> Result<&mut Self, BlockError> {
624        self.at_block(block_path, move |block| {
625            block.add_mark_range(range, mark)?;
626            Ok(())
627        })
628    }
629
630    /// Record [`Node::remove_mark_range`] over `range` in the block at `block_path`.
631    pub fn remove_mark_range_in(
632        &mut self,
633        block_path: Vec<usize>,
634        range: Range,
635        mark_type: impl Into<String>,
636    ) -> Result<&mut Self, BlockError> {
637        let mark_type = mark_type.into();
638        self.at_block(block_path, move |block| {
639            block.remove_mark_range(range, &mark_type)?;
640            Ok(())
641        })
642    }
643}