Open-Source Wikis

/

Helix

/

Features

/

Modal editing

helix-editor/helix

Modal editing

Helix is modal. The editor has three primary modes — Normal, Select, and Insert — plus transient sub-modes triggered by sticky keys (Goto, Match, Window, View, Space). The keymap and the editing model are intentionally Kakoune-style: a key first selects (or moves a selection) and then an action operates on the selection.

Modes

Mode is defined in helix-view/src/document.rs:

pub enum Mode {
    Normal,
    Select,
    Insert,
}
Mode Meaning Default keys
Normal Movement and command mode. Cursor selects one grapheme. Default at startup. Esc from any other mode.
Select "Visual" mode — every motion extends the selection. v toggles select; Esc returns to normal.
Insert Free-form character insertion. i, a, o, O, I, A.

The current mode lives on Editor::mode. A few invariants:

  • Insert-mode commands enforce single-grapheme cursors (the rope position is always on a grapheme boundary).
  • Switching from Insert to Normal collapses the selection to a one-grapheme block cursor and runs registered hooks (auto-format, auto-save, completion close).
  • Macro recording and replaying serialize key events through the active mode's keymap.

The keymap pipeline

graph LR
    KeyEvent --> EditorView[EditorView::handle_event]
    EditorView -->|mode| Keymaps
    Keymaps -->|walk| KeyTrie
    KeyTrie -->|leaf| Mappable[MappableCommand]
    KeyTrie -->|inner| Pending[Pending sequence + Info popup]
    Mappable -->|Static| Cmd[fn ctx]
    Mappable -->|Typable| TypedCmd[:typed-command]
    Cmd --> Doc[Document::apply]

Keymaps maintains a pending stack of KeyEvents. Each new key extends the stack; on a leaf match the command runs and the stack clears; on a non-leaf the Info popup appears showing what keys are valid next.

The default keymap is built in helix-term/src/keymap/default.rs using the keymap! macro:

let normal = keymap!({ "Normal mode"
    "h" | "left" => move_char_left,
    "g" => { "Goto"
        "g" => goto_file_start,
        "d" => goto_definition,
        // …
    },
    // …
});

User TOML keymaps are merged on top via keymap::merge_keys (helix-term/src/keymap.rs).

Selection-first commands

Every editing command takes the current Selection and produces either a new Selection (for movements) or a Transaction plus a new selection (for edits). The pattern is illustrated in commands.rs:

fn delete_selection(cx: &mut Context) {
    let (view, doc) = current!(cx.editor);
    let selection = doc.selection(view.id);
    let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
        (range.from(), range.to(), None)
    });
    doc.apply(&transaction, view.id);
}

Movements compose the same way: a movement function returns a new Range and the command lifts it across all ranges in the selection.

The "selection -> action" idea is documented in the project vision and is the most opinionated decision in the editor.

Sticky modes (Goto, Match, etc.)

Some keys (e.g. g, m, space, z, Z, [, ]) open a sub-mode. The compositor surfaces this as an info popup listing the valid follow-ups. Sticky modes are flagged in the KeyTrieNode::is_sticky field — when true, after running a leaf the prefix stays on the stack so the user can repeat without retyping.

Counts and registers

Every command in normal mode can be prefixed with:

  • A digit count (5j moves down 5 lines) — stored on Editor::count and consumed by the next command.
  • A register selector ("x selects register x for the next yank/paste/macro).

Both are captured in EditorView::handle_event before keymap lookup. See Context::count and Context::register in commands.rs.

Mode changes go through helix_event::dispatch so handlers can react:

  • Auto-save runs :write on Mode transitioning to Normal from Insert.
  • The completion popup closes on the same transition.
  • Macro recording serializes the key event into the active register.

Customizing

User keymaps in TOML use the same trie structure:

[keys.normal]
C-s = ":write"
"space" = { "f" = "file_picker" }

The TOML parser in helix-term/src/keymap.rs deserializes into a KeyTrie and merges with the defaults at config load time.

See also

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

Modal editing – Helix wiki | Factory