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//! ```
13//! use tiptap_rusty_parser::{apply, Node};
14//!
15//! let mut doc = Node::element("doc").with_child(Node::element("paragraph"));
16//! let original = doc.clone();
17//!
18//! let changes = {
19//! let mut tx = doc.transform();
20//! tx.set_attr(vec![0], "level", 1).unwrap();
21//! tx.insert(vec![], 1, Node::element("paragraph")).unwrap();
22//! tx.finish()
23//! };
24//!
25//! // The recorded log reproduces `doc` from a clone of the original.
26//! let mut replay = original.clone();
27//! apply(&mut replay, &changes).unwrap();
28//! assert_eq!(replay, doc);
29//!
30//! // …and inverts to an undo that restores the original.
31//! let undo = original.invert(&changes).unwrap();
32//! let mut back = doc.clone();
33//! apply(&mut back, &undo).unwrap();
34//! assert_eq!(back, original);
35//! ```
36
37use crate::diff::{apply, ApplyError, Change};
38use crate::node::{Mark, Node};
39use serde_json::Value;
40
41/// A mutation transaction over a [`Node`] tree: edits apply in place and are
42/// recorded as a [`Change`] log. Create with [`Node::transform`].
43///
44/// Each builder returns `Result<&mut Self, ApplyError>` so calls chain with `?`;
45/// an error (e.g. a path that doesn't resolve) leaves the tree as mutated by the
46/// changes recorded so far.
47pub struct Transform<'a> {
48 root: &'a mut Node,
49 changes: Vec<Change>,
50}
51
52impl Node {
53 /// Begin a [`Transform`] over this tree.
54 pub fn transform(&mut self) -> Transform<'_> {
55 Transform {
56 root: self,
57 changes: Vec::new(),
58 }
59 }
60}
61
62impl<'a> Transform<'a> {
63 /// Apply `change` in place and record it.
64 fn push(&mut self, change: Change) -> Result<&mut Self, ApplyError> {
65 apply(self.root, std::slice::from_ref(&change))?;
66 self.changes.push(change);
67 Ok(self)
68 }
69
70 /// Set (insert or overwrite) attribute `key` on the node at `path`.
71 pub fn set_attr(
72 &mut self,
73 path: Vec<usize>,
74 key: impl Into<String>,
75 value: impl Into<Value>,
76 ) -> Result<&mut Self, ApplyError> {
77 self.push(Change::SetAttr {
78 path,
79 key: key.into(),
80 value: value.into(),
81 })
82 }
83
84 /// Remove attribute `key` from the node at `path`.
85 pub fn remove_attr(
86 &mut self,
87 path: Vec<usize>,
88 key: impl Into<String>,
89 ) -> Result<&mut Self, ApplyError> {
90 self.push(Change::RemoveAttr {
91 path,
92 key: key.into(),
93 })
94 }
95
96 /// Set the text payload of the node at `path` (`None` clears it).
97 pub fn set_text(
98 &mut self,
99 path: Vec<usize>,
100 text: Option<String>,
101 ) -> Result<&mut Self, ApplyError> {
102 self.push(Change::SetText { path, text })
103 }
104
105 /// Replace the whole mark list of the node at `path` (`None` clears it).
106 pub fn set_marks(
107 &mut self,
108 path: Vec<usize>,
109 marks: Option<Vec<Mark>>,
110 ) -> Result<&mut Self, ApplyError> {
111 self.push(Change::SetMarks { path, marks })
112 }
113
114 /// Set (insert or overwrite) unknown top-level field `key` on the node at `path`.
115 pub fn set_extra(
116 &mut self,
117 path: Vec<usize>,
118 key: impl Into<String>,
119 value: impl Into<Value>,
120 ) -> Result<&mut Self, ApplyError> {
121 self.push(Change::SetExtra {
122 path,
123 key: key.into(),
124 value: value.into(),
125 })
126 }
127
128 /// Remove unknown top-level field `key` from the node at `path`.
129 pub fn remove_extra(
130 &mut self,
131 path: Vec<usize>,
132 key: impl Into<String>,
133 ) -> Result<&mut Self, ApplyError> {
134 self.push(Change::RemoveExtra {
135 path,
136 key: key.into(),
137 })
138 }
139
140 /// Insert `node` as a child of the node at `path` (the parent), at `index`.
141 pub fn insert(
142 &mut self,
143 path: Vec<usize>,
144 index: usize,
145 node: Node,
146 ) -> Result<&mut Self, ApplyError> {
147 self.push(Change::Insert { path, index, node })
148 }
149
150 /// Remove the child at `index` of the node at `path` (the parent).
151 pub fn remove(&mut self, path: Vec<usize>, index: usize) -> Result<&mut Self, ApplyError> {
152 self.push(Change::Remove { path, index })
153 }
154
155 /// Replace the node at `path` wholesale.
156 pub fn replace(&mut self, path: Vec<usize>, node: Node) -> Result<&mut Self, ApplyError> {
157 self.push(Change::Replace { path, node })
158 }
159
160 /// Relocate the child at `from` to `to` within the parent at `path`, without
161 /// cloning its subtree. See [`Change::Move`].
162 pub fn move_child(
163 &mut self,
164 path: Vec<usize>,
165 from: usize,
166 to: usize,
167 ) -> Result<&mut Self, ApplyError> {
168 self.push(Change::Move { path, from, to })
169 }
170
171 /// The changes recorded so far, without consuming the transaction.
172 pub fn changes(&self) -> &[Change] {
173 &self.changes
174 }
175
176 /// Finish the transaction, returning the recorded [`Change`] log.
177 pub fn finish(self) -> Vec<Change> {
178 self.changes
179 }
180}