Skip to content

Vim mode ​

The editor can run in vim mode: normal, insert, visual and replace modes, operators and motions, registers, : commands, a vimrc. It is built on the same vim engine many web editors use.

Turning it on ​

Settings › Vim › Vim mode. Off by default, one setting for every project, and it stays on until you turn it off.

Vim is active only while a hardware keyboard is connected. With the soft keyboard alone every letter would be a command, so without a keyboard the editor behaves as it always has, and while the soft keyboard is up vim pauses. The status line under the switch says where things stand: Off, Inactive (no keyboard, or the soft keyboard is up), or Active.

Two more ways to the switch: Toggle Vim mode in the command palette (Cmd+Shift+P), which you can also bind to a Cmd shortcut or a FAB gesture; and, from the command line, :action editor.toggleVim. Switching either way keeps your file exactly as it is.

While vim is on, the status bar shows the mode (NORMAL, INSERT, VISUAL…) as a pill at its left edge, coloured by mode — grey in normal, blue in insert, green in visual, red in replace — so a glance tells them apart; [RO] in a read-only file and the keys of a pending command follow it. With a hardware keyboard a caps pill shows while Caps Lock is on, since in normal mode a capital is another command.

Touch while vim is active ​

A selection made by touch — a drag, or a long-press and its handles — is visual mode: d, y, c and the rest act on it as on one made with the keys. In insert mode a touch selection stays in insert mode. A tap ends visual mode and puts the cursor where you tapped.

Which keys are vim's ​

Rules of thumb:

  • keys go to Vim first, then the app
  • combinations with Cmd or Option are skipped by Vim

So Cmd+S saves and Cmd+P opens the palette in every mode, regardless of whether vim mode is active. It is a good idea to include Cmd or Option in any app shortcut.

An app shortcut without Cmd or Option may be shadowed by vim: by its defaults (Ctrl+U, Ctrl+D, Ctrl+R…) or by anything your vimrc maps. While vim is on, Settings › Keyboard Shortcuts marks each such row with a warning icon beside its name — tap it for what takes the key, a vim default or a line of your vimrc — and the Vim settings screen counts them. The reliable way to reach an app action from vim is its :action command or a vimrc mapping, not a bare Ctrl combo.

A mapping of several keys waits a second for its next key, as vim does — so with ,w mapped, , on its own takes a second to repeat the last search. :set timeoutlen=300 shortens the wait (100 to 5000 milliseconds), for the session at the command line or for good in the vimrc; :set timeoutlen& goes back to the vimrc's value. See vimrc for what a vimrc may map.

The shortcuts vim shadows today ​

The app's default shortcuts that vim takes while it is on, and the vim way to the same action. On an iPhone or iPad the defaults are Cmd combos and only one collides; on Android every Cmd default is a Ctrl combo, so more do. Rebind any of these under Settings › Keyboard Shortcuts to a Cmd combo and it is yours again in every mode.

On iPhone and iPad.

App shortcutKeyVim has it inWhat vim does, and the vim way to the action
Delete to start of line<C-u>normal, visual, insertHalf a page up in normal mode; in insert mode vim's own <C-u> does the same delete

On Android, where every Cmd default is a Ctrl combo.

App shortcutKeyVim has it inWhat vim does, and the vim way to the action
Delete to start of line<C-u>normal, visual, insertAs on iOS
Search project by filename<C-p>normal, visual⌘P → Ctrl+P is k to vim; :e opens the picker
Search project by symbol<C-t>insert⌘T → Ctrl+T indents in insert mode; :sym opens the picker, and normal mode still forwards it
Outdent<C-[>normal, visual, insert⌘[ → Ctrl+[ is Esc, and must stay so on a keyboard without one; < outdents
Redo<C-y>normal, visualCtrl+Y scrolls a line; <C-r> and :redo redo
Find in file<C-f>normal, visual⌘F → Ctrl+F pages down; / searches
Add next occurrence to selection<C-d>normal, visual, insert⌘D → Ctrl+D is half a page down, and dedent in insert mode; * and gn reach the next occurrence
Open recent file<C-r>normal, visual, insert⌘R → Ctrl+R redoes, and inserts a register in insert mode; :action workspace.recentFiles
Switch between tabs<C-e>normal, visual⌘E → Ctrl+E scrolls a line; :ls
Close current tab<C-w>normal, insert⌘W → Ctrl+W is consumed as vim's window prefix in normal mode and deletes a word in insert mode; :q
Toggle sidebar<C-b>normal, visual⌘B → Ctrl+B pages up; :sidebar

The command line ​

: opens the command line at the bottom of the editor; Esc leaves it. Ranges, %, marks and the bang work as in vim. Tab does not complete. Every command the line accepts is below, with whether it is allowed in a read-only file.

:action <id> runs any action in the command palette by its id — :action editor.toggleVim, :action workspace.recentFiles — so everything the palette (Cmd+Shift+P) can do, a mapping can do too. The ids are listed in the Action ids table at the end of this page — the palette shows an action's name, not its id, so the table is where to look one up. The palette, like the tab switcher behind :b, matches as every list does; see Searching lists.

Several commands on one line ​

| runs one command after another: :w | q, :%s/a/b/g | w, :set ts=2 | retab!. Each runs only when the one before it succeeded, and the line stops at the first that refuses, with that command's message: :w | q on a file that cannot be saved leaves the tab open and says why. A command that waits for you, such as :e choosing a file, waits; Esc stops the line there.

  • The bar ends a command, with or without spaces around it (:w|q works). Quotes do not protect it; \| is how an argument holds a bar.
  • Some commands take the rest of the line, bars and all: :normal, :g, :v, the :map family and :grep. In those, text arrives as typed — :g/a\|b/d keeps vim's \| alternation.
  • :s ends after its flags. A bar inside its pattern or its replacement is part of them: :%s/a|b/x/g | w substitutes, then saves. Its c flag (confirm each change) is allowed only on the last command of a line.
  • :g runs one command on each line. A bar after it would run once, not per line, so :g/x/s/a/b/ | d is refused; write the two commands into a :command instead.
  • In a read-only file, a line is refused whole if any of its commands would edit; nothing runs. That check is made against the file open when you press Enter, so :e other.ts | %s/a/b/ typed in a read-only file is refused even if other.ts is writable — open it first. The other way round is caught as the line runs: a command that lands in a read-only file stops there.
  • The editor's own commands (:s, :d, :normal, …) say nothing back to the line, so one that shows an error — No matches — still lets the next command run. One that fails outright (an invalid pattern) stops it.
  • @: repeats the whole line, not its last command.
  • In a mapping, write the bar <Bar>, as in vim: nnoremap <leader>x :w<Bar>action workspace.closeTab<CR> runs both, the second only if the first succeeded. A bare | in a mapping does the same: the mapping keeps it, and the command line splits it.

Commands of your own ​

:command Name line names a line, and :Name runs it:

:command Ship w | action workspace.closeTab
:Ship
  • The name starts with a capital letter, as in vim, so it never takes the place of an editor command.
  • The line is any line the command line accepts, bars and all, and runs as a chain: :Ship | e saves, closes the tab, and then opens the file picker — each only if everything before it succeeded.
  • A bar after your command always ends it, as if vim's -bar were given: in vim, a command defined without it takes the bar into its argument. -bar and -nargs=… are accepted and ignored; vim's other attributes (-range, -complete=…, -count…) are refused.
  • <args> in the line is what follows the name when you run it, and <q-args> the same in double quotes: :command Open action project.quickOpen <args>, then :Open main.go.
  • Every command the line names must exist when you define it; otherwise the definition is refused, naming the one that does not. A command may name itself or another of yours; one that keeps running commands stops 20 deep.
  • An existing name needs :command! to be replaced.
  • :command alone lists yours; :delcommand Name removes one.
  • In a read-only file a command is allowed when everything its line runs is.
  • Your commands last until the editor reloads. To keep one, put the line in the vimrc.

Lua ​

:lua {chunk} runs the Lua you give it, as in vim and Neovim: :lua print(6 * 7). The whole rest of the line is Lua, | included (it is Lua's bitwise or).

Lua you want again is a script: kept under Settings › Scripts, named, and run as :Name — or from a key, the command palette or the accessory bar, with vim on or off. What a chunk or a script can use, the editor calls (buf, cursor, selection, action, syntax …), the limits and the undo step are all on Scripting with Lua.

The app's verbs. Each runs the app action named beside it in the command palette, and shows that action's refusal when there is one.

CommandRead-onlyWhat it does
:write (:w)yesSave the file. In a read-only file it reports so, as vim does; a file name is refused, since save-as is not supported.
:quit (:q)yesClose the window you are in: on a screen above the mode's root — a commit's details, a file's history — it goes back; at the editor root it closes the tab, saving first, and :q! closes without saving. One command with :wq and :x at the root, since the editor autosaves.
:wqyesSave and close; the same as :q.
:xit (:x)yesSave and close; the same as :q.
:edit (:e)yesOpen the file picker, prefilled with the argument.
:find (:fin)yesThe same as :e.
:grep (:gr)yesOpen content search, prefilled with the argument (a leading ! is part of the search).
:symyesOpen symbol search, prefilled with the argument, else with the selection.
:lsyesOpen the tab switcher.
:buffersyesThe same as :ls.
:bnext (:bn)yesThe next tab.
:bprevious (:bp)yesThe previous tab.
:bdelete (:bd)yes:q under its buffer name, bang included; a buffer name is refused (3.6 resolves one).
:gityesSwitch to the git view.
:histyesSwitch to the history view.
:homeyesGo Home.
:sidebaryesGive the sidebar the keys — the file tree, the git sidebar or the commit list, shown if hidden; Esc gives them back. :action workspace.toggleSidebar shows and hides it.
:actionsyesOpen the command palette.
:help (:h)yesOpen the help page; :h <slug> a page by slug, an unknown one refused by name.
:update (:up)yesWrite if modified — :w here, since a clean save is already a no-op.
:wall (:wa)yesSave every tab — :w here, since every other tab is already on disk (edits save on switch).
:qall (:qa)yesClose every tab, saving the active one first; :qa! is not a discard here and does the same.
:wqall (:wqa)yesThe same as :qa.
:buffer (:b)yesSwitch to a tab by name, the way the tab switcher matches — exact, then contains, then the letters in order: :b rdx switches when one tab matches it and opens the switcher, prefilled, when several do; none is refused; :b alone opens the switcher.

The app's own commands.

CommandRead-onlyWhat it does
:actionyes:action <binding id> [argument] runs any catalog action and shows its refusal. No short name: :a stays unknown.
:fmtnoFormat the file, or with a range (:3,9fmt, :'<,'>fmt) those lines, as Python and the web languages allow; Go formats only whole (:%fmt). Refused in a read-only file.
:command (:com)yes:command[!] Name body defines a user command (a capital first letter; ! to replace one); its body may chain with | and use <args>/<q-args>. :command alone lists them. In a read-only file a user command runs only if every command of its body may.
:delcommand (:delc)yes:delcommand Name removes a user command.
:lintyesLint the file and open the Problems panel.
:luayes:lua {chunk} runs the Lua given, in a sandbox.
:retab (:ret)no:[range]retab[!]: re-indent leading whitespace to expandtab/tabstop by column, the whole file unless a range is given; without the bang only runs containing a tab are touched, as in vim — the answer to :set noet doing nothing to what is there.
:cnext (:cn)yesThe next problem, wrapping; the command line says which of how many.
:cprevious (:cp)yesThe previous problem.
:copen (:cope)yesOpen the Problems panel.
:move (:m)noMove the range after an address — 0 the top, a number, ., $, a mark, with offsets; the library has no :m. Inside :g write the space: :g/re/m 0.
:copy (:co)noCopy the range after an address. Inside :g write the space: :g/re/co 0.
:tnoThe same as :copy: :g/re/t $.

Mappings and settings. The map family and :set as the vimrc page has them, for the session.

CommandRead-onlyWhat it does
:set (:se)yesSet an option — the app's, listed below, or the engine's own, several on one line (:set ts=4 sw=4 et); anything else is refused.
:nunmap (:nun)yesRemove a normal-mode session mapping; the library knows only the context-less :unmap.
:iunmap (:iu)yesRemove an insert-mode session mapping.
:vunmap (:vu)yesRemove a visual-mode session mapping.
:ounmap (:ou)yesRemove an operator-pending session mapping.
:setlocal (:setl)yesThe same as :set: the app has one settings store.
:setglobal (:setg)yesThe same as :set.
:mapyesA mapping in every mode, validated (no Alt), recorded for the session.
:imap (:im)yesAn insert-mode mapping.
:nmap (:nm)yesA normal-mode mapping.
:vmap (:vm)yesA visual-mode mapping.
:omap (:om)yesAn operator-pending mapping.
:noremap (:no)yesA non-recursive mapping in every mode.
:nnoremap (:nn)yesA non-recursive normal-mode mapping.
:vnoremap (:vn)yesA non-recursive visual-mode mapping.
:inoremap (:ino)yesA non-recursive insert-mode mapping.
:onoremap (:ono)yesA non-recursive operator-pending mapping.
:unmapyesRemove a session mapping; a default is never touched.
:mapclear (:mapc)yesClear session mappings in every mode, then reconcile.
:nmapclear (:nmapc)yesClear normal-mode session mappings.
:vmapclear (:vmapc)yesClear visual-mode session mappings.
:imapclear (:imapc)yesClear insert-mode session mappings.
:omapclear (:omapc)yesClear operator-pending session mappings.

The app's options for :set. Each maps to an editor setting; the command line sets it for good, the vimrc while Vim is active.

OptionShort
numbernu
relativenumberrnu
wrap
tabstopts
shiftwidthsw
expandtabet
list

The engine's own.

CommandRead-onlyWhat it does
:undo (:u)noUndo.
:redo (:red)noRedo.
:substitute (:s)noSubstitute in the range, :%s/a/b/g; refused in a read-only file.
:global (:g)noRun a command on matching lines; refused in a read-only file.
:vglobal (:v)noRun a command on non-matching lines.
:sort (:sor)noSort the range.
:delete (:d)noDelete the range into a register.
:join (:j)noJoin the range.
:normal (:norm)noRun normal-mode keys on the range.
:yank (:y)yesYank the range into a register; mirrored to the clipboard like a yy.
:put (:pu)noPut a register below the line; :put + reads the clipboard.
:startinsert (:start)noEnter insert mode.
:nohlsearch (:noh)yesClear the search highlight.
:marksyesList marks.
:delmarks (:delm)yesDelete marks.
:registers (:reg)yesList registers.
:colorscheme (:colo)yesAccepted and does nothing; the theme is a setting, and mapping it is a later step.
:version (:ve)yesShow the library version.

Not here, and not coming.

CommandWhy
:!No shell on a phone.
:split (:sp)No splits: one editor per screen.
:vsplit (:vs)No splits.
:tabnewTabs are files here; :e opens one.
:cdThe project is the directory.
:makeNo build here; :lint and :fmt are the tools.

Action ids ​

Every action :action <id> and a vimrc mapping can name, in the order the shortcuts screen lists them. The default key is the Mac's; on Android each ⌘ is Ctrl. An action with no key is reached from the palette, by :action, or by a key you bind under Settings › Keyboard. Kind says whether an action changes something (a command) or only reports (a query); may ask means it can open a picker and wait for your pick.

Project.

IdActionDefault keyWhereKind
project.quickOpenSearch project by filename⌘Panywherecommand, may ask
project.contentSearchSearch project by file content⇧⌘Fanywherecommand, may ask
project.goToSymbolInWorkspaceSearch project by symbol⌘Tanywherecommand, may ask

Workspace.

IdActionDefault keyWhereKind
workspace.closeAllTabsClose all tabs—anywherecommand
workspace.switchToTabSwitch to tab by name—anywherecommand, may ask
help.openOpen help—anywherecommand
workspace.fileStructureGo to symbol in file⇧⌘Oanywherecommand, may ask
workspace.goToDefinitionGo to definition—the editorcommand, may ask
workspace.findReferencesFind references—the editorcommand, may ask
workspace.recentFilesOpen recent file⌘Ranywherecommand, may ask
workspace.commandPaletteShow command palette⇧⌘Panywherecommand, may ask
workspace.openSettingsGo to Settings⌘,anywherecommand
workspace.goHomeGo to Home⌘0anywherecommand
workspace.modeEditorSwitch to Editor tab⌘1anywherecommand
workspace.modeGitSwitch to Git tab⌘2anywherecommand
workspace.modeGitHistorySwitch to Git History tab⌘3anywherecommand
workspace.tabSwitcherSwitch between tabs⌘Eanywherecommand, may ask
workspace.nextTabGo to next tab⇧⌘]anywherecommand
workspace.prevTabGo to previous tab⇧⌘[anywherecommand
workspace.closeTabClose current tab⌘Wanywherecommand
workspace.discardAndCloseClose current tab without saving—anywherecommand
workspace.toggleSidebarToggle sidebar⌘Banywherecommand
workspace.panesSplitShow both sidebar and editor—anywherecommand
workspace.panesSidebarOnlyShow sidebar only—anywherecommand
workspace.panesHideSidebarShow editor only—anywherecommand
workspace.focusSidebarFocus sidebar—anywherecommand
workspace.focusEditorFocus editor—anywherecommand
editor.toggleFullscreenShow editor in full screen⌘\the editorcommand
workspace.viewOptionsOpen view options—anywherecommand
workspace.goBackGo back—anywherecommand
workspace.toggleCheatsheetToggle keyboard cheatsheet—anywherecommand
workspace.activeFileThe active file—anywherequery
workspace.tabsThe open tabs—anywherequery
workspace.stateThe workspace: project, mode, sidebar—anywherequery

Editor.

IdActionDefault keyWhereKind
editor.saveSave file⌘Sthe editorcommand
editor.formatDocumentFormat file—the editorcommand
editor.lintDocumentLint file—the editorcommand
editor.formatSelectionFormat selection—the editorcommand
editor.formatRange <from to>Format a range—the editorcommand
editor.toggleMarkdownPreviewToggle preview⌥⌘Manywherecommand
editor.toggleVimToggle Vim mode—anywherecommand
editor.commentToggleToggle line comment⌘/the editorcommand
editor.toggleBlockCommentToggle block comment—the editorcommand
editor.indentIndent⌘]the editorcommand
editor.outdentOutdent⌘[the editorcommand
editor.indentSelectionRe-indent selection—the editorcommand
editor.openYankPickerOpen yank ring picker—the editorcommand, may ask
editor.snippetsPaletteOpen snippet picker⌘;anywherecommand, may ask
editor.insertBlankLineInsert blank line below⌘↵the editorcommand
editor.hideKeyboardHide keyboard—anywherecommand
editor.undoUndo⌘Zthe editorcommand
editor.redoRedo⇧⌘Zthe editorcommand
editor.insertText <text>Insert text at the cursor—the editorcommand
editor.moveCursor <direction>Move the cursor—the editorcommand
editor.copyCopy—the editorcommand
editor.cutCut—the editorcommand
editor.pastePaste—the editorcommand
editor.focusFocus the editor—the editorcommand
editor.findFind in file⌘Fthe editorcommand
editor.findNextFind next match⌘Gthe editorcommand
editor.findPrevFind previous match⇧⌘Gthe editorcommand
editor.selectNextOccurrenceAdd next occurrence to selection⌘Dthe editorcommand
editor.skipLastOccurrenceSkip current occurrence, add next⌥⌘Dthe editorcommand
editor.dropLastOccurrenceDrop last cursor⌘Uthe editorcommand
editor.addCursorAboveAdd cursor on the line above⌥⌘↑the editorcommand
editor.addCursorBelowAdd cursor on the line below⌥⌘↓the editorcommand
editor.simplifySelectionCollapse multi-cursor to single cursor⇧⌘Uthe editorcommand
editor.splitSelectionByLineSplit selection into one cursor per line—the editorcommand
editor.moveLineUpMove line up⌥↑the editorcommand
editor.moveLineDownMove line down⌥↓the editorcommand
editor.copyLineUpDuplicate line above—the editorcommand
editor.copyLineDownDuplicate line below—the editorcommand
editor.deleteLineDelete line⇧⌘Kthe editorcommand
editor.deleteToLineStartDelete to start of line⌃Uthe editorcommand
editor.deleteToLineEndDelete to end of line⌃Kthe editorcommand
editor.deleteWordLeftDelete previous word—the editorcommand
editor.deleteWordRightDelete next word—the editorcommand
editor.deleteSubwordLeftDelete previous subword—the editorcommand
editor.deleteSubwordRightDelete next subword—the editorcommand
editor.deleteTrailingWhitespaceTrim trailing whitespace—the editorcommand
editor.gotoLineGo to line⌃Gthe editorcommand, may ask
editor.matchBracketCursor to matching bracket⇧⌘\the editorcommand
editor.cursorWordLeftCursor to previous word—the editorcommand
editor.cursorWordRightCursor to next word—the editorcommand
editor.cursorSubwordLeftCursor to previous subword—the editorcommand
editor.cursorSubwordRightCursor to next subword—the editorcommand
editor.cursorSyntaxLeftCursor to previous syntax node—the editorcommand
editor.cursorSyntaxRightCursor to next syntax node—the editorcommand
editor.selectLineSelect line⌘Lthe editorcommand
editor.selectToMatchingBracketSelect to matching bracket⌥⌘\the editorcommand
editor.selectMatchesOfSelectionSelect all occurrences of selection⇧⌘Lthe editorcommand
editor.selectAllMatchesSelect all search matches—the editorcommand
editor.expandSelectionToSyntaxExtend selection to enclosing syntax⇧⌘Athe editorcommand
editor.selectSubwordLeftExtend selection to previous subword—the editorcommand
editor.selectWordLeftExtend selection to previous word—the editorcommand
editor.selectWordRightExtend selection to next word—the editorcommand
editor.selectSubwordRightExtend selection to next subword—the editorcommand
editor.selectSyntaxLeftExtend selection to previous syntax node—the editorcommand
editor.selectSyntaxRightExtend selection to next syntax node—the editorcommand
editor.toggleFoldToggle folding at cursor⌥⌘[the editorcommand
editor.foldCodeCollapse code block at cursor—the editorcommand
editor.unfoldCodeExpand code block at cursor—the editorcommand
editor.foldAllCollapse all code blocks—the editorcommand
editor.unfoldAllExpand all code blocks—the editorcommand
editor.increaseFontSizeIncrease editor font size—anywherecommand
editor.decreaseFontSizeDecrease editor font size—anywherecommand
editor.toggleLineNumbersToggle editor line numbers—anywherecommand
editor.saveStateWhether the file is saved—anywherequery
editor.selectionThe selection and cursor—the editorquery
editor.modeThe vim mode—the editorquery
editor.settingsThe editor settings—anywherequery
editor.outlineThe file outline—the editorquery
editor.nodeAtThe syntax node at a position—the editorquery
editor.diagnosticsThe lint findings—the editorquery
editor.fixAllFix all lint findings—the editorcommand
editor.fixDiagnostic <id>Fix one lint finding—the editorcommand
editor.syntaxQuery <pattern>Query the syntax tree—the editorquery

The keys ​

What the engine binds, by mode. Notes says where it parts from vim. In a read-only file the keys that would edit are refused with a message, and the rest work.

One difference the table cannot show: the characters that make a word (for w, e, iw, *) are fixed — letters of any script, digits and _ — and there is no iskeyword, so - splits a CSS class name or a kebab-case name in every file type, and a run of Chinese with no spaces is one word.

gd goes to the definition of the name under the cursor — the word * takes, under or after the cursor on the line — through the project's symbol index. It is a name lookup, not resolution: a common name opens the definition picker, and what it lands on may be a namesake. <C-o> returns within the file only; a jump into another file has no way back but the tab. The jump list holds the app's own moves too — a search result, an outline pick, a palette jump — so <C-o> goes back from any of them, and in visual mode a landing extends the selection to it. Where there is no index — the file workspace, the diff and blame views — it says so.

Operators.

KeysModesWhat it doesNotes
dnormal, visualDelete
cnormal, visualChange: delete, then insert
<normal, visualShift left
>normal, visualShift right
=normal, visualReindent
g~normal, visualToggle case
gunormal, visualLowercase
gUnormal, visualUppercase
gcnormal, visualToggle the line comment (the language's)not in vim itself
gCnormal, visualToggle the block comment (the language's): the range in place, whole lines for gCCnot in vim itself; a range that already holds a */ is wrapped as is, and may not parse
gqnormal, visualHard-wrap at textwidth
gwnormal, visualHard-wrap, cursor kept
g?normal, visualRot13

Edits.

KeysModesWhat it doesNotes
xnormal, visualDelete the character (the selection)
Xnormal, visualDelete the character before (visual: the lines)
<Del>normal, visualDelete the character (the selection); the library's alias of x
Dnormal, visualDelete to the end of the line (visual: the lines)
Cnormal, visualChange to the end of the line (visual: the lines)
snormal, visualSubstitute the character (the selection)
Snormal, visualSubstitute the line (visual: the lines)
~normal, visualToggle the case of the character (the selection)
Jnormal, visualJoin lines
gJnormal, visualJoin lines without a space
pnormal, visualPaste after (visual: over the selection)
Pnormal, visualPaste before (visual: over the selection)
rnormal, visualReplace the character (every selected one)
<C-a>normalIncrement the number under the cursor
<C-x>normalDecrement the number under the cursor
.normal, visualRepeat the last edita yank counts as the last edit here
unormalUndo
uvisualLowercase the selection
UvisualUppercase the selection
<C-r>normal, visualRedo
qnormal, visualRecord a macro into a register
<C-u>insertDelete to the start of the line
<C-w>insertDelete the word before the cursor
<C-t>insertIndent the line
<C-d>insertDedent the line
<C-h>normal, insertBackspace (h in normal mode); the app's own mapping, since the library leaves it unbound
]p [pnormal, visualPaste after, before, matching the line's indent
@normal, visualReplay a macro

Entering insert mode, and insert mode's own keys.

KeysModesWhat it doesNotes
inormalInsert before the cursor
anormalAppend after the cursor
Inormal, visualInsert at the first non-blank (visual: before the selection)
Anormal, visualAppend at the end of the line (visual: after the selection)
onormalOpen a line below
OnormalOpen a line above
ginormalInsert where insert mode last ended
gInormalInsert at column 1
Rnormal, visualReplace mode (visual: change the lines)
<Ins>normalInsert; the library's alias of i
<C-[>normal, visual, insertEsc
<C-c>normal, visual, insertEsc
<C-Esc>normal, visual, insertEsc (the iPad keyboard sends it for Ctrl+[)
<C-o>insertOne normal-mode command, then back to insert

Motions and text objects.

KeysModesWhat it doesNotes
<C-Space>normal, visualNext WORD (W)
<C-BS>normal, visualPrevious WORD (B)
<C-n>normal, visualDown a line (j)
<C-p>normal, visualUp a line (k)
<C-i>normal, visualForward in the jump list
<C-o>normal, visualBack in the jump list
gdnormalGo to the definition of the name under the cursor (the project's symbol index; several open the picker; <C-o> returns within the file)the local declaration; here a name lookup in the project, not resolution
h j k lnormal, visualLeft, down, up, right
gj gknormal, visualDown, up by display line
w W e E b B ge gEnormal, visualBy word and WORD
0 ^ $ <Bar>normal, visualLine start, first non-blank, line end, column
+ - _ <CR>normal, visualFirst non-blank of the next, previous, current line
gg Gnormal, visualFirst line, last line (or the count's)
H M Lnormal, visualTop, middle, bottom of the screen
{ } ( )normal, visualBy paragraph and sentence
%normal, visualThe matching bracket
f F t T ; ,normal, visualTo a character in the line, and repeat
' `normal, visualTo a mark (line, exact); m sets one
[ ]normal, visualTo a bracket, mark or section
n N * # g* g#normal, visualNext and previous match; the word under the cursor
gn gNnormal, visualSelect the next, previous match
/ ?normal, visualSearch forward, backward
aw iw aW iWvisualA word, WORD (after an operator too)
as is ap ipvisualA sentence, paragraph (after an operator too)
a( i( a[ i[ a{ i{visualA bracket block (after an operator too)
a< i< ab ib aB iBvisualAn angle block; b and B are ( and { (after an operator too)
a" i" a' i' a` i`visualA quoted string (after an operator too)
at itvisualA tag block (after an operator too); in any file with the More text objects extra on (on by default)

Scrolling, visual mode, registers, the command line.

KeysModesWhat it doesNotes
<C-a>visualConsumed, does nothing in visual modevim increments the selection's numbers
<C-x>visualConsumed, does nothing in visual modevim decrements the selection's numbers
<C-f>normal, visualPage down
<C-b>normal, visualPage up
<C-d>normal, visualHalf a page down
<C-u>normal, visualHalf a page up
<C-e>normal, visualScroll down a line
<C-y>normal, visualScroll up a line
<C-v>normal, visualBlockwise visual mode
<C-q>normal, visualBlockwise visual mode
<C-w>normalNothing, and consumed (vim's window prefix)no windows here
<C-r>insertInsert a register (<C-r>a)
v V gv o Onormal, visualVisual, linewise visual, reselect, swap the selection's ends
y Ynormal, visualYank (reading, allowed read-only)
"normal, visualChoose a register
zz zt zb z. z<CR> z-normal, visualScroll the cursor line to the centre, top, bottom
:normal, visualThe command line

The sidebar ​

:sidebar, or Focus sidebar in the palette, gives the keys to the sidebar on screen — the file tree, the git sidebar or a commit's file list — showing it first if it is hidden, and draws an outline around it while it has them. Two lists need no asking: the History tab's commit list has the keys whenever it is on screen, and a commit's file list has them on arrival — Enter on a file hands them to its diff, and :sidebar there brings them back. j k and the arrows move the highlight. Space opens the file, the diff or the commit and keeps the keys in the sidebar, so you can walk down a list and watch each one; Enter opens it and gives the keys back, to the editor in normal mode with the cursor where it was, or to the diff, where j and k scroll. In the tree h and l collapse and expand a folder, and Enter on a folder does the same; in the commit list Enter and Space both open the commit. Esc gives the keys back without opening anything (in the commit list there is nothing to give them back to, so Esc does nothing there).

Coming back. Each sidebar remembers where its highlight was when the keys left, so :sidebar picks up there rather than at the top.

No file open. With nothing in the editor, a key gives the sidebar the keys, showing it first if it is hidden; closing the last tab with :q does the same.

Going back. A screen above a mode's root — a commit's details, a file's history, this help — goes back on Esc from its list or from the page, on :q from its diff (where Esc is vim's own), and on Go back in the palette. Every screen with a back chevron in its header — the settings screens among them — goes back on Esc too; a field with the focus is left by the first Esc and the screen by the next. A page like this one also scrolls on j k, the arrows, PageUp and PageDown, and Space.

The panes. Three actions say what the screen shows, whatever it shows now — Panes: both, Panes: sidebar only, Panes: editor only — beside Sidebar: show or hide on ⌘B; being absolute, they are the ones to chain or map. Focus editor is :sidebar's reverse: it shows the editor (the diff, in the git tab) if the sidebar hides it, and gives it the keys.

Sheets and dialogs. A sheet the keyboard opens — the view options, the file structure, the Problems panel, a picker — carries a highlight: j k and the arrows move it, Enter presses the row, Space toggles a switch, h l and ←/→ adjust a stepper or a choice row (the font size, the line numbers), Esc closes the sheet. A dialog's buttons carry one too: h l, the arrows and Tab move it, Enter presses the highlighted button, Esc cancels. A dialog opens on its safe button, so a red one takes a move first. The highlight is drawn only with a keyboard attached, and none of this needs vim: see Basics.

Registers ​

:reg lists what they hold. Registers are shared across a project's tabs, and most of them cross into the other workspace and the conflict editor too; the table says which. Every yank that names no register also enters the yank ring (the accessory's picker, or :action editor.openYankPicker), where the last thirty can be picked from; deletes do not, even when the clipboard mirror sends them to the clipboard.

RegisterHoldsClipboardAcross workspacesNotes
"The unnamed register: what the last yank, delete or change wrote, whatever register it namedEvery write, while Mirror unnamed register to clipboard is onyes
0The last yank that named no register; the yank ring takes each new oneThrough the unnamed register, as aboveyes
1–9The last nine deletes and changes of a line or more, newest firstThrough the unnamed registeryes
-The last delete or change of less than a lineThrough the unnamed registeryes
a–zNamed registers; A–Z append. Macros record here (qa … q, @a)Never on their own; the unnamed register takes the same text, and its rule appliesyes
+The system clipboard: "+y writes it, "+p and :put + read itAlways written on "+y and "+d, switch or no switch: the register was named — so a "+d enters the yank ring as a copy wouldnoRead through the host on paste, so the register itself holds only what this editor last wrote to it
/The last search patternNevernoDoes not cross workspaces: n reads the engine's own search state, which the snapshot cannot carry; a search is repeated where it was made
:The last command line; @: runs it againNeveryes
.The last inserted textNevernoThis document's: it stays with the editor that inserted
_The black hole: "_d deletes without touching any registerNeverno

Esc on a keyboard without one ​

Many tablet keyboards have no Esc key. Three ways out:

  • Ctrl+[ is Esc in vim, on every keyboard.
  • imap jj <Esc> in your vimrc makes a quick jj leave insert mode; jk is the other common choice.
  • On an iPad, iPadOS can make another key Esc: Settings › General › Keyboard › Hardware Keyboard › Modifier Keys, then set Caps Lock (or Globe) to Escape. The app sees a real Esc.

Typing in another language ​

An input method belongs to insert mode, as in gvim. In insert mode the keyboard's input method composes as it does in any text field, with its candidate panel. In normal and visual mode it is switched off for you: j under Pinyin is j, whatever the keyboard's input source says. The command line composes too — / can search for a Chinese comment, and composes after the / as it would in any field.

Punctuation is the exception: a key the input method rewrites, such as : becoming the full-width : under Pinyin, reaches vim rewritten and opens nothing. Switch the input source (Ctrl+Space) before :, or map the command line to a letter key in your vimrc.

Two things to know. Esc while a composition is open cancels the composition and nothing else; a second Esc leaves insert mode, and the status bar still says INSERT in between. And switching the input source is the keyboard's own shortcut (Ctrl+Space on an iPhone or iPad), which works in insert mode and at the command line.

The clipboard ​

"+y and "+p are the system clipboard, always. Plain y, d and the rest write vim's unnamed register; with Mirror unnamed register to clipboard on, every such write reaches the clipboard too, so a yy here can be pasted in any app. On Android that shows the system's clipboard preview on every yank and delete, so the mirror is off there by default.

set clipboard=unnamed goes the whole way, as in vim: the unnamed register is the clipboard. Yanks and deletes go to it whatever the switch says, and a plain p pastes what the clipboard holds, so text copied in another app is one p away. set clipboard=unnamedplus means the same here, and the list forms a desktop vimrc uses work too (set clipboard^=unnamed,unnamedplus, +=, -=). set clipboard= turns the mirror off. Put it in your vimrc to keep it; at the command line it lasts until the editor is reloaded, and :set clipboard& goes back to the vimrc's.

  • Only a p you type reads the clipboard. Inside a macro, a ., a mapping or :normal, and for :put, the register is pasted — the same text, for anything vim itself yanked.
  • unnamed and unnamedplus are one thing here: set clipboard-=unnamedplus turns it off even if unnamed was set, and :set clipboard? answers unnamed for either.
  • iOS may ask to allow the paste when the text came from another app.

Extras ​

A few things a vim hand expects that are not vim's own, each with a switch under Settings › Vim › Extras — the ? beside a switch opens its section here. A switch turned off takes the extra away at once, in every open file. Every extra's edit is one undo step, repeats with ., runs from a mapping, a macro and :normal, and is refused in a read-only file.

KeysModeWhat it doesSwitch
]dnormalTo the next problem in the file; [d the previous; a count takes the n-thNavigation
]cin a diff or merge viewerTo the next change; [c the previousNavigation
ys{motion}{char}normalPut a pair around what the motion coversSurround
yss{char}normalPut a pair around the line, from its first to its last non-blank; a count is that many linesSurround
cs{old}{new}normalChange the pair around the cursor to anotherSurround
ds{char}normalRemove the pair around the cursorSurround
S{char}visualPut a pair around the selection (in place of vim's own S)Surround
ieafter an operator, visualThe whole file, without the blank lines at its start and end; ae with themMore text objects
iiafter an operator, visualThe lines indented as far as the cursor's; ai with the line above themMore text objects
ilafter an operator, visualThe line from its first to its last non-blank; al all of itMore text objects
iqafter an operator, visualThe contents of the quoted string, whichever quote it is in; aq with the quotesMore text objects
i,after an operator, visualThe argument around the cursor; a, with one commaMore text objects
itafter an operator, visualThe contents of the tag around the cursor, in any file; at with its tagsMore text objects
gr{motion}normalReplace what the motion covers with the register's text, the register kept; "agrw from register aReplace with register
grrnormalReplace the line; a count is that many linesReplace with register
grvisualReplace the selectionReplace with register
crsnormalThe word under the cursor in snake_caseChange case
crcnormal… in camelCaseChange case
crmnormal… in MixedCaseChange case
crunormal… in UPPER_CASEChange case
cr-normal… in dash-caseChange case
]wnormalTo the next part of an identifier (someLongName, some_long_name); [w the previous; a count repeatsSubwords
]enormalTo the end of the part; [e the previous endSubwords
iSafter an operator, visualThe part around the cursor; aS with its _ or -Subwords

On by default. ]d and [d go to the next and previous problem in the file — the lint's findings, the ones underlined — the next being the first that starts after the cursor, on the same line too; the ends wrap, with a message; 3]d is the third. After an operator they reach up to the problem and not into it (d]d). With no problems listed, a message and no move: the list is the last lint's, so with lint-on-type off it is what the last Lint File (or :lint) left. In a diff or merge view, ]c and [c go to the next and previous change from the cursor, as the view's own arrows do; the blame view has no changes to walk.

Subwords ​

Off by default. Motions over the parts of an identifier, on keys of their own, so w, b and e keep their meaning: ]w and [w go to the next and previous part's start, ]e and [e to the next and previous part's end; a count repeats. A part ends where a small letter or a digit meets a capital (some|Long, foo2|Bar), where a run of capitals meets a capitalised word (HTTP|Server), and at _, - and the identifier's edges; digits stay with what precedes them (v1alpha is one part). Letters are letters in any script: naïve|Über|Straße, 日本語|Text. Past the last part the keys go on into the next identifier, and a run of punctuation (&&) is a stop, as it is for w. After an operator ]w reaches up to the next part and ]e includes the end, as w and e do (d]w, c]e). iS and aS are the part around the cursor, aS with its _ or -.

Highlight yanked text ​

On by default. The text a yank took is highlighted for a moment — after yiw, y3w, yy, a visual y — so you see what yip or y} covered. Deletes and changes do not flash, and neither does the :yank command.

Surround ​

On by default. Adds a pair of any character around text, and changes or removes pairs of brackets, quotes and other punctuation.

  • Adding. ysiw) puts brackets around a word, ys$" quotes to the end of the line, yss} braces around the line; in visual mode S and a character surrounds the selection — a line selection gets the delimiters on lines of their own, its content indented. An opening bracket adds a space inside, a closing one does not: ysiw( gives ( word ), ysiw) gives (word). b, B, r and a are short for ), }, ] and >. Any other character is used as itself on both sides (ysiw*). After w, W, aw and aW the space after the word is left outside the pair.
  • Changing and removing. cs"' turns double quotes into single ones, cs)] brackets into square ones, ds" removes the quotes. They work on brackets and quotes — ( ) [ ] { } < >, the three quotes, and b B r a — and on other punctuation, which is found on the line: the nearest one before the cursor and the nearest after (ds* on *bold*, cs*_, ds|). Letters, digits and the space name nothing. Name a bracket pair by its opening character and the spaces just inside it go too (ds( on ( word ) leaves word); by its closing character they stay. A count reaches outward: 2ds) removes the second pair of brackets around the cursor.
  • Tags. dst removes the tag pair around the cursor, and cst changes it. To add a tag, or change something to one, type < (or t) and then the tag: ysiw<em>, cst<h3>, cs"<b>, and S<div> on a selection. The tag shows in the status bar as you type it; Backspace corrects it and Esc gives it up. End it with > to put exactly the tag you typed — attributes too, ysiw<a href="x"> — or with Enter to rename a tag and keep its attributes: cst<h3 Enter on <h2 class="a"> gives <h3 class="a"> (an attribute you type replaces the old one of that name). In a mapping or a script the tag is written as it is typed: nmap <leader>e ysiw<em>. A count reaches outward (2dst). Tags are found in any file — HTML, XML, Vue, Markdown, JSX and TSX, where a <>…</> fragment counts as a pair — and a tag with no closing tag of its own (<br>, <img>) is passed over for the one around it.
  • Which pair. For brackets and quotes, the one da( or da" would take, always. Inside a pair that is the pair around the cursor. Before a pair on the same line it is the next one to the right — vim itself looks to the right only for quotes, this editor for brackets too — and after the last one, nothing.
  • . repeats each of these with the same delimiter, u undoes it in one step, and no register is touched. Esc cancels while a delimiter is waited for; the status bar shows what is pending.
  • With the switch on, visual S is surround's and no longer vim's "change the selected lines" — Vc does that.

More text objects ​

On by default. Used after an operator or in visual mode like vim's own (diw, vip):

  • ie is the whole file without the blank lines at its start and end, ae with them — whole lines, so yae copies the file and vie selects it.
  • ii is the lines indented at least as far as the cursor's line — the body of a block — and ai adds the line above it. Blank lines inside the block belong to it; the ones after it do not. A line with no indent has no block. There is no aI.
  • il is the line from its first to its last non-blank, al all of it; neither takes the line break.
  • iq is the contents of a quoted string, whichever of ', " and ` it is in, and aq the string with its quotes — and, unlike vim's a", no space beside it. Before a string on the line it takes that string; between two strings it takes the second — vim's own i" takes the text between them. A string must close on the same line.
  • i, is the argument around the cursor — in a call, a list, an object, a type's <…> — and a, takes one comma with it, so da, leaves a well-formed list. d2a, takes two. On a comma it is the argument after it. Commas inside strings and nested brackets are not separators; one inside a comment is taken for one, and a string that spans lines is not recognised as one.
  • it and at, the contents of the tag around the cursor and the tag itself, work in any file with this switch on: HTML, XML, Vue, Markdown, JSX and TSX. d2it reaches the next tag out — a count, not a repeat: vatat stays on the tag, as va(a( does.

Replace with register ​

On by default. gr and a motion replaces what the motion covers with the register's text, and keeps the register — so one yank can replace a word here, a line there and a selection after that, where viwp would hand the replaced text back into the register each time. grr replaces the line, 3grr three lines; in visual mode gr replaces the selection. The unnamed register unless you name one ("agrw). Lines yanked with yy go in on lines of their own; a word over grr leaves one line of it. An empty register is refused with a message. (In some editors gr leads other commands; here it is this.)

Change case ​

On by default. cr and a letter rewrites the word under the cursor — crs to snake_case, crc to camelCase, crm to MixedCase, cru to UPPER_CASE, cr- to dash-case — from whichever case it was in; . repeats it on another word. The word is the run of letters, digits, _ and $ under the cursor, with hyphens joining them (dash-case is one word); underscores at its ends stay (_private_name becomes _privateName, __init__ stays). Any other letter after cr changes nothing.

Read-only files ​

Vim stays on in a read-only file: you can move, search and yank, and any command that would edit is refused with a message.

In diffs and blame ​

The diff (Patch or Unified) and the blame view run a navigation-only vim: motions, / and n, marks, G, yanking, the same keys as a read-only file. Visual mode is refused, and the command line takes :noh, a line number, :set and the app's verbs — :sidebar, :git, :hist, :home, :e, :ls, :bn, :bp, :actions, :action, :help — and nothing else; a :set there is the same as in the editor: it changes the setting for the whole app. In a diff, ]c and [c go to the next and previous change from the cursor (the Navigation extra); the view's own arrows move between changes too. The Split view shows no vim at all.

The vimrc ​

Mappings, settings and commands of your own that are there every time vim starts are the vimrc: Settings › Vim › vimrc, edited in the app's editor with every line checked. That page has the vocabulary, the set options and what will not work.

On a phone long lines wrap, so the example vimrc carries nnoremap j gj and nnoremap k gk, commented out: with them j and k move by screen line. A count then counts screen lines too — 5j goes down five screen lines — so with relative line numbers on, the count no longer matches the gutter on a wrapped line. Vim's usual way round that, an <expr> mapping that keeps counts on real lines, is refused by the vimrc (there is no expression language), so it is one or the other.

Last updated: