Skip to main content

Modal Editor

A vi-style cheatsheet for Rune's modal editor. The motion grammar, operators, text objects, marks, registers, and macros are all here. The : ex mode lives at the Command Prompt: same prompt, broader scope.

Enable it in config.yaml:

editor:
mode: "modal"

Where modal editing applies

Setting mode: "modal" does more than turn your file buffers modal; the same editor powers prompts and output surfaces throughout Rune, so the motions, operators, and text objects on this page work everywhere:

  • The shell. Rune's shell uses the modal editor as its prompt editor, so you can compose and edit command lines with full modal editing instead of a plain input line.
  • The agent dialogue. In Rune Agent, the dialogue prompt becomes a modal editor, giving you the same editing grammar while you write messages.
  • Terminals. Terminals gain a modal mode: enter it to edit the prompt line, and treat the whole terminal scrollback as a scrollable, navigable file under the modal editor.
  • Task and command output. When a task finishes, or a one-shot plugin command (! command) completes, its output opens in a modal editor so you can scroll and navigate the results.

Modes

ModeEnterExit
Normaldefault; <esc> from any other mode
Inserti I a A o O s S c{motion} R<esc> or <c-c>
Visual (char)v<esc>, v, operator
Visual (line)V<esc>, V, operator
Visual (block)<c-v><esc>, <c-v>, operator
ReplaceR<esc>
Replace onerafter one keystroke
Operator-pendingd c y > < = gu gU g~ gc gq gwafter motion / text object
g submodegone keystroke
z submodezone keystroke (h/j/k/l keep z armed)
Search/ ?<enter> (run), <esc> (cancel)

Counts

Most motions and operators accept a count prefix.

FormMeaning
3wthree words forward
5jdown five lines
2dwdelete two words
2d3wdelete six words (counts multiply)
5Gjump to line 5
5ggjump to line 5

A leading 0 is the start-of-line motion, not part of a count.

Cursor motions

Character / line

KeyMotion
hone column left
lone column right
jone buffer line down (same column)
kone buffer line up (same column)
0cursor to column 0 of current line
^cursor to first non-blank character of current line
$cursor to last character of current line
g_cursor to last non-blank character of current line
Hcursor to top of viewport; {n}H to row n from top
Mcursor to middle line of viewport
Lcursor to bottom of viewport; {n}L to row n from bottom
gjone display line down (next wrapped row, same screen column)
gkone display line up (previous wrapped row, same screen column)

Word

KeyMotion
w Wnext word / WORD start
e Eend of word / WORD
b Bprevious word / WORD start
ge gEprevious end of word / WORD

WORD (capital) is whitespace-delimited; word is also broken by punctuation.

Find char on line

KeyMotion
f{c}next c on line
F{c}previous c on line
t{c}up to (before) next c
T{c}up to (after) previous c
;repeat last f/F/t/T
,repeat reversed

File / paragraph / brace

KeyMotion
ggfirst line
Glast line, or {n}G to line n
{ }previous / next paragraph
%matching () [] {} <>
KeyMotion
/patforward search
?patbackward search
nnext match (search direction)
Nprevious match
*search word under cursor forward
#search word under cursor backward

The last pattern is stored in register /.

Jumps

KeyMotion
g;older change
g,newer change
`{a-z}jump to mark exact
'{a-z}jump to mark line
'. `.jump to last change
'< `>last visual selection start / end

Scrolling

KeyAction
<c-e>scroll one line down
<c-y>scroll one line up
<c-d>half page down
<c-u>half page up
<c-f>page forward
<c-b>page backward
zz z.center current line
ztcurrent line to top
zbcurrent line to bottom
zH zLscroll half-width left / right

Insert

KeyEnters insert at …
icursor
Ifirst non-blank
aafter cursor
Aend of line
onew line below
Onew line above
sdelete char, then insert
Sdelete line, then insert
c{motion}delete motion, then insert
Cdelete to EOL, then insert

Inside insert mode:

KeyAction
<esc> <c-c>back to normal
<c-h>backspace
<c-w>backspace word
<c-j>newline
<c-t>indent line one level
<c-d>outdent line one level
<c-n> <c-p>next / previous buffer-word completion
<c-r>{reg}insert register contents
<c-o>run one normal-mode command, then return
<tab>indent / accept completion
<enter>newline (autopair-aware)
<up> <down> <left> <right>move cursor

Completion popup

When <c-n> / <c-p> opens the popup:

KeyAction
<up> <c-k>previous candidate
<down> <c-j>next candidate
<enter> <tab>accept
<esc>cancel

Candidates are words already present in the buffer.

Replace

KeyAction
r{c}replace one char with c
Roverwrite from cursor until <esc>
~toggle case under cursor (normal mode)

In visual-block mode, r{c} fills the whole block with c.

Edits (normal mode)

KeyAction
xdelete char under cursor
Xdelete char before cursor
Jjoin line below with space
gJjoin line below without space
ppaste after cursor
Ppaste before cursor
gp gPpaste; cursor ends after the pasted text
uundo
<c-r>redo
.repeat last change

Operators

An operator + motion (or text object) applies the operator to the covered range. Doubling the operator applies it linewise to the current line.

OperatorEffectLinewise form
ddeletedd
cchange (delete + insert)cc
yyank (copy)yy
>shift right>>
<shift left<<
=reindent==
gulowercaseguu
gUuppercasegUU
g~toggle caseg~~
gctoggle line commentgcc
gqwrap paragraph at rulergqq
gwwrap paragraph, keep cursorgww
Dd$
Cc$
Yyank line (= yy)

Operators also accept /{pat}, ?{pat}, '{mark}, `{mark} as motions.

Text objects

After an operator (or in visual), i is "inner" (excludes delimiter), a is "around" (includes delimiter).

ObjectKey
wordw
WORDW
sentences
paragraphp
quoted" ' `
parens( ) b
braces{ } B
brackets[ ]
angles< > t

Examples: ciw change inner word, da" delete around quoted string, yi( yank inside parens, vap visually select around paragraph.

Visual mode

Enter with v (char), V (line), <c-v> (block).

KeyAction
oswap selection ends
Oswap corner (block)
y d x c sapply operator, exit visual
> < =shift / reindent, exit visual
ulowercase selection
Uuppercase selection
~toggle case
p Preplace selection with register contents
gc gq gwcomment / wrap selection
<esc> <c-c>exit (records `< / `>)

Visual-block extras:

KeyAction
Iblock insert (prepend on every row)
Ablock append (append on every row)
r{c}fill block with c

Marks

KeyAction
m{a-z}set mark
m{A-Z}set global mark
`{x}jump to mark (exact column)
'{x}jump to mark (line, first non-blank)
`. '.last change
`< `> '< '>last visual start / end

Registers

"{reg} before an operator selects the register.

RegisterContents
"unnamed (last yank / delete)
0last yank
+system clipboard
_black hole (discard)
/last search pattern
.last inserted text
-last small delete
a-znamed (overwrite)
A-Znamed (append)

Examples: "ayy yank line into a, "+p paste from clipboard, "_dd delete line without saving it.

Macros

A macro is a recorded sequence of keystrokes you can replay on demand. Record once, then repeat an edit across many places without rebuilding the key sequence each time.

KeyAction
q{reg}start recording into register {reg}
qstop recording
@{reg}play register {reg}
@@replay the last played register
{n}@{reg}play register {reg} n times
{n}@@replay the last played register n times

Recording

In normal mode, press q followed by a register name (a-z) to start recording. Every key you press is captured into that register until you press q again to stop. The q keys that start and stop the recording are not themselves captured.

For example, to record a macro into register a that surrounds the word under the cursor in parentheses:

  1. qa: start recording into register a.
  2. ciw(<ctrl-r>"): change the inner word, type (, paste the deleted word back from the unnamed register with <ctrl-r>", then type ).
  3. <esc>: leave insert mode.
  4. q: stop recording.

Recording into an uppercase register name (A-Z) appends to the macro already stored in the matching lowercase register instead of overwriting it, so you can extend a macro after the fact.

Playing back

Press @ followed by the register name to replay it: @a runs the keys stored in register a. @@ replays whichever register you played most recently, so after a single @a you can keep pressing @@ to repeat it.

Prefix a count to repeat the playback: 5@a plays register a five times, and 3@@ repeats the last register three times. Combined with a motion-aware macro, this is the fastest way to apply the same edit down a column of lines: record the edit and a j to step to the next line, then run @a with a large count.

Macros are registers

A macro lives in the same register as any yanked or deleted text, so the two share storage. You can yank a hand-written key sequence into a register with "ayy and then play it as a macro with @a, or inspect a recorded macro by pasting the register's contents into a buffer with "ap.

Because they are plain registers, recorded macros are also available to the echo command's {register} instruction, which lets you bind a recorded macro to a key or wrap it in an alias for the Command Prompt.

g submode reference

SequenceAction
ggfirst line (or {n}gg)
ge gEprevious end of word / WORD
gj gkdisplay down / up
g_end of line, last non-blank
gJjoin without space
gp gPpaste, cursor after text
gu{motion}lowercase
gU{motion}uppercase
g~{motion}toggle case
gc{motion}toggle line comment
gq{motion}wrap at ruler
gw{motion}wrap, keep cursor
g; g,older / newer change

z mode

z mode is Rune's own thing. In Vim, z is a one-shot prefix for scrolling and folds. Rune keeps those commands, but also turns z into a small structural-selection mode driven by a live-highlighted z-range.

The z-range

The moment you press z, Rune highlights a range around the cursor:

  • If the cursor sits inside a fold (function body, block, JSON object, …), the range is seeded to that whole fold.
  • Otherwise the range is empty and starts at the cursor.

From there you grow or shrink it interactively. z stays armed across h/j/k/l so resizing feels continuous, no need to re-press z between adjustments. Press any other key to leave z mode, or promote the range to a visual selection.

KeyAction
zh zkgrow the z-range outward (stays in z)
zl zjshrink the z-range inward (stays in z)
zv zVpromote the z-range to a visual selection

Once you press v or V, you're in normal visual mode with the z-range as the selection; apply any operator (d, c, y, >, gc, …) the usual way.

The grow / shrink semantics are syntactic, not line-based: in code, zh walks out to the enclosing scope, the enclosing function, the enclosing class, then the file. zl walks back in.

Scroll positioning

SequenceAction
zz z.center the current line in the viewport
ztscroll current line to the top
zbscroll current line to the bottom
zH zLscroll left / right by half a viewport width

z. additionally moves the cursor to the first non-blank.

Folds

SequenceAction
zoopen fold under cursor
zcclose fold under cursor
zatoggle fold under cursor
zO zRopen all folds
zC zMclose all folds
zAtoggle all folds

What's different from Vim

  • The z-range and its live highlight do not exist in Vim; zh/zl in Vim scroll the viewport horizontally one column at a time. Rune binds horizontal scroll to zH/zL (half-width jumps) and reuses lower-case zh/zl for structural-range grow / shrink.
  • zv/zV in Vim only unfold around the cursor. In Rune they promote the current z-range to a visual selection.
  • zR and zO both open all folds; zM and zC both close all folds. Rune does not distinguish recursive vs non-recursive.

Command Prompt

There is no built-in : ex mode. Rune's command prompt handles the equivalent; see the Command Prompt guide. The command prompt opens with : under the modal bootstrap default.

What's not here

Rune's modal editor implements the motion / operator / text-object / register / macro / mark grammar above. It is not a Vim clone, so the following Vim features are intentionally not present:

  • A : ex command mode (Rune commands run through the Command Prompt instead).
  • Q ex-replay mode.
  • :s/old/new/ substitute syntax (use a Rune command).
  • Vimscript or ~/.vimrc-style runtime config (Rune uses Starlark and YAML).
  • Window/buffer commands like :split, :bnext: those are Rune commands.

If you find a vi binding you expected and it isn't on this page, it probably isn't implemented; please open an issue with the keystroke and the buffer state where you expected it to work.

Ask Rune Agent