1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Utilities over [`Change`] lists: compose, compact, and position mapping.
//!
//! These complement [`diff`](crate::diff)/[`apply`](crate::apply)/[`invert`](crate::invert):
//! - [`compose`] concatenates two change lists into one apply-equivalent patch.
//! - [`compact`] coalesces redundant node-local ops (safely — structural ops act
//! as barriers, since [`apply`] interprets child indices against the live tree).
//! - [`map_path`] carries an index-path through a change list (the basis for
//! mapping a selection or decoration across an edit).
//!
//! Operational-transform style *rebasing* of concurrent edits is intentionally
//! out of scope: it needs a bidirectional position map with insertion tie-breaks
//! and stable node identity that the value/live-index [`Change`] model doesn't
//! provide.
use crateChange;
use HashMap;
/// A change list apply-equivalent to running `a` then `b`.
///
/// Because [`apply`](crate::apply) folds changes in order against the live tree,
/// `b`'s paths/indices already assume `a` has run — so plain concatenation is
/// the semantic identity. `compose` returns that concatenation in [`compact`]ed
/// form (redundant node-local ops coalesced).
///
/// ```
/// use tiptap_rusty_parser::{apply, compose, Node};
/// let base = Node::element("doc").with_child(Node::text("x"));
/// let a = vec![tiptap_rusty_parser::Change::SetText { path: vec![0], text: Some("y".into()) }];
/// let b = vec![tiptap_rusty_parser::Change::SetText { path: vec![0], text: Some("z".into()) }];
/// let composed = compose(&a, &b);
/// assert_eq!(composed.len(), 1); // the two SetTexts coalesced to the last
/// let mut t = base.clone();
/// apply(&mut t, &composed).unwrap();
/// assert_eq!(t.text_content(), "z");
/// ```
/// Identifies a node-local *field* write, so repeated writes to the same field
/// can be coalesced (last-wins). Structural ops have no key.
/// Coalesce redundant changes while preserving the [`apply`](crate::apply)
/// result. Only **safe** reductions are performed:
///
/// - repeated writes to the same node field (`SetText`/`SetMarks`, or
/// `SetAttr`/`RemoveAttr` / `SetExtra`/`RemoveExtra` on the same key) collapse
/// to the **last** one (each write fully determines that field's final value);
/// - an `Insert` immediately followed by a `Remove` of that same just-inserted
/// child cancels out.
///
/// Structural ops (`Insert`/`Remove`/`Move`/`Replace`) act as **barriers**:
/// because [`apply`] resolves indices against the live tree, field ops are never
/// coalesced across them. `compact(c)` is never longer than `c`.
///
/// Precondition: this preserves the `apply` result for change lists that apply
/// **cleanly** to their intended base (as produced by [`diff`](crate::diff) /
/// [`Transform`](crate::Transform)). For a deliberately invalid list it can
/// differ — e.g. an out-of-bounds `Insert` immediately followed by its matching
/// `Remove` cancels here, whereas applying the original would error; validity
/// can't be checked from a change list alone.
/// If `prefix` is a strict ancestor level of `q`, return that level's depth
/// (so the structural op at parent `prefix` acts on `q[depth]`).
/// Carry an index-path through a change list: where does the node originally at
/// `path` end up after applying `changes`? Returns `None` if it was removed or
/// replaced away.
///
/// Field changes never move nodes. Structural changes shift the index components
/// of `path` at the level they act on (an `Insert` at or before the index shifts
/// it right; a `Remove`/`Move` shifts accordingly; removing or replacing a node
/// on the path drops it). This mirrors [`apply`](crate::apply)'s live-index
/// semantics, so the mapped path addresses the same node in the applied tree.
///
/// ```
/// use tiptap_rusty_parser::{map_path, Change};
/// // A sibling inserted before index 1 shifts it to 2.
/// let changes = vec![Change::Insert { path: vec![], index: 0, node: Default::default() }];
/// assert_eq!(map_path(&[1, 0], &changes), Some(vec![2, 0]));
/// // Removing the node on the path drops it.
/// let removed = vec![Change::Remove { path: vec![], index: 1 }];
/// assert_eq!(map_path(&[1, 0], &removed), None);
/// ```