Skip to content

markdown: add source & range_for_source - #3281

Open
tidely wants to merge 2 commits into
longbridge:mainfrom
tidely:text-view-source-range-highlights
Open

tidely wants to merge 2 commits into
longbridge:mainfrom
tidely:text-view-source-range-highlights

Conversation

@tidely

@tidely tidely commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Description

#3215 added range highlights, which are addressed by offsets into rendered_text(). But an application often knows what was selected in terms of the Markdown source: TextViewState::selected_source_range() reports source ranges, and apps store those ranges alongside their own data (comments, annotations, citations). Until now there was no way to turn a source range back into a rendered range, so a stored source selection could not be highlighted again.

This PR adds RenderedText::range_for_source, which converts a UTF-8 byte range of the Markdown source into the matching range of the rendered text, so it can be passed to RangeHighlight. It also adds RenderedText::source, the source of the same parse, so callers can check that their ranges index the source the view actually rendered (while streaming it is a prefix; after set_text it may still be the previous text until the new parse lands).

How it works:

  • RenderedIndex now also builds a source map from the SourceSegments the parser already records for paragraphs, headings, table cells and code blocks. Custom blocks and inline plugin objects map as a whole to their source_range().
  • SourceSegment gets a linear flag: whether each rendered character came from a source character of the same length, so part of the segment can map to part of its source. Before, this was inferred from rendered.len() == source.len(), which is wrong for entities whose decoded text happens to be as long as the entity (∾̳ → ∾̳, 5 bytes each). Entities, escapes and soft breaks now always map as a whole.
  • Source that renders nothing (emphasis delimiters, heading/list markers, code fences, table pipes, link destinations) adds nothing, so **bold** and bold convert to the same range.
  • Round trip: converting the source range a selection reports gives the selected range back, except that a line break between blocks at either end is left out, since no source renders it.
  • HTML views record no source positions and always return None.

The linear flag also changes existing behavior: text after an escaped character now maps character for character. Previously the escape made the whole text run map only as a block, so selecting b in \*abc reported more source than was selected.

Public API

gpui-base (re-exported as gpui_component::text::RenderedText)

  • gpui_base::text::RenderedText::source(&self) -> &str: the Markdown source this rendered text was parsed from, whose byte ranges range_for_source takes.
  • gpui_base::text::RenderedText::range_for_source(&self, source: Range<usize>) -> Option<Range<usize>>: converts a source byte range (e.g. from selected_source_range()) into the rendered-text range a RangeHighlight takes; None for empty, reversed, out-of-bounds or non-char-boundary ranges, ranges that render nothing, and HTML views.

How to Test

  • cargo test -p gpui-base --lib text::
  • New unit tests in range_highlight.rs cover emphasis, entities, escapes, code blocks, tables, custom blocks, inline objects and HTML.
  • New TestAppContext tests in state.rs cover Select All and dragged-selection round trips, streaming appends, background parses landing, snapshots converting against their own source, and HTML views.
  • New tests in markdown.rs cover entities as long as their decoded characters (&acE;) and text after an escape.
  • Docs: see "Highlight ranges" in website/component/text-view.md (en and zh-CN) for a highlight_comments example that re-highlights stored source ranges.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes.
  • Tested macOS, Windows and Linux platforms performance (if the change is platform-specific)

@tidely
tidely marked this pull request as draft September 27, 2026 22:34
@tidely
tidely force-pushed the text-view-source-range-highlights branch from 1ba5468 to 7221242 Compare September 27, 2026 22:34
@tidely
tidely marked this pull request as ready for review September 27, 2026 22:37
@tidely tidely changed the title add source & range_for_source feat(markdown): add source & range_for_source Sep 27, 2026
@huacnlee huacnlee changed the title feat(markdown): add source & range_for_source markdown: add source & range_for_source Sep 28, 2026

@huacnlee huacnlee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue with code blocks now that they join the source map; everything else (escape splitting, the linear flag, range rejection, custom blocks, table cell offsets) checks out.

Comment thread crates/base/src/text/range_highlight.rs
@tidely
tidely requested a review from huacnlee September 29, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants