This guide is the comprehensive technical reference for Helix — the modal terminal text editor written in Rust. It is written for administrators, packagers, power users, and developers who need to understand how Helix is structured, deploy and configure it precisely, harden it, and extend or contribute to it.
Where the Helix User Guide explains how to use the editor, this guide explains how it works: the multi-crate workspace architecture, the complete configuration surface, the runtime-file model, the workspace-trust security boundary, and the internals a contributor needs.
- Project: Helix (
hx) - Version: 25.7.1
- License: MPL-2.0
- Language / edition: Rust, edition 2021
- Minimum Supported Rust Version (MSRV): 1.90
- Repository: https://github.com/helix-editor/helix
- Homepage: https://helix-editor.com
Table of Contents
- Executive Summary
- Architecture Overview
- Crate Reference
- Core Data Model
- Rendering and UI Layer
- Event System and Hooks
- LSP and DAP Integration
- System Requirements
- Build and Installation for Operators
- Runtime File Resolution
- Configuration Mastery
- Language and Grammar Configuration
- Theming Reference
- Security Analysis
- Operations
- Code Quality and Conventions
- Testing Strategy
- Potential Improvements
- Contributing
- Quick Reference
1. Executive Summary
Helix is a single self-contained binary (hx) plus a runtime directory of tree-sitter
grammars and queries. It has no plugin runtime, no embedded scripting language, and no
network services. All "smart" behavior — syntax highlighting, code navigation, completion,
diagnostics, debugging — is delivered either by compiled-in tree-sitter grammars or by
out-of-process language servers and debug adapters that Helix speaks to over stdio.
The codebase is a Rust Cargo workspace of 14 crates organized as a clean dependency stack: functional core editing primitives at the bottom, an imperative editor/view layer in the middle, and a terminal frontend at the top, with protocol clients (LSP, DAP) and support crates alongside. This layering is the single most important fact for understanding, packaging, or extending Helix.
Operationally, Helix is low-risk: it runs entirely as the invoking user, opens no sockets, and gates its only code-execution surfaces (project-local config and language-server startup) behind a workspace-trust prompt that is off by default for untrusted workspaces.
| Attribute | Value |
|---|---|
| Distribution unit | hx binary + runtime/ directory |
| Process model | Single process, runs as the user; spawns LSP/DAP child processes |
| Network surface | None (LSP/DAP communicate over stdio by default) |
| Persistence | Config files, log file, jumplist/trust lists on disk |
| Configuration | TOML (config.toml, languages.toml), with project-local merge |
| Extensibility (today) | Language servers, debug adapters, tree-sitter grammars, themes |
| Code-execution gates | Workspace trust (LSP + local config) |
2. Architecture Overview
Helix is built as a layered Cargo workspace. The dependency direction flows upward: lower crates know nothing about higher ones.
┌───────────────────────────────────────────┐
User ── terminal ───▶ │ helix-term (terminal frontend, event loop)│
│ commands.rs · keymap.rs · ui/* · main() │
└───────────────┬───────────────────────────┘
│ uses
┌──────────────────────────┴──────────────────────────┐
▼ ▼
┌────────────────────┐ ┌──────────────────────────┐
│ helix-view │ imperative shell: │ helix-tui │
│ Editor · Document │ open docs, view tree, │ TUI primitives (Surface, │
│ View · registers │ config, LSP registry │ Rect, widgets, backend) │
└─────────┬──────────┘ └──────────────────────────┘
│ uses uses ▲ ▲
▼ │ │
┌────────────────────┐ ┌──────────────┐ ┌─┴────────┐ ┌────┴───────┐
│ helix-core │ │ helix-lsp │ │ helix-dap│ │ helix-vcs │
│ Rope · Selection │ │ LSP client │ │ DAP clnt │ │ git/diff │
│ Transaction·Syntax │ └──────┬───────┘ └────┬─────┘ └────────────┘
└─────────┬──────────┘ │ │
│ ┌──────┴──────┐ ┌──────┴───────┐
│ │helix-lsp- │ │helix-dap- │
│ │types │ │types │
│ └─────────────┘ └──────────────┘
▼
┌────────────────────────────────────────────────────────────────┐
│ Foundations: helix-stdx (std extensions) · helix-event (events) │
│ helix-loader (fetch/build/load runtime) · helix-parsec (parser │
│ combinators) · xtask (dev task runner) │
└────────────────────────────────────────────────────────────────┘
The design intent (documented in the project's own architecture.md) is that helix-core
is purely functional — operations return new copies rather than mutating in place — while
helix-view is the imperative shell that ties documents, views, syntax, history, and
language servers together. helix-term is the concrete terminal frontend and event loop. The
core is "heavily based on CodeMirror 6."
Note
The
helix-viewlayer was originally intended to be frontend-agnostic; today it is tied to the terminal UI. A future custom GPU renderer (mentioned by the maintainers) would build on the sameview/coresplit.
3. Crate Reference
The workspace members, in dependency order from foundation to frontend. The default-members
is helix-term (so a bare cargo build builds the editor).
| Crate | Role |
|---|---|
helix-stdx |
Extensions to the Rust standard library (path canonicalization, etc.), in the spirit of rust-analyzer's stdx. |
helix-core |
Core editing primitives, functional: Rope, Selection/Range, Transaction, Syntax, history, snippets, increment/decrement, indentation. |
helix-event |
Primitives for defining and dispatching editor events and registering hooks (sync hooks and debounced AsyncHooks). |
helix-loader |
Fetching, building, and loading external resources — tree-sitter grammars and runtime files; runtime-directory resolution. |
helix-parsec |
Small parser-combinator library used internally (e.g. for command-line and snippet parsing). |
helix-lsp-types |
Language Server Protocol type definitions. |
helix-lsp |
The LSP client — talks to language servers over stdio. |
helix-dap-types |
Debug Adapter Protocol type definitions. |
helix-dap |
The DAP client — talks to debug adapters. |
helix-vcs |
Version-control integration: git diff gutters and change tracking (worker-based diffing). |
helix-tui |
TUI primitives — Surface, Rect, widgets, terminal backends. Forked from tui-rs, inspired by Cursive. |
helix-view |
The imperative editor shell: Editor, Document, View, registers, the view tree, config, and the language-server registry. |
helix-term |
The terminal frontend: main(), the Application event loop, commands.rs (every action), keymap.rs (key→command), and the ui/ components. |
xtask |
Ad-hoc dev task runner (e.g. cargo xtask docgen regenerates the command/language docs). |
Tip
When orienting yourself in the source,
helix-term/src/commands.rsis the highest-value file — it defines every command tied to a keybinding.helix-term/src/keymap.rsmaps keys to those commands, andhelix-term/src/ui/holds the rendered components.
Workspace build profiles
Defined in the root Cargo.toml:
| Profile | Inherits | Notable settings | Use |
|---|---|---|---|
release |
— | lto = "thin" |
Standard release |
opt |
release | lto = "fat", codegen-units = 1, strip, opt-level = 3 |
Maximum-performance distribution build |
integration |
test | opt-level = 2 for helix-core/tui/term |
Faster integration-test runs |
4. Core Data Model
These are the primitives in helix-core that everything else is built on. Understanding them
explains many of Helix's user-visible behaviors.
Rope
Buffer text is represented as a Rope (re-exported from the
ropey library). Ropes are cheap to clone, which lets Helix
take inexpensive snapshots of document state — the basis for undo history and concurrent
views.
Selection and Range
Multiple selections are a core primitive, not an add-on. A document's selection is a
Selection containing one or more Ranges. Each Range has a moving head and
an immovable anchor. A plain cursor is a single-width range where head and anchor
coincide. This is why every Helix command operates naturally on N selections at once.
Transaction
Edits are expressed as an OT-like Transaction — a single coherent change applied to a
rope. Transactions:
- can be inverted to produce an undo;
- allow selections and marks to be mapped through them, translating positions into the post-edit text state.
Transaction::change / Transaction::change_by_selection is the primary interface for
generating text edits.
Syntax
Syntax is the interface to tree-sitter ASTs, powering incremental syntax highlighting,
structural textobjects, indentation heuristics, and the ]f/[t-style navigation. Grammar
loading and the highlight engine are provided via the tree-house dependency (a tree-sitter
integration) plus the per-language query files in the runtime directory.
Important
The functional core means most operations return new values rather than mutating. This is what makes snapshots, undo, and multi-view consistency tractable — but it also means contributors must thread new state through rather than editing in place.
5. Rendering and UI Layer
The UI is composed of Components (widgets), found mostly in helix-term/src/ui. Key
abstractions (from helix-view and helix-tui):
| Abstraction | Responsibility |
|---|---|
Document |
Ties together the Rope, per-view Selections, Syntax, History, language server state — the full representation of an open file. |
View |
An open split: holds the displayed document ID plus gutter, status line, diagnostics, and the inner text area. Multiple views can show the same document (hence selections are stored per ViewId). |
Editor |
Global state: all open documents, the tree of view splits, configuration, and the language-server registry. |
Surface |
A buffer that widgets draw into; rendered to the screen each cycle. |
Rect |
An x/y/width/height region of a Surface constraining where a Component may render. |
Component |
A widget. Some (e.g. Popup, Overlay) take child components. |
Layer |
A Vec<Component> managed by the Compositor. |
Compositor |
Renders each layer in push order, so components stack — this is how a picker draws over the editor, a popup over a picker, and so on. |
Info |
The autoinfo box that shows available bindings while you wait on a prefix key like g or m. |
The render loop lives in helix-term: main() constructs an Application that runs the
event loop; on each top-level render call the Compositor paints the layer stack onto the
Surface.
6. Event System and Hooks
The helix-event crate defines the editor's internal event mechanism. Events cover document
open/change/close, language-server start/stop, and more.
- Define events with the
events!macro. - Register synchronous hooks with
register_hook!. - Dispatch editor-wide events to hooks with
helix_event::dispatch. - Use
AsyncHookfor cancellable, debounced work that should run a short time after an event settles (the basis for idle-triggered behaviors like completion and diagnostics refresh, governed byeditor.idle-timeout).
This event/hook system is the closest thing Helix has to an internal extension point today, and is expected to underpin a future plugin system.
7. LSP and DAP Integration
Language servers (LSP)
helix-lsp is a client that launches each configured language server as a child process
and communicates over stdio using the types in helix-lsp-types. The Editor holds a
registry of running servers. Multiple languages can share one server instance (e.g.
typescript-language-server serves JavaScript, JSX, TSX, and TypeScript by default).
Per-language and per-server behavior is configured in languages.toml (see
Language and Grammar Configuration). Feature
routing (only-features / except-features) lets operators compose several servers — for
example a dedicated formatter server plus a primary analysis server.
Debug adapters (DAP)
helix-dap is a Debug Adapter Protocol client (types in helix-dap-types). Debugging is
driven from the Space G minor mode or the :debug-start / :debug-remote commands and is
marked experimental.
Note
LSP and DAP are external programs that Helix executes. They are a code-execution surface and are therefore gated by workspace trust. Operators packaging Helix do not bundle language servers; users install them separately and place them on
$PATH.
8. System Requirements
Runtime
| Requirement | Detail |
|---|---|
| OS | Linux, macOS, Windows (and other Unix-likes) |
| Terminal | Any reasonably modern terminal. For full keybinding fidelity (Super key, Shift-Tab, some Alt combos), a terminal supporting the enhanced/Kitty keyboard protocol is recommended. |
| Runtime files | The runtime/ directory (grammars + queries + themes + tutor) must be discoverable (see Runtime File Resolution). |
| Language servers | Optional, per language; must be on $PATH. |
| Truecolor | Auto-detected; can be forced with editor.true-color = true. |
Build
| Requirement | Detail |
|---|---|
| Rust | The Rust toolchain; MSRV 1.90. Helix follows Firefox's MSRV policy and keeps it intentionally low for downstream packaging. |
| Git | Required. |
| C++ compiler | C++14-compatible (GCC or Clang) to build tree-sitter grammars. |
| musl note | With musl-libc, set RUSTFLAGS="-C target-feature=-crt-static" so grammars load. |
Important
When bumping the MSRV, three places must change together:
workspace.package.rust-versionin the rootCargo.toml,env.MSRVin.github/workflows/build.yml, andtoolchain.channelinrust-toolchain.toml.
9. Build and Installation for Operators
Reproducible vs. optimized builds
# Reproducible (locked dependencies)
cargo install --path helix-term --locked
# Optimized for the local CPU
cargo install \
--profile opt \
--config 'build.rustflags=["-C", "target-cpu=native"]' \
--path helix-term \
--locked
Both produce hx and build the grammars into the local runtime/ folder.
Grammar control at build time
| Mechanism | Effect |
|---|---|
HELIX_DISABLE_AUTO_GRAMMAR_BUILD |
Skip fetching/building grammars during the build. |
hx --grammar fetch / --grammar build |
Fetch/build grammars later, into the user runtime dir. |
use-grammars = { only = [...] } in languages.toml |
Restrict which grammars are processed. |
Packaging guidance
For distribution packages, set HELIX_DEFAULT_RUNTIME at build time to the final
install location of the runtime files so the shipped hx finds them out of the box:
export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime
cargo build --profile opt --locked
cp -r runtime "$BUILD_DIR/usr/lib/helix/"
cp target/opt/hx "$BUILD_DIR/usr/bin/hx"
Building the Debian package
cargo install cargo-deb
cargo deb -- --locked # output in target/debian/
The resulting .deb bundles shell completions (bash/fish/zsh), the .desktop file, the
icon, and a launcher that wires up the runtime.
Warning
A
cargo debrun may printwarning: Failed to find dependency specificationfor some grammar files — this is expected and harmless.
10. Runtime File Resolution
Helix searches multiple runtime directories in priority order; the first match for a given filename wins:
runtime/sibling of$CARGO_MANIFEST_DIR(development/testing only).runtime/under the OS-dependent Helix user config directory.$HELIX_RUNTIME.- The distribution fallback set at compile time via
HELIX_DEFAULT_RUNTIME. runtime/next to thehxexecutable.
| Platform | Config / runtime base |
|---|---|
| Linux / macOS | ~/.config/helix/ (runtime at ~/.config/helix/runtime/) |
| Windows | %AppData%\helix\ |
Validate resolution with hx --health. For source builds, either export HELIX_RUNTIME or
symlink the source runtime/ into the config dir; on Windows use a directory junction
(New-Item -ItemType Junction or mklink /D).
Caution
If syntax highlighting or indentation is missing, the cause is almost always an unresolved runtime directory or unbuilt grammar.
hx --health <language>pinpoints which.
11. Configuration Mastery
Configuration is TOML. The user file is ~/.config/helix/config.toml
(%AppData%\helix\config.toml on Windows). A project-local .helix/config.toml merges
on top of the user file and built-in defaults. Apply changes at runtime with
:config-reload or, on Unix, pkill -USR1 hx. A one-off file can be passed with hx -c.
Note
The
themekey and the[keys.*]remapping tables are top-level (not under[editor]). Key remapping is documented in the User Guide; this section is the exhaustive option reference.
[editor]
| Key | Description | Default |
|---|---|---|
scrolloff |
Lines of padding kept around the screen edge when scrolling | 5 |
mouse |
Enable mouse mode | true |
mouse-yank-register |
Register used for mouse yanks | * |
middle-click-paste |
Middle-click paste support | true |
default-yank-register |
Default register for yank/paste | " |
scroll-lines |
Lines scrolled per wheel step | 3 |
shell |
Shell for external commands | Unix ["sh","-c"] / Win ["cmd","/C"] |
line-number |
absolute or relative |
"absolute" |
cursorline |
Highlight cursor line(s) | false |
cursorcolumn |
Highlight cursor column(s) | false |
continue-comments |
Continue comment token on new line inside a comment | true |
gutters |
Gutters to show (diagnostics, diff, line-numbers, spacer) |
["diagnostics","spacer","line-numbers","spacer","diff"] |
auto-completion |
Auto popup of completion | true |
path-completion |
Filepath completion | true |
auto-format |
Format on save (also needs language auto-format) |
true |
idle-timeout |
ms after last keypress before idle timers fire | 250 |
completion-timeout |
ms after typing before completions show (5 = instant) | 250 |
preview-completion-insert |
Apply completion instantly when selected | true |
completion-trigger-len |
Min word length to trigger autocompletion | 2 |
completion-replace |
Completions replace the whole word | false |
auto-info |
Show info boxes | true |
true-color |
Force truecolor | false |
undercurl |
Force undercurl support | false |
rulers |
Columns to draw rulers at | [] |
bufferline |
always, never, or multiple |
"never" |
color-modes |
Color mode indicator per mode | false |
text-width |
Max line length (for :reflow, soft-wrap) |
80 |
workspace-lsp-roots |
Dirs treated as LSP roots (set in .helix/config.toml) |
[] |
default-line-ending |
native, lf, crlf, ff, cr, nel |
"native" |
insert-final-newline |
Insert trailing newline on write if missing | true |
atomic-save |
Atomic file writes (prevents data loss; may confuse watchers) | true |
trim-final-newlines |
Remove extra trailing newlines on write | false |
trim-trailing-whitespace |
Trim trailing whitespace on write | false |
popup-border |
popup, menu, all, or none |
"none" |
indent-heuristic |
simple, tree-sitter, or hybrid |
"hybrid" |
jump-label-alphabet |
Characters used for two-char jump labels | "abc…xyz" |
end-of-line-diagnostics |
Min severity rendered at end of line (disable to off) |
"hint" |
clipboard-provider |
Clipboard backend (see below) | platform-specific |
editor-config |
Read .editorconfig files |
true |
rainbow-brackets |
Rainbow matching brackets (needs rainbows.scm) |
false |
kitty-keyboard-protocol |
enabled, disabled, or auto |
"auto" |
insecure |
Disable workspace-trust gating (see Security) | false |
[editor.clipboard-provider]
Either a built-in name — pasteboard (macOS), wayland, x-clip, x-sel, win32-yank,
termux, tmux, windows, termcode, none — or a custom command set:
[editor.clipboard-provider.custom]
yank = { command = "cat", args = ["test.txt"] }
paste = { command = "tee", args = ["test.txt"] }
# optional: primary-yank / primary-paste
Custom commands communicate the clipboard contents over stdin/stdout.
[editor.statusline]
Three regions (left / center / right). Sub-keys:
| Key | Default |
|---|---|
left |
["mode","spinner","file-name","read-only-indicator","file-modification-indicator"] |
center |
[] |
right |
["diagnostics","selections","register","position","file-encoding"] |
separator |
"│" |
mode.normal / mode.insert / mode.select |
"NOR" / "INS" / "SEL" |
diagnostics |
["warning","error"] |
workspace-diagnostics |
["warning","error"] |
Available elements: mode, spinner, file-name, file-absolute-path, file-base-name,
current-working-directory, file-modification-indicator, file-encoding,
file-line-ending, file-indent-style, read-only-indicator, total-line-numbers,
file-type, diagnostics, workspace-diagnostics, selections, primary-selection-length,
position, position-percentage, separator, spacer, version-control, register.
[editor.lsp]
| Key | Description | Default |
|---|---|---|
enable |
Master switch for LSP | true |
display-messages |
Show window/showMessage below statusline |
true |
display-progress-messages |
Show LSP progress messages | false |
auto-signature-help |
Auto popup signature help | true |
auto-document-highlight |
Highlight symbol references at cursor | false |
display-inlay-hints |
Show inlay hints | false |
inlay-hints-length-limit |
Max displayed inlay-hint length | unset |
display-color-swatches |
Show color swatches | true |
display-signature-help-docs |
Docs under signature help | true |
snippets |
Snippet completions (needs :lsp-restart to toggle) |
true |
goto-reference-include-declaration |
Include declaration in references | true |
[editor.cursor-shape]
Per-mode shape — block, bar, underline, or hidden. Defaults: normal/insert/select
all "block". (Only the primary cursor can change shape, a terminal limitation.)
[editor.file-picker]
| Key | Default |
|---|---|
hidden |
true |
follow-symlinks |
true |
deduplicate-links |
true |
parents |
true |
ignore |
true |
git-ignore |
true |
git-global |
true |
git-exclude |
true |
max-depth |
unset |
Helix also reads .ignore, ~/.ignore, a workspace .helix/ignore, and a global
~/.config/helix/ignore.
[editor.file-explorer]
Same key set as the file picker but with defaults tuned to show most files (hidden,
ignore, git-* default to false), plus flatten-dirs (default true).
[editor.buffer-picker]
| Key | Description | Default |
|---|---|---|
start-position |
Initially selected buffer (current / previous) |
current |
[editor.auto-pairs]
true/false, or a table mapping open→close characters. Default pairs:
() {} [] '' "" `` `` ``. Can also be set per language in languages.toml`.
[editor.auto-save]
| Key | Description | Default |
|---|---|---|
focus-lost |
Save when focus leaves Helix (needs terminal focus events) | false |
after-delay.enable |
Save after a delay since last edit | false |
after-delay.timeout |
Delay in ms | 3000 |
[editor.search]
| Key | Description | Default |
|---|---|---|
smart-case |
Case-insensitive unless pattern has uppercase | true |
wrap-around |
Wrap after the last match | true |
[editor.whitespace]
render is all, none, or a per-character table (space, nbsp, nnbsp, tab,
newline). characters overrides the glyphs used. Toggle live with
:set whitespace.render all.
[editor.indent-guides]
| Key | Description | Default |
|---|---|---|
render |
Draw indent guides | false |
character |
Guide glyph | "│" |
skip-levels |
Indent levels to skip | 0 |
[editor.gutters]
layout is a vector of gutter components, default
["diagnostics","spacer","line-numbers","spacer","diff"]. Sub-sections:
gutters.line-numbers (min-width, default 3), gutters.diagnostics (unused),
gutters.diff (colors from theme diff.plus/minus/delta; no options), gutters.spacer
(unused).
[editor.soft-wrap]
| Key | Description | Default |
|---|---|---|
enable |
Enable soft wrapping | false |
max-wrap |
Max free space at line end | 20 |
max-indent-retain |
Max indentation carried to wrapped lines | 40 |
wrap-indicator |
Text before wrapped lines | "↪ " |
wrap-at-text-width |
Wrap at text-width instead of viewport |
false |
[editor.smart-tab]
| Key | Description | Default |
|---|---|---|
enable |
Tab runs move_parent_node_end when non-whitespace is to the left |
true |
supersede-menu |
smart-tab takes precedence over menu cycling | false |
[editor.inline-diagnostics]
| Key | Description | Default |
|---|---|---|
cursor-line |
Min severity shown inline on the cursor line (disable to off) |
"warning" |
other-lines |
Min severity shown inline on other lines | "disable" |
prefix-len |
Horizontal bars before the text | 1 |
max-wrap |
As soft-wrap.max-wrap for diagnostics |
20 |
max-diagnostics |
Max diagnostics rendered inline per line | 10 |
Severity values: error, warning, info, hint.
[editor.word-completion]
| Key | Description | Default |
|---|---|---|
enable |
Complete words from open buffers | true |
trigger-length |
Word chars before triggering | 7 |
12. Language and Grammar Configuration
Language behavior lives in languages.toml, merged across three locations (built-in → user
config dir → project .helix/).
[[language]] keys
| Key | Description |
|---|---|
name |
Language name |
language-id |
LSP language id |
scope |
TextMate-style scope, e.g. source.rust |
injection-regex |
Pattern for language-injection matching |
file-types |
Extensions and/or glob tables |
shebangs |
Interpreters from the shebang line |
roots |
Marker files for LSP root selection |
auto-format |
Format on save |
diagnostic-severity |
Minimum severity to display |
comment-tokens / block-comment-tokens |
Line / block comment tokens |
indent |
{ unit, tab-width } |
language-servers |
Servers for this language (with optional feature filters) |
grammar |
Tree-sitter grammar name (defaults to name) |
formatter |
External formatter (stdin→stdout) |
soft-wrap / text-width / rulers |
Per-language overrides |
path-completion / word-completion / rainbow-brackets |
Per-language overrides |
workspace-lsp-roots |
Stop the LSP root search early |
persistent-diagnostic-sources |
Sources whose positions Helix tracks internally |
Project and LSP root selection
- The workspace root is found once by walking up from the working directory to the first
directory containing
.git,.svn,.jj, or.helix(else the cwd). - Root markers (
roots) are searched starting at the file, using the topmost marker up to the workspace root. workspace-lsp-roots(set in$PROJECT/.helix/config.toml) stops the search early for nested-root projects.- A server's
required-root-patternsis validation after root selection — if none match, the server is not started.
File-type detection
file-types accepts strings (extension match) and glob tables (full-path match, always
/-separated). Globs take priority over extensions.
[language-server.<name>] keys
| Key | Description |
|---|---|
command |
Binary name/path (must be in $PATH) |
args |
Arguments |
config |
Initialization options |
timeout |
Max request time in seconds (default 20) |
environment |
Env vars for the server |
required-root-patterns |
Globs that must exist for the server to start |
A language may list multiple servers; only-features / except-features route the supported
features. Feature priority follows the array order, except diagnostics, code-action,
completion, document-symbols, and workspace-symbols, which merge across all enabled
servers. The full feature list: format, goto-declaration, goto-definition,
goto-type-definition, goto-reference, goto-implementation, signature-help, hover,
document-highlight, completion, code-action, document-links, workspace-command,
document-symbols, workspace-symbols, diagnostics, pull-diagnostics, rename-symbol,
inlay-hints, document-colors, call-hierarchy.
[[grammar]] and grammar selection
[[grammar]]
name = "mylang"
source = { git = "https://github.com/example/mylang", rev = "<commit>", subpath = "optional" }
Fetch/build with hx --grammar fetch / hx --grammar build. A top-level
use-grammars = { only = [...] } or { except = [...] } (declared before any
[[language]]/[[grammar]]) limits which grammars are processed.
Tip
Helix ships query files for several hundred languages (the
runtime/queries/<lang>/directories), each potentially providinghighlights.scm,indents.scm,textobjects.scm,injections.scm,locals.scm, andrainbows.scm. Structural textobjects and tree-sitter navigation only work for languages whosetextobjects.scmexists.
13. Theming Reference
Themes are TOML files in ~/.config/helix/themes/ (or %AppData%\helix\themes\). Set with
theme = "<name>", or split light/dark:
[theme]
dark = "catppuccin_frappe"
light = "catppuccin_latte"
# fallback = "catppuccin_frappe"
Style syntax
key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl" }, modifiers = ["bold","italic"] }
- Colors: CSS hex (
#RGB/#RRGGBB) or a[palette]name. The default palette is the terminal's 16 colors (black,red, …,white, pluslight-*). No alpha/transparency. - Modifiers:
bold,dim,italic,underlined(deprecated),slow_blink,rapid_blink,reversed,hidden,crossed_out. - Underline styles:
line,curl,dashed,dotted,double_line. - Inheritance:
inherits = "<theme>". - Rainbow brackets: a
rainbow = [...]list of styles.
Note
When matching a syntax highlight, the longest matching key wins (e.g.
function.builtinbeatsfunction).defaultandbase16_defaultare reserved built-in names.
Scope families
Syntax scopes mirror tree-sitter highlight names and follow Sublime/TextMate naming —
top-level families include type (builtin/parameter/enum.variant), constant
(numeric.integer/float, character.escape, builtin.boolean), string
(regexp, special.path/url/symbol), comment (line.documentation,
block.documentation, unused), variable (mutable, builtin, parameter,
other.member.private), keyword (control.conditional/repeat/import/return/exception,
operator, directive, function, storage.type/modifier), function
(builtin/method/macro/special), tag, namespace, label, punctuation
(delimiter/bracket/special), operator, special, markup
(heading.1..6, list, bold, italic, link.*, quote, raw.inline/block), and diff
(plus/minus/delta with gutter variants).
Interface scopes theme the editor chrome — ui.background, ui.cursor.*, ui.gutter,
ui.linenr(.selected), ui.statusline(.normal/insert/select/inactive/separator),
ui.bufferline.*, ui.popup(.info), ui.picker.header.*, ui.window, ui.help,
ui.text(.focus/inactive/info/directory/symlink), ui.virtual.*
(ruler/whitespace/indent-guide/inlay-hint/wrap/jump-label), ui.menu(.selected/scroll),
ui.selection(.primary), ui.cursorline.*, ui.cursorcolumn.*, plus diagnostic scopes
(warning, error, info, hint, their .diagnostic.inline variants, and
diagnostic.*), and tabstop for snippet placeholders.
14. Security Analysis
Helix's threat surface is small and well-defined. It runs as the invoking user, opens no network sockets, and has no embedded scripting runtime. The notable surfaces are below.
Code execution via workspace features (the primary boundary)
Two features can lead to running code defined by a repository:
- Project-local configuration —
.helix/config.tomland.helix/languages.tomlcan specify formatters, language-server commands, and other behavior. - Language servers — starting a server executes an external binary against project files.
Helix mitigates this with Workspace Trust:
- No workspace is trusted by default.
- Opening a file in an undecided workspace prompts the user.
Neverexcludes the workspace permanently from prompts.:workspace-trust/:workspace-untrustmanage the current workspace.- Trust/exclude lists live in
~/.local/share/helix/trusted_workspacesand.../excluded_workspaces.
Caution
Setting
editor.insecure = truedisables this protection — every.helix/config is loaded and language servers start without prompting. Do not enable it on machines that open untrusted repositories.
Shell expansions and command execution
The command line supports %sh{...} expansions and shell commands (:sh, :pipe, !,
|). These run with the user's editor.shell. They execute only when the user invokes them,
but keybindings and project config could in principle pre-stage such commands — another reason
the trust boundary matters.
File writing
atomic-save (default true) writes files atomically to prevent corruption on interruption.
Operators relying on file watchers/hot-reload should be aware atomic writes (rename-in-place)
can confuse some watchers; disable per the trade-off if needed.
Clipboard providers
Custom clipboard providers run external commands for yank/paste. Treat them as trusted
configuration; a malicious clipboard-provider.custom command would run on every clipboard
operation.
Defense-in-depth recommendations
- Keep
editor.insecureunset. - Review
.helix/directories before trusting unfamiliar repositories. - Install language servers from trusted sources and pin versions where possible.
- For debugging untrusted code, run inside a container or VM (DAP executes the target).
15. Operations
Day-to-day operator commands
| Task | How |
|---|---|
| Health check | hx --health (or hx --health <language>) |
| Open log | :log-open, or launch with hx --log /path and tail -f |
| Increase log verbosity | hx -v (repeatable: -vv, -vvv); HELIX_LOG_LEVEL for integration tests |
| Reload config | :config-reload or pkill -USR1 hx (Unix) |
| Restart a language server | :lsp-restart (or :lsp-stop) |
| Set/inspect options live | :set <key> <value>, :toggle <key>, :get <key> |
| Change working directory | :cd <path> (:pwd to show) |
Log file locations
The log file path is reported by hx --health (typically under the OS cache directory, e.g.
~/.cache/helix/helix.log). Override with --log.
Upgrades
Helix is a single binary plus a runtime/ directory. Upgrade via your package manager, or
rebuild from source. After upgrading a source build, rebuild grammars if the grammar set
changed (hx --grammar fetch && hx --grammar build). Many crash fixes ship per release, so
staying current is the main reliability lever.
Backup and state
User-relevant state on disk:
| Path | Contents |
|---|---|
~/.config/helix/config.toml, languages.toml |
User configuration |
~/.config/helix/themes/ |
User themes |
~/.config/helix/runtime/ |
User runtime (grammars/queries/themes), if used |
~/.local/share/helix/trusted_workspaces, excluded_workspaces |
Workspace-trust decisions |
~/.cache/helix/helix.log (typical) |
Log file |
16. Code Quality and Conventions
| Aspect | Convention |
|---|---|
| Language | Rust, edition 2021, MSRV 1.90 |
| Formatting | rustfmt (rustfmt.toml at repo root) |
| Workspace | 14-crate Cargo workspace; layered dependency stack (core → view → term) |
| Core style | Functional — operations return new values rather than mutating in place |
| Errors | anyhow::Result at boundaries (e.g. arg parsing), thiserror for typed errors |
| Concurrency | tokio async runtime; parking_lot locks; arc-swap for hot-swappable config |
| Key dependencies | ropey (rope), nucleo (fuzzy matcher for pickers), tree-house/tree-sitter (syntax), slotmap (arena IDs), toml (config), termina (terminal), globset (ignore globs), etcetera (config dirs) |
| Generated docs | Command and language tables are generated via cargo xtask docgen |
| Logging | log crate macros (log::info!, warn!, error!); verbosity via -v |
Tip
Some book pages (the
:commandlist and language-support matrix) are generated from the code withcargo xtask docgen. When adding commands or languages, regenerate them rather than hand-editing.
17. Testing Strategy
| Test type | Command | Notes |
|---|---|---|
| Unit + doc tests | cargo test --workspace |
All packages |
| Integration tests (helix-term) | cargo integration-test |
Helpers in helix-term/tests/test/helpers.rs; set HELIX_LOG_LEVEL=debug for logs |
| Build/test speed | integration profile (opt-level 2 for core/tui/term) |
Faster integration runs |
Integration tests exercise editor commands end-to-end (see
helix-term/tests/test/commands). Contributors are strongly encouraged to add integration
tests for new behavior, using existing tests as templates.
Warning
On macOS, integration tests may fail with
Too many open files (os error 24). Raise the limit first:ulimit -n 10240.
18. Potential Improvements
Observations drawn from the codebase, the project's own documentation, and the community issue tracker. These are opportunities, not defects in normal use.
- Plugin system. Helix has no end-user plugin runtime today; extensibility is limited to
language servers, debug adapters, grammars, and themes. The
helix-eventhook system and documented intentions point toward a future plugin architecture. Diff providers beyond git are explicitly noted as "eventually supported by a future plugin system." - Frontend-agnostic
viewlayer.helix-viewwas meant to be frontend-agnostic but is currently coupled to the terminal UI; decoupling would enable the mooted custom GPU renderer. - Crash hardening. The issue tracker shows a recurring class of panics around changeset/
position math and selection edge cases (e.g.
#12582,
#9830,
#13325). Many are fixed per release;
continued fuzzing/property testing of
TransactionandSelectionmapping would reduce this class further. - Terminal key-protocol fragmentation. Several reported "binding doesn't work" issues
(e.g. macOS
Alt/Opt#2280) stem from inconsistent terminal support for modifier keys. Broader enhanced-keyboard-protocol adoption upstream and clearer in-app diagnostics would help. - Window resizing. Manual split resizing is a long-standing requested feature (#1176).
- Documentation gaps. The project notes that "not all functionality is fully documented";
the auto-generated keymap/command tables are the most reliable reference, and
from-vim.mdcarries TODOs (textobjects/surround/registers coverage). - Grammar/textobject coverage. Structural textobjects and tree-sitter navigation depend on
per-language
textobjects.scmfiles, which exist for only a subset of the supported languages — a steady contribution opportunity.
19. Contributing
- Guidelines:
docs/CONTRIBUTING.md. Good first issues are labeledE-easy. - Internals overview:
docs/architecture.md(summarized in this guide). - Dev build:
cargo run(debug; faster to compile). For faster compiles try the mold linker. With rustup, you may needrustup component add rust-analyzerbecauserust-toolchain.tomlpins the MSRV. - Print debugging: use
log::info!/warn!/error!and run with-v(morevs = higher verbosity), orcargo run -- --log foo.logandtail -f foo.log. - Generated docs: run
cargo xtask docgenafter changing commands or languages. Preview the book withmdbook serve book. - API docs:
cargo doc --open. - High-value files:
helix-term/src/commands.rs(all commands),helix-term/src/keymap.rs(key bindings),helix-term/src/ui/(components),helix-core/(editing primitives). - MSRV bumps: update
Cargo.toml,.github/workflows/build.yml, andrust-toolchain.tomltogether; follow Firefox's MSRV policy.
20. Quick Reference
Environment variables
| Variable | Purpose |
|---|---|
HELIX_RUNTIME |
Override the runtime directory location |
HELIX_DEFAULT_RUNTIME |
Compile-time distribution fallback runtime path (packagers) |
HELIX_DISABLE_AUTO_GRAMMAR_BUILD |
Skip grammar fetch/build during the build |
HELIX_LOG_LEVEL |
Log level for integration tests |
RUSTFLAGS="-C target-feature=-crt-static" |
Required for musl builds |
CLI flags
hx [FLAGS] [files] — -h/--help, -V/--version, --tutor, --health [LANG],
-g/--grammar fetch|build, -c/--config <path>, --log <path>, -w/--working-dir <path>,
--vsplit, --hsplit, --strict, -v (repeatable), -- (end of flags). File positions:
file:row:col, +N file, + file.
Key paths
| Path | Purpose |
|---|---|
~/.config/helix/config.toml (%AppData%\helix\config.toml) |
User config |
~/.config/helix/languages.toml |
Language config |
~/.config/helix/themes/ |
User themes |
~/.config/helix/runtime/ |
User runtime files |
<project>/.helix/{config,languages}.toml |
Project-local overrides (trust-gated) |
~/.local/share/helix/trusted_workspaces / excluded_workspaces |
Trust decisions |
Crate map (one line each)
helix-stdx std extensions · helix-core functional editing primitives · helix-event
events/hooks · helix-loader runtime/grammar loading · helix-parsec parser combinators ·
helix-lsp-types/helix-lsp LSP types/client · helix-dap-types/helix-dap DAP
types/client · helix-vcs git diff · helix-tui TUI primitives · helix-view editor shell ·
helix-term terminal frontend · xtask dev tasks.
Operator checklist
- [ ]
hx --healthreports config, language, log, and runtime paths correctly. - [ ] Runtime directory resolves (grammars built; syntax highlighting works).
- [ ] Required language servers are installed and on
$PATH. - [ ]
editor.insecureis unset; workspace trust prompts behave as expected. - [ ] Themes/config in
.helix/reviewed before trusting unfamiliar repos. - [ ] Backups cover
~/.config/helix/and~/.local/share/helix/trust lists.