Open-Source Wikis

/

Helix

/

Packages

/

helix-core

helix-editor/helix

helix-core

The core editing primitives. Frontend-agnostic, mostly functional, ~19,600 lines of Rust.

Purpose

helix-core defines the fundamental data structures Helix manipulates: ropes, selections, transactions, and the wiring around tree-sitter syntax. Everything above it (helix-view, helix-term) builds on these primitives. Operations return new values rather than mutating in place; mutability is concentrated in the upper layers.

Directory layout

helix-core/src
├── lib.rs                  # public re-exports (Rope, Selection, Transaction, …)
├── transaction.rs          # ChangeSet, Operation, Transaction (the OT-style edit)
├── selection.rs            # Range, Selection
├── position.rs             # char↔visual coordinate conversions, soft wrap math
├── movement.rs             # word/line/grapheme motion (~2.2k LOC)
├── textobject.rs           # tree-sitter-aware text objects
├── match_brackets.rs       # bracket matching via tree-sitter
├── object.rs               # selection extension over tree-sitter nodes
├── search.rs               # wrap-around search helpers
├── surround.rs             # `ms`/`md` surround commands
├── auto_pairs.rs           # configurable auto-pairing of brackets/quotes
├── comment.rs              # block & line comment toggling
├── indent.rs               # indentation heuristics + tree-sitter `indents.scm`
├── editor_config.rs        # `.editorconfig` support
├── line_ending.rs          # CRLF/LF detection and normalization
├── chars.rs, graphemes.rs  # Unicode-aware character classification
├── case_conversion.rs      # camel/snake/title case
├── command_line.rs         # parser for `:typed-commands` (flags, expansions)
├── completion.rs           # CompletionItem type
├── diagnostic.rs           # Diagnostic + Severity
├── diff.rs                 # rope diff helper
├── doc_formatter.rs        # virtual line formatter (soft wrap, virtual text)
├── doc_formatter/          # supporting modules for the formatter
├── fuzzy.rs                # nucleo configuration
├── history.rs              # undo tree
├── increment/              # number/date/word incrementers (`Ctrl-a`/`Ctrl-x`)
├── snippets.rs, snippets/  # snippet parser, elaborator, renderer
├── syntax.rs, syntax/      # tree-sitter wrapper + per-language config
├── text_annotations.rs     # virtual text overlays
├── uri.rs                  # `Uri` type used across LSP integration
├── wrap.rs                 # text-wrap helpers
├── macros.rs               # `hashmap!` and friends
├── rope_reader.rs          # `Read` adapter over a Rope
└── test.rs                 # selection-aware test helpers

Key abstractions

Type File Purpose
Rope (re-export from ropey) lib.rs Persistent text buffer; cheap clone.
Range selection.rs (anchor, head) pair using char-gap indexing.
Selection selection.rs Non-empty list of Ranges + primary index.
ChangeSet / Operation transaction.rs OT-style sequence of Retain/Delete/Insert.
Transaction transaction.rs A ChangeSet plus an optional updated Selection. Reversible, composable, mappable.
Assoc transaction.rs How a position should track edits (Before, After, AfterWord, BeforeSticky, …).
Position position.rs Visual (row, col); helpers for soft wrap & virtual text.
Syntax syntax.rs Tree-sitter parse tree + language data registry, wrapping tree-house.
LanguageData / Configuration syntax/config.rs Deserialized languages.toml.
History history.rs Undo tree of transactions with branching and persistence.
IndentStyle, IndentQuery indent.rs Indent guessing + tree-sitter indents.scm evaluator.
Tendril lib.rs SmartString<LazyCompact>; small-string optimised text fragment.

How transactions work

A Transaction wraps a ChangeSet (a flat Vec<Operation>) and an optional new Selection. The change set's len and len_after fields are validated when applying so a stale transaction can't corrupt the rope.

let transaction = Transaction::change_by_selection(rope, selection, |range| {
    let from = range.from();
    let to = range.to();
    (from, to, Some(replacement.into()))
});

// apply to the rope
transaction.apply(rope);

// invert for undo
let inverse = transaction.invert(rope_before);

// translate a selection through the edit
let new_selection = old_selection.map(transaction.changes());

Two transactions can be composed (Transaction::compose), which is how undo coalescing works. The map operation on ChangeSet translates positions across edits — used by Selection::map, by LSP to update server state (helix-lsp::util), and by anchored marks like view positions and bookmarks.

Selections and cursors

A Range has an anchor (fixed) and head (moved when extending). Cursor convention: a block cursor sits one grapheme before the head when the range is forward, or at the head when zero-width. Range::cursor, Range::cursor_line, and Range::put_cursor help work in terms of the user's cursor without breaking the range invariants.

A Selection always has at least one range and a primary index. Operations like Selection::transform give every range to a closure that returns a new range, then the result is normalized (sorted, merged where adjacent or overlapping). See helix-core/src/selection.rs for merge_consecutive_ranges, union, and the main constructor Selection::new.

Tree-sitter integration

Helix talks to tree-sitter through the tree-house crate (a Helix-maintained wrapper). The migration to tree-house happened in the 25.07 release (CHANGELOG.md).

Syntax (syntax.rs) holds the parse tree per document and can incrementally re-parse on edits. Per-language settings (LSP servers, formatters, comment tokens, file-type detection) are loaded from languages.toml into Configuration and LanguageConfiguration (syntax/config.rs). LanguageData lazily compiles indent/textobject/tag/rainbow queries on first use.

graph LR
    Toml[languages.toml] --> Config[Configuration]
    Config --> LangData[LanguageData]
    LangData -- compile_syntax_config --> SynCfg[tree_house LanguageConfig]
    LangData -- compile_indent_query --> IndQ[IndentQuery]
    LangData -- compile_textobject_query --> TxtQ[TextObjectQuery]
    LangData -- compile_tag_query --> TagQ[TagQuery]
    SynCfg --> Syntax
    Syntax --> TreeHouse[tree-house highlighter]
    TreeHouse --> Highlights[HighlightEvent stream]

Movement and text objects

movement.rs (the second-largest core file at ~2,200 lines) implements grapheme-aware word, line, paragraph, and visual motions. textobject.rs implements tree-sitter-aware selection of functions, classes, parameters, comments, and tests via textobjects.scm queries; falls back to bracket-pair heuristics for languages without queries.

Snippets

The snippet system is split across snippets.rs and snippets/:

  • parser.rs — tokenizes the LSP snippet syntax.
  • elaborate.rs — resolves variables and choices.
  • render.rs — produces a Transaction plus an ActiveSnippet describing tab-stops.
  • active.rs — drives navigation between tab-stops as the user fills the snippet.

Indent

indent.rs is the largest "data" module (~1,650 lines). It implements both the legacy heuristic indent (looking at the previous lines and bracket nesting) and a tree-sitter-driven indent powered by indents.scm queries. The IndentationHeuristic enum lets users opt into either or a hybrid.

History

History (history.rs) stores undo state as a tree, not a linear stack. Each commit holds a transaction plus its inverse. Branches are reachable through :earlier/:later typed commands. The UndoKind enum lets callers ask for "1 step", "1 second", "to revision N", etc.

Re-exports

helix-core/src/lib.rs re-exports the most-used names: Rope, RopeSlice, Selection, Range, Transaction, ChangeSet, Position, Tendril, Syntax, Diagnostic, LineEnding, plus the regex, tree_sitter, and ropey crates. Downstream code typically imports from the crate root.

Integration points

  • helix-view uses Document::apply (helix-view/src/document.rs) to push transactions through the rope and update history.
  • helix-term's commands (commands.rs) build transactions from the current selection then call doc.apply(...).
  • helix-lsp maps server-side text edits to ChangeSets using helpers in helix-lsp/src/lib.rs::util.

Entry points for modification

For details on the underlying types, see primitives/rope, primitives/selection, and primitives/transaction.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

helix-core – Helix wiki | Factory