base: Move TextView to gpui-base - #2881
Merged
Merged
Conversation
TextView took its async use of `futures` to `gpui-base`, so the declaration left in `crates/ui` is now unused and fails `cargo machete`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QapTx3YhSfQqDtbTECRH22
`TextViewStyle` crosses the gpui-base seam, so its fields are now private behind `with_*` builders and accessors of the same name; `TextViewDefaults` takes the same naming. The component facade builds its style as one chain rather than a builder call followed by field assignment. Selection was a literal `hsla(0.58, 0.85, 0.62, 0.35)` in three places. It is now a `colors.selection` semantic token, mapped from `Theme::selection`, with a serde default so older palettes still load. `from_theme` no longer derives selection from `accent`, which was a near-white wash in the light palette. `compat` compared two whole `HighlightTheme`s every frame to decide whether the caller had replaced it; it now compares Arc identity against the shared default. Restores the three code-block highlighter tests dropped by the move: two follow the highlighter to `crates/ui`, where the registry and theme now live, and one covers style invalidation in Base. `CodeBlock::from_code` lets anyone writing a highlighter build a block to test it against. Restores the field and builder documentation trimmed from the component `TextViewStyle`, and documents the facade and `SelectableText` builders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QapTx3YhSfQqDtbTECRH22
`token_names_match_gpui_base_fields` asserts that the script-facing color names mirror `gpui_base::ColorTokens` field for field, so adding `selection` to Base left gpui-shell one name short and failed the Shell Core job. The token now resolves for reads and `set_theme` writes it, which means a script passing a literal token object must supply `selection` — the same rule every other color already follows. Documentation lists it with the other highlight roles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QapTx3YhSfQqDtbTECRH22
madcodelife
added a commit
that referenced
this pull request
Aug 31, 2026
## Description A trackpad swipe over a horizontally scrollable Markdown table scrolled the document instead, and scrolling the document slid the table sideways. #2881 moved the table renderer into `gpui-base` and replaced `horizontal_scroll_area()` — an `overflow_hidden` viewport plus a sibling `ScrollableMask` — with a plain `div().overflow_x_scroll()`, dropping #2468, #2608 and #2648 in one step. The only trace was an `#[allow(dead_code)]` added to the now-unreachable helper, so CI stayed green: the mask's tests cover the mask, not whether the table uses it. `ScrollableMask` moves to `gpui-base` with its 16 tests (its dependencies — `ScrollbarHandle`, `AxisExt`, `StyledExt`, `OngoingScrollExt` — already lived there), and the table calls `horizontal_scroll_area` again. The viewport keeps the border and background #2881 put on it, folded into the refinement. `gpui-component`'s public API is unchanged: `gpui_component::scroll::ScrollableMask` still resolves, now re-exported from Base. ## Why the mask, and not `restrict_scroll_to_axis` `.lock_scroll_axis()` alone fixes only half of it. GPUI's overflow listener runs in the bubble phase and never stops propagation, and `gpui::list` — the body under `TextView::scrollable(true)` — registers its listener after its items paint, so bubble's reverse order runs the list first. A diagonal swipe therefore loses its vertical component to the document no matter what the table does. Winning requires consuming the event in the capture phase, which is what the mask is for. ## Test plan - `cargo test -p gpui-base --lib scrollable_mask` — 16 tests, including `horizontal_scroll_area_in_list_keeps_horizontal_dominant_wheel`, which is the regression this PR fixes - `cargo test --workspace --exclude gpui-shell` - `cargo test -p gpui-component --doc` - `cargo check -p gpui-component --no-default-features` - `cargo clippy --workspace --exclude gpui-shell --all-targets -- --deny warnings` - `cargo fmt --check` - By hand with a trackpad: `cargo run -p markdown_table`, and at several frame widths via `WIN_W=<px>` --- Parts of this PR were written with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
huacnlee
added a commit
that referenced
this pull request
Aug 31, 2026
…ue (#2893) ## Summary This is fix for #2881 included issue. - avoid reparsing unchanged markdown when stateless TextView plugin closures are rebuilt during render - keep parser and renderer handles refreshed without triggering a render loop - restore upward and downward drag-selection auto-scroll in the Markdown and HTML examples by keeping vertical viewport edges interactive - add regressions for stateless markdown settling and bidirectional selection auto-scroll ## Test Plan - `cargo fmt --all -- --check` - `cargo check --example markdown --example html` - `cargo test -p gpui-base -p gpui-component` Co-authored-by: Codex <codex@openai.com>
4 tasks
madcodelife
added a commit
that referenced
this pull request
Sep 4, 2026
## Summary - Fix markdown (and any other `TextView`) scrolling on its own after a single click: click on text, then move the pointer toward the top or bottom edge of the viewport, and the view starts auto scrolling and keeps going. - Root cause: `WindowSelectionState::end()` keeps the anchor after mouse up so a shift-click can extend it, and only clears `is_selecting`. #2881 moved the auto scroll call out of `update_impl` (which has the `is_selecting` guard) into `update_in_window`, so `update_auto_scroll` ran on every pointer move with a live anchor. Once the pointer was in the edge zone it started the anchor auto scroll, and the synthesized `ScrollWheelEvent` re-entered the same path and kept it alive. - Guard `update_auto_scroll` on `is_selecting`, restoring the pre-#2881 behavior: only a live drag may scroll. - Add the regression test `pointer_moves_after_a_click_do_not_auto_scroll`: begin a gesture on text, end it, then drive `update_in_window` past the viewport edge and assert auto scroll stays idle. The fix and the test were written with Claude Code. ## Test plan - [x] `pointer_moves_after_a_click_do_not_auto_scroll` fails before the fix and passes after. - [x] `cargo test -p gpui-base --lib text_selection` — all 49 tests pass. - [x] `cargo test -p gpui-component --lib text::window_selection` — all 51 tests pass, including drag auto scroll in both directions. - [x] `cargo clippy -p gpui-base --all-targets -- --deny warnings` and `cargo fmt --check` are clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
feigeCode
pushed a commit
to feigeCode/gpui-kit
that referenced
this pull request
Sep 5, 2026
…gbridge#2941) ## Summary - Fix markdown (and any other `TextView`) scrolling on its own after a single click: click on text, then move the pointer toward the top or bottom edge of the viewport, and the view starts auto scrolling and keeps going. - Root cause: `WindowSelectionState::end()` keeps the anchor after mouse up so a shift-click can extend it, and only clears `is_selecting`. longbridge#2881 moved the auto scroll call out of `update_impl` (which has the `is_selecting` guard) into `update_in_window`, so `update_auto_scroll` ran on every pointer move with a live anchor. Once the pointer was in the edge zone it started the anchor auto scroll, and the synthesized `ScrollWheelEvent` re-entered the same path and kept it alive. - Guard `update_auto_scroll` on `is_selecting`, restoring the pre-longbridge#2881 behavior: only a live drag may scroll. - Add the regression test `pointer_moves_after_a_click_do_not_auto_scroll`: begin a gesture on text, end it, then drive `update_in_window` past the viewport edge and assert auto scroll stays idle. The fix and the test were written with Claude Code. ## Test plan - [x] `pointer_moves_after_a_click_do_not_auto_scroll` fails before the fix and passes after. - [x] `cargo test -p gpui-base --lib text_selection` — all 49 tests pass. - [x] `cargo test -p gpui-component --lib text::window_selection` — all 51 tests pass, including drag auto scroll in both directions. - [x] `cargo clippy -p gpui-base --all-targets -- --deny warnings` and `cargo fmt --check` are clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
TextView— the Markdown and HTML renderer, its parser, its inline layout andits selection — lived in
crates/ui, so agpui-baseapplication could notrender rich text without depending on the whole component library. This moves
the implementation to
gpui-baseand leaves a compatibility facade behind ingpui_component::text.What moved:
document,format(Markdown and HTML),inline,inline_flow,markdown_ext,node,selection,selection_adapter,state,style,text_view,utils. What stayed: the componentTextViewStyle, thetree-sittersyntax highlighting, and the theme adapter that mapsgpui_component::Themeonto the Base style.Base rich text is usable on its own.
TextViewStyle::default()is a complete,readable style rather than an empty customization bag, and syntax highlighting
is opt-in through
code_block_highlighterso Base keeps no tree-sitterlanguage dependency.
gpui-componentis unchangedgpui_component::text::TextViewkeeps every builder, both associated elementtypes,
Text,TextViewPlugin,markdown()andhtml(). Existing codecompiles as it did:
The facade folds a component style onto the one the active theme already
derived, so a caller who set one
StyleRefinementfield keeps the themedpadding and colors for the rest.
crates/ui/tests/base_compat.rsand thelegacy_*unit tests hold that contract.A new
SelectableTextelementPlain text that joins the window-scoped selection, for applications that want
copyable text without a rich-text document.
New semantic token:
colors.selectionSelection had been a literal
hsla(0.58, 0.85, 0.62, 0.35)written out inthree places:
TextViewStyle::default(), the fallback inInline::paint, andSelectableText::paint. It is now one semantic token that the componentadapter maps from
Theme::selection, so Base text selection follows theapplication palette instead of a fixed blue. The field carries a serde default,
so palettes written before it existed still deserialize.
TextViewStyle::from_themehad derived selection asaccent.alpha(0.4), whichin the default light palette is a near-white wash that barely reads as a
selection at all. It now uses the token.
Breaking Changes
Only
gpui-baseis affected.gpui-component's public API is unchanged.TextViewStylecrosses thegpui-baseseam, so its fields are now privatewith
with_*builders and accessors of the same name — a later field becomesan additive change instead of a breaking one.
TextViewDefaultsfollows the same naming:gpui-shell mirrors the Base color tokens in its script API, so
selectionjoins them there too. A script that passes a literal token object to
set_thememust now supply it, the same rule every other color alreadyfollows:
set_theme({ appearance: "dark", tokens: { colors: { /* ... */ - border: color, input: color, ring: color, + border: color, input: color, ring: color, selection: color, }, /* ... */ }});ColorTokens::default()was every field zeroed, which is transparent ontransparent. It is now
ColorTokens::light(), so a Base application that neverinstalls a palette renders legibly instead of black on black. This also removes
the
gpui-shellbinary's shipped palette and the "can this palette tell inkfrom paper" guard in
failure_surface, both of which existed only to workaround the zeroed default.
A palette deserialized from JSON with
#[serde(default)]fields now falls backto the light value for anything it omits, rather than to transparent.
Test plan
cargo test --workspace --exclude gpui-shell --features gpui-component-story/test-supportcargo test -p gpui-component --doccargo check -p gpui-component --no-default-featurescargo clippy -p gpui-component -p gpui-component-story -p gpui-component-assets -- --deny warningscargo macheteParts of this PR were written with Claude Code.