(macros)
Editing

Modal editing (evil)

Macros offers modal editing in two flavors, both built entirely on the editor's keymaps, both opt-in, and both fully overridable from Scheme:

  • evil (this page) — Vim-style, verb-then-motion (d w deletes a word). Enable with (evil-mode).
  • helix — selection-first, motion-then-verb (w selects a word, d deletes it), with first-class multiple selections. Enable with (helix-mode).

Turn on one or the other, not both. Macros uses standard Emacs bindings unless you opt in — modal editing is not on by default.

evil-mode

Macros ships with evil-mode: Vim-style modal editing that mirrors GNU Emacs's evil-mode — plain Vim modal editing, no leader key.

Enable it from your init.scm (see Configuration):

(evil-mode)     ; opt in to Vim-style modal editing
(evil-disable)  ; turn it back off

The Rust side only decides when plain keys are commands (Normal/Visual states) versus text (Insert state); every binding is Scheme and fully overridable.

States

  • Normal — keys are commands. This is where you start.
  • Insert — keys insert text. Enter with i / a / o (and capitals); leave with Esc.
  • Visual — character selection for operators. Enter with v.

Esc (keyboard-quit) always returns you to Normal.

Normal-state motions

Key Moves
h j k l left / down / up / right
w b e g e (and W B E g E) word / WORD motions
0 ^ $ start / first non-blank / end of line
g g / G first / last line (5 G: line 5)
{ } / ( ) paragraph / sentence
% matching bracket
f F t T + char, ; , find on the line, repeat
H M L top / middle / bottom of the window
] m [ m (] f [ f), ] M [ M next / previous function start, end (tree-sitter)
] ] [ [, ] [ [ ] next / previous class (section) start, end
] d [ d next / previous diagnostic

Counts work everywhere: 3 w, 5 j, 2 ] m.

Entering insert state

Key Action
i / I insert before cursor / at first non-blank
a / A append after cursor / at end of line
o / O open a line below / above

In Insert state, C-r + a register name inserts that register.

Operators

Operators combine with any motion or text object, with a count on either side — Vim's {count}{operator}{count}{motion}: d 2 w, 2 d 3 w, c i ", y a p, g U i w, > i p, d / foo RET, d ' a. Doubling the operator acts on lines (d d, 3 > >, g U U, g c c), and v / V after the operator force the motion charwise / linewise (d v j). Motions are exclusive, inclusive, or linewise exactly as in Vim.

Operator Does
d c y delete / change / yank
> < = shift right / left, reindent
g u g U g ~ g ? lowercase / uppercase / toggle case / rot13
g q g w format to *fill-column* (gw keeps the cursor)
g c toggle line comments

Text objects: i w a w, quotes (i " i ' i `), brackets (i ( / i b, i { / i B, i [, i <), paragraphs (i p), and — from the syntax tree — functions (a f i f), classes / types (a c i c), arguments / parameters (a a i a) and comments (a / i /). In Visual state the same keys select the object, and operator keys act on the selection.

Single-key edits: x s S D C Y r ~ J g J p P, u / C-r undo / redo. C-a increments the number at or after the cursor (decimal, hex, binary; with a count) and g - decrements; in Visual, C-a / g C-a / g C-x work per line. Vim's C-x decrement is off by default because C-x is the Emacs prefix — enable it with (evil-enable-ctrl-x-decrement).

Repeat with .

. repeats the last change — an operator with its motion, an Insert session (c w … Esc, o …, A …), x, r, p, J, > >, and so on. A count replaces the original one (3 .). Each change, including its Insert session, undoes as one step.

Registers

" + a name picks the register for the next yank, delete, or put: "a–"z ("A–"Z append), "0 (last yank), "1–"9 (deleted lines), "- (small deletes), "_ (black hole), "+ / "* (system clipboard), and the read-only ". (last insert), ": (last command line), "/ (last search). The unnamed register is the kill ring, kept in sync with the system clipboard. :registers lists them.

Macros

q a records into register a (q A appends), q stops; @ a replays (3 @ a), @ @ repeats the last one, @ : the last ex command.

Marks and jumps

m a sets a mark (m A a file mark), ' a jumps to its line, a</kbd> to the exact spot. Marks move with edits. Automatic marks: <kbd>' '</kbd> (before the last jump),'[ '](last change/yank),'< '>(last Visual area — <kbd>g v</kbd> reselects it),'.and'^. :marks` lists them.

Jumps (G, /, n, %, {, marks, g d, …) are recorded: C-o goes back, C-i (or Tab) forward; :jumps lists them.

Windows

C-w then: h j k l move between windows, w / W cycle, p previous; s / v split, n new buffer, c / q close, o only; = balance, + - < > resize, _ | maximize; H J K L move the window to the far side, x exchange, r rotate. Emacs users get windmove-left/-right/-up/-down and balance-windows (C-x +).

Folds

z a toggle, z c / z o close / open, z M / z R fold / open everything, z j / z k next / previous fold.

Key Action
/ / ? search forward / backward
n / N next / previous match
* / # search the word under the cursor

Ex commands

Press : to enter an Ex command (from Visual it starts with '<,'>). Ranges: %, ., $, line numbers, 'a marks, /pat/ and ?pat?, with +n / -n offsets and , or ; between.

:%s/old/new/g          substitute (flags: g c i I n e &, and a count)
:'<,'>s/x/y/c          …asking y/n/a/q/l for each match
:&&  &  g&             repeat the last substitute
:g/pat/d   :v/pat/d    run a command on (non-)matching lines
:g/TODO/normal A!      …including Normal-state keys
:%norm A;              run keys on every line of a range
:5   :$   :.+3         go to a line
:d  :y  :m0  :t.  :co$  :j  :>  :<  :sort[!] [inu]  :put
:noh  :registers  :marks  :jumps  :new  :vnew  :wincmd h

Scrolling

Key Action
C-d / C-u half-page down / up

No leader key by default

Like vanilla evil-mode in Emacs, Macros' evil has no Space leader bindings. A leader menu is a Doom/Spacemacs convention, not part of evil itself, so it's left to you. Adding one is a few lines in your init.scm:

;; Roll your own Spacemacs-style leader. Commands are 'name symbols (a bare
;; command identity or a string name work too).
(evil-set-leader "space")                    ; the default; set BEFORE binding
(evil-leader-set "space" 'helm-execute-command) ; SPC SPC
(evil-leader-set "f f" 'helm-find-files)
(evil-leader-set "b b" 'helm-buffers)
(evil-leader-set "n o" 'delete-other-windows)

Leader chords bound with evil-leader-set work everywhere, not only in file buffers:

  • Special buffers (magit, dired, package dashboards like k8s-dashboard) — SPC reaches your leader even though these buffers drive their own keymaps.
  • Terminals and Insert state, where SPC has to type a space — use the alt leader instead, M-SPC by default (Doom's doom-leader-alt-key): M-SPC n o in a terminal runs the same command as SPC n o in a file. Change it with (evil-set-leader-alt "ctrl-space"), or disable it with (evil-set-leader-alt ""), before your bindings.

Per-mode leader bindings shadow the global ones in that mode, including special-buffer modes:

(evil-leader-set-for-modes '("rust" "go")
  (list (cons "g g" 'lsp-find-definition)))
(evil-leader-set-for-modes '("k8s-mode")
  (list (cons "k r" 'k8s-refresh)))

Customizing

evil bindings live in the "evil-normal", "evil-insert", "evil-visual" (and -line / -block) maps, and motions / text objects for operators in "evil-operator". Rebind exactly like any other key:

(define-key "evil-normal" "shift-q" 'kill-buffer)

Because it's all Scheme, you can add operators, motions, and text objects of your own:

;; An operator: bind a key to (evil-operator "name") and define what it does
;; with the range [start, end) of kind "char" or "line".
(evil-define-operator "sort-lines"
  (lambda (kind start end) (evil-ex (string-append (number->string (+ 1 (line-of-pos start))) ","
                                                    (number->string (+ 1 (line-of-pos (- end 1)))) "sort"))))
(define-key "evil-normal" "g s" "evil-operator \"sort-lines\"")

;; A motion for operators: return (list type (point) target).
(evil-define-motion "next-blank-line"
  (lambda () (list "line" (point) (paragraph-forward-pos (point)))))
(define-key "evil-operator" "g b" "evil-motion \"next-blank-line\"")

;; Tree-sitter node kinds for a language the defaults miss.
(evil-ts-set-kinds "kotlin:function" '("function_declaration"))

A key binding whose command contains %c reads one more key as its argument (that is how m, ", @, and f under an operator work). Commands registered with (set-jump-command! 'name) are recorded in the jump list. See Scripting with Steel.