tui¶
The terminal UI. Namespace core::tui, directory src/core/tui/, targets core::tui_output and
core::tui (only with CORE_CPP_WITH_TUI, which is on by default and off under Emscripten).
Imported from endo's src/tui at f774a210, which includes the coroutine-runtime work fastcached
upstreamed there. The runtime has since been rewritten onto core::net::EventLoop.
Status
Available. Its runtime is composed on core::net::EventLoop, so core::tui links
core::net; core::tui_output still links base alone.
core::tui_output¶
A static library that links base and nothing else: no libunicode, no coroutines, not
even platform. Its row in the module table says DEPS base, so the configure
refuses any other link from it. A program that only prints styled text and progress — Lightweight's
dbtool — links this and takes nothing else with it.
TerminalOutputbuffers escape sequences and flushes them: styled text (Style,RgbColor,UnderlineStyle), cursor movement, erasing, the alternate screen, the scroll region, double-width and double-height lines, cursor shapes (CursorShape), sixel payloads, OSC 52 clipboard writes and OSC 8 hyperlinks, plus the queries a capability probe sends (cursor position, cell size, DECRQM, DA1).writeToDestination()is the one seam every byte goes through. Override it to retarget the stream — a capture buffer in a test, a pipe, a second terminal — without reimplementing any of the composition.isTerminal()answers for that same destination: the default asks the operating system about the process's standard output, and a subclass answers for its own.SyncGuardbrackets a frame in DEC mode 2026 so a terminal does not paint a half-drawn one. It writes through theTerminalOutputit was made from, so a retargeted output is bracketed on its own stream.buildSgrSequence()turns aStyleinto one SGR sequence.core::tui::protocolsholds the sequence constants the input and output sides share (the Kitty keyboard protocol, bracketed paste, mouse and focus tracking, colour-scheme notification, win32-input-mode, OSC 8),appendHyperlinkOpen(), andparseSixelFromDeviceAttributes(), which reads a DA1 answer.Result<T>andVoidResult, the module'sstd::expectedaliases.
core::tui¶
The rest, on top of core::tui_output, platform, async and libunicode,
and on stb when CORE_CPP_WITH_IMAGES is on. Native only: there is no terminal under Emscripten.
- Input.
TerminalInputputs the terminal in raw mode, enables the protocols above and decodes what comes back throughVtParserinto anInputEvent— keys (KeyCode,Modifier), mouse, paste, focus, resize, and the protocol reports a query waits for.Terminalpairs it with aTerminalOutputand owns the query round-trips (queryCursorPosition(),queryCellSize(),queryDecMode(),queryDeviceAttributes()), each on an injected clock and each bounded. - Drawing.
Bufferis a grid ofCells,Canvasa clipped view of one, andScreenthe renderer that diffs a frame against the last and writes only what changed, inline, full-screen or in a fixed area.Theme,StyledText,Text,Box,RectandHyperlinkEmittersit under it. - Components.
Componentand the widgets over it:InputField(multi-line editing, undo, kill ring, ghost text, selection),List,TreeTableView,Dialog,StatusBar,LogPanel,Spinner,ProgressBar,Tooltip,QuestionComponent, and the popupsCompletionPopup,CommandPalettePopupandFuzzyPickerPopupwithPopupKeyDispatchandScrollableSelection. - Completion.
core::tui::completer:CompleterwithCompletionConfig,CompletionProvider,CompletionItem,FuzzyMatchwithFuzzyConfigandFuzzyMatchResult, andSmartCaseMatchwithSmartCaseConfig. The namespace is the one the directory names, asruntime/iscore::tui::runtime; endo's TUI is one flatnamespace tui, and the import kept that until core-cpp#30. - Markdown and syntax.
MarkdownRendererwithMarkdownTable,MarkdownHtmland the inline grammar, andGenericSyntaxHighlighter, a lexer per language, withSyntaxHighlighterRegistryfor the languages core::tui does not ship (below). - Images. With
CORE_CPP_WITH_IMAGES:loadImage(),resizeImage()andreadClipboardImage()over stb,encodeSixel(), andFilesystemImageProvider, which implements the always-presentImageProviderinterfaceMarkdownRenderertakes. - Runtime.
core::tui::runtime:TuiRuntimegives the TUI its input vocabulary --co_await runtime.nextEvent(),nextEventFor(),nextActivity(),nextAgentReady()-- and forwards everything else to the event loop it is constructed on:blockOn(),spawn(),delay(),sleepUntil(),waitReadable(),waitWritable(), the clock and the root stop source. Input arrives through an injectedInputSource, which names the handles to watch and decodes what is ready behind them;TerminalInputSourceis that over aTerminal, andTuiRuntime(loop, terminal)makes one for you.runModal()drives a modal to its result; for a deadline usecore::net::withTimeout(&runtime.loop(), …). - Test doubles.
MockTerminalOutputrecords what a renderer did semantically instead of emitting VT,runtime::testing::ScriptedInputSourcescripts the decoding (readiness comes fromcore::net::testing::ScriptedBackendor from a realcore::platform::SystemPipe), andTestHelpers.hppreads a renderedBufferback as text.
Registering a language¶
GenericSyntaxHighlighter ships a lexer for the languages of LanguageId — C and C++, CMake,
Python, Bash, Markdown, JSON, YAML, git diffs, assembly, PowerShell, CMD, XML and INI — and for no
others. An application that has its own language teaches it to core::tui rather than core::tui
shipping it, which is what keeps one application's vocabulary out of a library four of them link
(core-cpp#24).
A SyntaxHighlighterRegistry is that seam. It is an ordinary object: construct one, fill it, and
pass it to whatever renders the text. There is no process-wide registry, so two parts of one
program can hold different ones and a test never has to undo a registration.
auto highlighters = core::tui::SyntaxHighlighterRegistry {};
auto const wobble = highlighters.registerLanguage({
.name = "wobble",
.extensions = { ".wob" },
.fenceTags = { "wobble", "wob" },
.highlight = [](std::string_view line, core::tui::HighlightState state) {
return highlightWobbleLine(line, state); // the application's own lexer
},
});
if (!wobble)
log("wobble was refused: {}", wobble.error().token);
registerLanguage() returns a LanguageId of its own, from the reserved range that begins at
FirstRegisteredLanguageId, or a LanguageRegistrationFailure saying which name, extension or
fence tag was already claimed. It refuses rather than shadows — replacing would repoint an id
already handed out, and whoever held that id would get a wrong answer that looks right — and a
refused definition leaves the registry exactly as it was.
Every entry point then takes the registry as a trailing argument that defaults to nullptr,
meaning the built-in languages alone:
auto renderer = core::tui::MarkdownRenderer { output, theme, &highlighters }; // ```wobble fences
auto const styled = core::tui::StyledText::fromMarkdown(text, width, &theme, &highlighters);
auto const language = core::tui::detectLanguageFromPath("draft.wob", &highlighters);
auto const [map, next] = core::tui::highlightLine(line, language, state, &highlighters);
A registry answers for the built-in languages too — built-in rows are consulted first — so registering one costs an application nothing it already had.
A registered id belongs to the registry that issued it
Registered ids are dense from FirstRegisteredLanguageId in registration order and carry
nothing that identifies their registry, so passing one to a different registry is a
precondition violation — the same contract a std::vector::iterator has with its container.
If that registry issued an id in the same position, the line is highlighted as its
language, silently and wrongly; only an id past the end of it gives plain text. A program that
holds one registry, which is the shape this is designed for, cannot hit this. One that holds
two keeps each id with its own registry.
Built-in ids — everything below FirstRegisteredLanguageId — are not issued by anybody and
are portable: they mean the same language in any registry and in none.
The three built-in tables the module ships are ExtensionLanguageTable, FenceTagLanguageTable
and FilenameLanguageTable, and a registered language claims extensions and fence tags but never
a file name: CMakeLists.txt, .clang-format and .editorconfig are well known beyond any
one project, whereas an application knows what its own configuration file is called and names the
language for it itself, rather than asking detectLanguageFromPath() to guess. A registration
whose extension would be shadowed by a file-name row is refused rather than left dead.
Layout¶
The module's root holds only platform-independent code. What goes through the operating system is
in posix/ and windows/, which the per-platform source lists select, so no file there guards
itself with an #ifdef of its platform:
posix/— thetermiosraw mode, the SIGWINCH self-pipe,poll(2), thewrite/readretry loop,isatty, and the clipboard tools (wl-paste,xclip).windows/— the console modes and code pages, console input records decoded as UTF-8,WaitForMultipleObjects, the resize event, andGetConsoleScreenBufferInfo.
TerminalInput's own handles live in an opaque NativeState those two define, so
<core/tui/TerminalInput.hpp> names neither <termios.h> nor <windows.h>. No file in this
module chooses its platform with an #ifdef, and runtime/ has no platform directory at all: the
multiplexed wait its two TerminalEventSource bodies held is the event loop's, on every
platform.