Helix Editor

repository·master·Indexed 13 days ago

https://github.com/helix-editor/helix

A terminal-based text editor written in Rust featuring a Kakoune-inspired modal editing model. It includes built-in LSP support, multiple selections, and tree-sitter-based syntax highlighting. The documentation covers installation, building from source, command-line usage, and extending the editor with new languages and themes.

Tokens
109.4K
Snippets
327
Records
514
Agent score
96%

What's inside Helix

  1. Overview of helix-tui

    master
    The helix-tui library is a fork of tui-rs. It is designed to provide a terminal user interface (TUI) foundation for Helix. Unlike the original tui-rs, helix-tui focuses on using the double buffer implementation and render diffing, while bypassing the original library's widget and layouting systems.
  2. Overview of Helix Editor features

    master

    Helix is a terminal-based text editor written in Rust, inspired by Kakoune and Neovim. It uses a modal editing model heavily based on Kakoune's design decisions.

    Key features include:

    • Vim-like modal editing: Traditional modal interaction patterns.
    • Multiple selections: Support for multiple cursors/selections.
    • Built-in Language Server Protocol (LSP) support: Integrated language intelligence.
    • Tree-sitter integration: Smart, incremental syntax highlighting and code editing.

    Note: Indentation definitions are language-specific. You can check available indentation queries in the runtime/queries/<lang>/indents.scm files within the installation directory.

  3. Using pickers in Helix

    master

    Pickers are interactive windows used to select items like files or symbols. They are primarily accessed via keybindings in space mode. Once a picker is open, it uses its own specific keymap for navigation.

    Common pickers include:

    • File picker: For selecting files.
    • Global search picker: For searching text across the project.
    • Workspace symbol picker: For finding symbols via the language server.
  4. How Helix uses tags queries for symbol navigation

    master

    Helix provides LSP-like features, such as document and workspace symbol pickers, using Tree-sitter queries defined in tags.scm files. For a language to support these features, it must have a Tree-sitter grammar and a tags.scm query file that pattern matches specific nodes in the syntax tree.

    To implement or test tags for a language:

    1. Location: Query files should be placed in runtime/queries/{language}/tags.scm for core support. For local testing, place them in your local runtime directory (e.g., ~/.config/helix/runtime on Linux).
    2. Mechanism: The queries use Tree-sitter syntax to identify nodes and assign them specific 'captures' that Helix uses to categorize symbols.
  5. Use minor modes and nested keybindings

    master

    Minor modes allow you to create dedicated binding sets accessed by pressing a specific key. You can define these by nesting keys within the mode configuration. This is useful for creating custom command sets or modifying behavior in specific modes (like view mode).

    For example, you can create a new minor mode bound to the + key in normal mode, or create a sequence like jk to exit insert mode.

    # Create a minor mode bound to '+' in normal mode
    [keys.normal."+"]
    m = ":run-shell-command make"
    c = ":run-shell-command cargo build"
    
    # Map 'jk' to exit insert mode using nesting
    [keys.insert.j]
    k = "normal_mode"
  6. Use the rainbow.include-children property

    master

    By default, Helix requires that any node captured with @rainbow.bracket must be a direct descendant of a node captured with @rainbow.scope to be highlighted.

    If your language structure has intermediate nodes between the scope-changing node and the bracket (common in markup languages like HTML), you must use the rainbow.include-children property to allow indirect descendants to be highlighted.

    Example: HTML implementation

    In HTML, an (element) node contains (start_tag) and (end_tag) nodes, which in turn contain the bracket characters. To ensure the brackets are colored correctly, apply the property to the (element) scope:

    ((element) @rainbow.scope
     (#set! rainbow.include-children))
    ((element) @rainbow.scope
     (#set! rainbow.include-children))
  7. Use expansions in the command line

    master

    Expansions allow you to insert dynamic values into commands using the % token. They follow the pattern %[<kind>]<open><contents><close>.

    Supported Delimiters:

    • ( ), [ ], { }, < >
    • Single characters: ', ", or | (e.g., %{variable} is equivalent to %|variable|)

    Expansion Kinds:

    • Variables: No <kind> provided. Replaces the pattern with an editor value (e.g., %{cursor_line}).
    • Shell (%sh{..}): Executes the contents in the configured shell. Shell expansions are evaluated recursively.
    • Unicode (%u{..}): Converts hexadecimal codepoints to Unicode characters (e.g., %u{25CF} for ).
    • Register (%reg{..}): Inserts the content of a specific register (e.g., %reg{a}).

    Note: To use a literal % character inside an expansion, double it (%%). Expansions are only evaluated when the Enter key is pressed.

    [keys.normal]
    # Example: Print git blame for the current line to the statusline
    space.B = ":echo %sh{git blame -L %{cursor_line},+1 %{buffer_name}}"
  8. How locals and highlights.scm interact

    master

    The locals system and highlights.scm run in parallel:

    1. Baseline: highlights.scm always determines the baseline highlight for a node.
    2. Override: locals.scm can only override the baseline highlight when a @local.reference successfully resolves to a @local.definition.
    3. Discards: Discard patterns (non-@local.* captures) in locals.scm have no effect on the highlights.scm results; they only prevent the locals system from resolving a reference.
  9. How Workspace Trust works in Helix

    master

    Helix uses a workspace trust model to protect users from executing arbitrary code contained in potentially malicious projects (e.g., a freshly cloned repository or a checked-out PR).

    Certain features are gated behind explicit per-workspace trust:

    • Local workspace configuration: Loading .helix/config.toml or .helix/languages.toml.
    • Git integration: Loading configuration from a repository's .git/config (specifically git filters that execute external programs).

    By default, Helix operates in a restricted mode where:

    • Language Servers (LSP) start automatically (using binaries from your $PATH, not from the workspace).
    • Debug Adapters (DAP) are allowed to run once you launch them manually.
    • Local configs and Git configs are not loaded until you grant trust.

    Trust is remembered across sessions, similar to direnv. When you grant trust, Helix records a hash of every file under .helix/ to detect if the configuration changes later.

  10. What actions create a jump in the jumplist

    master

    A jump is automatically created whenever you perform a "significant movement." While the list is non-exhaustive, the following actions will add a jump to the jumplist:

    Buffer and File Navigation

    • Switching buffers (via buffer picker, next/previous buffer, or last accessed/modified file).
    • Creating a new file (:new FILE).
    • Opening a file (:open FILE, :log-open, :config-open, :config-open-workspace, or :tutor).
    • Navigating via pickers, global search, the file explorer, or goto_file (gf).

    Large In-File Movements

    • Regex operations: select_regex (s) and split_regex (S).
    • Searching: search (/).
    • Selection management: keep_selections (K) and remove_selections (<A-K>).
    • File boundaries: goto_file_start (gg), goto_file_end, and goto_last_line (ge).
    • Line jumps: :goto 123, :123, or 123G.
    • Code navigation: goto_definition (gd), goto_declaration (gD), goto_type_definition (gy), and goto_reference (gr).

    Other Actions

    • Manual jump creation via Ctrl-s.
    • Switching to a buffer when attempting to close a modified buffer.
    • Jumping stack frames within the debugger.
  11. Understand the Helix Core architecture and data structures

    master

    The helix-core crate provides the fundamental, functional building blocks for the editor. Most operations are functional, meaning they return new copies of data rather than modifying it in place.

    Key Concepts

    • Rope: The primary data structure for representing buffers, powered by the ropey library. Ropes are efficient to clone and allow for easy state snapshots.
    • Selection: Represents multiple selections in the document. A Selection consists of one or more Ranges. Each Range contains a moving head and an immovable anchor. A single cursor is a Selection with one Range where the head and anchor are at the same position.
    • Transaction: Used to modify Ropes. A Transaction represents a single coherent change and can be inverted to support undo operations. Selections and marks can be mapped over a transaction to update their positions after the change is applied.
    • Syntax: The interface for interacting with tree-sitter ASTs for syntax highlighting and other language-aware features.
    // Main interface for generating text edits
    Transaction::change
    Transaction::change_by_selection
  12. Understand the View layer and Document representation

    master

    The view layer provides the logic for displaying documents in the UI. While currently tied to the terminal, it acts as an imperative layer built on top of core.

    Key Abstractions

    • Document: A comprehensive representation of an open file that ties together the Rope, Selection(s), Syntax, History, and the language server.
    • View: Represents an open split in the UI. It manages the gutter, status line, diagnostics, and the inner code area. Note that multiple Views can display the same Document; in this case, the Document stores separate selections for each view. Use document.selection(view_id) to retrieve the correct selection for a specific view.
    • Surface & Rect: A Surface is a buffer that widgets draw to. Rects define specific areas (x, y, width, height) within a Surface to limit where a Component can render (e.g., limiting a Markdown component to the bounds of a Popup).
    • Component & Layer: Widgets are called Components. Multiple components are organized into Layers (a Vec<Component>). The Compositor manages these layers, rendering them in order so that newer components (like a file picker) appear on top of existing ones.
    • Editor: The global state container. It holds all open documents, the tree of view splits, configuration, and the language server registry. Use the Editor to open or close files.