Open-Source Wikis

/

Helix

/

Packages

/

helix-term

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, symbols

Key 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
    end

The 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 the static_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 in commands/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.
  • Picker for file/buffer/symbol/diagnostic pickers.
  • Prompt for : and /.
  • Menu<CompletionItem> for autocompletion.
  • Overlay for 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 the Backend and Buffer types the compositor draws into.
  • helix-view — the Editor is owned by Application::editor; Document::apply is the hot path for edits.
  • helix-lsp / helix-dap — clients are owned by the Editor registry; messages are surfaced through the application's tokio select loop.
  • helix-event — every async hook in handlers/ uses AsyncHook; synchronous hooks dispatch on DocumentDidChange, SelectionDidChange, etc.

Entry points for modification

  • Adding a new bindable command: add a function in commands.rs, register it in the static_commands! macro invocation, and bind it in keymap/default.rs.
  • Adding a typable command: add a TypableCommand entry to TYPABLE_COMMAND_LIST in commands/typed.rs; re-run cargo xtask docgen.
  • Adding a UI layer: create a new module in ui/, implement Component, push it onto cx.callback in a command.
  • Adding an event-driven feature: add a handler module in handlers/, register hooks in handlers::setup, expose the sink on the Handlers struct (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.

helix-term – Helix wiki | Factory