Skip to content

text: Paint TextView selection under the glyphs - #3439

Merged
huacnlee merged 3 commits into
longbridge:mainfrom
cesartalen:fix/text-view-selection-under-glyphs
Oct 12, 2026
Merged

huacnlee merged 3 commits into
longbridge:mainfrom
cesartalen:fix/text-view-selection-under-glyphs

Conversation

@cesartalen

@cesartalen cesartalen commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #3438

Description

Inline::paint painted the text before the selection quad, so the quad
covered the selected glyphs. An opaque selection color hid the text
completely; a translucent one only tinted it, which is why the default
theme masks the problem.

This paints the text in two passes around the selection: run backgrounds
first, then the selection, then the glyphs, using GPUI's
TextLayout::paint_background and paint_foreground. The selection is now
a background under the glyphs, as TextViewStyle::with_selection
documents, and it still shows on runs that carry their own background, such
as <mark> highlights. It matches how range highlights are already painted
in the same function and how Input paints its selection. The selection
range only needs the text layout, which is available before painting, so
nothing else changes.

This touches gpui-base because the paint order lives there.

If the inline state lock is poisoned, paint still paints the glyphs before
it returns early. Before this change the text was painted ahead of the lock,
so a poisoned lock must not hide it now.

crates/kit/tests/rendering.rs gains
selected_text_stays_over_an_opaque_selection: it selects all text under an
opaque selection color and checks that both selection and glyph pixels are
painted. It fails on the previous paint order and passes with this change.

Screenshot

Opaque selection color, all text selected next to the same content
unselected (right). Windows 11.

Before

repro3

After

fixed-opaque

How to Test

In any example with a TextView, set an opaque selection color and select
text, including a fenced code block:

Theme::global_mut(cx).selection = hsla(0.6, 0.9, 0.5, 1.);
Theme::sync_base(cx);

Before: the selected text is hidden by a solid block. After: the text stays
readable on top of it.

Built-in themes with an opaque selection color show it in the Story
Gallery without code changes: Aurora Light, macOS Classic Dark, Kibble.

Also ran cargo test -p gpui-base --lib text:: (340 passed) and
cargo fmt --check. The rendering test runs on macOS and Linux:

cargo test -p gpui-kit --features "test-support component assets" --test rendering

AI-assisted: the change was written with Claude Code and reviewed by me.

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)

🤖 Generated with Claude Code

cesartalen and others added 3 commits October 11, 2026 20:13
The selection quad was painted after the text, so it covered the selected
glyphs: an opaque selection color hid them and a translucent one tinted
them. Paint the text after the selection, as with_selection documents.

Closes longbridge#3438
Painting the whole text after the selection drew run backgrounds, such as
mark highlights, over it. Paint the run backgrounds first, then the
selection, then the glyphs.
Add a rendering test that selects all text with an opaque selection
color and checks the glyphs are still painted over it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@huacnlee huacnlee changed the title fix(text): paint TextView selection under the glyphs text: Paint TextView selection under the glyphs Oct 12, 2026
@huacnlee
huacnlee enabled auto-merge (squash) October 12, 2026 02:08
@huacnlee

Copy link
Copy Markdown
Member

Thank you.

@huacnlee
huacnlee merged commit 4e2a00e into longbridge:main Oct 12, 2026
16 checks passed
@huacnlee huacnlee mentioned this pull request Oct 12, 2026
huacnlee added a commit that referenced this pull request Oct 12, 2026
## Description

Prepare v0.7.2 on `stable` from the PRs merged into `main` since v0.7.1,
leaving out every change that breaks existing consumers. `stable`
already carries #3371 and #3373; this adds the remaining compatible PRs
with `git cherry-pick -x` and bumps the workspace to 0.7.2.

### Included

- kit: Add optional gpui-fast backend (#3375)
- tooltip: Make show delay and grace period configurable with
`TooltipDefaults` (#3376)
- docs: Explain recovering focus when the focused element stops
rendering (#3386)
- diff: Add readonly patch viewer (#3378)
- kit: Use one Windows manifest with GPUI Fast (#3380)
- test: Make Progress and TitleBar findable in UI tests (#3383)
- input: Preserve selection, focus ring and context menu actions (#3382)
- diff: Add a Git history example and complete review APIs (#3388)
- text_view: Ignore gestures that stay inside one block plugin (#3390)
- text_view: Round the first and last table rows to the frame radius
(#3309)
- menu: Open a context menu with a long press on touch (#3393)
- text_view: Keep narrow table columns on one line (#3399)
- settings: Keep focus rings inside the list viewport (#3394)
- scrollable_mask: Keep rounded scroll-table corners clean at any scroll
offset (#3400)
- dialog: Add margin_top to AlertDialog (#3404)
- input: Add InputState::add_selection for programmatic multi-cursor
(#3403)
- text_selection: Stop writing selection state on every frame (#3407)
- table: Make the sort icon a labelled button (#3396)
- table: Report visible row ranges of one row or none (#3397)
- dialog: Support configurable entrance animations (#3405)
- docs: Explain the open_dialog builder and footer contracts (#3406)
- dock: Hand the focus to the tab that replaces a closed one (#3409)
- dropdown_button: Add A11y names for the menu trigger (#3410)
- text, resizable: Stop notifying state that did not change (#3418)
- editor: Add on-demand completion commands (#3411)
- table: Clear a right-clicked cell's outline on the next click (#3413)
- tab: Scroll a clicked tab into view with the next one peeking (#3419)
- table: Allow a header row height separate from the body (#3420)
- menu: Focus a context menu before its first frame (#3422)
- text: Preserve logical selection across presentation changes (#3421)
- text: Keep inline HTML mark highlights in Markdown (#3425)
- input: Anchor completion/code-action popups to the current frame's
caret (#3412)
- input: Run custom context menu builders after releasing the input
(#3433)
- input: Keep auto-grow rows fitted when the limits change (#3430)
- text: Paint TextView selection under the glyphs (#3439)

Already on `stable`: #3371, #3373.

### Excluded (Breaking Changes)

- #3432 motion: `Easing::sample` now returns values outside `[0, 1]` for
overshooting curves.
- #3415 tab: new `TabVariant::Folder` breaks exhaustive matches.
- #3402 kit: pointer helpers now keep held modifiers; nested
context-menu behavior changes in tests.
- #3384 test: `TestWindowExt` gains required methods.
- #3302 resizable: `ResizeHandle::on_drag` takes `&T` instead of
`Rc<T>`.
- #3389, #3395, #3398 webview: wrapper API changes, `gpui-wry` →
`gpui-webview` rename, and the GPUI Fast requirement.

#3411, #3405 and #3396 have a Breaking Changes section that reads "None"
and are included. #3376 only deprecates `TooltipRequest::placement`, and
#3397 / #3382 change behavior compatibly, so they are included too.

### Adaptations

- `crates/kit/tests/collections.rs` and `crates/kit/tests/rendering.rs`:
conflict resolutions drop the tests that belong to #3402 and keep each
backported PR's own test.
- `test: Keep backported tests on the v0.7.1 test API`: the menu and
completion tests from #3422 and #3412 used `find_all` and
`pointer_down`/`pointer_up` from #3384/#3402. They now query
`gpui_kit::base::test_support::snapshots` and dispatch mouse events
directly.
- `kit: Prepare v0.7.2`: workspace version bump; `Cargo.lock` only
updates workspace members.

## Public API

No new public API beyond what the included PRs add; each lists its items
in its own `## Public API` section (notably #3375, #3376, #3378, #3388,
#3403, #3404, #3405, #3411, #3420).

## How to Test

- `cargo clippy --workspace --all-targets -- --deny warnings` passes on
Linux.
- `cargo test -p gpui-kit --features "test-support component assets"
--test collections --test menu --test input` passes on Linux.
- The macOS-only `rendering` test (#3439 adds one case, the count is
updated to 11) was not run.

AI-assisted: the backport, conflict resolution and test adaptations were
done with Claude Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Floyd Wang <codelife721@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gustave <gustavel@gmail.com>
Co-authored-by: luren <84951845+lurenjia534@users.noreply.github.com>
Co-authored-by: Ylin <ylinwind@gmail.com>
Co-authored-by: ihavecoke <mixboip@gmail.com>
Co-authored-by: Lorenzo <150276837+Bombatomica64@users.noreply.github.com>
Co-authored-by: MR. XIE <113512030+xxww0098@users.noreply.github.com>
Co-authored-by: xxww0098 <shefkie1957@gmail.com>
Co-authored-by: Sola 🌙🤖 <266175376+sola-ryu@users.noreply.github.com>
Co-authored-by: Sola <sola-ryu@users.noreply.github.com>
Co-authored-by: Andy Hsu <i@nn.ci>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Floyd Wang <gassnake999@gmail.com>
Co-authored-by: Daiki Tomioka <20753105+TomiXRM@users.noreply.github.com>
Co-authored-by: Henrik Øgård <henrik@ogard.no>
Co-authored-by: Henrik Øgård <henrikogaard@users.noreply.github.com>
Co-authored-by: Endless <endless@longbridge.com>
Co-authored-by: John Costa <costajohnt@gmail.com>
Co-authored-by: Alexander <87220898+sneffets@users.noreply.github.com>
Co-authored-by: Cesar Talén <154442565+cesartalen@users.noreply.github.com>
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.

TextView paints selection over glyphs, hiding text with opaque selection colors

2 participants