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:
- 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
| Mode | Enter | Exit |
|---|---|---|
| Normal | default; <esc> from any other mode | |
| Insert | i 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 |
| Replace | R | <esc> |
| Replace one | r | after one keystroke |
| Operator-pending | d c y > < = gu gU g~ gc gq gw | after motion / text object |
g submode | g | one keystroke |
z submode | z | one keystroke (h/j/k/l keep z armed) |
| Search | / ? | <enter> (run), <esc> (cancel) |
Counts
Most motions and operators accept a count prefix.
| Form | Meaning |
|---|---|
3w | three words forward |
5j | down five lines |
2dw | delete two words |
2d3w | delete six words (counts multiply) |
5G | jump to line 5 |
5gg | jump to line 5 |
A leading 0 is the start-of-line motion, not part of a count.
Cursor motions
Character / line
| Key | Motion |
|---|---|
h | one column left |
l | one column right |
j | one buffer line down (same column) |
k | one buffer line up (same column) |
0 | cursor 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 |
H | cursor to top of viewport; {n}H to row n from top |
M | cursor to middle line of viewport |
L | cursor to bottom of viewport; {n}L to row n from bottom |
gj | one display line down (next wrapped row, same screen column) |
gk | one display line up (previous wrapped row, same screen column) |
Word
| Key | Motion |
|---|---|
w W | next word / WORD start |
e E | end of word / WORD |
b B | previous word / WORD start |
ge gE | previous end of word / WORD |
WORD (capital) is whitespace-delimited; word is also broken by
punctuation.
Find char on line
| Key | Motion |
|---|---|
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
| Key | Motion |
|---|---|
gg | first line |
G | last line, or {n}G to line n |
{ } | previous / next paragraph |
% | matching () [] {} <> |
Search
| Key | Motion |
|---|---|
/pat | forward search |
?pat | backward search |
n | next match (search direction) |
N | previous match |
* | search word under cursor forward |
# | search word under cursor backward |
The last pattern is stored in register /.
Jumps
| Key | Motion |
|---|---|
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
| Key | Action |
|---|---|
<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 |
zt | current line to top |
zb | current line to bottom |
zH zL | scroll half-width left / right |
Insert
| Key | Enters insert at … |
|---|---|
i | cursor |
I | first non-blank |
a | after cursor |
A | end of line |
o | new line below |
O | new line above |
s | delete char, then insert |
S | delete line, then insert |
c{motion} | delete motion, then insert |
C | delete to EOL, then insert |
Inside insert mode:
| Key | Action |
|---|---|
<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:
| Key | Action |
|---|---|
<up> <c-k> | previous candidate |
<down> <c-j> | next candidate |
<enter> <tab> | accept |
<esc> | cancel |
Candidates are words already present in the buffer.
Replace
| Key | Action |
|---|---|
r{c} | replace one char with c |
R | overwrite 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)
| Key | Action |
|---|---|
x | delete char under cursor |
X | delete char before cursor |
J | join line below with space |
gJ | join line below without space |
p | paste after cursor |
P | paste before cursor |
gp gP | paste; cursor ends after the pasted text |
u | undo |
<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.
| Operator | Effect | Linewise form |
|---|---|---|
d | delete | dd |
c | change (delete + insert) | cc |
y | yank (copy) | yy |
> | shift right | >> |
< | shift left | << |
= | reindent | == |
gu | lowercase | guu |
gU | uppercase | gUU |
g~ | toggle case | g~~ |
gc | toggle line comment | gcc |
gq | wrap paragraph at ruler | gqq |
gw | wrap paragraph, keep cursor | gww |
D | d$ | |
C | c$ | |
Y | yank 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).
| Object | Key |
|---|---|
| word | w |
| WORD | W |
| sentence | s |
| paragraph | p |
| 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).
| Key | Action |
|---|---|
o | swap selection ends |
O | swap corner (block) |
y d x c s | apply operator, exit visual |
> < = | shift / reindent, exit visual |
u | lowercase selection |
U | uppercase selection |
~ | toggle case |
p P | replace selection with register contents |
gc gq gw | comment / wrap selection |
<esc> <c-c> | exit (records `< / `>) |
Visual-block extras:
| Key | Action |
|---|---|
I | block insert (prepend on every row) |
A | block append (append on every row) |
r{c} | fill block with c |
Marks
| Key | Action |
|---|---|
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.
| Register | Contents |
|---|---|
" | unnamed (last yank / delete) |
0 | last yank |
+ | system clipboard |
_ | black hole (discard) |
/ | last search pattern |
. | last inserted text |
- | last small delete |
a-z | named (overwrite) |
A-Z | named (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.
| Key | Action |
|---|---|
q{reg} | start recording into register {reg} |
q | stop 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:
qa: start recording into registera.ciw(<ctrl-r>"): change the inner word, type(, paste the deleted word back from the unnamed register with<ctrl-r>", then type).<esc>: leave insert mode.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
| Sequence | Action |
|---|---|
gg | first line (or {n}gg) |
ge gE | previous end of word / WORD |
gj gk | display down / up |
g_ | end of line, last non-blank |
gJ | join without space |
gp gP | paste, 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.
| Key | Action |
|---|---|
zh zk | grow the z-range outward (stays in z) |
zl zj | shrink the z-range inward (stays in z) |
zv zV | promote 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
| Sequence | Action |
|---|---|
zz z. | center the current line in the viewport |
zt | scroll current line to the top |
zb | scroll current line to the bottom |
zH zL | scroll left / right by half a viewport width |
z. additionally moves the cursor to the first non-blank.
Folds
| Sequence | Action |
|---|---|
zo | open fold under cursor |
zc | close fold under cursor |
za | toggle fold under cursor |
zO zR | open all folds |
zC zM | close all folds |
zA | toggle all folds |
What's different from Vim
- The z-range and its live highlight do not exist in Vim;
zh/zlin Vim scroll the viewport horizontally one column at a time. Rune binds horizontal scroll tozH/zL(half-width jumps) and reuses lower-casezh/zlfor structural-range grow / shrink. zv/zVin Vim only unfold around the cursor. In Rune they promote the current z-range to a visual selection.zRandzOboth open all folds;zMandzCboth 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). Qex-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.