Architecture#
toe is a Go-native modal terminal editor built on Bubbletea, Tree-sitter, and Chroma. This page explains the current package layout, the main data flow, and the integration points that exist in the code today.
Design Principles#
- toe edits Go projects, not the universe. Features exist because Go development needs them, not because other editors have them.
- Persistent editing values. The core text and edit values (
Rope,ChangeSet,Transaction,Selection, andRange) return new values rather than mutating their inputs.Historyis the exception in the current implementation: it is owned by a document and mutates its revision cursor and revision list while storing immutable transactions. - Modular ownership. Language server, version control, pickers, image display, themes, and command modules keep their state and configuration close to the module that owns the behavior. The editor exposes narrow interfaces where decoupled services need to plug in.
- Render once, cache everything expensive. The render path runs on every keystroke. Parsed syntax queries, syntax caches, raw document text, highlight spans, search spans, preview entries, and line-prefix scans are cached and invalidated by revision or input changes.
Package Layers#
Dependencies point downward; lower layers never import higher ones.
Core Model#
Packages: internal/core.
Persistent text (Rope), selections, ranges, movement, transactions (ChangeSet, Transaction), search, comments, indentation, brackets, surround helpers, text objects, wrapping, and undo/redo history. This package has no terminal UI or file I/O. Most editing values are immutable value types; History is stateful and lives inside a Document.
Editor State#
Packages: internal/view and its subpackages.
The editor, documents, pane tree (splits), sessions, file I/O, overlays, diagnostics, and service interfaces. A Document owns text, revision, language, history, diagnostics, language-server overlay state, and per-view selections; a View is a window onto a document. Image and terminal panes are separate pane implementations with their own rendering and persistence. The Editor owns the document table, split tree, focus, runtime options, registers, document observers, and optional service controllers. Subpackages:
view/config: raw editor config loading/merging and EditorConfig support.view/language: language configuration, matching, formatter metadata, server metadata, indentation, auto-pair, and soft-wrap settings.view/register: the in-memory register store, including the default and black-hole registers.view/action: reusable editor actions invoked by commands and UI components.
view.Options is deliberately limited to innate editor behavior that core editor, document, action, or renderer code must consult directly. Module-owned settings live with their module.
Command System#
Packages: internal/term/command, internal/term/builtin.
term/command provides the machinery: command signatures, argument parsing, tokenization and expansion, completion, key parsing, key tries, keymaps, option registration, config sections, and the registry. term/builtin provides the content: built-in command modules, default key bindings, module-owned config structs, and live option handlers. Many commands resolve directly to view/action calls; others bridge to the UI model, the language server, version control, shell commands, sessions, or config reload.
Terminal UI#
Packages: internal/term/ui, internal/tui.
term/ui contains the Bubbletea model: document, image, and terminal pane rendering; status lines; prompt; pickers; completion popup; hover and signature popups; overlays; macro handling; mouse handling; and event routing. internal/tui is the low-level terminal layer: cell buffers, styles, Kitty graphics primitives, spans, and ANSI rendering.
Every overlay (completion, hover, signature help, code actions, pickers, the command prompt) implements BufferOverlayComponent: Layout reports where it goes and how big it is, PaintBuffer draws into a buffer it owns, and the compositor blits that buffer onto the frame at the reported position. Each overlay caches its own paint buffer and skips repainting when nothing about it changed since the last frame (size, content, and theme all unchanged), so a popup that only moves re-blits instead of redrawing. Bordered popups share the popup helper so content and border render in one pass.
Syntax And Themes#
Packages: internal/term/syntax, internal/term/highlight, internal/term/theme.
syntax owns the Tree-sitter language registry, embedded highlight, injection, and textobject queries, query inheritance, parser/query caches, Tree-sitter tokenization, syntax-aware selection, bracket matching, and surround-pair lookup for the supported languages. highlight is the Chroma fallback and also provides language detection and fallback styles. theme decodes embedded Catppuccin themes and maps scope names onto tui.Style values. Editor rendering caches highlight spans per document revision; picker previews cache spans for open documents by revision and file previews by path. Binary picker previews decode PNG, JPEG, and GIF files into image previews when supported.
Highlight queries are bundled for every supported Tree-sitter language. Injection and textobject queries are bundled where toe has behavior that consumes them.
Services#
Packages: internal/lsp, internal/vcs.
internal/lspimplements the language-server client: transport, lifecycle, capability negotiation, dynamic file-watch registration, document sync, diagnostics, completion, hover, signature help, navigation, symbols, code actions, rename, formatting, document links, inlay hints, document colors, progress, and workspace edits. Server metadata comes from mergedlanguages.tomldata.internal/vcsimplements version-control integration behind a provider registry. Git is the only provider today, shelling out to the git binary. A debounced per-document diff worker (vcs.Differ) computes line hunks;vcs.Attachwires a session into document lifecycle observers, installs the editor’sview.VersionControlimplementation, and exposes update events for gutter/status/picker rendering.
Support#
Packages: internal/geom, internal/i18n, internal/locale, internal/loader, internal/glob, internal/health, internal/testutil.
geom owns shared points, sizes, areas, and clamping used by layout and rendering. locale derives the startup locale fallback chain from the environment, while i18n loads embedded message catalogs and formats localized messages and errors. Runtime path lookup, embedded assets, TOML merge helpers, theme loading, and workspace trust live in loader. glob provides glob matching used by language/config behavior. health powers the runtime health checks; testutil holds shared test infrastructure.
Data Flow#
toe is a single Bubbletea program. One frame looks like:
- Terminal input arrives as a Bubbletea message.
- The model routes it: modal overlays (picker, completion, prompt) get first refusal; otherwise the key trie resolves it against the active mode’s keymap.
- The resolved command runs its handler. Editing handlers usually call
view/actionhelpers that build aTransactionagainst the document’s currentRope. - For an edit, applying the transaction produces a new
Rope, increments the document revision, records history unless the edit is being accumulated for insert mode, maps selections through theChangeSet, and notifies observers such as language-server document sync and the version-control differ. - The renderer draws the visible viewport from cached highlight spans and gutter state into the cell buffer, and Bubbletea diffs it onto the screen.
Because document text is a persistent Rope, background workers can keep the text snapshot they were handed. Mutable document snapshot fields are protected by document locks where async language-server goroutines need to read or update them.
Extension Points#
- Languages and language servers: add or override
[[language]]entries and[language-server.<name>]sections in the mergedlanguages.tomldata. No code changes are needed for a new server. Tree-sitter highlighting for a new language requires adding the grammar import tointernal/term/syntax, registering it in the language registry, and bundling a highlight query. - Version-control providers: implement the
vcs.Providerinterface.vcs.Attachconstructs the session withGit{}as its provider directly; adding another provider means threading aProvidervalue throughAttachfromcmd/toe/internal/app.gorather than hardcoding a second choice insidevcs. The editor consumes only theview.VersionControlseam. - Commands: add a command module under
term/builtinthat registers signatures against the command registry. Registered commands automatically participate in key binding, prompt completion, and the command palette. - Actions: put reusable editing behavior in
view/actionso commands, keymaps, and UI components can share it. - Themes: themes are TOML scope-to-style maps decoded by
internal/term/themeand loaded throughloader. The four embedded Catppuccin variants (latte,frappe,macchiato,mocha) are the supported theme names today. - Clipboard: register yanks and pastes use
view/register. System clipboard actions detect external tools (pbcopy/pbpaste,xclip,xsel, orwl-copy/wl-paste) directly inview/action. An OSC 52 (terminal clipboard escape sequence) layer wraps the system clipboard so a copy also reaches the clipboard of a terminal reached over SSH; custom command providers are not implemented yet. - UI components: overlays implement
BufferOverlayComponent(Layout+PaintBuffer) and are composed by the compositor via blit, never by drawing directly into the shared frame buffer. Pickers share source/list/render helpers for matching, hit testing, scrolling, preview caching, and cursor visibility.
Testing Strategy#
Tests are black-box (package_test) throughout the repo, and shared test helpers have their own tests. CI entry points, runtime asset validation, command/keybinding registration tests, behavior tests, integration tests, and render benchmarks cover the supported surface.
Rendering-sensitive code has benchmarks with -benchmem coverage for long single lines, picker previews, large highlighted files, scrolling, and visual-column calculations. Service packages are exercised with real fixtures where practical: temporary git repositories for version control and an in-process test language server.