text_view: Add range highlights - #3215
Conversation
Let applications highlight ranges of the rendered text, e.g. search results, without reparsing the document. Refs longbridge#3214
|
@white07S I simplified the range highlight API in 1c2458b: removed |
|
@huacnlee Thanks, that matches what I intended, and dropping the snapshot argument makes it simpler to use. Two small things: the |
Closes #3214 Follows #3215, which added the range highlights this builds on. ## Description Adds `reveal_range`, which scrolls the line where a range starts into view, for example the current search result, or a line deep inside a long paragraph. It works in a scrollable `TextView` and inside an app's `gpui::list`, such as a chat. Other scroll containers, like a `div` with `overflow_y_scroll`, can use the new `TextView::on_reveal` callback. It gets the line's position, so the app can scroll its own container. It doesn't scroll when the line is already visible, and it follows the content the same way highlights do. It also gives up after a second if the line can't be shown, so it never jumps late. The markdown example gets previous and next buttons. Enter and Shift+Enter in the find field do the same. Not covered: text scrolled sideways inside a table, and a scrollable `TextView` inside an app list, which only scrolls itself. Also fixes a `RangeHighlight` doc comment that was cut off mid-sentence in #3215. ## Public API ### gpui-base Also available on the `gpui_component::text::TextView` wrapper. - `TextViewState::reveal_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) -> Result<(), RangeHighlightError>`: scrolls the line where `range` starts into view. Like `set_range_highlights`, `range` points into the current `rendered_text()`. It's best effort: `Ok(())` means the request was taken, not that the view has scrolled. - `TextView::on_reveal(self, handler: impl Fn(Bounds<Pixels>, &mut Window, &mut App) + 'static) -> Self`: lets a container that ignores scroll requests follow a reveal, using the line's bounds in window coordinates. ## How to Test - `cargo test -p gpui-base --lib text::` - `cargo run -p example-markdown`, search in the "Find in preview" field, then step through the matches with the arrows or Enter / Shift+Enter. ## Checklist - [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines. - [x] 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) --------- Co-authored-by: Jason Lee <huacnlee@gmail.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Refs #3214
Description
Adds range highlights to the Markdown
TextView. This covers highlighting in #3214; scrolling to a range will follow in a separate PR.Applications can search
rendered_text()and paint results or citations behind the rendered text without reparsing the document. Ranges use UTF-8 byte offsets into the text produced by plain copy, so a search forhello worldfindshello **world**, and repeated text is identified by position. Highlights do not change layout, selection, or copy behavior.Highlights remain on unchanged text when content updates, including appended text during streaming. After an edit inside a table, cells in and after the edited row lose their highlights because cells are identified by position. The Markdown example now has a “Find in preview” field and match count to demonstrate the API.
Public API
Added in
gpui-baseand re-exported fromgpui_component::text:TextViewState::rendered_text(&self) -> RenderedTextgives applications the visible text to search when computing highlight ranges. The setter itself reads the current text internally.TextViewState::set_range_highlights(&mut self, highlights: impl IntoIterator<Item = RangeHighlight>, cx: &mut Context<Self>) -> Result<(), RangeHighlightError>replaces highlights for the current rendered text; compute the ranges and set them in the same state update.TextViewState::clear_range_highlights(&mut self, cx: &mut Context<Self>)removes all highlights.RenderedTextprovidesas_str(&self) -> &str,len(&self) -> usize, andis_empty(&self) -> bool. Its view/revision identity is used only for equality, allowing observers such as the Markdown example to skip searching unchanged text again.RangeHighlight::new(range: Range<usize>, background: impl Into<Hsla>) -> Selfcreates a highlight with a required background color;range(&self) -> Range<usize>andbackground(&self) -> Hslaexpose its values.RangeHighlightErrorreportsUnsupported(HTML views) orInvalidRange(usize); rejected updates leave existing highlights intact.How to Test
cargo test -p gpui-base --lib text::for rendered-text, range validation, update retention, and paint behavior.cargo run -p example-markdown, then type into “Find in preview” at the bottom. Matches should be highlighted in the preview and the count should update.Checklist
cargo runfor story tests related to the changes.