Skip to content

textarea: expose cached visual row count - #3540

Closed
TomiXRM wants to merge 1 commit into
longbridge:mainfrom
TomiXRM:upstream-textarea-visual-rows
Closed

TomiXRM wants to merge 1 commit into
longbridge:mainfrom
TomiXRM:upstream-textarea-visual-rows

Conversation

@TomiXRM

@TomiXRM TomiXRM commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Problem

A host that owns a textarea's preferred height cannot currently query its cached visual content height in rows. rows and auto_grow control layout constraints, not the total number of wrapped content rows. Using auto-grow as a minimum also prevents a surrounding flex layout from shrinking a composer in a short window.

Change

Expose TextareaState::visual_row_count() -> usize as an O(1) read of the existing display-map cache. It counts explicit and soft-wrapped content rows independently of minimum rows, auto-grow limits and viewport height. It does not shape or copy text, allocate, create a second cache, or force layout.

Document the cache contract: it may be zero before first layout; laid-out empty text has one row; width/font changes become visible after layout. Hosts can keep rows(1), constrain the outer preferred height, and consume the refreshed count on a subsequent render.

Coverage exercises pre-layout behavior, fixed one-row textareas, preferred 3–12-row sizing, constrained flex shrink, internal scrolling and deletion, Unicode wrapping, width changes, and disabling soft wrap.

Verification

  • cargo test -p gpui-base --lib: 1,375 passed.
  • cargo test -p gpui-kit --features test-support --test integration --test ui: 395 passed.
  • cargo clippy -p gpui-base -p gpui-kit --lib --tests --features gpui-kit/test-support: passed; only the existing block 0.1.6 future-incompatibility notice.
  • cargo fmt --all -- --check: passed.
  • Throwaway native macOS Metal smoke program (removed afterward): 3/6/20 content rows, a 120px constrained viewport, internal scroll offsets 0/-18/-298px, and real Unicode text wrapping from 4 to 10 rows when narrowed from 480px to 200px. Captured using the native text system and renderer, not the test renderer.

Native Metal: constrained textarea with CJK, combining accents and emoji

Downstream motivation: Kagi's official-dependency migration. This API removes the last cached-row accessor requirement without another wrapping implementation or a dependency patch.

@huacnlee

Copy link
Copy Markdown
Member

Thanks for the thorough write-up and tests. The use case is valid, but we'd rather not add a new accessor for it, so I'm closing this PR.

DisplayMap is already public, and display_map().wrap_row_count() is the documented way to size to content (see "Sizing to content" in website/component/editor.md). The only gap is that display_map() (and prewrap) live in the EditorMode-only impl block, so TextareaState can't reach them.

The fix we'd accept: move display_map() and prewrap into the impl shared by all modes, so a textarea host reads text.read(cx).display_map().wrap_row_count() exactly as an editor host does. The cache notes from your doc comment (zero before the first layout, refreshed by layout, prewrap for the first frame) belong on those methods.

You're welcome to open a new PR along those lines. Your integration tests (preferred height with flex shrink, internal scrolling, Unicode wrapping and resize) would carry over directly on top of display_map().wrap_row_count(). Please also keep the docs change minimal by pointing textarea.md at the same pattern as editor.md, and sync it to website/zh-CN.

@TomiXRM

TomiXRM commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

ご指定の方針に合わせた新しい PR #3564 を作成しました。既存の display_map() / prewrap() を共通 impl へ移し、専用 getter は追加していません。キャッシュの説明、Textarea の初回レイアウト・preferred height/flex shrink・内部スクロール・Unicode/resize の検証、英語/中国語の最小限のドキュメントを含めています。macOS native Metal の描画証跡と実行した検証コマンドも新しい PR に記載しました。

huacnlee pushed a commit that referenced this pull request Oct 17, 2026
## Description

Follow-up to #3540, implementing the [requested API
shape](#3540): expose the
existing `display_map()` and `prewrap()` on the shared
`InputBaseState<M: InputModeKind>` implementation instead of adding a
textarea-specific row-count getter.

- Move the two existing methods out of the Editor-only implementation;
their bodies and signatures are unchanged.
- Document the cached count, first-layout/prewrap timing, resize/font
refresh, and distinction between content rows and viewport/auto-grow
limits.
- Cover textarea first-frame preparation, disabled soft wrapping,
fixed-row internal scrolling, host-owned preferred height with flex
shrink, and Unicode wrapping across resize. Existing Editor prewrap
tests remain in place.
- Add a short link to the existing Editor sizing pattern in both English
and Chinese textarea documentation, with a reminder to use the
textarea's actual text style.

No new row-count getter, wrapping implementation, cache, or dependency
is introduced. Existing Editor callers are unchanged.

## Screenshot

Fresh native macOS text-system + Metal-renderer captures from this
branch, not the previous proposal. These are offscreen native renders,
not OS IME/accessibility coverage.

![Wide
textarea](https://github.com/user-attachments/assets/68e8f302-c5aa-4b60-89e4-e946726a4404)

![Constrained textarea with Unicode wrapping and internal
scrolling](https://github.com/user-attachments/assets/11599866-e4f8-4ca6-9569-c4646a4d0d7c)

## Public API

### `gpui-base`

The following existing methods now belong to `impl<M: InputModeKind>
InputBaseState<M>` instead of the Editor-only implementation. They
become available on `InputState` and `TextareaState`; `EditorState`
retains the same API and behavior.

```rust
pub fn display_map(&self) -> &DisplayMap;
```

Returns the cached display map, including `wrap_row_count()`, without
forcing layout or preparing text.

```rust
pub fn prewrap(
    &mut self,
    font: gpui::Font,
    font_size: Pixels,
    width: Pixels,
    cx: &mut Context<Self>,
);
```

Prepares wrapping using the supplied text style and text-area width
before the first layout; remains a no-op after the input has been laid
out.

These additive methods are also reachable through the existing state
re-exports used by `gpui_kit::component::input`. No re-export is added
or removed, and no existing caller needs migration.

## How to Test

Completed on macOS arm64:

```sh
cargo test -p gpui-base --features test-support --lib
# 1,384 passed
cargo test -p gpui-kit --features test-support --test integration --test ui
# 400 passed across the two suites
cargo fmt --all -- --check
cargo build -p gpui-base -p gpui-kit
cargo clippy -p gpui-base -p gpui-kit --lib --tests --features gpui-kit/test-support
```

A disposable `cargo run` harness using `HeadlessAppContext`, the native
platform text system, and `current_headless_renderer` also passed
assertions for:

- first-frame textarea prewrap;
- content-driven preferred heights (3/6/20 content rows →
104/176/320px);
- shrinking into a 120px viewport, internal scroll offset, and visible
end-of-document caret;
- Japanese text, combining characters and family emoji wrapping when
narrowed;
- preservation of text and the existing post-layout prewrap no-op.

The harness was removed after capturing the images; the deterministic
integration coverage is retained in this PR.

Notes: running the base unit suite without `test-support` stops at the
checkout's `a11y_disabled.rs:298` reference to feature-gated
`crate::test_support`, so the command above enables that feature rather
than changing an unrelated test. Cargo also reports the existing `block
v0.1.6` future-incompatibility notice. Native runtime verification here
is macOS only.

## Checklist

- [x] Read and followed CONTRIBUTING.
- [x] Reviewed the changes and the AI-assisted code.
- [x] Ran the affected behavior through a native Metal `cargo run`
harness and inspected its captures; this was not a story-app run.
- [x] No platform-specific implementation change; existing method bodies
are preserved. No Windows/Linux native verification is claimed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants