This note records the extension points available in the pinned native text stack before GPUI exposes layout-affecting projection primitives publicly.
Scope
The desired renderer facts are:
- inline insertions anchored to UTF-16 document positions that consume shaped width and participate in wrapping;
- block insertions anchored before or after logical lines that contribute real display rows and scroll height;
- deterministic document ↔ visual coordinate mapping while inserted content remains outside the Rope, revisions, selections, and undo history.
Application policy such as completion acceptance, notebook execution, language services, diagnostics, and editor chrome must remain consumer-owned.
Pinned stack
The native host currently pins:
- GPUI at
7bddd16a09cf0084cefb3d98468b178343b9f1e2; gpui-componentat5c5eb1db04dc1146bdec903a8ac74407bd6c2098.
The latter provides native text-decoration collections, which are sufficient for foreground style runs because those ranges do not change layout. They are not a projection mechanism.
Display-map architecture
gpui-component's editable input uses this pipeline:
Rope buffer positions
-> WrapMap (soft wrapping)
-> FoldMap (whole-line visibility)
-> DisplayMap (buffer/display row conversion)
-> Input element line shaping and paintingDisplayMap publicly maps BufferPoint and DisplayPoint, and FoldRange
publicly represents inclusive whole-line candidates. Its wrapping input is still
the Rope text, and InputState keeps DisplayMap private. The pinned state API
populates candidates internally from tree-sitter and exposes only code-editor
folding enablement; current upstream main additionally exposes
apply_highlighter_fold_candidates/2, but still does not expose authoritative
active-fold state for ordinary consumers. Consequently consumers cannot provide
complete fold state without pretending to be a highlighter or reaching through
private state. The generic API request is tracked in
gpui-component#2826.
The fold projection only hides interior whole-line wrap rows while keeping both boundary lines visible. It does not render a placeholder or represent arbitrary intra-line hidden ranges. Hidden ranges therefore remain deferred separately.
Inline completion is not a general insertion API
The input's inline-completion path shapes a completion suffix separately and paints it after the caret. Additional completion lines are painted as ghost rows and increase a local extra-height value.
This path cannot serve as a neutral inline insertion because:
- it is tied to the focused primary caret and
CompletionProviderpolicy; - state and setters are private to the input implementation;
- only one completion item is represented;
- the first line is painted after layout and does not participate in WrapMap;
- its background intentionally covers existing text;
- hit testing, selection, range geometry, and display mapping do not include it;
- accepting it mutates the document as completion text.
Reusing this path would misrepresent completion mechanics as document projection and would produce incorrect wrapping and coordinates.
Block insertions
No public block-decoration or custom-row collection exists in the pinned input.
The input assumes display rows are uniformly one line_height tall. Viewport
range calculation divides scroll offsets by that fixed height, and painting zips
shaped lines with visible buffer lines. Inline-completion ghost rows are a local
special case rather than entries in DisplayMap.
A true block insertion therefore needs an explicit row-projection layer that contributes variable or declared row heights to:
- total scroll extent;
- visible-range lookup;
- line and gutter y offsets;
- caret and range geometry;
- pointer hit testing;
- document/display conversion.
Painting current GPUI.Text.BlockProjection overlays cannot satisfy those
requirements and remains intentionally documented as non-layout-affecting.
Required upstream extension
A suitable native extension should add a projection layer owned by InputState,
not by application-specific editor code. A minimal shape would include:
InlineInsertion { id, byte_anchor, text, shaping_style, affinity }
BlockInsertion { id, line_anchor, placement, height, element/text }
ProjectionSnapshot { stable bounded collections }The display map would become conceptually:
Rope buffer
-> InlineMap (external shaped insertions)
-> WrapMap
-> FoldMap
-> BlockMap (external rows)
-> DisplayMapExact ordering may differ, but all conversion APIs must observe the same projection snapshot. The native API must also define:
- anchor affinity when edits occur at projection boundaries;
- precedence for overlapping inline insertions;
- mapping a pointer inside insertion content back to a document position;
- selection behavior across insertion content;
- reconciliation without resetting IME, focus, selection, or scroll;
- bounded collection sizes and stable identities;
- behavior under bidi text, ligatures, fallback fonts, and soft wrapping.
Decision
Do not expose InlineInsertion or BlockInsertion in Elixir yet. Neither GPUI
nor the pinned gpui-component has a public extension that can implement their
claimed layout and coordinate semantics. Do not reuse inline completion, inject
projection text into the Rope, or promote overlay annotations as true rows.
The existing APIs remain truthful:
GPUI.Text.InlineProjectionis a visual overlay anchored to geometry;GPUI.Text.BlockProjectionis a visual overlay around a logical line;GPUI.Text.StyleRunparticipates in shaping but does not alter text length;- hidden ranges remain deferred pending a public consumer-fold contract.
Revisit layout-affecting projections when the native input exposes a coherent projection collection or after a narrowly reviewed upstream contribution adds one.