The text editor
A real text editor
TextEdit is a multi-line, editable text widget — the kind a code or source pane needs, not a one-line field. It powers the Editor's .prisma source view and its log pane, and it is modelled on the parts of a mature text editor that matter: 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.
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; invalid intermediate text simply is not committed 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.