Skip to main content

Vim mode — Command Reference

This page explains how each key's behavior changes once you apply this plugin.

Unlike For everyone and macOS (Emacs) style, these Vim mode behavior upgrades are not command-to-hotkey assignments.

When the toggle is ON, it directly replaces Obsidian's own built-in Vim mode's internal behavior definitions; when OFF, no replacement happens and Vim mode's standard behavior is kept as-is (the behavior described by each row's "Previously" text below).

Motion upgrades​

Fixes Vim's basic cursor-movement behavior.

Table cells are treated as if they were lines: h/l stop at a cell's start/end, while w/b/e cross past a cell's start/end.

KeysFunction Summary
h l xTable-aware
Also stops at the line start/end for text inside a cell (h/l), and character deletion at the line end works correctly too (x).
Previously, it would cross past the line start/end into the next cell, or fail to delete a character at the line end and move to the next cell instead.

Unicode-safe
Counts characters that internally span multiple code units — emoji and the like — as a single character. (Surrogate pairs such as "🟩"/"𠮷", and emoji with variation selectors such as "☺️")
Previously, moving with a count like 4l would drift one character per emoji, and deleting with a count like 3x could corrupt an emoji into mojibake. Also, with a character like "☺️", the cursor could become invisible.
j kTable-aware
When moving to the row below/above in a table, keeps the cursor's column position (vim's own goal column) intact. Also stops correctly at text that wraps to a new line within a cell.

Previously, crossing a row would lose the column position and jump to the cell's start, or — when moving through text wrapped inside a cell — skip the line right before leaving the cell (the last line for j, the first line for k).
w b e
(and W B E ge gE)
Table-aware
Crosses cell/row boundaries, the same way vim's own w/b/e cross past a line's start/end into the next line. Reaching the table's edge exits the table entirely (into the surrounding text before/after it).
Previously, it would get stuck at a cell's start/end and couldn't proceed further.

CJK-aware
Moves by Chinese/Japanese or English word units (Chinese/Japanese is based on dictionary-based word segmentation; W/B/E/gE still split on whitespace only).
Previously, a run of Chinese/Japanese characters was treated as a single unbroken word.
gg GTable-aware
Moves to the note's first/last line even from inside a table.
The landing cursor position follows the Smart home setting.
If G moves to the note's end and that turns out to be a table, it appends a blank line outside the table and lands there instead (it never lands inside the table).
gg, on the other hand, doesn't add a blank line this way — if the note starts with a table, it lands at the start of the header row's leftmost cell.

Previously, from inside a table, it would only move to the current cell's first/last line.
gj gkTable-aware
The visual-line (display-line) version of j/k above.
Moves by visual line even inside a wrapped cell, keeping the on-screen horizontal position (the visual cursor position) when crossing a row. Also stops correctly at a line that wraps within a cell.

Previously, crossing a row would lose the column position and jump to the cell's start, or — among lines wrapped inside a cell — skip the line right before leaving the cell (the last line for gj, the first line for gk). Also, inside a wrapped cell, it wouldn't stop at each visual line and would instead jump straight to the neighboring table row (or out of the table entirely, from a cell in the last row).
$Table-aware
After moving to the line end with $, moving up/down with j/k or gj/gk keeps sticking to each line's end (sticky). This holds even when crossing a table row — entering a table from plain text lands at the rightmost cell. Stickiness is also preserved when mixing j/k and gj/gk.

Previously, crossing a table row would not stick to the line end and would instead jump to the cell's start. It also always entered the leftmost cell when entering a table. Also, mixing j/k and gj/gk could lose stickiness — a point where this differed from real Vim/Neovim's own behavior.
^ ISmart home
Moves ^'s destination and I's insertion point to the start of the content text — skipping not just whitespace but also leading Markdown syntax (lists, blockquotes, etc.) at the line start. Applies both inside and outside tables.
When Smart home (standard) is OFF, this stays as vim's original behavior; when ON, it skips list, checkbox, indentation, and blockquote markers.
When Smart home (advanced) is ON, it additionally skips heading, footnote, and callout type markers too.
For a visual sense of how Smart home behaves, see the video on the macOS (Emacs) style page.

Previously, this was vim's own original behavior: "skip whitespace characters only."
JSmart join
Removes the second line's leading Markdown syntax (following Smart home's own rules) before joining the lines with a single space in between.
When Smart join is OFF (the default), this stays as vim's original behavior; when ON, it removes list, checkbox, indentation, and blockquote markers.
When Smart home (advanced) is ON, this additionally removes heading, footnote, and callout type markers too.
For a visual sense of how Smart join behaves, see the video on the macOS (Emacs) style page.

Previously, it only stripped the whitespace at the start of the second line, then joined with a single space in between (blockquote and list markers were left intact).

Table structure​

Lets you run Obsidian's own existing table-structure commands from Vim mode, as key sequences starting with <leader>t. The leader key <leader> is Space by default, but \ can be chosen instead.

Except for tm, these are no-ops outside a table.

Following vim's own line-insert commands o/O, inserting a table row below is to and above is tO — but the same commands can also be run as tiJ/tiK (they're aliases). This forms a tiH/tiJ/tiK/tiL set that pairs row insertion (up/down) against column insertion (left/right).

RowColumn
Insert<leader>to / <leader>tO
<leader>tiJ / <leader>tiK
(below/above)
<leader>tiH / <leader>tiL
(left/right)
Move<leader>tK / <leader>tJ
(up/down)
<leader>tH / <leader>tL
(left/right)
Delete<leader>tdd<leader>tdc
Duplicate<leader>tyyp<leader>tyc
Align—<leader>tal (left)
<leader>tac (center)
<leader>tar (right)

<leader>tm: Inserts a table at the cursor position. The only command listed here that works outside an existing table.

Table navigation​

Commands that only move the cursor between cells. No-op outside a table cell.

KeysFunction
<leader>tj / <leader>tkJumps to the cell below/above in the same column, landing at its own content start.
This differs from j/k, which preserve the cursor's column position instead.
Also, no-op at the table's top/bottom edge.
<leader>th / <leader>tlJumps to the cell to the left/right in the same row, landing at its own content start.

Obsidian's built-in Tab/Shift + Tab cell navigation selects the destination cell's entire text, and wraps around to the opposite side of the next row from a row's left/right edge. It also inserts a new row once it runs out of table.
But th/tl create no selection, always stay within the current row, and are no-ops at the row's left/right edge.
<leader>tx / <leader>tXExits the current table downward/upward. Smart home skips over any leading Markdown on the landing line.
Running tx on a table at the note's end appends a blank line and lands there.
tX, on the other hand, when run on a table at the note's start, doesn't add a blank line at the note's start — it lands at the header row's leftmost cell instead.
Works in both Live Preview and Source Mode.