Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Helix mode — selection-first editing

An opt-in selection-first editing model, alongside — not replacing — bemtvi’s vim grammar. Where vim is verb→noun on a point cursor (d waits for a motion), Helix is noun→verb on a persistent anchor..head range: a motion re-selects on every keystroke, and a verb acts on the current selection immediately, with no operator-pending wait. Every cursor is a selection; a bare cursor is a width-1 selection, and multi-selection is the always-on default.

This is the Helix editing model ported natively — not its keybindings bolted on. The hard part isn’t rebinding keys; it’s that a motion means something different: w moves and re-selects the next word on every selection, so wd deletes a word with no motion-wait in between. That semantic change is why the model is native Rust (a genuine core-grammar change), while the key layout ships as the bundled btv.helix plugin.

Vim behavior is untouched. Helix is per-session and off by default.

Turning it on

-- ~/.config/bemtvi/init.lua
btv.helix.enable()          -- enter Helix normal mode at startup

Or interactively: :helix toggles it (and btv.helix.disable() turns it off). Both are idempotent. The status line reads HELIX (normal) or HELIX-SEL (select); the mode codes are hn / hs, distinct from vim’s n/v so plugins and ModeChanged can tell them apart.

Search defaults to smart-case in Helix without touching the global ignorecase/smartcase; opt out with btv.helix.enable{ smart_case = false }.

The two modes

ModeStatusWhat motions do
Helix normal (hn)HELIXword/find motions move and re-select; plain char/line motions collapse to a point at the target
Helix select (hs)HELIX-SELevery motion moves only the head, extending the selection (anchor held)

v toggles between them (keeping the selection and its shape). <Esc> in select drops to normal; in normal it collapses to a point and drops secondary selections.

Motions

Motions return a range, so the selection follows the cursor as you move.

KeysSelection
w / Wthe next word / WORD plus trailing whitespace, stopping before the next word (vim’s w lands on it)
b / B, e / Eback to the previous word start / forward to the next word end
h j k l, 0 ^ $, G, arrowsplain moves — collapse to a point at the target (in normal), extend (in select)
f{c} / t{c} / F{c} / T{c}select forward/backward to (or till) {c} — and, unlike vim, they scan the whole document, matching the next occurrence on any line
; , (after a find)repeat / reverse the last find

Word motions are Helix-semantic (a line’s leading indentation is its own word; blank lines are skipped; a selection never spans a newline). W/B/E collapse the word/punct classes so a run of any non-blank chars is one WORD.

Verbs act now — no operator-pending

A verb operates on the current selection immediately, across every selection, as one undo group:

KeyEffect
ddelete the selection
cdelete and enter (multi-cursor) Insert — <Esc> resumes Helix normal
yyank (the selection stays highlighted, Helix-style)
> / <indent / unindent
=reindent
~switch case (keeps the selection)
r{c}replace every selected character with {c}
Rreplace the selection with the yank
Jjoin the lines the selection spans
p / Ppaste after / before the selection (not the cursor char); with multiple selections each pastes its own slice of the last multi-yank

So wd = select a word, delete it. wcXyz<Esc> = change a word to “Xyz”. No motion-wait ever.

Selection verbs

Transforms on the selection set itself — these have no direct vim analog:

KeyEffect
x / Xextend the selection to whole lines; repeat grows one line down / up
%select the whole file
_trim each selection to its non-whitespace content
;collapse each selection to its head (a caret)
, / Alt-,keep only the primary selection / drop the primary, keep the rest
Alt-;flip anchor and head
( / )rotate which selection is primary through document order
Alt-( / Alt-)rotate the contents (text moves between selections; ranges stay put)
&align every selection’s start onto the same column (“align the = signs”)

Multiple selections

Multi-selection is the default, so most verbs already fan out. To grow a selection set:

  • C / Alt-C — copy the primary selection onto the next / previous line(s), each copy becoming the new primary (walk it down/up with repeats).
  • The regex spawners below (s / S).

Insert entry fans out too: i a I A collapse each selection to its insert point and open a multi-cursor Insert at all of them; o / O open a fresh line at every selection.

Match mode (m) — brackets, text objects, surround

m opens a sub-grammar:

KeysEffect
mmjump to the matching bracket (like vim’s %)
mi{o} / ma{o}select the inner / around text object {o} at each selection
ms{c}surround each selection with the {c} delimiter pair
md{c}delete the {c} pair surrounding each selection
mr{from}{to}replace the {from} surrounding pair with {to} (two-stage, with a live preview)

The text-object alphabet is the full shared set — vim objects (w W p s, pairs ( { [ <, quotes " ' `) and the tree-sitter captures f function / a argument / c comment / t class — so mif selects the enclosing function body, maf the whole function, mia the argument. (Requires a grammar with textobjects.scm; btv.textobject.map keys work here too.)

Regex selection (s / S / K / Alt-K)

Each opens a /-style prompt that transforms the selection set; the would-be result previews live as you type:

KeyEffect
sselect each regex match within the selection (one selection per match)
Ssplit the selection on the regex
K / Alt-Kkeep / remove selections that contain a match

s over % (whole file) then a verb is the “change every occurrence” workflow — e.g. %sfoo<CR>c….

Registers, view, and more

  • Register select "{reg} — target register {reg} for the next y/d/c/p/P/R (one-shot), reusing vim’s registers.
  • View menu z — zt / zz / zb reposition the viewport around the cursor line, leaving the selection put.
  • Search / ? n N — a match becomes the selection in normal mode; in select mode each search / n adds the match as a new selection (growing a multi-selection).
  • Undo / redo — u / U.

The bundled btv.helix plugin binds the keys a bare grammar key can’t reach:

  • Insert entry — i a I A o O (all per-selection).
  • Goto menu g — gg file start, ge last line, gh / gl line start/end, gs first non-blank, plus LSP gd/gy/gr/gi (definition / type / references / implementation).
  • Space menu <Space> — the picker + LSP leader: find files, global search, buffers, document/workspace symbols, diagnostics, hover, rename, code action, jumplist, marks.

which-key

The native which-key (btv.on_key_pending, see UI primitives) lights up for Helix too — both the plugin menus (g, <Space>) and the native sub-grammars: m match mode, mi/ma (the object alphabet, tree-sitter captures included), z view, f/t find, r replace, and " register.

Rebinding

Both Helix modes share one keymap bucket, helix, which falls through to the native grammar on no match (so Helix stays fully usable with no config — the plugin only adds keys). Every verb is exposed by name through btv.helix.actions.<name>, so any key is rebindable:

-- alias X to "extend line" (x); user maps win over defaults
btv.keymap.set("helix", "X", btv.helix.actions.extend_line_below)

-- add a goto entry: gm -> last line (a count typed before it still applies)
btv.keymap.set("helix", "gm", btv.helix.actions.goto_last_line, { desc = "Last line" })

The action names mirror Helix’s own command names (extend_line_below, select_regex, flip_selections, rotate_selection_contents_forward, …), so a Helix user’s muscle memory carries over. An unknown name fails loud.

Try it

The examples/helix config is a runnable, annotated walkthrough:

BEMTVI_CONFIG=examples/helix cargo run -p bemtvi -- examples/helix/sample.txt

How it works (in brief)

Helix reuses vim’s machinery rather than forking it. A Range { anchor, head } / Selections vocabulary projects over the existing stores — the primary lives in the vim cursor + visual anchor, secondaries in the same CURSOR_NS / ANCHOR_NS extmarks the multi-cursor mode uses — so selections ride the buffer’s edit choke point and undo for free. Mode::HelixNormal / HelixSelect have their own parse step (handle_helix) but share the operator / multi-cursor engine; the one predicate Mode::shows_selection() (visual or Helix) lets Helix reuse the visual selection’s rendering and multi-cursor sweep wholesale.

For the full design — the projection seam, the grammar, the named-action registry, and every verb — see the Helix editing model design record.