helix-editor/helix
helix-term
The terminal frontend. The crate produces the hx binary and contains the application loop, the keymap-to-command pipeline, and every UI component the user sees. ~30,600 lines of Rust — the largest crate in the workspace.
Purpose
helix-term glues a terminal renderer (helix-tui) to the editor state (helix-view) and the protocol clients (helix-lsp, helix-dap). It owns the async event loop, all keybindings, and the picker/prompt/menu/popup widgets specific to Helix.
Directory layout
helix-term/src
├── main.rs # binary entry, arg parsing, --health/--tutor/--grammar dispatch
├── lib.rs # re-exports modules to the binary and tests
├── application.rs # Application struct, async event loop (~1.4k LOC)
├── compositor.rs # Component trait, Compositor, EventResult, Callback
├── args.rs # CLI flag parsing (no external clap dep — hand-rolled)
├── config.rs # Config (theme + keys + editor) load/merge
├── events.rs # helix-term-specific events (PostInsertChar, …)
├── handlers.rs # event hook registration entry point
├── handlers/ # auto-save, completion, diagnostics, document colors,
│ # document highlight, document links, prompt, signature help,
│ # snippets, workspace trust
├── health.rs # `hx --health` impl: clipboard, languages, terminal probes
├── job.rs # Jobs queue: futures that callback on the main loop
├── keymap.rs # Keymaps, KeyTrie, KeyTrieNode, walk semantics
├── keymap/
│ ├── default.rs # default normal/select/insert keybindings
│ └── macros.rs # `keymap!` DSL
├── commands.rs # All keyboard commands (~7.1k LOC)
├── commands/
│ ├── dap.rs # DAP-related commands
│ ├── lsp.rs # LSP-related commands
│ ├── syntax.rs # tree-sitter motion commands (textobjects, sibling, ancestor)
│ └── typed.rs # `:typable-commands` (~4.5k LOC)
└── ui/ # UI components (see below)The ui/ subtree is large enough to warrant its own breakdown:
helix-term/src/ui
├── mod.rs # shared helpers (prompt, picker bootstraps, file_picker) (~28k chars)
├── editor.rs # EditorView: the bottom layer of the compositor (~1.7k LOC)
├── completion.rs # LSP completion popup
├── document.rs # Helpers to render a document into a surface (line nums, soft wrap, virtual text)
├── info.rs # Auto-info popup (sticky-key hints)
├── markdown.rs # Markdown renderer for hover/diagnostics
├── menu.rs # Generic selectable list (used by completion)
├── overlay.rs, popup.rs # Modal layers
├── prompt.rs # `:` and `/` line editor
├── picker.rs, picker/ # Fuzzy file/symbol/buffer pickers
├── select.rs # Modal "select one" prompt
├── spinner.rs # Animated LSP progress indicators
├── statusline.rs # Configurable bottom status line
├── text.rs, text_decorations.rs, text_decorations/
│ # virtual text rendering (inline diagnostics, inlay hints, jump labels)
└── lsp.rs, lsp/ # LSP-specific UI: code actions menu, signature help, symbolsKey abstractions
| Type | File | Purpose |
|---|---|---|
Application |
application.rs |
Owns terminal, compositor, editor, jobs, signals. run() is the main loop. |
Compositor |
compositor.rs |
Stack of Components. Top layer receives events first. |
Component |
compositor.rs |
Trait: handle_event, render, cursor. |
EventResult |
compositor.rs |
Ignored(Option<Callback>) or Consumed(Option<Callback>); controls propagation. |
Context |
commands.rs |
Per-command context: editor, jobs, count, register, callback. |
MappableCommand |
commands.rs |
A bindable command — either a Rust fn or a typable command + args. |
Keymaps |
keymap.rs |
Mode → KeyTrie; tracks the pending key sequence. |
KeyTrie / KeyTrieNode |
keymap.rs |
Recursive key map. Sticky modes are flagged. |
EditorView |
ui/editor.rs |
Bottom compositor layer; renders documents and dispatches input through Keymaps. |
Jobs |
job.rs |
Queue of Future<Output = anyhow::Result<Callback>>; callbacks run on the main loop. |
Config |
config.rs |
Theme + keymap + editor config; merges global and workspace TOML. |
Handlers |
handlers.rs |
Bundle of AsyncHook event sinks (completion, signature help, auto-save, diagnostics, etc.). |
The application loop
sequenceDiagram
participant Main as main()
participant App as Application
participant Term as Terminal Backend
participant Comp as Compositor
participant LSP as LSP Registry
participant DAP as DAP Registry
participant Jobs
Main->>App: Application::new
App->>Comp: push EditorView
Main->>App: run()
loop tokio::select
Term-->>App: KeyEvent / Resize / Paste
LSP-->>App: Server response / notification
DAP-->>App: Debug adapter message
Jobs-->>App: Job future resolved
App->>App: editor.idle_timer
App->>Comp: handle_event
Comp->>App: maybe redraw
App->>Term: render
endThe actual select arms include redraw requests, file save streams, terminal signals (SIGWINCH, SIGCONT, SIGTSTP), and config reloads. See Application::handle_terminal_events, handle_language_server_message, handle_debugger_message, and handle_idle_timeout in application.rs.
Commands and keymap
Two flavours of command:
- MappableCommand::Static — a
fn(&mut Context)declared with thestatic_command!macro. Default normal/select/insert keymaps reference these by name (e.g.move_char_left). - MappableCommand::Typable — a typable command parsed from the prompt (
:write,:lsp-restart …). Defined incommands/typed.rs.
The default keymap in keymap/default.rs is built with the keymap! macro (keymap/macros.rs). User TOML keymaps are merged in by config.rs via keymap::merge_keys.
When a key is pressed, EditorView::handle_event (ui/editor.rs) consults Keymaps. If the trie matches a leaf, it runs the command. If it matches a non-leaf, it stores the pending sequence and may show an info popup. If it doesn't match, the event is passed to the active mode's input handling (e.g. inserting a character in insert mode).
Compositor layers
The bottom layer is always EditorView. Other layers are pushed on top by commands:
Popup<Markdown>for hover, signature help, diagnostic details.Pickerfor file/buffer/symbol/diagnostic pickers.Promptfor:and/.Menu<CompletionItem>for autocompletion.Overlayfor full-screen views like:tutor.
Each layer's handle_event returns Ignored (pass through) or Consumed. Modal layers (Picker, Prompt) consume almost everything until dismissed.
Handlers (event hooks)
handlers/ registers hooks against editor events to drive features that are reactive rather than command-driven:
| Handler | Purpose |
|---|---|
completion |
Spawn LSP completion requests after debounce on text change |
signature_help |
Show LSP signature help when in argument context |
diagnostics |
Pull-model diagnostics fetcher (servers without publishDiagnostics) |
document_colors |
Request and render LSP document colors |
document_highlight |
Highlight references to the symbol under the cursor |
document_links |
Resolve and render clickable links |
auto_save |
Save documents after idle timeout |
prompt |
Update prompt completions as the user types |
snippet |
Track active snippet tab-stops |
workspace_trust |
Prompt before loading workspace-local config |
All handlers wire up in handlers::setup (handlers.rs).
Health checks
hx --health [<lang>|clipboard|languages|all-languages|all] runs health::print_health. It probes the clipboard provider, terminal capabilities, and per-language tooling (formatter, language servers, query files).
Integration points
helix-tui— provides theBackendandBuffertypes the compositor draws into.helix-view— theEditoris owned byApplication::editor;Document::applyis the hot path for edits.helix-lsp/helix-dap— clients are owned by theEditorregistry; messages are surfaced through the application's tokio select loop.helix-event— every async hook inhandlers/usesAsyncHook; synchronous hooks dispatch onDocumentDidChange,SelectionDidChange, etc.
Entry points for modification
- Adding a new bindable command: add a function in
commands.rs, register it in thestatic_commands!macro invocation, and bind it inkeymap/default.rs. - Adding a typable command: add a
TypableCommandentry toTYPABLE_COMMAND_LISTincommands/typed.rs; re-runcargo xtask docgen. - Adding a UI layer: create a new module in
ui/, implementComponent, push it ontocx.callbackin a command. - Adding an event-driven feature: add a handler module in
handlers/, register hooks inhandlers::setup, expose the sink on theHandlersstruct (helix-view/src/handlers/mod.rs).
For details on specific subsystems, see features/pickers, features/language-servers, and features/syntax-highlighting.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.