Skip to main content

taino_edit_core/
node.rs

1//! [`Node`] — a single element (or text run) in the document tree — and
2//! [`NodeType`], its schema-bound descriptor.
3
4use std::sync::Arc;
5
6use crate::attrs::Attrs;
7use crate::fragment::Fragment;
8use crate::mark::Mark;
9use crate::schema::NodeSpec;
10
11#[derive(Debug)]
12pub(crate) struct NodeTypeInner {
13    pub(crate) id: usize,
14    pub(crate) name: String,
15    pub(crate) spec: NodeSpec,
16    pub(crate) groups: Vec<String>,
17    pub(crate) is_text: bool,
18    /// `true` when the compiled content expression accepts no children.
19    pub(crate) content_is_empty: bool,
20}
21
22/// A schema-bound node type. Cheap to clone; identity is by schema id.
23#[derive(Debug, Clone)]
24pub struct NodeType(pub(crate) Arc<NodeTypeInner>);
25
26impl NodeType {
27    /// The type's unique name within its schema.
28    pub fn name(&self) -> &str {
29        &self.0.name
30    }
31
32    /// The schema-assigned id (stable for the lifetime of the schema).
33    pub fn id(&self) -> usize {
34        self.0.id
35    }
36
37    /// The spec this type was built from.
38    pub fn spec(&self) -> &NodeSpec {
39        &self.0.spec
40    }
41
42    /// Whether this is the text node type.
43    pub fn is_text(&self) -> bool {
44        self.0.is_text
45    }
46
47    /// Whether this type is inline (text, or a spec marked `inline`).
48    pub fn is_inline(&self) -> bool {
49        self.0.is_text || self.0.spec.inline
50    }
51
52    /// Whether this type is a block (the negation of [`is_inline`]).
53    ///
54    /// [`is_inline`]: NodeType::is_inline
55    pub fn is_block(&self) -> bool {
56        !self.is_inline()
57    }
58
59    /// Whether this type never has content (a leaf such as an image or
60    /// horizontal rule, or any node whose content expression is empty).
61    pub fn is_leaf(&self) -> bool {
62        self.0.content_is_empty
63    }
64
65    /// Whether this type is treated as a single opaque unit (`atom` in the
66    /// spec, or any leaf).
67    pub fn is_atom(&self) -> bool {
68        self.0.spec.atom || self.is_leaf()
69    }
70
71    /// Whether this type belongs to content group `group`.
72    pub fn is_in_group(&self, group: &str) -> bool {
73        self.0.groups.iter().any(|g| g == group)
74    }
75}
76
77impl PartialEq for NodeType {
78    fn eq(&self, other: &Self) -> bool {
79        self.0.id == other.0.id
80    }
81}
82impl Eq for NodeType {}
83
84#[derive(Debug)]
85pub(crate) struct NodeInner {
86    pub(crate) type_: NodeType,
87    pub(crate) attrs: Attrs,
88    pub(crate) content: Fragment,
89    pub(crate) marks: Vec<Mark>,
90    /// `Some` only for text nodes.
91    pub(crate) text: Option<String>,
92}
93
94/// A node in the document tree: an element with attributes, child content and
95/// marks, or — when [`is_text`](Node::is_text) — a marked text run.
96///
97/// Positions follow the ProseMirror model. A text node's size is its length
98/// in Unicode scalar values (`char`s — note this differs from ProseMirror's
99/// UTF-16 units; the DOM bridge maps between them). A non-text leaf has size
100/// 1; any other node has size `content.size + 2`.
101#[derive(Debug, Clone)]
102pub struct Node(pub(crate) Arc<NodeInner>);
103
104impl Node {
105    pub(crate) fn new_element(
106        type_: NodeType,
107        attrs: Attrs,
108        content: Fragment,
109        marks: Vec<Mark>,
110    ) -> Node {
111        Node(Arc::new(NodeInner {
112            type_,
113            attrs,
114            content,
115            marks,
116            text: None,
117        }))
118    }
119
120    pub(crate) fn new_text(type_: NodeType, text: String, marks: Vec<Mark>) -> Node {
121        Node(Arc::new(NodeInner {
122            type_,
123            attrs: Attrs::new(),
124            content: Fragment::empty(),
125            marks,
126            text: Some(text),
127        }))
128    }
129
130    /// This node's type.
131    pub fn node_type(&self) -> &NodeType {
132        &self.0.type_
133    }
134
135    /// This node's attributes.
136    pub fn attrs(&self) -> &Attrs {
137        &self.0.attrs
138    }
139
140    /// This node's child fragment (empty for text and leaf nodes).
141    pub fn content(&self) -> &Fragment {
142        &self.0.content
143    }
144
145    /// The marks applied to this node.
146    pub fn marks(&self) -> &[Mark] {
147        &self.0.marks
148    }
149
150    /// The text of a text node, or `None` for element nodes.
151    pub fn text(&self) -> Option<&str> {
152        self.0.text.as_deref()
153    }
154
155    /// Whether this is a text node.
156    pub fn is_text(&self) -> bool {
157        self.0.text.is_some()
158    }
159
160    /// Whether this node is a leaf (no content).
161    pub fn is_leaf(&self) -> bool {
162        self.0.type_.is_leaf()
163    }
164
165    /// Whether this node is inline.
166    pub fn is_inline(&self) -> bool {
167        self.0.type_.is_inline()
168    }
169
170    /// Whether this node is a block.
171    pub fn is_block(&self) -> bool {
172        self.0.type_.is_block()
173    }
174
175    /// Number of direct children.
176    pub fn child_count(&self) -> usize {
177        self.0.content.child_count()
178    }
179
180    /// Borrow the child at index `i`.
181    ///
182    /// # Panics
183    /// Panics if `i >= child_count()`.
184    pub fn child(&self, i: usize) -> &Node {
185        self.0.content.child(i)
186    }
187
188    /// The number of positions this node occupies in its parent.
189    pub fn node_size(&self) -> usize {
190        if let Some(t) = &self.0.text {
191            t.chars().count()
192        } else if self.0.type_.is_leaf() {
193            1
194        } else {
195            self.0.content.size() + 2
196        }
197    }
198
199    /// The concatenated text of this node and its descendants.
200    pub fn text_content(&self) -> String {
201        if let Some(t) = &self.0.text {
202            return t.clone();
203        }
204        let mut s = String::new();
205        for child in self.0.content.iter() {
206            s.push_str(&child.text_content());
207        }
208        s
209    }
210
211    /// Return a copy of this node with `marks` as its mark set.
212    pub fn with_marks(&self, marks: Vec<Mark>) -> Node {
213        let inner = &*self.0;
214        Node(Arc::new(NodeInner {
215            type_: inner.type_.clone(),
216            attrs: inner.attrs.clone(),
217            content: inner.content.clone(),
218            marks,
219            text: inner.text.clone(),
220        }))
221    }
222
223    /// Return a copy of this node with the same type/attrs/marks but the
224    /// given content. Not schema-validated — callers that need validation
225    /// (e.g. the replace algorithm) check separately.
226    pub(crate) fn copy_content(&self, content: Fragment) -> Node {
227        debug_assert!(self.0.text.is_none(), "copy_content on a text node");
228        let inner = &*self.0;
229        Node(Arc::new(NodeInner {
230            type_: inner.type_.clone(),
231            attrs: inner.attrs.clone(),
232            content,
233            marks: inner.marks.clone(),
234            text: None,
235        }))
236    }
237
238    /// Return a copy of this text node carrying `text`.
239    pub(crate) fn with_text(&self, text: String) -> Node {
240        debug_assert!(self.0.text.is_some(), "with_text on a non-text node");
241        Node::new_text(self.0.type_.clone(), text, self.0.marks.clone())
242    }
243
244    /// Whether two nodes have the same type, attributes and marks (text
245    /// equality aside) — i.e. adjacent text runs can be merged.
246    pub(crate) fn same_markup(&self, other: &Node) -> bool {
247        self.0.type_ == other.0.type_
248            && self.0.attrs == other.0.attrs
249            && self.0.marks == other.0.marks
250    }
251
252    /// Attributes (mutable-copy helper for `AttrStep`): return a copy with
253    /// `attrs` replacing the current attribute map.
254    pub(crate) fn with_attrs(&self, attrs: Attrs) -> Node {
255        let inner = &*self.0;
256        Node(Arc::new(NodeInner {
257            type_: inner.type_.clone(),
258            attrs,
259            content: inner.content.clone(),
260            marks: inner.marks.clone(),
261            text: inner.text.clone(),
262        }))
263    }
264
265    /// The node that begins at absolute position `pos` within this node's
266    /// subtree (descending into children), or `None` if `pos` does not land
267    /// exactly on a node boundary.
268    pub fn node_at(&self, pos: usize) -> Option<Node> {
269        let mut node = self.clone();
270        let mut pos = pos;
271        loop {
272            let (index, offset) = node.0.content.find_index(pos);
273            let child = node.0.content.children().get(index)?.clone();
274            if offset == pos || child.is_text() {
275                return Some(child);
276            }
277            pos -= offset + 1;
278            node = child;
279        }
280    }
281
282    /// Slice this node's content (text for text nodes) between content
283    /// positions `from..to`, returning a same-markup copy.
284    pub(crate) fn cut(&self, from: usize, to: usize) -> Node {
285        if let Some(t) = &self.0.text {
286            let chars: Vec<char> = t.chars().collect();
287            let to = to.min(chars.len());
288            if from == 0 && to == chars.len() {
289                return self.clone();
290            }
291            return self.with_text(chars[from..to].iter().collect());
292        }
293        if from == 0 && to == self.0.content.size() {
294            return self.clone();
295        }
296        self.copy_content(self.0.content.cut(from, to))
297    }
298}
299
300impl PartialEq for Node {
301    fn eq(&self, other: &Self) -> bool {
302        if Arc::ptr_eq(&self.0, &other.0) {
303            return true;
304        }
305        self.0.type_ == other.0.type_
306            && self.0.text == other.0.text
307            && self.0.attrs == other.0.attrs
308            && self.0.marks == other.0.marks
309            && self.0.content == other.0.content
310    }
311}
312impl Eq for Node {}