Skip to main content

blitz_dom/
range.rs

1//! Live DOM ranges. Boundary offsets are UTF-16 code units for CharacterData
2//! and child indices for other nodes. The mutation hooks also cover detached
3//! trees and documents sharing this arena.
4
5use std::cmp::Ordering;
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex, Weak};
8
9use crate::document::BoundingRect;
10use crate::{BaseDocument, NodeData, NodeId, NodeTree};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct RangeBoundary {
14    pub node: NodeId,
15    pub offset: usize,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct RangeBounds {
20    pub start: RangeBoundary,
21    pub end: RangeBoundary,
22}
23
24impl RangeBounds {
25    pub fn collapsed(self) -> bool {
26        self.start == self.end
27    }
28}
29
30#[derive(Clone, Debug)]
31pub struct LiveRange(pub(crate) Arc<Mutex<RangeBounds>>);
32
33impl LiveRange {
34    pub fn bounds(&self) -> RangeBounds {
35        *self.0.lock().unwrap()
36    }
37
38    pub fn set_bounds(&self, bounds: RangeBounds) {
39        *self.0.lock().unwrap() = bounds;
40    }
41
42    pub fn same_range(&self, other: &Self) -> bool {
43        Arc::ptr_eq(&self.0, &other.0)
44    }
45}
46
47#[derive(Clone, Debug)]
48pub enum RangeContent {
49    Full(NodeId),
50    Partial(NodeId, Vec<RangeContent>),
51    Data(NodeId, usize, usize),
52}
53
54pub fn utf16_slice(value: &str, start: usize, end: usize) -> String {
55    let units: Vec<u16> = value
56        .encode_utf16()
57        .skip(start)
58        .take(end.saturating_sub(start))
59        .collect();
60    String::from_utf16_lossy(&units)
61}
62
63fn contains(nodes: &NodeTree, ancestor: NodeId, mut descendant: NodeId) -> bool {
64    loop {
65        if ancestor == descendant {
66            return true;
67        }
68        let Some(parent) = nodes.get(descendant).and_then(|node| node.parent) else {
69            return false;
70        };
71        descendant = parent;
72    }
73}
74
75impl BaseDocument {
76    pub fn create_live_range(&mut self, bounds: RangeBounds) -> LiveRange {
77        self.live_ranges.retain(|range| range.strong_count() != 0);
78        let range = LiveRange(Arc::new(Mutex::new(bounds)));
79        self.live_ranges.push(Arc::downgrade(&range.0));
80        range
81    }
82
83    pub fn range_character_data(&self, id: NodeId) -> Option<&str> {
84        match &self.get_node(id)?.data {
85            NodeData::Text(text) => Some(&text.content),
86            NodeData::Comment { contents } => Some(contents),
87            _ => None,
88        }
89    }
90
91    pub fn range_node_length(&self, id: NodeId) -> Option<usize> {
92        let node = self.get_node(id)?;
93        Some(match self.range_character_data(id) {
94            Some(text) => text.encode_utf16().count(),
95            None => node.children.len(),
96        })
97    }
98
99    pub fn range_contains(&self, ancestor: NodeId, descendant: NodeId) -> bool {
100        contains(&self.nodes, ancestor, descendant)
101    }
102
103    fn range_path(&self, mut id: NodeId) -> Vec<NodeId> {
104        let mut path = vec![id];
105        while let Some(parent) = self.get_node(id).and_then(|node| node.parent) {
106            path.push(parent);
107            id = parent;
108        }
109        path.reverse();
110        path
111    }
112
113    pub fn range_root(&self, id: NodeId) -> NodeId {
114        self.range_path(id)[0]
115    }
116
117    pub fn range_common_ancestor(&self, bounds: RangeBounds) -> Option<NodeId> {
118        self.range_path(bounds.start.node)
119            .iter()
120            .zip(self.range_path(bounds.end.node))
121            .take_while(|(left, right)| **left == *right)
122            .map(|(left, _)| *left)
123            .last()
124    }
125
126    pub fn compare_range_boundaries(
127        &self,
128        left: RangeBoundary,
129        right: RangeBoundary,
130    ) -> Option<Ordering> {
131        if left.node == right.node {
132            return Some(left.offset.cmp(&right.offset));
133        }
134        let a = self.range_path(left.node);
135        let b = self.range_path(right.node);
136        if a.first() != b.first() {
137            return None;
138        }
139        let common = a.iter().zip(&b).take_while(|(a, b)| a == b).count();
140        let parent = self.get_node(a[common - 1])?;
141        if common == a.len() {
142            let index = parent.index_of_child(b[common])?;
143            return Some(if index < left.offset {
144                Ordering::Greater
145            } else {
146                Ordering::Less
147            });
148        }
149        if common == b.len() {
150            let index = parent.index_of_child(a[common])?;
151            return Some(if index < right.offset {
152                Ordering::Less
153            } else {
154                Ordering::Greater
155            });
156        }
157        Some(
158            parent
159                .index_of_child(a[common])?
160                .cmp(&parent.index_of_child(b[common])?),
161        )
162    }
163
164    fn update_live_ranges(&mut self, mut update: impl FnMut(&mut RangeBoundary)) {
165        self.live_ranges.retain(|weak| {
166            let Some(range) = weak.upgrade() else {
167                return false;
168            };
169            let mut bounds = range.lock().unwrap();
170            update(&mut bounds.start);
171            update(&mut bounds.end);
172            true
173        });
174    }
175
176    pub(crate) fn range_remove_node(&mut self, id: NodeId) {
177        if self.live_ranges.is_empty() {
178            return;
179        }
180        let Some(parent) = self.get_node(id).and_then(|node| node.parent) else {
181            return;
182        };
183        let Some(index) = self
184            .get_node(parent)
185            .and_then(|node| node.index_of_child(id))
186        else {
187            return;
188        };
189        let nodes = &self.nodes;
190        self.live_ranges.retain(|weak| {
191            let Some(range) = weak.upgrade() else {
192                return false;
193            };
194            let mut guard = range.lock().unwrap();
195            let bounds = &mut *guard;
196            for point in [&mut bounds.start, &mut bounds.end] {
197                if contains(nodes, id, point.node) {
198                    *point = RangeBoundary {
199                        node: parent,
200                        offset: index,
201                    };
202                } else if point.node == parent && point.offset > index {
203                    point.offset -= 1;
204                }
205            }
206            true
207        });
208    }
209
210    pub(crate) fn range_insert_nodes(&mut self, parent: NodeId, index: usize, count: usize) {
211        if count == 0 || self.live_ranges.is_empty() {
212            return;
213        }
214        self.update_live_ranges(|point| {
215            if point.node == parent && point.offset > index {
216                point.offset += count;
217            }
218        });
219    }
220
221    pub(crate) fn range_replace_data(
222        &mut self,
223        id: NodeId,
224        offset: usize,
225        removed: usize,
226        added: usize,
227    ) {
228        if self.live_ranges.is_empty() {
229            return;
230        }
231        self.update_live_ranges(|point| {
232            if point.node != id {
233                return;
234            }
235            if point.offset > offset && point.offset <= offset + removed {
236                point.offset = offset;
237            } else if point.offset > offset + removed {
238                point.offset = point.offset - removed + added;
239            }
240        });
241    }
242
243    pub(crate) fn range_split_text(
244        &mut self,
245        old: NodeId,
246        new: NodeId,
247        offset: usize,
248        parent_position: Option<(NodeId, usize)>,
249    ) {
250        self.update_live_ranges(|point| {
251            if point.node == old && point.offset > offset {
252                point.node = new;
253                point.offset -= offset;
254            } else if let Some((parent, index)) = parent_position
255                && point.node == parent
256                && point.offset == index + 1
257            {
258                point.offset += 1;
259            }
260        });
261    }
262
263    /// Normalize transfers endpoints before the merged Text node is removed.
264    pub fn range_merge_text(&mut self, keeper: NodeId, removed: NodeId, prefix: usize) {
265        let position = self.get_node(removed).and_then(|node| {
266            let parent = node.parent?;
267            Some((parent, self.get_node(parent)?.index_of_child(removed)?))
268        });
269        self.update_live_ranges(|point| {
270            if point.node == removed {
271                point.node = keeper;
272                point.offset += prefix;
273            } else if let Some((parent, index)) = position
274                && point.node == parent
275                && point.offset == index
276            {
277                *point = RangeBoundary {
278                    node: keeper,
279                    offset: prefix,
280                };
281            }
282        });
283    }
284
285    pub fn range_contents(&self, bounds: RangeBounds) -> Vec<RangeContent> {
286        if bounds.collapsed() {
287            return Vec::new();
288        }
289        let Some(common) = self.range_common_ancestor(bounds) else {
290            return Vec::new();
291        };
292        if self.range_character_data(common).is_some() {
293            return vec![RangeContent::Data(
294                common,
295                bounds.start.offset,
296                bounds.end.offset,
297            )];
298        }
299        self.get_node(common)
300            .into_iter()
301            .flat_map(|node| node.children.iter().copied())
302            .filter_map(|id| self.range_content(id, bounds))
303            .collect()
304    }
305
306    fn range_content(&self, id: NodeId, bounds: RangeBounds) -> Option<RangeContent> {
307        let node = self.get_node(id)?;
308        let parent = node.parent?;
309        let index = self.get_node(parent)?.index_of_child(id)?;
310        let before = RangeBoundary {
311            node: parent,
312            offset: index,
313        };
314        let after = RangeBoundary {
315            node: parent,
316            offset: index + 1,
317        };
318        if self.compare_range_boundaries(bounds.end, before)? != Ordering::Greater
319            || self.compare_range_boundaries(bounds.start, after)? != Ordering::Less
320        {
321            return None;
322        }
323        if self.compare_range_boundaries(bounds.start, before)? != Ordering::Greater
324            && self.compare_range_boundaries(bounds.end, after)? != Ordering::Less
325        {
326            return Some(RangeContent::Full(id));
327        }
328        if let Some(text) = self.range_character_data(id) {
329            let start = if bounds.start.node == id {
330                bounds.start.offset
331            } else {
332                0
333            };
334            let end = if bounds.end.node == id {
335                bounds.end.offset
336            } else {
337                text.encode_utf16().count()
338            };
339            return Some(RangeContent::Data(id, start, end));
340        }
341        Some(RangeContent::Partial(
342            id,
343            node.children
344                .iter()
345                .filter_map(|id| self.range_content(*id, bounds))
346                .collect(),
347        ))
348    }
349
350    pub fn range_collapse_after_deletion(&self, bounds: RangeBounds) -> RangeBoundary {
351        if self.range_contains(bounds.start.node, bounds.end.node) {
352            return bounds.start;
353        }
354        let common = self
355            .range_common_ancestor(bounds)
356            .expect("range has one root");
357        let mut child = bounds.start.node;
358        while self.get_node(child).and_then(|node| node.parent) != Some(common) {
359            child = self
360                .get_node(child)
361                .and_then(|node| node.parent)
362                .expect("range ancestor");
363        }
364        RangeBoundary {
365            node: common,
366            offset: self
367                .get_node(common)
368                .unwrap()
369                .index_of_child(child)
370                .unwrap()
371                + 1,
372        }
373    }
374
375    pub fn range_text_parts(&self, bounds: RangeBounds) -> Vec<(NodeId, usize, usize)> {
376        fn append(
377            doc: &BaseDocument,
378            content: &RangeContent,
379            result: &mut Vec<(NodeId, usize, usize)>,
380        ) {
381            match content {
382                RangeContent::Data(id, start, end) => {
383                    if doc.get_node(*id).is_some_and(|node| node.is_text_node()) {
384                        result.push((*id, *start, *end));
385                    }
386                }
387                RangeContent::Partial(_, children) => {
388                    for child in children {
389                        append(doc, child, result);
390                    }
391                }
392                RangeContent::Full(id) => {
393                    let mut stack = vec![*id];
394                    while let Some(id) = stack.pop() {
395                        let Some(node) = doc.get_node(id) else {
396                            continue;
397                        };
398                        if let NodeData::Text(text) = &node.data {
399                            result.push((id, 0, text.content.encode_utf16().count()));
400                        } else {
401                            stack.extend(node.children.iter().rev().copied());
402                        }
403                    }
404                }
405            }
406        }
407        let mut result = Vec::new();
408        for content in self.range_contents(bounds) {
409            append(self, &content, &mut result);
410        }
411        result
412    }
413
414    pub fn range_string(&self, bounds: RangeBounds) -> String {
415        let mut result = String::new();
416        for (id, start, end) in self.range_text_parts(bounds) {
417            if let Some(text) = self.range_character_data(id) {
418                result.push_str(&utf16_slice(text, start, end));
419            }
420        }
421        result
422    }
423
424    fn range_layout_extents(&self, root: NodeId) -> HashMap<NodeId, (usize, usize)> {
425        use parley::PositionedLayoutItem;
426        let mut result: HashMap<NodeId, (usize, usize)> = HashMap::new();
427        let Some(inline) = self
428            .get_node(root)
429            .and_then(|node| node.element_data())
430            .and_then(|element| element.inline_layout_data.as_ref())
431        else {
432            return result;
433        };
434        for line in inline.layout.lines() {
435            for item in line.items() {
436                if let PositionedLayoutItem::GlyphRun(run) = item
437                    && let Some(id) = run.style().brush.text_node
438                {
439                    let range = run.run().text_range();
440                    result
441                        .entry(id)
442                        .and_modify(|(start, end)| {
443                            *start = (*start).min(range.start);
444                            *end = (*end).max(range.end);
445                        })
446                        .or_insert((range.start, range.end));
447                }
448            }
449        }
450        result
451    }
452
453    /// Translate DOM text offsets into the inline layout's byte offsets.
454    /// Extents are built once per inline root for this operation.
455    pub fn range_layout_ranges(&self, bounds: RangeBounds) -> Vec<(NodeId, usize, usize)> {
456        let mut extents = HashMap::new();
457        let mut result: Vec<(NodeId, usize, usize)> = Vec::new();
458        for (id, start, end) in self.range_text_parts(bounds) {
459            let Some(root) = self
460                .get_node(id)
461                .filter(|node| node.flags.is_in_document())
462                .and_then(|node| node.inline_root_ancestor())
463                .map(|node| node.id)
464            else {
465                continue;
466            };
467            let map = extents
468                .entry(root)
469                .or_insert_with(|| self.range_layout_extents(root));
470            let Some(&(base, limit)) = map.get(&id) else {
471                continue;
472            };
473            let Some(text) = self.range_character_data(id) else {
474                continue;
475            };
476            let start = (base + utf16_slice(text, 0, start).len()).min(limit);
477            let end = (base + utf16_slice(text, 0, end).len()).min(limit);
478            if start == end {
479                continue;
480            }
481            if let Some((last_root, _, last_end)) = result.last_mut()
482                && *last_root == root
483                && *last_end == start
484            {
485                *last_end = end;
486            } else {
487                result.push((root, start, end));
488            }
489        }
490        result
491    }
492
493    pub fn range_boundary_from_layout(
494        &self,
495        root: NodeId,
496        offset: usize,
497        end_boundary: bool,
498    ) -> Option<RangeBoundary> {
499        let extents = self.range_layout_extents(root);
500        let mut candidates: Vec<_> = extents.into_iter().collect();
501        candidates.sort_by_key(|(_, (start, _))| *start);
502        for (id, (start, end)) in candidates {
503            let matches = if end_boundary {
504                offset > start && offset <= end
505            } else {
506                offset >= start && offset < end
507            };
508            if matches {
509                let text = self.range_character_data(id)?;
510                let mut bytes = offset.saturating_sub(start).min(text.len());
511                while !text.is_char_boundary(bytes) {
512                    bytes -= 1;
513                }
514                return Some(RangeBoundary {
515                    node: id,
516                    offset: text[..bytes].encode_utf16().count(),
517                });
518            }
519        }
520        None
521    }
522
523    pub fn range_client_rects(&self, bounds: RangeBounds) -> Vec<crate::kurbo::Rect> {
524        use parley::{Affinity, Cursor, Selection};
525
526        fn element_rects(
527            doc: &BaseDocument,
528            content: &RangeContent,
529            result: &mut Vec<crate::kurbo::Rect>,
530        ) {
531            match content {
532                RangeContent::Full(id)
533                    if doc.get_node(*id).is_some_and(|node| node.is_element()) =>
534                {
535                    result.extend(doc.node_client_rects(*id).into_iter().map(|rect| {
536                        crate::kurbo::Rect::new(
537                            rect.x,
538                            rect.y,
539                            rect.x + rect.width,
540                            rect.y + rect.height,
541                        )
542                    }));
543                }
544                RangeContent::Partial(_, children) => {
545                    for child in children {
546                        element_rects(doc, child, result);
547                    }
548                }
549                _ => {}
550            }
551        }
552
553        let mut result = Vec::new();
554        for content in self.range_contents(bounds) {
555            element_rects(self, &content, &mut result);
556        }
557        for (root_id, start, end) in self.range_layout_ranges(bounds) {
558            let Some(root) = self.get_node(root_id) else {
559                continue;
560            };
561            let Some(inline) = root
562                .element_data()
563                .and_then(|element| element.inline_layout_data.as_ref())
564            else {
565                continue;
566            };
567            let layout = &inline.layout;
568            let scale = layout.scale() as f64;
569            let box_layout = root.final_layout();
570            let position = root.absolute_position(0.0, 0.0);
571            let x = position.x as f64 + (box_layout.padding.left + box_layout.border.left) as f64
572                - self.viewport_scroll.x;
573            let y = position.y as f64 + (box_layout.padding.top + box_layout.border.top) as f64
574                - self.viewport_scroll.y;
575            let selection = Selection::new(
576                Cursor::from_byte_index(layout, start, Affinity::Downstream),
577                Cursor::from_byte_index(layout, end, Affinity::Downstream),
578            );
579            selection.geometry_with(layout, |rect, _| {
580                let rect = self.transformed_client_rect(
581                    root_id,
582                    BoundingRect {
583                        x: x + rect.x0 / scale,
584                        y: y + rect.y0 / scale,
585                        width: (rect.x1 - rect.x0) / scale,
586                        height: (rect.y1 - rect.y0) / scale,
587                    },
588                );
589                result.push(crate::kurbo::Rect::new(
590                    rect.x,
591                    rect.y,
592                    rect.x + rect.width,
593                    rect.y + rect.height,
594                ));
595            });
596        }
597        result
598    }
599}
600
601pub(crate) type WeakRange = Weak<Mutex<RangeBounds>>;