A markdown editor with vim keybindings
Vim commands are built from parts: an operator (what to do) combined with a motion or text object (to what). Learn the parts and you can construct hundreds of commands from a small vocabulary.
| Operator | Action |
|---|---|
| d | Delete (cut — goes to a register for pasting) |
| c | Change (delete, then enter Insert mode) |
| y | Yank (copy) |
| gq | Reflow / hard-wrap text |
| gw | Reflow, keep cursor |
| > | Indent |
| < | Dedent |
| Motion / Text Object | Target |
|---|---|
| w | From cursor to next word |
| iw / aw | Inner word / a word (includes trailing space) |
| is / as | Inner sentence / a sentence |
| ip / ap | Inner paragraph / a paragraph |
| i" / i( / i{ | Inside quotes / parens / braces |
| $ | To end of line |
| } | To next blank line |
| G | To end of file |
Any operator + any motion = a valid command. dap deletes a paragraph. ciw changes a word. gqap reflows a paragraph. >} indents to the next blank line. Every new motion you learn works with every operator you already know.
Semantic line breaks mean putting one sentence per line in your source. The rendered output joins them into paragraphs — the line breaks are for the writer, not the reader.
Vim is a natural fit for this style. Its line-oriented commands become sentence-oriented: dd deletes a sentence, yy copies one, p moves one. { and } jump between paragraphs since blank lines mark the boundaries. And gqap reflows a paragraph while respecting its structure.
These motions work in Normal mode. They also work after an operator — d} deletes to the next blank line, c$ changes to end of line.
| Key | Action |
|---|---|
| h j k l | Left, down, up, right |
| w / b | Next word / previous word |
| e | End of current word |
| 0 / $ | Start / end of line |
| ^ | First non-blank character |
| ( / ) | Previous / next sentence |
| { / } | Previous / next blank line (paragraph boundary) |
| gg / G | Top / bottom of document |
42G or :42 |
Jump to line 42 |
| Ctrl-d / Ctrl-u | Scroll half-screen down / up |
| fx / Fx | Jump to next / previous x on this line |
| tx / Tx | Jump to just before next / after previous x |
| ; / , | Repeat / reverse last f/F/t/T |
| % | Matching bracket |
| * / # | Next / previous occurrence of word under cursor |
Each of these drops you into Insert mode at a different position. Press Esc to return to Normal.
| Key | Action |
|---|---|
| i / a | Insert before / after cursor |
| I / A | Insert at start / end of line |
| o / O | Open new line below / above |
| R | Replace mode (overwrite characters in place) |
The c (change) operator deletes text and drops you into Insert mode. The d (delete) operator removes text and stays in Normal. Both work with any motion or text object.
| Key | Action |
|---|---|
| ciw | Change inner word (whole word, regardless of cursor position) |
| cw | Change from cursor to end of word |
| cc | Change entire line |
| C | Change from cursor to end of line |
| ci" | Change inside quotes (also ' ` ( [ {) |
| dd | Delete entire line |
| D | Delete from cursor to end of line |
| diw | Delete inner word |
| dap | Delete a paragraph (including trailing blank line) |
| di( | Delete inside parentheses |
| x | Delete character under cursor |
| ~ | Toggle case of character |
| Ctrl-a | Increment number under cursor |
| Ctrl-x | Decrement number under cursor |
Vim calls copying “yanking.” The y operator works with any motion or text object, just like d and c.
| Key | Action |
|---|---|
| yy | Yank (copy) entire line |
| ymotion | Yank over motion (e.g. yap = yank a paragraph) |
| p | Paste after cursor (or below, for whole lines) |
| P | Paste before cursor (or above) |
Use the "+ or "* register to yank and paste with the system clipboard:
| Key | Action |
|---|---|
| "+yy | Yank current line to system clipboard |
| "+ymotion | Yank motion to system clipboard |
| "+p | Paste from system clipboard after cursor |
| "+P | Paste from system clipboard before cursor |
In the Preview pane, the Copy button copies the document as rich text (minimal HTML) for pasting into Word, email, or other rich text editors. The plain-text fallback is the raw markdown.
Text objects select structured chunks of text. Prefix with i for “inner” (contents only) or a for “around” (includes delimiters or trailing whitespace). Use them with any operator.
| Object | Meaning | Example |
|---|---|---|
| w | Word | diw = delete inner word |
| W | WORD (whitespace-delimited) | daW = delete around WORD |
| s | Sentence | cis = change inner sentence |
| p | Paragraph | gqap = reflow around paragraph |
| " ' ` | Quoted string | ci" = change inside quotes |
| ( [ { | Bracket pair | di( = delete inside parens |
The gq and gw operators hard-wrap text to a
specified width. Set your preferred width with
:set tw=72. If textwidth is 0, they wrap
at 79 columns (matching vim’s default). gq moves
the cursor to the end of the formatted text; gw keeps the
cursor in place.
| Key | Action |
|---|---|
| gqq | Reflow current line |
| gqap | Reflow current paragraph |
| gq} | Reflow from cursor to next blank line |
| gqj | Reflow current line and next |
| Visual + gq | Reflow selected lines |
| gwap | Reflow paragraph, keep cursor position |
With textwidth set, lines also wrap automatically as
you type in Insert mode. Lines are broken at word boundaries and
indentation is preserved.
Pressing Enter in Insert mode on a markdown list item
starts the next line with the same marker and indent. Numbered lists
increment automatically (1. → 2.),
and task-list checkboxes carry forward unchecked.
Supported markers: -, *, +,
1. / 1), and GFM task lists (- [ ]
/ - [x]).
To exit a list, clear the empty marker with Backspace, or press Esc to return to Normal mode and use dd to remove the empty line.
| Key | Action |
|---|---|
| /pattern | Search forward |
| ?pattern | Search backward |
| n / N | Next / previous match |
| * / # | Search for word under cursor (forward / backward) |
:noh |
Clear search highlighting |
| Command | Action |
|---|---|
:s/old/new/ |
Replace first match on current line |
:s/old/new/g |
Replace all on current line |
:%s/old/new/g |
Replace all in file |
:%s/old/new/gc |
Replace all in file, confirm each |
:g/pat/cmd |
Run cmd on every line matching pat |
:v/pat/cmd |
Run cmd on every line not matching pat |
:g/TODO/d deletes every TODO line.
:v/^#/d keeps only headings.
:g/^$/d strips blank lines.
The most powerful key in vim. . repeats your last change — whatever you just did, do it again.
The dot command repeats the entire last edit, including what you typed in Insert mode. So ciw + new text + Esc is a complete repeatable unit.
Where . repeats your last change, a macro records an arbitrary sequence of keys into a named register and replays it on demand.
| Key | Action |
|---|---|
| qa | Start recording into register a (any letter a–z) |
| q | Stop recording (while recording) |
| @a | Replay macro from register a |
| @@ | Replay the last macro you ran |
| Key | Action |
|---|---|
| u | Undo |
| Ctrl-r | Redo |
Most commands accept a numeric prefix: 5j moves down 5 lines, 3dd deletes 3 lines, 2dw deletes 2 words.
Bookmarks within your document.
| Key | Action |
|---|---|
| ma | Set mark a (any lowercase letter) |
| 'a | Jump to the line of mark a |
| `a | Jump to the exact position of mark a |
Select text, then act on the selection.
| Key | Action |
|---|---|
| v | Character-wise visual mode |
| V | Line-wise visual mode |
Use any motion to extend the selection, then apply an operator: d to delete, c to change, y to yank, gq to reflow, > to indent.
Type : in Normal mode to enter the command line.
| Command | Action |
|---|---|
:w |
Save current buffer to storage |
:e |
Reload current buffer (with name: open buffer) |
:editor |
Switch to Editor tab |
:pre[view] |
Switch to Preview tab |
:help |
Show this help |
:tog[gle] |
Toggle between Editor and Preview |
:persist |
Enable auto-save (default) |
:nopersist |
Disable auto-save |
:clear |
Wipe saved content from storage |
:settings |
Show all current settings |
:exrc |
Edit startup commands |
:wq |
Save and quit (in exrc: save and apply) |
:q! |
Quit without saving (in exrc: discard changes) |
:ls |
List all buffers |
:b name |
Switch to buffer |
:bd |
Delete buffer |
:sav[eas] name |
Save-as to new buffer |
:f name |
Show or rename current buffer |
vi.html supports multiple named buffers — like files on disk, but stored in your browser. Each buffer has its own content and cursor position. Buffers persist across sessions until you delete them.
| Command | Action |
|---|---|
:e name |
Open or create a buffer (without name: reload current) |
:b name |
Switch to a buffer by name |
:b# |
Switch to the alternate (previous) buffer |
| Ctrl-^ | Toggle alternate buffer (Normal mode) |
:ls / :buffers |
List all buffers |
:w name |
Name an unnamed buffer, or save-as to a new name |
:sav[eas] name |
Copy current buffer to a new name |
:f name |
Rename current buffer (without name: show current) |
:bd [name] |
Delete a buffer (default: current buffer) |
:w before
switching.
Set with :set option. Boolean options can be negated with
:set nooption. Use :exrc to persist settings across sessions.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
textwidth |
tw |
number | 0 (off) | Auto-wrap lines at this column in Insert mode |
number |
nu |
bool | on | Show line numbers |
relativenumber |
rnu |
bool | off | Relative line numbers |
tabstop |
ts |
number | 4 | Tab display width |
shiftwidth |
sw |
number | 4 | Indent width for >> / << |
expandtab |
et |
bool | on | Insert spaces instead of tabs |
wrap |
bool | on | Soft-wrap long lines | |
spell |
bool | off | Enable spell checking (uses browser spellcheck) | |
spelllang |
spl |
string | en | Spell checking language (e.g. en_us, fr, de) |
foldgutter |
fdc |
bool | on | Show fold indicators in gutter |
tabstop and shiftwidth default to 4 (vim:
8), expandtab defaults to on (vim: off), and
number defaults to on (vim: off). These are UX choices
for a markdown editor. spell uses the browser’s
built-in spellcheck rather than vim’s spell engine.
| Key | Context | Action |
|---|---|---|
| \p | Normal mode | Toggle between Editor and Preview |
| \ | Preview / Help | Return to Editor |
| Ctrl-^ | Normal mode | Toggle alternate buffer |
Define short text expansions that trigger automatically in Insert
mode. When you type an abbreviation followed by a non-keyword
character (space, punctuation, Enter), it expands to the full text.
To persist abbreviations across sessions, add them to your
:exrc.
| Command | Action |
|---|---|
:ab[breviate] {lhs} {rhs} |
Define abbreviation: typing lhs expands to rhs |
:ab[breviate] |
List all defined abbreviations |
:ab[breviate] {lhs} |
Show the abbreviation for lhs |
:una[bbreviate] {lhs} |
Remove the abbreviation for lhs |
:abc[lear] |
Remove all abbreviations |
Example: :ab teh the — now typing
teh in Insert mode produces the .
Multi-word expansions work too:
:ab sig Best regards, Alice.
See :help Abbreviations in the vim docs.
vi.html supports an exrc — a list of Ex commands
that run every time the editor loads. Use it to persist your
:set options, abbreviations, and other configuration.
Type :exrc to edit your startup commands. The editor
switches to your exrc text. Use :wq to save and apply,
or :q! to discard changes.
Lines starting with " are comments. Blank lines are
ignored. Example:
" My vi.html config set ts=2 set sw=2 set tw=72 ab teh the ab dont don't persist
Any valid Ex command works. Changes take effect immediately after saving.
See :help exrc in the vim docs.
Lines below the end of the document are marked with
~, matching vim’s
end-of-buffer indicator. These are not part of the document —
they show where the file ends and empty screen space begins.
The bar across the bottom of the editor shows the current vim mode,
the active buffer name, the cursor position (line:column), and a
word count with an estimated reading time at 200 words per minute
(e.g. 543w · 3m). For documents under 200
words the reading-time estimate is omitted. When you select text in
Visual mode, the indicator switches to count just the selection
(e.g. 47w sel).
Markdown headings can be folded to hide their content. Folding is nested: closing a heading hides everything under it, including sub-headings.
| Command | Description |
|---|---|
| zo | Open fold at cursor |
| zc | Close fold at cursor |
| za | Toggle fold at cursor |
| zR | Open all folds in buffer |
| zM | Close all folds in buffer |
:set nofoldgutter /
:set foldgutter.
:bd. Use :ls to
see all your buffers.
:exrc.
:nopersist. Re-enable
with :persist. To persist this setting, add
nopersist to your :exrc.
:clear.
The Preview tab renders your markdown with full GitHub Flavored Markdown support: tables, task lists, strikethrough, and fenced code blocks.
SmartyPants typography is applied automatically in the preview:
| You type | Rendered as |
|---|---|
"hello" |
“hello” |
'hello' |
‘hello’ |
it's |
it’s |
-- |
– |
--- |
— |
... |
… |