Text input
Text-input primitives
Application-owned text, native composition and queries, and retained geometry for custom controls.
TextInputBase and TextAreaBase provide ordinary editable string controls
without a look. For a control with its own storage, selections, rendering or
editing policy, attach a TextInputClient to an element:
ui.Box(c).
HandleTextInput(app.input).
HandleInput(app.input.HandleInput).
Draw(app.input.Draw).
Label("Document").
Role(ui.RoleTextField)The primitive connects your state to the platform's input methods and text
queries. Your client owns its buffer, selection model, marked text and undo
history. It can use a rope, persistent tree or incremental paragraph store;
MyGo does not copy a whole document on each edit or choose formatting behavior.
The existing HandleInput, TextCaret, TextInput and TextArea APIs remain
available. TextCaret is useful for terminals and other widgets that need a
candidate-window location but do not provide a text document.
InputMethod(false) turns input methods off for either while keys still
type text, which then reaches ReplaceText with no composition, as a
modal editor's normal mode wants; browsers cannot turn them off.
The built-in TextInput and TextArea also implement this client contract.
Their *string bindings publish committed text when the widget builds;
native queries already see queued navigation and edits before that frame.
Preedit is a virtual insertion into the native document until committed,
and its replacement and commit share one undo transaction. Password fields
provide no committed text to native queries; read-only fields disable input.
Paragraph indexes include UTF-16 offsets so native queries near the end of
a large document do not scan its prefix.
String bindings still produce a whole string on each committed edit. For
indexed storage, use TextBuffer with TextInputBuffer or TextAreaBuffer:
// Keep the buffer across frames; its zero value is also usable.
document := ui.NewTextBuffer(initialText)
// In the view:
input := ui.TextAreaBuffer(c, document).Fill().Font("monospace")
if input.Changed() {
// Length/version queries read tree summaries without flattening text.
version := document.Version()
_ = version
}TextInputBufferBase and TextAreaBufferBase provide the unstyled variants.
They share the string controls' selection, styling, clipboard, IME and undo
behavior. Native mutations update the buffer immediately, and Changed
reports them when the widget builds. A programmatic buffer edit refreshes the
control and clears its stale selection/undo state; use Window.Update to
schedule such changes and invalidate the view.
TextBuffer is a balanced tree of bounded, owned UTF-8 chunks. It indexes
bytes, runes, UTF-16 units and newline counts. Small edits copy affected chunks
and tree paths; they do not copy the whole document or shift every following
paragraph's absolute offsets. Snapshot shares unchanged chunks and remains
valid after edits; Restore adopts one in constant time. Buffer methods are
safe from any goroutine, and snapshots can be read concurrently with edits.
Replace(start, end, text) and Slice(start, end) count runes, matching
Element.TextSelection. ReplaceUTF16, TextForRange, UTF16Offset and
RuneOffset bridge native ranges and keep surrogate pairs whole. Snapshots
also supply Line, LineRange and LineAt for custom editor layout.
String explicitly materializes the document. For file export, WriteTo
streams chunks to an io.Writer. Ordinary multiline editing and bounded IME
queries do not flatten the document; an explicit full-document query or the
current accessibility Value protocol can request its complete text. Layout
continues to shape one logical paragraph at a time. Changes to the number of
lines update the control's paragraph/height metadata.
The text-buffer example shows a 100,000-line
editable document.
Custom editors can use the same buffer through their TextInputClient and
choose their own selection, history and rendering policy.
The text-input example shows a small application-owned text field. It demonstrates composition, pointer selection, Edit-menu commands and custom rendering. It is a primitive example, not an editor implementation.
Client contract
Pass a pointer to a client kept across frames. HandleTextInput(nil) (including
a typed nil pointer) disconnects it. The element is focusable and has the text
cursor; its size, appearance and other input behavior are yours.
| Method | Purpose |
|---|---|
TextForRange(range) |
Return requested text and the actual clamped/adjusted range. Full-document queries are supported; no fixed surrounding-window restriction applies. |
Selection() |
Report the primary native selection and its direction. Additional cursors or discontiguous ranges stay in application state. |
MarkedRange() |
Report preedit in the current document, or false when none exists. |
ReplaceText(range, text) |
Commit text in an explicit range; a nil range means preedit if present, otherwise the selection. Clear preedit according to your editing policy. |
SetMarkedText(range, text, selected) |
Replace preedit, retaining its marked range. selected is relative to the newly marked text. |
UnmarkText() |
Clear marked status while retaining document text. Also called when focus is lost, the client changes, or the element is disposed. |
BoundsForRange(range) |
Return element-relative text/caret geometry, the actual range represented, and whether it is available. Return false for unavailable/offscreen text. |
IndexForPoint(point) |
Hit-test an element-relative point and return a document offset, or false. |
Ranges count UTF-16 code units, matching native text services and GPUI's
input-handler contract. This is an interop coordinate system, not a storage
format. ui.UTF16Len and ui.UTF16ByteOffset help simple UTF-8 buffers convert
at the boundary; an indexed buffer can provide faster conversions itself.
A surrogate pair must stay whole, and your editing policy should respect
Unicode grapheme boundaries. TextForRange returns its adjusted range so the
platform knows what text it received.
Callbacks run synchronously on the UI thread, before the next frame. They must
not wait for work requiring that thread. The client owns synchronization with
other goroutines; schedule application state changes with Window.Update as
for other native UI views. After a native mutation, MyGo asks for a frame and
reads the latest selection/caret before the next input event. Stale native
references cannot modify an unfocused, replaced or disposed client.
On macOS the client supplies NSTextInputClient selection, marked ranges,
substring queries, range bounds and point hit testing. GTK uses bounded
surrounding text, preserves UTF-8/rune/UTF-16 conversions, and forwards
surrounding deletions and preedit/commit callbacks. Windows IMM32 uses bounded
document-feed/reconversion context and forwards composition and commit ranges.
Changing the client resets native preedit so a candidate cannot move to a
newly focused field. The platform callbacks are allocated once at startup.
Keyboard navigation, pointer selection, clipboard policy and Edit-menu
commands come through HandleInput. They belong to your control. This permits
an editor to choose multiple selections, custom keymaps, grouping and history
without overriding behavior built into a framework-owned editor.
Retained text geometry
ShapeText(text, font, width) and ShapeRichText(spans, font, width) produce a
TextLayout using Core Text, Pango or DirectWrite. Positive width wraps text;
zero only breaks at newlines. Cache layouts by paragraph or line and replace
only those affected by an edit.
layout := ui.ShapeText(paragraph, ui.Font{Size: 16}, availableWidth)
caret := layout.Caret(offset)
offset = layout.IndexAt(ui.Point{X: pointerX, Y: pointerY})
rects := layout.SelectionRects(selectedRanges...)
// Inside Draw:
p.TextLayout(layout, originX, originY, color)Layout offsets are UTF-16 too. Caret and IndexAt snap to whole graphemes;
PreviousBoundary and NextBoundary support logical navigation/deletion.
SelectionRects accepts multiple ranges and keeps the separate visual pieces
of bidi selections. It supplies geometry without inventing a single-range
selection model or choosing an editor's navigation policy. Size and geometry
queries are safe from any goroutine; painting is part of a UI frame.
ShapeRichTextWith lays spans out aligned (TextShape.Align), as the
paragraphs of a rich text editor. A span with an ObjectWidth makes its
object replacement characters (U+FFFC) inline objects: boxes of
ObjectWidth×ObjectHeight standing on the baseline, which lines break
around and which draw nothing; TextLayout.Objects gives their boxes,
where an editor draws its images.
For visual navigation, retain TextCaretPosition, including its Affinity.
TextDownstream chooses the following logical text's edge; TextUpstream
chooses the preceding edge. The edges may differ at a bidi boundary or wrap.
CaretAt paints that edge, PositionAt preserves it when hit-testing, and
MoveCaret(position, direction) moves one visual grapheme left (negative)
or right (positive). SelectionRanges(anchor, caret) maps a visual gesture
to logical UTF-16 ranges, which can have gaps in mixed-direction text:
caret = layout.MoveCaret(caret, 1)
ranges := layout.SelectionRanges(anchor, caret)
rects := layout.SelectionRects(ranges...)
// Use the same ranges for copy, deletion and replacement in your buffer.The built-in controls use these visual edges and range sets too. Copy joins the selected fragments in logical order; replacement preserves text in the gaps, and undo restores the visual selection as well as the text.