The text editor

A real text editor

TextEdit is a multi-line, editable text widget with full multi-line layout rather than a single input line. It powers the Editor's .prisma source view and its log pane, and it provides a line-number gutter, word-wrap, proportional-font-aware caret placement, and multi-line selection.

Visual rows unify wrap and no-wrap

The layout is built around a visual row: one row per wrapped subline, or one per logical line when wrap is off. Caret math, selection rectangles, and mouse-to-position mapping all work against visual rows, so they behave identically whether or not wrapping is on. Word-wrap breaks at spaces; an optional header toggle flips wrapping live.

Mouse mapping is proportional-font aware: it inverts the layout by measuring substrings rather than assuming a fixed character width, so clicking lands on the right character in a variable-width font. Click places the caret, drag selects, double-click selects a word.

Editing

In editable mode it is UTF-8-aware throughout: insert by codepoint, Backspace/Delete with line merging, Enter to split, arrow/Home/End navigation (Shift extends the selection, otherwise it collapses), and ⌘A / ⌘C / ⌘X. Edits fire onChange(text()), and the view keeps the caret visible as you type.

Editing is undoable in place: a bounded per-field history snapshots the text before each mutating edit, so ⌘Z and ⌘⇧Z step backward and forward through your own typing, deletes and pastes — without escaping to a global action or leaving the field. A fresh edit after an undo clears the redo branch, the standard linear model.

auto editor = std::make_unique<ui::TextEdit>();
editor->editable    = true;
editor->showNumbers = true;                 // line-number gutter
editor->onChange    = [&](const std::string& s){ reparse(s); };
editor->onCopy      = [&](const std::string& sel){ sys.setClipboardText(sel); };

Seams, not assumptions

TextEdit exposes seams rather than hardcoding behaviour: onChange for edits, onCopy for clipboard (the platform provides setClipboardText), and onContext for a right-click menu. The Editor wires these to commit edited .prisma text back to the live document: when the text parses, it commits through the document's one mutation surface, so editing the source updates every other pane and is undoable, and intermediate text waits to commit until it parses.

That is the toolkit's pattern in miniature: a focused widget with clean seams, drawn through the theme and driven by synthesized intent, that an application composes into something larger.