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