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.