Skip to main content

gpui_base/text/
range_highlight.rs

1//! Application-supplied highlights over the text a [`TextViewState`] renders,
2//! and scrolling one of its ranges into view.
3//!
4//! Ranges address the rendered text, the string plain copy produces: an
5//! application searches [`TextViewState::rendered_text`] and hands the ranges
6//! it found back. Each range is split into the text leaves it covers (a
7//! paragraph, a heading, a code block, a table cell), which paint it as a
8//! background behind their glyphs, so a highlight never changes layout. Text
9//! outside every leaf (the separators between blocks and cells, custom blocks,
10//! HTML blocks, inline objects) is left unpainted.
11//!
12//! An application that keeps ranges of the Markdown source instead, such as
13//! those [`TextViewState::selected_source_range`] returns, converts them with
14//! [`RenderedText::range_for_source`], which reads the source position the
15//! parser recorded for each rendered character.
16//!
17//! [`TextViewState`]: super::TextViewState
18//! [`TextViewState::rendered_text`]: super::TextViewState::rendered_text
19//! [`TextViewState::selected_source_range`]: super::TextViewState::selected_source_range
20
21#[cfg(not(target_family = "wasm"))]
22use std::time::Instant;
23use std::{
24    ops::Range,
25    sync::{Arc, Mutex, OnceLock},
26    time::Duration,
27};
28#[cfg(target_family = "wasm")]
29use web_time::Instant;
30
31use gpui::{Bounds, EntityId, Hsla, Pixels, SharedString};
32
33use super::{
34    document::ParsedDocument,
35    node::{BlockNode, Paragraph, SourceSegment},
36    stream_fade::{TextLeaf, TextLeafKey, text_leaves},
37};
38
39/// A snapshot of the text a [`TextViewState`](super::TextViewState) renders,
40/// as of one parse of its content.
41///
42/// Offsets into it are UTF-8 byte offsets. It is the string plain copy
43/// produces: `hello **world**` renders as `hello world`, escapes are
44/// resolved, and heading markers and list markers are left out. Blocks end
45/// with a newline and table cells are joined with a space; those separators
46/// belong to no block, so no highlight paints them.
47///
48/// Two snapshots are equal when they come from the same view and the same
49/// parse. Comparing the current [`rendered_text`] with the one last searched
50/// tells an observer of the view whether its content changed, so setting
51/// highlights, which notifies the view too, does not start another search.
52/// The text itself is only built when it is first read, from the parsed
53/// document the snapshot holds on to, so drop a snapshot that is no longer
54/// needed rather than keeping it past many changes.
55///
56/// [`rendered_text`]: super::TextViewState::rendered_text
57#[derive(Clone)]
58pub struct RenderedText {
59    owner: EntityId,
60    revision: usize,
61    document: ParsedDocument,
62    index: Arc<OnceLock<RenderedIndex>>,
63}
64
65impl std::fmt::Debug for RenderedText {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("RenderedText")
68            .field("owner", &self.owner)
69            .field("revision", &self.revision)
70            .finish_non_exhaustive()
71    }
72}
73
74impl RenderedText {
75    /// The text of `document`, whose index `index` holds once built.
76    pub(super) fn new(
77        owner: EntityId,
78        revision: usize,
79        document: ParsedDocument,
80        index: Arc<OnceLock<RenderedIndex>>,
81    ) -> Self {
82        Self {
83            owner,
84            revision,
85            document,
86            index,
87        }
88    }
89
90    /// The rendered text.
91    pub fn as_str(&self) -> &str {
92        &self.index().text
93    }
94
95    /// The length of the rendered text, in bytes.
96    pub fn len(&self) -> usize {
97        self.index().text.len()
98    }
99
100    /// Whether the view renders no text.
101    pub fn is_empty(&self) -> bool {
102        self.index().text.is_empty()
103    }
104
105    /// The source this text was rendered from, whose byte ranges
106    /// [`range_for_source`](Self::range_for_source) takes.
107    ///
108    /// It comes from the same parse as the text, so it trails the text last
109    /// given to the view until that text's parse lands. Before converting a
110    /// range, check that it indexes this source: while text is streamed in,
111    /// this source is a prefix of the text the application holds, and after
112    /// [`set_text`](super::TextViewState::set_text) it may be different text.
113    pub fn source(&self) -> &str {
114        &self.document.source
115    }
116
117    /// The range of this text rendered from `source`, a UTF-8 byte range of
118    /// [`Self::source`].
119    ///
120    /// This converts a range of the Markdown source, such as one
121    /// [`selected_source_range`] returned or one an application stored with
122    /// its own data, into the range a [`RangeHighlight`] takes. A character is
123    /// rendered from `source` when any of the source it was rendered from lies
124    /// in it: all of `&amp;` for `&`, the `\` and the `*` of `\*`, the whole
125    /// source of an inline object for its text. Source that renders nothing,
126    /// such as emphasis delimiters, heading and list markers, code fences,
127    /// table pipes and link destinations, adds nothing, so `**bold**` and
128    /// `bold` give the same range.
129    ///
130    /// The result is the smallest range holding every character rendered from
131    /// `source`. When `source` spans blocks, it also holds the separators
132    /// between them, which a highlight leaves unpainted. Text the parser
133    /// recorded no source position for, such as the text of inline HTML, is
134    /// only included when it lies between characters that are.
135    ///
136    /// Converting the source range a selection of this text reports gives the
137    /// selected range back, widened only to whole characters where several
138    /// share their source, like the text of an inline object. The separators
139    /// between blocks are rendered from no source, so one at either end of the
140    /// selection is left out: Select All gives back everything but the line
141    /// break after the last block.
142    ///
143    /// Returns `None` when `source` is empty, reversed, out of bounds, or not on
144    /// a character boundary, or when nothing is rendered from it. HTML views
145    /// record no source positions, so they always return `None`.
146    ///
147    /// [`selected_source_range`]: super::TextViewState::selected_source_range
148    pub fn range_for_source(&self, source: Range<usize>) -> Option<Range<usize>> {
149        let text = self.source();
150        if source.start >= source.end
151            || source.end > text.len()
152            || !text.is_char_boundary(source.start)
153            || !text.is_char_boundary(source.end)
154        {
155            return None;
156        }
157        self.index().range_for_source(&source)
158    }
159
160    pub(super) fn index(&self) -> &RenderedIndex {
161        self.index
162            .get_or_init(|| RenderedIndex::new(&self.document))
163    }
164}
165
166impl PartialEq for RenderedText {
167    fn eq(&self, other: &Self) -> bool {
168        self.owner == other.owner && self.revision == other.revision
169    }
170}
171
172impl Eq for RenderedText {}
173
174/// A background painted behind one range of a [`RenderedText`].
175///
176/// It is painted under the text and under the selection, and never changes
177/// layout. Where highlights overlap, the later one paints over the earlier.
178#[derive(Clone, Debug, PartialEq)]
179pub struct RangeHighlight {
180    range: Range<usize>,
181    background: Hsla,
182}
183
184impl RangeHighlight {
185    /// A highlight over `range`, in byte offsets of a [`RenderedText`].
186    pub fn new(range: Range<usize>, background: impl Into<Hsla>) -> Self {
187        Self {
188            range,
189            background: background.into(),
190        }
191    }
192
193    pub fn range(&self) -> Range<usize> {
194        self.range.clone()
195    }
196
197    pub fn background(&self) -> Hsla {
198        self.background
199    }
200}
201
202/// Why setting range highlights or revealing a range was rejected. Existing
203/// highlights and reveals stay unchanged.
204#[derive(Clone, Copy, Debug, Eq, PartialEq)]
205#[non_exhaustive]
206pub enum RangeHighlightError {
207    /// The view renders HTML, which records no source positions to address
208    /// its text by.
209    Unsupported,
210    /// The range at this index, the highlight's or the one revealed, is
211    /// reversed, out of bounds, or not on a character boundary.
212    InvalidRange(usize),
213}
214
215impl std::fmt::Display for RangeHighlightError {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        match self {
218            Self::Unsupported => f.write_str("HTML views do not support ranges of their text"),
219            Self::InvalidRange(ix) => write!(f, "range {ix} is not a range of the text"),
220        }
221    }
222}
223
224impl std::error::Error for RangeHighlightError {}
225
226/// The rendered text of one parsed document, and where each text leaf sits
227/// in it.
228#[derive(Debug, Default)]
229pub(super) struct RenderedIndex {
230    text: SharedString,
231    /// In document order, so by their position in `text`.
232    leaves: Vec<LeafSpan>,
233    /// Where the text of each top-level block sits, in document order.
234    blocks: Vec<Range<usize>>,
235    /// Where the pieces of `text` came from in the source, with `rendered` in
236    /// offsets of `text`, in the order of `text`. Text the parser recorded no
237    /// source position for is in none.
238    source_map: Vec<SourceSegment>,
239}
240
241#[derive(Debug)]
242struct LeafSpan {
243    /// Where the leaf's text sits in the rendered text.
244    range: Range<usize>,
245    key: TextLeafKey,
246    /// Inline objects in the leaf's text, in leaf offsets. They paint as
247    /// objects rather than as text, so no highlight paints them.
248    objects: Vec<Range<usize>>,
249}
250
251impl LeafSpan {
252    /// `offset` in the leaf's text, moved out of an inline object onto the
253    /// text after it, or before it at the end of the leaf. `None` when the
254    /// leaf has no text outside its objects.
255    fn text_offset_near(&self, offset: usize) -> Option<usize> {
256        let object_at = |offset: usize| self.objects.iter().find(|object| object.contains(&offset));
257        let mut after = offset;
258        while let Some(object) = object_at(after) {
259            after = object.end;
260        }
261        if after < self.range.len() {
262            return Some(after);
263        }
264        let mut before = offset;
265        while let Some(object) = object_at(before) {
266            before = object.start.checked_sub(1)?;
267        }
268        Some(before)
269    }
270}
271
272impl RenderedIndex {
273    pub(super) fn new(document: &ParsedDocument) -> Self {
274        let mut builder = IndexBuilder::default();
275        let mut blocks = Vec::with_capacity(document.blocks.len());
276        for block in document.blocks.iter() {
277            let start = builder.text.len();
278            builder.push_block(block);
279            blocks.push(start..builder.text.len());
280        }
281        let index = Self {
282            text: builder.text.into(),
283            leaves: builder.leaves,
284            blocks,
285            source_map: builder.source_map,
286        };
287        debug_assert_eq!(index.text.as_ref(), document.text());
288        index
289    }
290
291    /// The leaf ranges `range` paints over, which are none when it covers no
292    /// leaf text, or `None` when it is not a range of the text.
293    fn resolve(&self, range: &Range<usize>) -> Option<Vec<(TextLeafKey, Range<usize>)>> {
294        if range.start > range.end
295            || range.end > self.text.len()
296            || !self.text.is_char_boundary(range.start)
297            || !self.text.is_char_boundary(range.end)
298        {
299            return None;
300        }
301
302        let first = self
303            .leaves
304            .partition_point(|leaf| leaf.range.end <= range.start);
305        let mut pieces = Vec::new();
306        for leaf in &self.leaves[first..] {
307            if leaf.range.start >= range.end {
308                break;
309            }
310            let end = range.end.min(leaf.range.end) - leaf.range.start;
311            let mut cursor = range.start.max(leaf.range.start) - leaf.range.start;
312            for object in &leaf.objects {
313                if object.start >= end {
314                    break;
315                }
316                if object.end <= cursor {
317                    continue;
318                }
319                if object.start > cursor {
320                    pieces.push((leaf.key, cursor..object.start));
321                }
322                cursor = object.end;
323            }
324            if cursor < end {
325                pieces.push((leaf.key, cursor..end));
326            }
327        }
328        Some(pieces)
329    }
330
331    /// Where `range` starts: the line of the first leaf text it covers, or,
332    /// when it covers none, as an empty range does, of the leaf text at its
333    /// start or last before it in its top-level block, or else that whole
334    /// block. `None` when it is not a range of the text, or there is none.
335    fn locate(&self, range: &Range<usize>) -> Option<RevealTarget> {
336        if let Some((key, leaf_range)) = self.resolve(range)?.into_iter().next() {
337            return Some(RevealTarget::Line {
338                key,
339                offset: leaf_range.start,
340            });
341        }
342        let block_ix = self
343            .blocks
344            .partition_point(|block| block.end <= range.start)
345            .min(self.blocks.len().checked_sub(1)?);
346        let block_start = self.blocks[block_ix].start;
347        let ix = self
348            .leaves
349            .partition_point(|leaf| leaf.range.end <= range.start);
350        let leaf_offset = match self.leaves.get(ix) {
351            Some(leaf) if leaf.range.contains(&range.start) => {
352                Some((leaf, range.start - leaf.range.start))
353            }
354            // A position after the text of a leaf, on the separators after
355            // it or at the end of the text, is on the line of the last
356            // character before it in its block.
357            _ => ix
358                .checked_sub(1)
359                .and_then(|ix| self.leaves.get(ix))
360                .filter(|leaf| leaf.range.start >= block_start)
361                .and_then(|leaf| {
362                    let (last, _) = self.text[leaf.range.clone()].char_indices().last()?;
363                    Some((leaf, last))
364                }),
365        };
366        if let Some((leaf, offset)) = leaf_offset
367            && let Some(offset) = leaf.text_offset_near(offset)
368        {
369            return Some(RevealTarget::Line {
370                key: leaf.key,
371                offset,
372            });
373        }
374        Some(RevealTarget::Block { ix: block_ix })
375    }
376
377    /// The smallest range of the text holding every character whose source
378    /// overlaps `source`, a valid, non-empty range of the source.
379    fn range_for_source(&self, source: &Range<usize>) -> Option<Range<usize>> {
380        self.source_map
381            .iter()
382            .filter(|segment| {
383                segment.source.start < source.end && segment.source.end > source.start
384            })
385            .map(|segment| {
386                if !segment.linear {
387                    return segment.rendered.clone();
388                }
389                let start = segment.source.start.max(source.start) - segment.source.start;
390                let end = segment.source.end.min(source.end) - segment.source.start;
391                self.text
392                    .floor_char_boundary(segment.rendered.start + start)
393                    ..self.text.ceil_char_boundary(segment.rendered.start + end)
394            })
395            .reduce(|found, range| found.start.min(range.start)..found.end.max(range.end))
396    }
397}
398
399/// Builds the rendered text the way `BlockNode::text` does, recording each
400/// leaf, and where its text came from in the source, as it goes.
401#[derive(Default)]
402struct IndexBuilder {
403    text: String,
404    leaves: Vec<LeafSpan>,
405    source_map: Vec<SourceSegment>,
406}
407
408impl IndexBuilder {
409    fn push_block(&mut self, block: &BlockNode) {
410        let start = self.text.len();
411        match block {
412            BlockNode::Root { children, .. } | BlockNode::Blockquote { children, .. } => {
413                for child in children {
414                    self.push_block(child);
415                }
416            }
417            BlockNode::List { children, .. } | BlockNode::ListItem { children, .. } => {
418                for child in children {
419                    self.push_block(child);
420                }
421                return;
422            }
423            BlockNode::Paragraph(paragraph) => {
424                self.push_paragraph(
425                    paragraph,
426                    paragraph.span.map(|span| TextLeafKey::block(span.start)),
427                );
428            }
429            BlockNode::Heading { children, span, .. } => {
430                self.push_paragraph(children, span.map(|span| TextLeafKey::block(span.start)));
431            }
432            BlockNode::Table(table) => {
433                let mut ordinal = 0;
434                for row in table.children.iter().filter(|row| !row.children.is_empty()) {
435                    for (ix, cell) in row.children.iter().enumerate() {
436                        if ix > 0 {
437                            self.text.push(' ');
438                        }
439                        self.push_paragraph(
440                            &cell.children,
441                            table
442                                .span
443                                .map(|span| TextLeafKey::table_cell(span.start, ordinal)),
444                        );
445                        ordinal += 1;
446                    }
447                    self.text.push('\n');
448                }
449            }
450            BlockNode::CodeBlock(code_block) => {
451                let code = code_block.code();
452                self.push_source_segments(self.text.len(), &code, &code_block.source_segments);
453                self.push_leaf(
454                    &code,
455                    code_block.span.map(|span| TextLeafKey::block(span.start)),
456                    Vec::new(),
457                );
458            }
459            BlockNode::Custom(node) => {
460                self.push_object_source(self.text.len(), node.as_text(), node.source_range());
461                self.text.push_str(node.as_text());
462            }
463            BlockNode::Definition { .. }
464            | BlockNode::Break { .. }
465            | BlockNode::HorizontalRule { .. }
466            | BlockNode::Unknown => {}
467        }
468        if self.text.len() > start {
469            self.text.push('\n');
470        }
471    }
472
473    fn push_paragraph(&mut self, paragraph: &Paragraph, key: Option<TextLeafKey>) {
474        let start = self.text.len();
475        let mut text = String::new();
476        let mut objects = Vec::new();
477        for child in &paragraph.children {
478            let offset = start + text.len();
479            if let Some(custom) = &child.custom {
480                objects.push(text.len()..text.len() + child.text.len());
481                self.push_object_source(offset, &child.text, custom.source_range());
482            } else {
483                self.push_source_segments(offset, &child.text, &child.source_segments);
484            }
485            text.push_str(&child.text);
486        }
487        self.push_leaf(&text, key, objects);
488    }
489
490    /// Records `segments` of `text`, which is about to be pushed at `offset`.
491    ///
492    /// A segment that is empty or does not address `text` maps nothing and is
493    /// left out, so a bad one cannot map a range to text it did not render.
494    fn push_source_segments(&mut self, offset: usize, text: &str, segments: &[SourceSegment]) {
495        self.source_map.extend(
496            segments
497                .iter()
498                .filter(|segment| {
499                    !segment.rendered.is_empty()
500                        && !segment.source.is_empty()
501                        && segment.rendered.end <= text.len()
502                        && text.is_char_boundary(segment.rendered.start)
503                        && text.is_char_boundary(segment.rendered.end)
504                })
505                .map(|segment| SourceSegment {
506                    rendered: offset + segment.rendered.start..offset + segment.rendered.end,
507                    ..segment.clone()
508                }),
509        );
510    }
511
512    /// Records that all of `text`, an object about to be pushed at `offset`,
513    /// was rendered from `source`, which maps only as a whole.
514    fn push_object_source(&mut self, offset: usize, text: &str, source: Option<Range<usize>>) {
515        if let Some(source) = source {
516            self.push_source_segments(
517                offset,
518                text,
519                &[SourceSegment {
520                    rendered: 0..text.len(),
521                    source,
522                    linear: false,
523                }],
524            );
525        }
526    }
527
528    fn push_leaf(&mut self, text: &str, key: Option<TextLeafKey>, objects: Vec<Range<usize>>) {
529        let start = self.text.len();
530        self.text.push_str(text);
531        if let Some(key) = key
532            && !text.is_empty()
533        {
534            self.leaves.push(LeafSpan {
535                range: start..self.text.len(),
536                key,
537                objects,
538            });
539        }
540    }
541}
542
543/// The last cell and source end of each table row. Collect them once so
544/// remapping cells neither rescans a table nor recomputes a row's end.
545fn table_row_source_ends(blocks: &[BlockNode], rows: &mut Vec<(TextLeafKey, Option<usize>)>) {
546    for block in blocks {
547        match block {
548            BlockNode::Table(table) => {
549                let Some(span) = table.span else {
550                    continue;
551                };
552                let mut cell_count = 0;
553                for row in &table.children {
554                    if row.children.is_empty() {
555                        continue;
556                    }
557                    cell_count += row.children.len();
558                    let end = row
559                        .children
560                        .iter()
561                        .filter_map(|cell| paragraph_source_end(&cell.children))
562                        .max();
563                    rows.push((TextLeafKey::table_cell(span.start, cell_count - 1), end));
564                }
565            }
566            BlockNode::Root { children, .. }
567            | BlockNode::Blockquote { children, .. }
568            | BlockNode::List { children, .. }
569            | BlockNode::ListItem { children, .. } => table_row_source_ends(children, rows),
570            _ => {}
571        }
572    }
573}
574
575/// Where the source of `paragraph`'s text ends, when the parser recorded it.
576fn paragraph_source_end(paragraph: &Paragraph) -> Option<usize> {
577    paragraph
578        .children
579        .iter()
580        .flat_map(|node| {
581            node.source_segments
582                .iter()
583                .map(|segment| segment.source.end)
584                .chain(
585                    node.custom
586                        .as_ref()
587                        .and_then(|custom| custom.source_range())
588                        .map(|range| range.end),
589                )
590        })
591        .max()
592}
593
594/// The highlights each leaf paints, resolved once when they change so
595/// rendering only looks up its leaf.
596#[derive(Debug, Default)]
597pub(crate) struct RangeHighlightFrame {
598    /// Sorted by key. A leaf's backgrounds keep the order the application
599    /// gave them in, so a later one paints over an earlier one.
600    leaves: Vec<(TextLeafKey, Vec<(Range<usize>, Hsla)>)>,
601}
602
603impl RangeHighlightFrame {
604    /// Validates `highlights` against `text` and resolves them to leaves.
605    pub(super) fn new(
606        text: &RenderedText,
607        highlights: impl IntoIterator<Item = RangeHighlight>,
608    ) -> Result<Option<Self>, RangeHighlightError> {
609        let mut pieces = Vec::new();
610        for (ix, highlight) in highlights.into_iter().enumerate() {
611            let leaf_ranges = text
612                .index()
613                .resolve(&highlight.range)
614                .ok_or(RangeHighlightError::InvalidRange(ix))?;
615            pieces.extend(
616                leaf_ranges
617                    .into_iter()
618                    .map(|(key, range)| (key, range, highlight.background)),
619            );
620        }
621
622        // Stable, so each leaf keeps the application's order.
623        pieces.sort_by_key(|(key, _, _)| *key);
624        let mut leaves: Vec<(TextLeafKey, Vec<(Range<usize>, Hsla)>)> = Vec::new();
625        for (key, range, background) in pieces {
626            match leaves.last_mut() {
627                Some((last, backgrounds)) if *last == key => backgrounds.push((range, background)),
628                _ => leaves.push((key, vec![(range, background)])),
629            }
630        }
631        Ok((!leaves.is_empty()).then_some(Self { leaves }))
632    }
633
634    /// The backgrounds of leaf `key`, in its rendered byte space.
635    pub(crate) fn backgrounds(&self, key: TextLeafKey) -> &[(Range<usize>, Hsla)] {
636        self.leaves
637            .binary_search_by_key(&key, |(leaf, _)| *leaf)
638            .map_or(&[], |ix| self.leaves[ix].1.as_slice())
639    }
640
641    /// The highlights that still describe `new`, the document `remap` maps
642    /// the old one to: each follows its leaf as far as the leaf's text is
643    /// unchanged, and is dropped with a leaf that is gone.
644    pub(super) fn remap(&self, remap: &LeafRemap) -> Option<Self> {
645        let mut leaves = self
646            .leaves
647            .iter()
648            .filter_map(|(key, backgrounds)| {
649                let (new_key, unchanged) = remap.leaf(*key)?;
650                let clipped = backgrounds
651                    .iter()
652                    .filter(|(range, _)| range.start < unchanged)
653                    .map(|(range, background)| (range.start..range.end.min(unchanged), *background))
654                    .collect::<Vec<_>>();
655                (!clipped.is_empty()).then_some((new_key, clipped))
656            })
657            .collect::<Vec<_>>();
658        // Moving keys keeps their order, but stay safe for the binary search.
659        leaves.sort_by_key(|(key, _)| *key);
660        (!leaves.is_empty()).then_some(Self { leaves })
661    }
662}
663
664/// Where the text leaves of one parsed document are found in the document
665/// parsed after it.
666///
667/// A block that starts before the first change of the source is found at the
668/// same offset, and one after the last change at an offset moved by the
669/// change in length; a block that starts between them is gone. A leaf keeps
670/// its text up to where it first differs from before.
671pub(super) struct LeafRemap<'a> {
672    old_len: usize,
673    new_len: usize,
674    /// With an append, where the block it parsed again starts: every leaf
675    /// before it is unchanged.
676    tail_start: Option<usize>,
677    unchanged_prefix: usize,
678    unchanged_suffix: usize,
679    old_leaves: Vec<(TextLeafKey, TextLeaf<'a>)>,
680    new_leaves: Vec<(TextLeafKey, TextLeaf<'a>)>,
681    /// Sorted by each row's last cell. Unneeded for append-only remapping.
682    table_rows: Vec<(TextLeafKey, Option<usize>)>,
683}
684
685impl<'a> LeafRemap<'a> {
686    /// With `tail_only`, `new` was parsed by appending to `old`, which parses
687    /// only the last block of `old` again and keeps the others as they were.
688    pub(super) fn new(old: &'a ParsedDocument, new: &'a ParsedDocument, tail_only: bool) -> Self {
689        let (old_len, new_len) = (old.source.len(), new.source.len());
690        // An append starts after the old source, and parses its last block
691        // again, or only the new text when that block has no span.
692        let tail_start = tail_only.then(|| {
693            old.blocks
694                .last()
695                .and_then(BlockNode::span)
696                .map_or(old_len, |span| span.start)
697        });
698        let (unchanged_prefix, unchanged_suffix) = if tail_only {
699            (old_len, 0)
700        } else {
701            let prefix = old
702                .source
703                .bytes()
704                .zip(new.source.bytes())
705                .take_while(|(old, new)| old == new)
706                .count();
707            let shorter = old_len.min(new_len);
708            if prefix == shorter {
709                // One source extends the other, as when text is appended.
710                (prefix, 0)
711            } else {
712                // Where the two overlap, as when a deleted block starts like
713                // the block after it, the end wins: the blocks after a change
714                // keep following their text rather than their offset.
715                let suffix = old
716                    .source
717                    .bytes()
718                    .rev()
719                    .zip(new.source.bytes().rev())
720                    .take_while(|(old, new)| old == new)
721                    .count()
722                    .min(shorter);
723                (prefix.min(shorter - suffix), suffix)
724            }
725        };
726
727        fn leaves_from(
728            document: &ParsedDocument,
729            tail_start: Option<usize>,
730        ) -> Vec<(TextLeafKey, TextLeaf<'_>)> {
731            let mut leaves = Vec::new();
732            for block in document.blocks.iter().rev() {
733                if let Some(tail_start) = tail_start
734                    && block.span().is_none_or(|span| span.start < tail_start)
735                {
736                    break;
737                }
738                text_leaves(block, &mut leaves);
739            }
740            leaves.sort_by_key(|(key, _)| *key);
741            leaves
742        }
743
744        let mut table_rows = Vec::new();
745        if !tail_only {
746            table_row_source_ends(&old.blocks, &mut table_rows);
747            table_rows.sort_by_key(|(key, _)| *key);
748        }
749
750        Self {
751            old_len,
752            new_len,
753            tail_start,
754            unchanged_prefix,
755            unchanged_suffix,
756            old_leaves: leaves_from(old, tail_start),
757            new_leaves: leaves_from(new, tail_start),
758            table_rows,
759        }
760    }
761
762    /// Where leaf `key` is in the new document, and how much of its text is
763    /// unchanged, or `None` when it is gone.
764    pub(super) fn leaf(&self, key: TextLeafKey) -> Option<(TextLeafKey, usize)> {
765        if self
766            .tail_start
767            .is_some_and(|tail_start| key.block_start() < tail_start)
768        {
769            return Some((key, usize::MAX));
770        }
771        let new_key = key.moved_to(self.moved(key.block_start())?);
772        let old_leaf = Self::find(&self.old_leaves, key)?;
773        // A table's cells are only known by their place in it, so after a
774        // change inside the table a cell is the same one only when the source
775        // of its whole row ends before that change.
776        if key.cell_ix().is_some()
777            && key.block_start() < self.unchanged_prefix
778            && self.tail_start.is_none()
779            && self
780                .row_source_end(key)
781                .is_none_or(|end| end > self.unchanged_prefix)
782        {
783            return None;
784        }
785        let new_leaf = Self::find(&self.new_leaves, new_key)?;
786        Some((new_key, new_leaf.common_prefix_len(old_leaf)))
787    }
788
789    fn row_source_end(&self, key: TextLeafKey) -> Option<usize> {
790        let ix = self.table_rows.partition_point(|(last, _)| *last < key);
791        let (last, end) = self.table_rows.get(ix)?;
792        if last.block_start() != key.block_start() {
793            return None;
794        }
795        *end
796    }
797
798    /// Where the block starting at `start` in the old document starts in the
799    /// new one.
800    fn moved(&self, start: usize) -> Option<usize> {
801        if start < self.unchanged_prefix {
802            Some(start)
803        } else if start >= self.old_len - self.unchanged_suffix {
804            Some(start + self.new_len - self.old_len)
805        } else {
806            None
807        }
808    }
809
810    fn find<'b>(
811        leaves: &'b [(TextLeafKey, TextLeaf<'a>)],
812        key: TextLeafKey,
813    ) -> Option<&'b TextLeaf<'a>> {
814        let ix = leaves.binary_search_by_key(&key, |(leaf, _)| *leaf).ok()?;
815        Some(&leaves[ix].1)
816    }
817}
818
819#[cfg(test)]
820mod tests {
821    use std::{ops::Range, sync::Arc};
822
823    use gpui::{EntityId, hsla};
824
825    use super::{LeafRemap, RangeHighlight, RangeHighlightFrame, RenderedText, TextLeafKey};
826    use crate::text::{
827        document::ParsedDocument,
828        format,
829        node::{BlockNode, CodeBlock, NodeContext, Paragraph},
830    };
831
832    fn parse(markdown: &str) -> ParsedDocument {
833        format::markdown::parse(markdown, &mut NodeContext::default()).expect("parse Markdown")
834    }
835
836    #[test]
837    fn table_row_index_keeps_empty_cells_in_their_row() {
838        let source = "| a | b |\n|---|---|\n| é |   |\n|   |   |\n| c | d |\n";
839        let document = parse(source);
840        let remap = LeafRemap::new(&document, &document, false);
841        assert_eq!(remap.table_rows.len(), 4);
842        let ends = [Some("b"), Some("é"), None, Some("d")];
843        for (row, text) in ends.into_iter().enumerate() {
844            let expected = text.map(|text| source.find(text).unwrap() + text.len());
845            for column in 0..2 {
846                let key = TextLeafKey::table_cell(0, row * 2 + column);
847                assert_eq!(remap.row_source_end(key), expected, "{key:?}");
848            }
849        }
850        assert_eq!(remap.row_source_end(TextLeafKey::table_cell(0, 8)), None);
851    }
852
853    #[test]
854    fn table_row_index_finds_nested_tables_without_crossing_between_them() {
855        let source = concat!(
856            "| a |\n|---|\n| b |\n\n",
857            "> | c |\n> |---|\n> | d |\n\n",
858            "- | e |\n  |---|\n  | f |\n",
859        );
860        let document = parse(source);
861        let remap = LeafRemap::new(&document, &document, false);
862        assert_eq!(remap.table_rows.len(), 6);
863        for (header, body) in [("a", "b"), ("c", "d"), ("e", "f")] {
864            let start = source.find(&format!("| {header} |")).unwrap();
865            for (cell, text) in [header, body].into_iter().enumerate() {
866                let key = TextLeafKey::table_cell(start, cell);
867                assert_eq!(
868                    remap.row_source_end(key),
869                    Some(source.find(text).unwrap() + text.len()),
870                );
871            }
872            assert_eq!(
873                remap.row_source_end(TextLeafKey::table_cell(start, 2)),
874                None
875            );
876            assert_eq!(
877                remap.row_source_end(TextLeafKey::table_cell(start + 1, 0)),
878                None
879            );
880        }
881    }
882
883    #[test]
884    fn long_and_wide_tables_remap_highlights_by_whole_rows() {
885        for (rows, columns) in [(4096, 1), (2, 1024), (64, 16)] {
886            let row = format!("|{}\n", " x |".repeat(columns));
887            let separator = format!("|{}\n", "---|".repeat(columns));
888            let source = format!("{row}{separator}{}", row.repeat(rows));
889            let old = parse(&source);
890            let mut changed = source.clone();
891            // Even unchanged cells earlier in the edited row must lose their
892            // highlights, as must the unchanged rows that follow it.
893            let edit = row.len() + separator.len() + row.rfind('x').unwrap();
894            changed.replace_range(edit..edit + 1, "y");
895            let new = parse(&changed);
896            let remap = LeafRemap::new(&old, &new, false);
897            assert_eq!(remap.table_rows.len(), rows + 1);
898            let frame = RangeHighlightFrame {
899                leaves: remap
900                    .old_leaves
901                    .iter()
902                    .map(|(key, _)| (*key, vec![(0..1, hsla(0.15, 1., 0.5, 0.4))]))
903                    .collect(),
904            };
905            assert_eq!(frame.leaves.len(), (rows + 1) * columns);
906            let kept = frame.remap(&remap).unwrap();
907            assert_eq!(kept.leaves.len(), columns);
908            for column in 0..columns {
909                assert_eq!(
910                    kept.backgrounds(TextLeafKey::table_cell(0, column)),
911                    &[(0..1, hsla(0.15, 1., 0.5, 0.4))],
912                );
913            }
914        }
915    }
916
917    #[test]
918    fn append_remapping_does_not_build_a_table_row_index() {
919        let source = "| a |\n|---|\n| b |\n\n| c |\n|---|\n| d |\n";
920        let old = parse(source);
921        let new = parse(&format!("{source}| e |\n"));
922        let remap = LeafRemap::new(&old, &new, true);
923        assert!(remap.table_rows.is_empty());
924        let first = TextLeafKey::table_cell(0, 0);
925        assert_eq!(remap.leaf(first), Some((first, usize::MAX)));
926        let last_start = source.find("| c |").unwrap();
927        for cell in 0..2 {
928            let key = TextLeafKey::table_cell(last_start, cell);
929            assert_eq!(remap.leaf(key), Some((key, 1)));
930        }
931    }
932
933    #[test]
934    fn a_position_in_an_inline_object_moves_onto_text() {
935        use super::{LeafSpan, TextLeafKey};
936        // "ab" then two objects of 2 bytes each, then "cd".
937        let leaf = |len: usize| LeafSpan {
938            range: 10..10 + len,
939            key: TextLeafKey::block(0),
940            objects: vec![2..4, 4..6],
941        };
942        assert_eq!(leaf(8).text_offset_near(1), Some(1));
943        // Onto the text after the objects.
944        assert_eq!(leaf(8).text_offset_near(3), Some(6));
945        assert_eq!(leaf(8).text_offset_near(5), Some(6));
946        // At the end of the leaf, onto the text before them.
947        assert_eq!(leaf(6).text_offset_near(5), Some(1));
948    }
949
950    #[test]
951    fn range_highlight_requires_a_background() {
952        let color = hsla(0.15, 1., 0.5, 0.4);
953        let highlight = RangeHighlight::new(2..5, color);
954        assert_eq!(highlight.range(), 2..5);
955        assert_eq!(highlight.background(), color);
956    }
957
958    fn rendered(document: ParsedDocument) -> RenderedText {
959        RenderedText::new(EntityId::from(1), 0, document, Arc::default())
960    }
961
962    /// The range of `markdown` holding the `nth` (zero-based) occurrence of
963    /// `needle`.
964    fn nth(markdown: &str, needle: &str, nth: usize) -> Range<usize> {
965        let (start, _) = markdown
966            .match_indices(needle)
967            .nth(nth)
968            .unwrap_or_else(|| panic!("{needle:?} occurs {nth} times in {markdown:?}"));
969        start..start + needle.len()
970    }
971
972    /// The text rendered from `source` of `markdown`.
973    fn converted(markdown: &str, source: Range<usize>) -> Option<String> {
974        let text = rendered(parse(markdown));
975        let range = text.range_for_source(source)?;
976        Some(text.as_str()[range].to_string())
977    }
978
979    /// The text rendered from the first occurrence of `needle` in `markdown`.
980    fn converted_needle(markdown: &str, needle: &str) -> Option<String> {
981        converted(markdown, nth(markdown, needle, 0))
982    }
983
984    #[test]
985    fn source_rendering_nothing_converts_to_none() {
986        for (markdown, needle) in [
987            ("# Title", "# "),
988            ("hello **world**", "**"),
989            ("a ~~b~~ c", "~~"),
990            ("- item", "- "),
991            ("1. item", "1. "),
992            ("- [x] done", "[x] "),
993            ("> quote", "> "),
994            ("```rust\nlet x\n```", "```rust\n"),
995            ("```rust\nlet x\n```", "\n```"),
996            ("| a | b |\n|---|---|\n| c | d |", "|---|---|"),
997            ("| a | b |\n|---|---|\n| c | d |", " | "),
998            (
999                "see [docs](https://example.com) now",
1000                "(https://example.com)",
1001            ),
1002            ("a ![alt](image.png) b", "![alt](image.png)"),
1003            ("a\n\n---\n\nb", "---"),
1004            (
1005                "para\n\n[ref]: https://example.com",
1006                "[ref]: https://example.com",
1007            ),
1008        ] {
1009            assert_eq!(
1010                converted_needle(markdown, needle),
1011                None,
1012                "{needle:?} of {markdown:?}"
1013            );
1014        }
1015    }
1016
1017    #[test]
1018    fn delimiters_in_a_range_add_nothing() {
1019        let markdown = "hello **world** and `code`";
1020        assert_eq!(
1021            converted(markdown, 0..markdown.len()).as_deref(),
1022            Some("hello world and code")
1023        );
1024        assert_eq!(
1025            converted_needle(markdown, "**world**").as_deref(),
1026            Some("world")
1027        );
1028        assert_eq!(
1029            converted_needle(markdown, "o **wor").as_deref(),
1030            Some("o wor")
1031        );
1032        assert_eq!(
1033            converted_needle(markdown, "`code`").as_deref(),
1034            Some("code")
1035        );
1036        assert_eq!(
1037            converted_needle("# **Title**", "# **Title**").as_deref(),
1038            Some("Title")
1039        );
1040        assert_eq!(
1041            converted_needle("see [the docs](https://x.y) now", "[the docs](https://x.y)")
1042                .as_deref(),
1043            Some("the docs")
1044        );
1045    }
1046
1047    #[test]
1048    fn repeated_text_converts_to_the_occurrence_addressed() {
1049        let markdown = "foo **foo** foo";
1050        let text = rendered(parse(markdown));
1051        assert_eq!(text.as_str(), "foo foo foo\n");
1052        assert_eq!(text.range_for_source(nth(markdown, "foo", 0)), Some(0..3));
1053        assert_eq!(text.range_for_source(nth(markdown, "foo", 1)), Some(4..7));
1054        assert_eq!(text.range_for_source(nth(markdown, "foo", 2)), Some(8..11));
1055    }
1056
1057    #[test]
1058    fn fenced_code_backslashes_convert_individually() {
1059        let markdown = "```\na\\\\b\n```";
1060        let text = rendered(parse(markdown));
1061        assert_eq!(text.range_for_source(5..6), Some(1..2));
1062        assert_eq!(text.range_for_source(6..7), Some(2..3));
1063        assert_eq!(text.range_for_source(5..7), Some(1..3));
1064    }
1065
1066    #[test]
1067    fn literal_code_escapes_convert_individually() {
1068        for (markdown, first, second, rendered_start) in [
1069            ("`a\\\\b`", 2, 3, 1),
1070            (r"`a\*b`", 2, 3, 1),
1071            ("    one\n    a\\\\b", 13, 14, 5),
1072            ("- ```\n  a\\\\b\n  ```", 9, 10, 1),
1073            ("> ```\n> a\\\\b\n> ```", 9, 10, 1),
1074            ("```\na\\*b\n```", 5, 6, 1),
1075            ("```\na\\\\b\r\n```", 5, 6, 1),
1076            ("```\na\\\\\nb\n```", 5, 6, 1),
1077        ] {
1078            let text = rendered(parse(markdown));
1079            assert_eq!(
1080                text.range_for_source(first..first + 1),
1081                Some(rendered_start..rendered_start + 1),
1082                "{markdown:?}"
1083            );
1084            assert_eq!(
1085                text.range_for_source(second..second + 1),
1086                Some(rendered_start + 1..rendered_start + 2),
1087                "{markdown:?}"
1088            );
1089        }
1090        let text = rendered(parse(r"a\*b"));
1091        assert_eq!(text.range_for_source(1..2), Some(1..2));
1092        assert_eq!(text.range_for_source(2..3), Some(1..2));
1093        let text = rendered(parse("```\na\\\\\nb\n```"));
1094        assert_eq!(text.range_for_source(7..8), Some(3..4));
1095        assert_eq!(text.range_for_source(8..9), Some(4..5));
1096    }
1097
1098    #[test]
1099    fn a_character_converts_when_any_of_its_source_is_in_the_range() {
1100        // `&amp;` renders `&`: its name alone still renders that `&`.
1101        assert_eq!(converted_needle("a &amp; b", "amp").as_deref(), Some("&"));
1102        assert_eq!(converted_needle("a &amp; b", "&amp;").as_deref(), Some("&"));
1103        assert_eq!(
1104            converted_needle("&#65;&#x42;", "&#x42;").as_deref(),
1105            Some("B")
1106        );
1107        // `\*` renders `*`, from either of its characters.
1108        assert_eq!(converted_needle(r"a \* b", r"\").as_deref(), Some("*"));
1109        assert_eq!(converted_needle(r"a \* b", "*").as_deref(), Some("*"));
1110        // Text after an escape that starts a text node still converts
1111        // character for character.
1112        assert_eq!(converted_needle(r"\*abc", "b").as_deref(), Some("b"));
1113        assert_eq!(converted_needle(r"**\*abc**", "*a").as_deref(), Some("*a"));
1114        // A soft line break renders a space from the newline.
1115        assert_eq!(converted_needle("soft\nbreak", "\n").as_deref(), Some(" "));
1116        assert_eq!(
1117            converted_needle("soft\r\nbreak", "\r\n").as_deref(),
1118            Some(" ")
1119        );
1120        assert_eq!(
1121            converted_needle("soft\r\nbreak", "\n").as_deref(),
1122            Some(" ")
1123        );
1124    }
1125
1126    #[test]
1127    fn a_decoded_entity_converts_only_as_a_whole() {
1128        // `&acE;` decodes to `∾̳`, whose two characters take as many bytes
1129        // as the entity's source, but not character for character.
1130        let markdown = "a &acE; b";
1131        let text = rendered(parse(markdown));
1132        assert_eq!(text.as_str(), "a ∾̳ b\n");
1133        assert_eq!(converted_needle(markdown, "a").as_deref(), Some("a"));
1134        assert_eq!(converted_needle(markdown, "b").as_deref(), Some("b"));
1135        assert_eq!(converted_needle(markdown, "cE").as_deref(), Some("∾̳"));
1136        assert_eq!(converted_needle(markdown, "&acE;").as_deref(), Some("∾̳"));
1137        assert_eq!(converted_needle(markdown, "a &a").as_deref(), Some("a ∾̳"));
1138    }
1139
1140    #[test]
1141    fn multibyte_text_converts_on_character_boundaries() {
1142        let markdown = "中文 **粗体** 🎉 é";
1143        assert_eq!(converted_needle(markdown, "粗").as_deref(), Some("粗"));
1144        assert_eq!(
1145            converted_needle(markdown, "文 **粗").as_deref(),
1146            Some("文 粗")
1147        );
1148        assert_eq!(converted_needle(markdown, "🎉").as_deref(), Some("🎉"));
1149        assert_eq!(converted_needle(markdown, "é").as_deref(), Some("é"));
1150        // A range splitting a character is not a range of the source.
1151        let split = nth(markdown, "粗", 0);
1152        assert_eq!(converted(markdown, split.start..split.start + 1), None);
1153        assert_eq!(converted(markdown, split.start + 1..split.end), None);
1154    }
1155
1156    #[test]
1157    fn ranges_that_are_not_ranges_of_the_source_convert_to_none() {
1158        let text = rendered(parse("hello world"));
1159        assert_eq!(text.range_for_source(3..3), None);
1160        assert_eq!(text.range_for_source(Range { start: 5, end: 3 }), None);
1161        assert_eq!(text.range_for_source(0..12), None);
1162        assert_eq!(text.range_for_source(20..30), None);
1163        assert_eq!(text.range_for_source(0..11), Some(0..11));
1164
1165        let empty = rendered(parse(""));
1166        assert_eq!(empty.source(), "");
1167        assert_eq!(empty.range_for_source(0..0), None);
1168    }
1169
1170    #[test]
1171    fn a_range_across_blocks_holds_the_separators_between_them() {
1172        let markdown = "ab\n\ncd\n\n# ef";
1173        // "ab\ncd\nef\n"
1174        assert_eq!(
1175            converted_needle(markdown, "b\n\ncd\n\n# e").as_deref(),
1176            Some("b\ncd\ne")
1177        );
1178
1179        let table = "| a | b |\n|---|---|\n| c | d |";
1180        assert_eq!(
1181            converted_needle(table, "b |\n|---|---|\n| c").as_deref(),
1182            Some("b\nc")
1183        );
1184        assert_eq!(converted_needle(table, "c | d").as_deref(), Some("c d"));
1185
1186        let list = "- one\n- two\n  - three";
1187        assert_eq!(
1188            converted_needle(list, "ne\n- two\n  - th").as_deref(),
1189            Some("ne\ntwo\nth")
1190        );
1191    }
1192
1193    #[test]
1194    fn code_blocks_convert_their_body() {
1195        let fenced = "```rust\nlet x = 1;\nlet y = 2;\n```";
1196        assert_eq!(
1197            converted_needle(fenced, "x = 1;\nlet y").as_deref(),
1198            Some("x = 1;\nlet y")
1199        );
1200        assert_eq!(
1201            converted(fenced, 0..fenced.len()).as_deref(),
1202            Some("let x = 1;\nlet y = 2;")
1203        );
1204        let indented = "    let x = 1;\n    let y = 2;";
1205        assert_eq!(
1206            converted_needle(indented, "x = 1").as_deref(),
1207            Some("x = 1")
1208        );
1209        let tilde = "~~~\nwavy\n~~~";
1210        assert_eq!(converted(tilde, 0..tilde.len()).as_deref(), Some("wavy"));
1211    }
1212
1213    #[test]
1214    fn an_image_between_texts_keeps_the_range_contiguous() {
1215        let markdown = "a ![alt](image.png) b";
1216        let text = rendered(parse(markdown));
1217        let range = text.range_for_source(0..markdown.len()).unwrap();
1218        assert_eq!(&text.as_str()[range], "a  b");
1219    }
1220
1221    #[test]
1222    fn html_text_converts_nothing() {
1223        let html = "<p>one <b>two</b></p>";
1224        let document = format::html::parse(html, &mut NodeContext::default()).expect("parse HTML");
1225        let text = rendered(document);
1226        assert_eq!(text.source(), html);
1227        assert!(!text.is_empty());
1228        for start in 0..html.len() {
1229            for end in start + 1..=html.len() {
1230                assert_eq!(text.range_for_source(start..end), None, "{start}..{end}");
1231            }
1232        }
1233    }
1234
1235    #[test]
1236    fn custom_blocks_convert_whole() {
1237        let extensions = crate::text::MarkdownExtensions::default().block_parser(|node, cx| {
1238            let markdown::mdast::Node::Paragraph(paragraph) = node else {
1239                return None;
1240            };
1241            let [markdown::mdast::Node::Text(text)] = paragraph.children.as_slice() else {
1242                return None;
1243            };
1244            text.value.starts_with('$').then(|| {
1245                crate::text::MarkdownNode::new("ticker", ())
1246                    .text(text.value.clone())
1247                    .markdown(cx.node_source(node).unwrap_or_default())
1248            })
1249        });
1250        let markdown = "before\n\n$TSLA.US\n\nafter";
1251        let mut cx = NodeContext {
1252            markdown_extensions: extensions.into(),
1253            ..NodeContext::default()
1254        };
1255        let text = rendered(format::markdown::parse(markdown, &mut cx).unwrap());
1256
1257        assert_eq!(text.as_str(), "before\n$TSLA.US\nafter\n");
1258        let ticker = nth(markdown, "$TSLA.US", 0);
1259        assert_eq!(text.range_for_source(ticker.clone()), Some(7..15));
1260        // Part of the block's source converts the whole block.
1261        assert_eq!(
1262            text.range_for_source(ticker.start + 1..ticker.start + 3),
1263            Some(7..15)
1264        );
1265        assert_eq!(
1266            text.range_for_source(nth(markdown, "re\n\n$T", 0)),
1267            Some(4..15)
1268        );
1269    }
1270
1271    /// A block whose text is a leaf, in the order of the rendered text.
1272    enum LeafNode<'a> {
1273        Paragraph(&'a Paragraph),
1274        Code(&'a CodeBlock),
1275    }
1276
1277    impl LeafNode<'_> {
1278        fn text(&self) -> String {
1279            match self {
1280                Self::Paragraph(paragraph) => paragraph
1281                    .children
1282                    .iter()
1283                    .map(|child| child.text.as_ref())
1284                    .collect(),
1285                Self::Code(code_block) => code_block.code().to_string(),
1286            }
1287        }
1288
1289        /// Selects `range` of its text, as painting a selection does.
1290        ///
1291        /// A paragraph paints the run of text before each inline image in
1292        /// that image's state and the rest in its own, so each state gets its
1293        /// run and the part of `range` inside it.
1294        fn select(&self, range: Option<Range<usize>>) {
1295            match self {
1296                Self::Paragraph(paragraph) => {
1297                    let text = self.text();
1298                    let select_run = |state: &std::sync::Mutex<_>, run: Range<usize>| {
1299                        let mut state: std::sync::MutexGuard<'_, crate::text::inline::InlineState> =
1300                            state.lock().unwrap();
1301                        state.set_text(text[run.clone()].to_string().into());
1302                        state.selection = range
1303                            .as_ref()
1304                            .map(|range| range.start.max(run.start)..range.end.min(run.end))
1305                            .filter(|selected| selected.start < selected.end)
1306                            .map(|selected| {
1307                                (selected.start - run.start..selected.end - run.start).into()
1308                            });
1309                    };
1310                    let (mut run_start, mut offset) = (0, 0);
1311                    for child in &paragraph.children {
1312                        assert!(child.custom.is_none(), "inline objects select on their own");
1313                        if child.image.is_some() {
1314                            select_run(&child.state, run_start..offset);
1315                            run_start = offset;
1316                        }
1317                        offset += child.text.len();
1318                    }
1319                    select_run(&paragraph.state, run_start..offset);
1320                }
1321                Self::Code(code_block) => match range {
1322                    Some(range) => code_block.set_selection(range),
1323                    None => code_block.clear_selection(),
1324                },
1325            }
1326        }
1327    }
1328
1329    /// The blocks holding text, the way `IndexBuilder` walks them.
1330    fn leaf_nodes<'a>(blocks: &'a [BlockNode], leaves: &mut Vec<LeafNode<'a>>) {
1331        for block in blocks {
1332            match block {
1333                BlockNode::Root { children, .. }
1334                | BlockNode::Blockquote { children, .. }
1335                | BlockNode::List { children, .. }
1336                | BlockNode::ListItem { children, .. } => leaf_nodes(children, leaves),
1337                BlockNode::Paragraph(paragraph) => leaves.push(LeafNode::Paragraph(paragraph)),
1338                BlockNode::Heading { children, .. } => leaves.push(LeafNode::Paragraph(children)),
1339                BlockNode::Table(table) => {
1340                    for row in &table.children {
1341                        for cell in &row.children {
1342                            leaves.push(LeafNode::Paragraph(&cell.children));
1343                        }
1344                    }
1345                }
1346                BlockNode::CodeBlock(code_block) => leaves.push(LeafNode::Code(code_block)),
1347                _ => {}
1348            }
1349        }
1350        leaves.retain(|leaf| !leaf.text().is_empty());
1351    }
1352
1353    /// Checks that selecting any range of `markdown`'s rendered text that
1354    /// starts and ends inside text, and converting the source range the
1355    /// selection reports, gives the selected range back.
1356    ///
1357    /// The selection's source range comes from `selected_source_range`, which
1358    /// maps the other way with code of its own, so the two check each other.
1359    fn assert_selections_round_trip(markdown: &str) {
1360        let document = parse(markdown);
1361        let text = rendered(document.clone());
1362        // Only a character maps as a whole, or the characters one entity
1363        // decodes to: a longer piece would widen every range inside it.
1364        for segment in text
1365            .index()
1366            .source_map
1367            .iter()
1368            .filter(|segment| !segment.linear)
1369        {
1370            let rendered = &text.as_str()[segment.rendered.clone()];
1371            let source = &markdown[segment.source.clone()];
1372            assert!(
1373                rendered.chars().count() == 1 || (source.starts_with('&') && source.ends_with(';')),
1374                "{markdown:?}: {rendered:?} maps from {source:?} only as a whole"
1375            );
1376        }
1377        let mut leaves = Vec::new();
1378        leaf_nodes(&document.blocks, &mut leaves);
1379        let spans = &text.index().leaves;
1380        assert_eq!(leaves.len(), spans.len(), "leaves of {markdown:?}");
1381        let leaves = leaves
1382            .into_iter()
1383            .zip(spans.iter().map(|span| span.range.clone()))
1384            .collect::<Vec<_>>();
1385        for (_, range) in &leaves {
1386            assert!(text.as_str().is_char_boundary(range.start));
1387        }
1388
1389        // Every character boundary inside a leaf, as (leaf, offset in text).
1390        let boundaries = leaves
1391            .iter()
1392            .enumerate()
1393            .flat_map(|(ix, (_, range))| {
1394                let leaf_text = &text.as_str()[range.clone()];
1395                leaf_text
1396                    .char_indices()
1397                    .map(|(offset, _)| offset)
1398                    .chain([leaf_text.len()])
1399                    .map(move |offset| (ix, range.start + offset))
1400            })
1401            .collect::<Vec<_>>();
1402
1403        for (start_ix, &(first, start)) in boundaries.iter().enumerate() {
1404            if start == leaves[first].1.end {
1405                continue;
1406            }
1407            for &(last, end) in &boundaries[start_ix + 1..] {
1408                if end == leaves[last].1.start || end <= start {
1409                    continue;
1410                }
1411                for (ix, (leaf, range)) in leaves.iter().enumerate() {
1412                    let selected = (first..=last).contains(&ix).then(|| {
1413                        start.max(range.start) - range.start..end.min(range.end) - range.start
1414                    });
1415                    leaf.select(selected);
1416                }
1417                let source = document.selected_source_range().unwrap_or_else(|| {
1418                    panic!("{markdown:?}: selecting {start}..{end} maps to no source")
1419                });
1420                // Characters that share their source, like the two an entity
1421                // can decode to, are only selected together.
1422                let expected = text
1423                    .index()
1424                    .source_map
1425                    .iter()
1426                    .filter(|segment| {
1427                        !segment.linear
1428                            && segment.rendered.start < end
1429                            && segment.rendered.end > start
1430                    })
1431                    .fold(start..end, |range, segment| {
1432                        range.start.min(segment.rendered.start)..range.end.max(segment.rendered.end)
1433                    });
1434                assert_eq!(
1435                    text.range_for_source(source.clone()),
1436                    Some(expected),
1437                    "{markdown:?}: selecting {:?} ({start}..{end}) reports source {:?} ({source:?})",
1438                    &text.as_str()[start..end],
1439                    &markdown[source.clone()],
1440                );
1441            }
1442        }
1443        for (leaf, _) in &leaves {
1444            leaf.select(None);
1445        }
1446    }
1447
1448    const ROUND_TRIP_CORPUS: &[&str] = &[
1449        "plain text",
1450        "hello **world** and *em* and ~~del~~ and `code`",
1451        "nested **bold *and italic* text** and ***both***",
1452        "`` code with ` backtick `` then text",
1453        "# Heading with **bold**",
1454        "Setext heading\n===",
1455        "> quoted **text**\n> continued\n>\n> second paragraph",
1456        "- item one\n- item **two**\n  - nested *item*\n\n  continued item",
1457        "3. third\n4. fourth",
1458        "- [x] done\n- [ ] todo",
1459        "| a | **b** |\n|---|:---:|\n| c `d` | e |\n| | f |",
1460        "```rust\nlet x = 1;\n\nlet y = 2;\n```",
1461        "    indented code\n    more",
1462        "~~~\ntilde fence\n~~~",
1463        "a [link](https://example.com \"title\") b",
1464        "a [reference] b\n\n[reference]: https://example.com",
1465        "<https://auto.example> and https://gfm.example",
1466        "soft\nbreak\nlines",
1467        "hard  \nbreak\\\nagain",
1468        "trailing spaces   \nnext line",
1469        "escapes \\* \\_ \\` \\\\ and \\[not a link\\]",
1470        "\\*starts escaped and **\\*bold** and *\\_em*",
1471        "entity &acE; as long as its characters",
1472        "entities &amp; &lt; &#65; &#x42; &copy; end",
1473        "中文 **粗体** 和 `代码` 🎉 é and e\u{301}",
1474        "crlf\r\nlines\r\n\r\nnext paragraph",
1475        "image ![alt](image.png) between",
1476        "one\n\ntwo\n\n---\n\nthree",
1477        "# Title\n\nIntro with **bold**.\n\n- first\n- second\n\n| a | b |\n|---|---|\n| c | d |\n\n```\ncode\n```\n\n> quote",
1478    ];
1479
1480    #[test]
1481    fn selections_round_trip_through_source_ranges() {
1482        for markdown in ROUND_TRIP_CORPUS {
1483            assert_selections_round_trip(markdown);
1484        }
1485    }
1486}
1487
1488/// Where a range to reveal starts.
1489#[derive(Clone, Copy, Debug, PartialEq)]
1490enum RevealTarget {
1491    /// A line of a text leaf: the leaf, and the offset in its text.
1492    Line { key: TextLeafKey, offset: usize },
1493    /// A whole top-level block, for text that belongs to no leaf.
1494    Block { ix: usize },
1495}
1496
1497/// How long a reveal keeps trying. One that has not been carried out by
1498/// then, e.g. because its view was not painted, is dropped rather than
1499/// scrolling long after it was asked for.
1500const REVEAL_TIMEOUT: Duration = Duration::from_secs(1);
1501
1502/// How many frames a reveal whose line was laid out but not visible keeps
1503/// trying, e.g. while an enclosing container scrolls to it.
1504const REVEAL_ATTEMPTS: usize = 8;
1505
1506/// Where the line a reveal starts on was laid out in one frame, in window
1507/// coordinates, and whether it was inside the visible area.
1508#[derive(Clone, Copy, Debug)]
1509struct RevealReport {
1510    line: Bounds<Pixels>,
1511    visible: bool,
1512}
1513
1514/// A range [`TextViewState::reveal_range`](super::TextViewState::reveal_range)
1515/// is scrolling into view.
1516///
1517/// The `Inline` that lays out the start of the range asks the enclosing list
1518/// to scroll its line into view during prepaint, and reports where the line
1519/// ended up. The view reads the report once painted, after any list has
1520/// scrolled, and is done once the line is visible.
1521#[derive(Debug)]
1522pub(super) struct PendingReveal {
1523    target: RevealTarget,
1524    requested_at: Instant,
1525    /// What the target's `Inline` reported this frame; `None` when it was not
1526    /// laid out.
1527    report: Arc<Mutex<Option<RevealReport>>>,
1528    attempts: usize,
1529}
1530
1531/// How a pending reveal went in one frame.
1532pub(super) enum RevealProgress {
1533    /// The line is visible, so the reveal is done.
1534    Shown,
1535    /// The line was laid out at these window bounds without being visible.
1536    Hidden(Bounds<Pixels>),
1537    /// The line was not laid out.
1538    NotLaidOut,
1539}
1540
1541impl PendingReveal {
1542    /// The start of `range` in `text`, asked for at `now`, or `None` when
1543    /// the range is not a range of it.
1544    pub(super) fn new(text: &RenderedText, range: &Range<usize>, now: Instant) -> Option<Self> {
1545        Some(Self {
1546            target: text.index().locate(range)?,
1547            requested_at: now,
1548            report: Arc::default(),
1549            attempts: 0,
1550        })
1551    }
1552
1553    pub(super) fn is_expired(&self, now: Instant) -> bool {
1554        now.saturating_duration_since(self.requested_at) > REVEAL_TIMEOUT
1555            || self.attempts >= REVEAL_ATTEMPTS
1556    }
1557
1558    /// Whether the reveal is of a whole block rather than a line.
1559    pub(super) fn is_block(&self) -> bool {
1560        matches!(self.target, RevealTarget::Block { .. })
1561    }
1562
1563    /// The index of the top-level block of `document` the reveal starts in.
1564    pub(super) fn block_ix(&self, document: &ParsedDocument) -> Option<usize> {
1565        match self.target {
1566            RevealTarget::Line { key, .. } => document.blocks.iter().rposition(|block| {
1567                block
1568                    .span()
1569                    .is_some_and(|span| span.start <= key.block_start())
1570            }),
1571            RevealTarget::Block { ix } => (ix < document.blocks.len()).then_some(ix),
1572        }
1573    }
1574
1575    /// Whether the line was laid out in the previous frame.
1576    pub(super) fn was_laid_out(&self) -> bool {
1577        self.report.lock().is_ok_and(|report| report.is_some())
1578    }
1579
1580    /// Starts a frame: forgets the previous report and hands the line to
1581    /// rendering. A block has no line.
1582    pub(super) fn request(&self) -> Option<RevealRequest> {
1583        if let Ok(mut report) = self.report.lock() {
1584            *report = None;
1585        }
1586        let RevealTarget::Line { key, offset } = self.target else {
1587            return None;
1588        };
1589        Some(RevealRequest {
1590            key,
1591            offset,
1592            report: self.report.clone(),
1593        })
1594    }
1595
1596    /// Ends a frame with what the line reported, counting a frame in which
1597    /// it was laid out but hidden as an attempt.
1598    pub(super) fn progress(&mut self) -> RevealProgress {
1599        let report = self.report.lock().ok().and_then(|report| *report);
1600        match report {
1601            Some(report) if report.visible => RevealProgress::Shown,
1602            Some(report) => {
1603                self.attempts += 1;
1604                RevealProgress::Hidden(report.line)
1605            }
1606            None => RevealProgress::NotLaidOut,
1607        }
1608    }
1609
1610    /// The reveal in the document `remap` maps the old one to, as long as
1611    /// the text it starts at is unchanged.
1612    pub(super) fn remap(mut self, remap: &LeafRemap) -> Option<Self> {
1613        let RevealTarget::Line { key, offset } = self.target else {
1614            return None;
1615        };
1616        let (key, unchanged) = remap.leaf(key)?;
1617        (offset < unchanged).then_some(())?;
1618        self.target = RevealTarget::Line { key, offset };
1619        Some(self)
1620    }
1621}
1622
1623/// The start of a pending reveal, as rendering hands it to the `Inline`
1624/// that lays that text out.
1625#[derive(Clone, Debug)]
1626pub(crate) struct RevealRequest {
1627    key: TextLeafKey,
1628    offset: usize,
1629    report: Arc<Mutex<Option<RevealReport>>>,
1630}
1631
1632impl RevealRequest {
1633    /// The start of the reveal, when it is in `key`'s text between `start`
1634    /// and `end`, rebased to `start`.
1635    pub(crate) fn at(
1636        &self,
1637        key: Option<TextLeafKey>,
1638        start: usize,
1639        end: usize,
1640    ) -> Option<RevealAt> {
1641        if key != Some(self.key) {
1642            return None;
1643        }
1644        RevealAt {
1645            offset: self.offset,
1646            report: self.report.clone(),
1647        }
1648        .rebase(start, end)
1649    }
1650}
1651
1652/// The start of a pending reveal, in the byte space of one run of text.
1653#[derive(Clone, Debug)]
1654pub(crate) struct RevealAt {
1655    offset: usize,
1656    report: Arc<Mutex<Option<RevealReport>>>,
1657}
1658
1659impl RevealAt {
1660    pub(crate) fn offset(&self) -> usize {
1661        self.offset
1662    }
1663
1664    /// The reveal in the text between `start` and `end`, rebased to `start`,
1665    /// or `None` when it starts outside it.
1666    pub(crate) fn rebase(&self, start: usize, end: usize) -> Option<Self> {
1667        (start..end).contains(&self.offset).then(|| Self {
1668            offset: self.offset - start,
1669            report: self.report.clone(),
1670        })
1671    }
1672
1673    /// The reveal moved into the text between `start` and `end` and rebased
1674    /// to `start`: one before it moves to its first character, one after it
1675    /// to its end.
1676    pub(crate) fn clamp(&self, start: usize, end: usize) -> Self {
1677        Self {
1678            offset: self.offset.clamp(start, end) - start,
1679            report: self.report.clone(),
1680        }
1681    }
1682
1683    /// Report where the line the reveal starts on was laid out, in window
1684    /// coordinates, and whether it was inside the visible area.
1685    pub(crate) fn report(&self, line: Bounds<Pixels>, visible: bool) {
1686        if let Ok(mut report) = self.report.lock() {
1687            *report = Some(RevealReport { line, visible });
1688        }
1689    }
1690}