Skip to main content

tiptap_rusty_parser/
block.rs

1//! Block-level structural editing: split, join, wrap, lift, retype.
2//!
3//! Where [`range`](crate::range) edits the inline content *within* one block,
4//! these ops restructure the **block tree itself**, addressed by index-path.
5//! They operate directly on the mutable tree (no clones on the direct path);
6//! for a recorded, invertible patch use the matching [`Transform`](crate::Transform)
7//! builders, which run the op and recover a [`Change`](crate::Change) list via
8//! [`diff`](crate::diff).
9//!
10//! A contiguous run of sibling blocks is a [`BlockRange`].
11//!
12//! ```
13//! use tiptap_rusty_parser::Node;
14//! // doc > [paragraph "a", paragraph "b"]; wrap them in a blockquote.
15//! let mut doc = Node::element("doc").with_children([
16//!     Node::element("paragraph").with_text("a"),
17//!     Node::element("paragraph").with_text("b"),
18//! ]);
19//! doc.wrap_range(
20//!     &tiptap_rusty_parser::BlockRange::new(vec![], 0, 2),
21//!     "blockquote",
22//!     None,
23//! ).unwrap();
24//! assert_eq!(doc.child_count(), 1);
25//! assert_eq!(doc.child(0).unwrap().node_type.as_deref(), Some("blockquote"));
26//! assert_eq!(doc.child(0).unwrap().child_count(), 2);
27//! ```
28
29use crate::node::Node;
30use crate::normalize::{normalize_children, NormalizeOptions};
31use crate::range::{ensure_boundary, Position, RangeError};
32use serde_json::{Map, Value};
33use std::fmt;
34
35/// A contiguous run of sibling blocks `[start, end)` under a common parent.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct BlockRange {
38    /// Index-path to the shared parent node.
39    pub parent: Vec<usize>,
40    /// First child index (inclusive).
41    pub start: usize,
42    /// One-past-last child index (exclusive).
43    pub end: usize,
44}
45
46impl BlockRange {
47    /// Construct a range.
48    pub fn new(parent: Vec<usize>, start: usize, end: usize) -> Self {
49        Self { parent, start, end }
50    }
51
52    /// The single-block range for a node `path` (`parent = path[..n-1]`, the run
53    /// `[i, i+1)`). An empty path yields an empty range at the root.
54    pub fn single(path: &[usize]) -> Self {
55        match path.split_last() {
56            Some((&i, parent)) => Self::new(parent.to_vec(), i, i + 1),
57            None => Self::new(Vec::new(), 0, 0),
58        }
59    }
60
61    /// Number of blocks in the run.
62    pub fn len(&self) -> usize {
63        self.end.saturating_sub(self.start)
64    }
65
66    /// Whether the run is empty.
67    pub fn is_empty(&self) -> bool {
68        self.end <= self.start
69    }
70}
71
72/// Why a block-structural operation could not be applied.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum BlockError {
75    /// No node exists at the given path.
76    PathNotFound {
77        /// The unresolved path.
78        path: Vec<usize>,
79    },
80    /// A child index is out of range for the parent.
81    IndexOutOfRange {
82        /// The parent path.
83        parent: Vec<usize>,
84        /// The offending index.
85        index: usize,
86    },
87    /// The operation needs a parent (and sometimes a grandparent) the path lacks.
88    NoParent,
89    /// `join_blocks` was asked to merge index 0 (no previous sibling).
90    NoPreviousSibling {
91        /// The path that has no previous sibling.
92        path: Vec<usize>,
93    },
94    /// A [`BlockRange`] is inverted or extends past the parent's children.
95    InvalidRange {
96        /// The parent path.
97        parent: Vec<usize>,
98        /// Start index.
99        start: usize,
100        /// End index.
101        end: usize,
102    },
103    /// A mid-text split boundary could not be resolved (forwarded from [`range`](crate::range)).
104    Range(RangeError),
105}
106
107impl From<RangeError> for BlockError {
108    fn from(e: RangeError) -> Self {
109        BlockError::Range(e)
110    }
111}
112
113impl fmt::Display for BlockError {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        match self {
116            BlockError::PathNotFound { path } => write!(f, "block: no node at path {path:?}"),
117            BlockError::IndexOutOfRange { parent, index } => {
118                write!(f, "block: index {index} out of range under {parent:?}")
119            }
120            BlockError::NoParent => write!(f, "block: operation requires a parent node"),
121            BlockError::NoPreviousSibling { path } => {
122                write!(f, "block: no previous sibling to join at {path:?}")
123            }
124            BlockError::InvalidRange { parent, start, end } => {
125                write!(f, "block: invalid range [{start},{end}) under {parent:?}")
126            }
127            BlockError::Range(e) => write!(f, "block: {e}"),
128        }
129    }
130}
131
132impl std::error::Error for BlockError {}
133
134/// Clone a node's metadata (type/attrs/marks/extra) with fresh `content`.
135fn with_content(meta: &Node, content: Vec<Node>) -> Node {
136    Node {
137        node_type: meta.node_type.clone(),
138        attrs: meta.attrs.clone(),
139        marks: meta.marks.clone(),
140        extra: meta.extra.clone(),
141        content: Some(content),
142        text: None,
143    }
144}
145
146impl Node {
147    /// Change the type (and attrs) of the block at `path` **in place, keeping its
148    /// content** — unlike a wholesale [`Change::Replace`](crate::Change::Replace).
149    /// `attrs = None` clears attrs. The root (`&[]`) may be retyped.
150    pub fn set_block_type(
151        &mut self,
152        path: &[usize],
153        new_type: impl Into<String>,
154        attrs: Option<Map<String, Value>>,
155    ) -> Result<(), BlockError> {
156        let node = self.at_block(path)?;
157        node.node_type = Some(new_type.into());
158        node.attrs = attrs;
159        Ok(())
160    }
161
162    /// Split the block at `path` at child-boundary index `at` into two siblings:
163    /// the left keeps children `[..at]`, a new right sibling takes `[at..]`
164    /// (same type/attrs/marks/extra). `depth` also splits that many ancestors
165    /// (ProseMirror-style), clamped at the root.
166    pub fn split_block(
167        &mut self,
168        path: &[usize],
169        at: usize,
170        depth: usize,
171    ) -> Result<(), BlockError> {
172        if path.is_empty() {
173            return Err(BlockError::NoParent);
174        }
175        let block = self.node_at(path).ok_or_else(|| BlockError::PathNotFound {
176            path: path.to_vec(),
177        })?;
178        let len = block.children().len();
179        if at > len {
180            return Err(BlockError::IndexOutOfRange {
181                parent: path.to_vec(),
182                index: at,
183            });
184        }
185
186        // Detach the right portion of the target block.
187        let block_mut = self.node_at_mut(path).unwrap();
188        let right_children = block_mut.children_mut().split_off(at);
189        normalize_children(block_mut.children_mut(), &NormalizeOptions::default());
190        let mut new_node = with_content(block_mut, right_children);
191        normalize_children(
192            new_node.content.as_mut().unwrap(),
193            &NormalizeOptions::default(),
194        );
195
196        // Walk up `depth` levels, pulling following siblings into the new node and
197        // splitting each ancestor. Stop if there's no grandparent left to host it.
198        let mut cur = path.to_vec();
199        for _ in 0..depth {
200            if cur.len() < 2 {
201                break; // parent is the root: cannot split it
202            }
203            let pidx = *cur.last().unwrap();
204            let ppath = &cur[..cur.len() - 1];
205            let parent = self.node_at_mut(ppath).unwrap();
206            let following = parent.children_mut().split_off(pidx + 1);
207            let mut content = Vec::with_capacity(following.len() + 1);
208            content.push(new_node);
209            content.extend(following);
210            new_node = with_content(parent, content);
211            cur.truncate(cur.len() - 1);
212        }
213
214        // Insert `new_node` as the next sibling of the block at `cur`.
215        let insert_idx = *cur.last().unwrap() + 1;
216        let host = self.node_at_mut(&cur[..cur.len() - 1]).unwrap();
217        host.insert_child(insert_idx, new_node);
218        Ok(())
219    }
220
221    /// Split the block at `path` at an inline [`Position`] (materializing a
222    /// mid-text boundary first), then split like [`Node::split_block`].
223    pub fn split_block_at(
224        &mut self,
225        path: &[usize],
226        pos: Position,
227        depth: usize,
228    ) -> Result<(), BlockError> {
229        // Validate up front so a failed precondition never leaves a half-split
230        // text node behind (ensure_boundary below mutates).
231        if path.is_empty() {
232            return Err(BlockError::NoParent);
233        }
234        let at = {
235            let block = self
236                .node_at_mut(path)
237                .ok_or_else(|| BlockError::PathNotFound {
238                    path: path.to_vec(),
239                })?;
240            ensure_boundary(block.children_mut(), pos)?
241        };
242        self.split_block(path, at, depth)
243    }
244
245    /// Merge the block at `parent[index]` into its previous sibling
246    /// `parent[index-1]` (appending its content, re-merging inline text at the
247    /// seam). The previous sibling's type/attrs win on a mismatch.
248    pub fn join_blocks(&mut self, parent: &[usize], index: usize) -> Result<(), BlockError> {
249        if index == 0 {
250            let mut path = parent.to_vec();
251            path.push(0);
252            return Err(BlockError::NoPreviousSibling { path });
253        }
254        let parent_node = self
255            .node_at_mut(parent)
256            .ok_or_else(|| BlockError::PathNotFound {
257                path: parent.to_vec(),
258            })?;
259        let children = match parent_node.content.as_mut() {
260            Some(c) if index < c.len() => c,
261            _ => {
262                return Err(BlockError::IndexOutOfRange {
263                    parent: parent.to_vec(),
264                    index,
265                })
266            }
267        };
268        let right = children.remove(index);
269        if let Some(rc) = right.content {
270            if !rc.is_empty() {
271                let left = &mut children[index - 1];
272                left.children_mut().extend(rc);
273                normalize_children(left.children_mut(), &NormalizeOptions::default());
274            }
275        }
276        Ok(())
277    }
278
279    /// Wrap the single block at `path` in a new parent of `wrapper_type`.
280    pub fn wrap(
281        &mut self,
282        path: &[usize],
283        wrapper_type: impl Into<String>,
284        attrs: Option<Map<String, Value>>,
285    ) -> Result<(), BlockError> {
286        self.wrap_range(&BlockRange::single(path), wrapper_type, attrs)
287    }
288
289    /// Wrap a contiguous run of sibling blocks (one level) in a new parent of
290    /// `wrapper_type`. Nested structures (e.g. `bulletList > listItem`) are
291    /// composed by the caller.
292    pub fn wrap_range(
293        &mut self,
294        range: &BlockRange,
295        wrapper_type: impl Into<String>,
296        attrs: Option<Map<String, Value>>,
297    ) -> Result<(), BlockError> {
298        let parent = self
299            .node_at_mut(&range.parent)
300            .ok_or_else(|| BlockError::PathNotFound {
301                path: range.parent.clone(),
302            })?;
303        let children = parent.children_mut();
304        if range.start > range.end || range.end > children.len() {
305            return Err(BlockError::InvalidRange {
306                parent: range.parent.clone(),
307                start: range.start,
308                end: range.end,
309            });
310        }
311        let run: Vec<Node> = children
312            .splice(range.start..range.end, std::iter::empty())
313            .collect();
314        let wrapper = Node {
315            node_type: Some(wrapper_type.into()),
316            attrs,
317            content: Some(run),
318            ..Node::default()
319        };
320        children.insert(range.start, wrapper);
321        Ok(())
322    }
323
324    /// Lift the block at `path` out of its parent into its grandparent,
325    /// splitting the parent around it so preceding/following siblings are kept
326    /// (a sole-child parent simply collapses to the lifted node).
327    pub fn lift(&mut self, path: &[usize]) -> Result<(), BlockError> {
328        let n = path.len();
329        if n < 2 {
330            return Err(BlockError::NoParent);
331        }
332        self.node_at(path).ok_or_else(|| BlockError::PathNotFound {
333            path: path.to_vec(),
334        })?;
335        let child_idx = path[n - 1];
336        let parent_idx = path[n - 2];
337
338        // Mutate the parent: split its children around `child_idx`.
339        let parent = self.node_at_mut(&path[..n - 1]).unwrap();
340        let mut tail = parent.children_mut().split_off(child_idx);
341        let moved = tail.remove(0);
342        let right_children = tail;
343        let left_children = std::mem::take(parent.content.as_mut().unwrap());
344
345        // Rebuild [left?, moved, right?] from the parent's metadata.
346        let mut insert: Vec<Node> = Vec::with_capacity(3);
347        if !left_children.is_empty() {
348            insert.push(with_content(parent, left_children));
349        }
350        insert.push(moved);
351        if !right_children.is_empty() {
352            insert.push(with_content(parent, right_children));
353        }
354
355        // Replace the (now-gutted) parent in the grandparent with that run.
356        let gp = self.node_at_mut(&path[..n - 2]).unwrap();
357        gp.children_mut().splice(parent_idx..parent_idx + 1, insert);
358        Ok(())
359    }
360
361    /// Resolve a block path mutably or return [`BlockError::PathNotFound`].
362    fn at_block(&mut self, path: &[usize]) -> Result<&mut Node, BlockError> {
363        self.node_at_mut(path)
364            .ok_or_else(|| BlockError::PathNotFound {
365                path: path.to_vec(),
366            })
367    }
368}