helix-editor/helix
helix-view
The mutable, frontend-aware editor state. Owns documents, view splits, registers, themes, and the integrations with LSP, DAP, and VCS. ~14,500 lines of Rust.
Purpose
Where helix-core is functional and immutable, helix-view is the imperative shell. It defines the Editor (the global state container), Document (one open file), and View (a viewport with its own selection). Frontends — currently only helix-term — drive the editor through this crate.
The crate was originally intended to be frontend-agnostic. In practice it has terminal-shaped types like Rect, Style, CursorKind, but the Editor/Document/View API itself is the seam any future frontend would target.
Directory layout
helix-view/src
├── lib.rs # DocumentId/ViewId types, align_view helper, public re-exports
├── editor.rs # Editor struct, Config, Action, EditorEvent (~2.5k LOC)
├── document.rs # Document, Mode, History integration (~2.7k LOC)
├── view.rs # View, ViewPosition, gutter assignments, scroll math
├── tree.rs # Tree (split layout) and Layout enum
├── theme.rs # TOML theme loader, Style/Color resolution
├── gutter.rs # Gutter renderers (line numbers, diagnostics, diff signs, breakpoints)
├── register.rs # Named registers (`"x`)
├── clipboard.rs # System clipboard providers (osc52, wayland, x11, win, mac, none)
├── input.rs # KeyEvent + KeyCode parsing/serialization (~1.4k LOC)
├── keyboard.rs # KittyEnhancedKeyboardProtocol detection
├── graphics.rs # Rect, Color, Modifier, Style, CursorKind
├── info.rs # Auto-info popup data
├── annotations/ # InlineDiagnostics + virtual text plumbing
├── annotations.rs
├── handlers/, handlers.rs # Cross-subsystem handler registry (LSP, DAP, completion)
├── events.rs # DocumentDidChange/Open/Close/FocusLost events
├── expansion.rs # `%{}` variable expansion in commands and external args
├── macros.rs # log macros & `view!`/`current!` shortcutsKey abstractions
| Type | File | Purpose |
|---|---|---|
Editor |
editor.rs |
Global state: documents map, view tree, registers, language servers, diagnostics, debug adapters, theme loader, syn loader. |
Document |
document.rs |
Open file: rope + selections + syntax + history + diagnostics + diff handle + per-language config. |
View |
view.rs |
Open viewport: DocumentId, ViewPosition (scroll), gutter offsets, jumplist. |
Tree |
tree.rs |
Hierarchical Container/Node representation of view splits, with Layout::Horizontal/Vertical. |
Mode |
document.rs |
Normal / Select / Insert. |
Action |
editor.rs |
What to do when opening a document: Replace, HorizontalSplit, VerticalSplit, Load. |
Config |
editor.rs |
The [editor] section of the user TOML; ~80 fields covering autocompletion, gutters, soft wrap, LSP, search, statusline. |
Theme |
theme.rs |
Named scopes → Style. Loader merges parent themes. |
Registers |
register.rs |
Named registers including special ones (", _, *, +, #). |
EditorEvent |
editor.rs |
The async event type the main loop selects over. |
Editor: the central data structure
pub struct Editor {
pub mode: Mode,
pub tree: Tree,
pub documents: BTreeMap<DocumentId, Document>,
pub registers: Registers,
pub language_servers: helix_lsp::Registry,
pub debug_adapters: helix_dap::registry::Registry,
pub diff_providers: DiffProviderRegistry,
pub diagnostics: BTreeMap<Uri, Vec<(lsp::Diagnostic, DiagnosticProvider)>>,
pub theme: Theme,
pub syn_loader: Arc<ArcSwap<syntax::Loader>>,
pub config: Arc<dyn DynAccess<Config>>,
pub macro_recording: Option<(char, Vec<KeyEvent>)>,
/* … */
}Anything cross-document (a register, a language server registry) lives on Editor. Anything per-file lives on Document. Anything per-viewport lives on View.
The current! and view! macros from macros.rs are the canonical way to grab the focused view + document inside a command.
Document lifecycle
graph LR
Open[Editor::open<br/>or Document::open] --> NewDoc[Document]
NewDoc -- emit --> DDO[DocumentDidOpen event]
NewDoc --> Apply[doc.apply Transaction]
Apply --> History[History stores commit]
Apply -- emit --> DDC[DocumentDidChange event]
Apply --> ReParse[Syntax incremental parse]
Apply --> LspNotify[LSP didChange]
Apply --> Diff[VCS DiffHandle update]
Save[doc.save] -- emit --> DDS[DocumentSaved future]
Close[Editor::close] -- emit --> Closed[DocumentDidClose event]The events fire through helix_event::dispatch. Hooks declared elsewhere (in helix-term/src/handlers/) react to these to drive completion, document highlights, inlay hints, and so on.
Views and the tree
A View knows the DocumentId it shows, its scroll position (ViewPosition), the area Helix gave it from the layout tree, and a jumplist. Multiple views can show the same document; selections are stored on the Document keyed by ViewId. Closing the last view of a document does not close the document — :bc (buffer-close) does that explicitly.
The view tree is a binary Tree of horizontal and vertical splits (tree.rs). Tree::split and Tree::remove perform layout updates; Tree::traverse iterates leaves in display order.
Gutters
Gutters are configurable in TOML (editor.gutters) and rendered in gutter.rs. Built-in gutters: line numbers, diagnostics, diff signs (from helix-vcs), spacer, and breakpoints. Each gutter declares a width and a per-line render closure.
Themes
theme.rs loads *.toml files from runtime/themes/ (and the user's config dir). A theme is a scope-to-Style map; scopes are dotted paths like function.method. Theme::find_scope_index walks from most specific to least specific to find a matching style — the same fallback rule tree-sitter uses.
There are 200+ bundled themes in runtime/themes/, validated by cargo xtask theme-check.
Registers and macros
register.rs implements the named registers users access via "x syntax. Special registers integrate with the system clipboard (*, +), the search history (/), the command history (:), and the black-hole register (_). Macro recording (Q/q) writes a sequence of KeyEvents into a register; replaying (@x) feeds them back through the keymap.
Configuration
editor::Config (editor.rs) is the largest config struct in the workspace. New fields require:
- Adding the field with a
Defaultvalue. - Documenting it in
book/src/editor.md. - (If the user can toggle it via
:setor:toggle) ensuring it resolves correctly through the TOML deserializer (#[serde(deny_unknown_fields)]on the parent).
Integration points
helix-core—Selection,Transaction,Syntaxare central;Documentowns one of each.helix-event— theevents!macro declares the document/selection events; hooks live inhelix-term/src/handlers/.helix-lsp—Editor::language_serversishelix_lsp::Registry; per-document language server IDs live onDocument::language_servers.helix-dap—Editor::debug_adaptersandEditor::breakpoints.helix-vcs—Editor::diff_providers; per-documentDiffHandlelives onDocument.helix-tui— only at the type level (Rect,Style); rendering happens inhelix-term/src/ui/.
Entry points for modification
- Adding an editor config field: see "Configuration" above.
- Adding a new event the editor emits: declare it with
events!inevents.rsand dispatch viahelix_event::dispatchin the spot that mutates state. - Adding a new gutter: implement the gutter closure in
gutter.rsand add it toGutterType. - Tweaking selection rendering or virtual text: see
text_decorationswhich renders annotations declared inhelix-view.
For a deeper dive into Document, View, and Tree, see primitives/document and primitives/view.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.