Appearance
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 shortcut | Key | Vim has it in | What vim does, and the vim way to the action |
|---|---|---|---|
| Delete to start of line | <C-u> | normal, visual, insert | Half 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 shortcut | Key | Vim has it in | What vim does, and the vim way to the action |
|---|---|---|---|
| Delete to start of line | <C-u> | normal, visual, insert | As 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, visual | Ctrl+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|qworks). 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:mapfamily and:grep. In those, text arrives as typed —:g/a\|b/dkeeps vim's\|alternation. :sends after its flags. A bar inside its pattern or its replacement is part of them::%s/a|b/x/g | wsubstitutes, then saves. Itscflag (confirm each change) is allowed only on the last command of a line.:gruns one command on each line. A bar after it would run once, not per line, so:g/x/s/a/b/ | dis refused; write the two commands into a:commandinstead.- 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 ifother.tsis 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 | esaves, 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
-barwere given: in vim, a command defined without it takes the bar into its argument.-barand-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. :commandalone lists yours;:delcommand Nameremoves 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.
| Command | Read-only | What it does |
|---|---|---|
:write (:w) | yes | Save 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) | yes | Close 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. |
:wq | yes | Save and close; the same as :q. |
:xit (:x) | yes | Save and close; the same as :q. |
:edit (:e) | yes | Open the file picker, prefilled with the argument. |
:find (:fin) | yes | The same as :e. |
:grep (:gr) | yes | Open content search, prefilled with the argument (a leading ! is part of the search). |
:sym | yes | Open symbol search, prefilled with the argument, else with the selection. |
:ls | yes | Open the tab switcher. |
:buffers | yes | The same as :ls. |
:bnext (:bn) | yes | The next tab. |
:bprevious (:bp) | yes | The previous tab. |
:bdelete (:bd) | yes | :q under its buffer name, bang included; a buffer name is refused (3.6 resolves one). |
:git | yes | Switch to the git view. |
:hist | yes | Switch to the history view. |
:home | yes | Go Home. |
:sidebar | yes | Give 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. |
:actions | yes | Open the command palette. |
:help (:h) | yes | Open the help page; :h <slug> a page by slug, an unknown one refused by name. |
:update (:up) | yes | Write if modified — :w here, since a clean save is already a no-op. |
:wall (:wa) | yes | Save every tab — :w here, since every other tab is already on disk (edits save on switch). |
:qall (:qa) | yes | Close every tab, saving the active one first; :qa! is not a discard here and does the same. |
:wqall (:wqa) | yes | The same as :qa. |
:buffer (:b) | yes | Switch 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.
| Command | Read-only | What it does |
|---|---|---|
:action | yes | :action <binding id> [argument] runs any catalog action and shows its refusal. No short name: :a stays unknown. |
:fmt | no | Format 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. |
:lint | yes | Lint the file and open the Problems panel. |
:lua | yes | :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) | yes | The next problem, wrapping; the command line says which of how many. |
:cprevious (:cp) | yes | The previous problem. |
:copen (:cope) | yes | Open the Problems panel. |
:move (:m) | no | Move 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) | no | Copy the range after an address. Inside :g write the space: :g/re/co 0. |
:t | no | The same as :copy: :g/re/t $. |
Mappings and settings. The map family and :set as the vimrc page has them, for the session.
| Command | Read-only | What it does |
|---|---|---|
:set (:se) | yes | Set 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) | yes | Remove a normal-mode session mapping; the library knows only the context-less :unmap. |
:iunmap (:iu) | yes | Remove an insert-mode session mapping. |
:vunmap (:vu) | yes | Remove a visual-mode session mapping. |
:ounmap (:ou) | yes | Remove an operator-pending session mapping. |
:setlocal (:setl) | yes | The same as :set: the app has one settings store. |
:setglobal (:setg) | yes | The same as :set. |
:map | yes | A mapping in every mode, validated (no Alt), recorded for the session. |
:imap (:im) | yes | An insert-mode mapping. |
:nmap (:nm) | yes | A normal-mode mapping. |
:vmap (:vm) | yes | A visual-mode mapping. |
:omap (:om) | yes | An operator-pending mapping. |
:noremap (:no) | yes | A non-recursive mapping in every mode. |
:nnoremap (:nn) | yes | A non-recursive normal-mode mapping. |
:vnoremap (:vn) | yes | A non-recursive visual-mode mapping. |
:inoremap (:ino) | yes | A non-recursive insert-mode mapping. |
:onoremap (:ono) | yes | A non-recursive operator-pending mapping. |
:unmap | yes | Remove a session mapping; a default is never touched. |
:mapclear (:mapc) | yes | Clear session mappings in every mode, then reconcile. |
:nmapclear (:nmapc) | yes | Clear normal-mode session mappings. |
:vmapclear (:vmapc) | yes | Clear visual-mode session mappings. |
:imapclear (:imapc) | yes | Clear insert-mode session mappings. |
:omapclear (:omapc) | yes | Clear 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.
| Option | Short |
|---|---|
number | nu |
relativenumber | rnu |
wrap | |
tabstop | ts |
shiftwidth | sw |
expandtab | et |
list |
The engine's own.
| Command | Read-only | What it does |
|---|---|---|
:undo (:u) | no | Undo. |
:redo (:red) | no | Redo. |
:substitute (:s) | no | Substitute in the range, :%s/a/b/g; refused in a read-only file. |
:global (:g) | no | Run a command on matching lines; refused in a read-only file. |
:vglobal (:v) | no | Run a command on non-matching lines. |
:sort (:sor) | no | Sort the range. |
:delete (:d) | no | Delete the range into a register. |
:join (:j) | no | Join the range. |
:normal (:norm) | no | Run normal-mode keys on the range. |
:yank (:y) | yes | Yank the range into a register; mirrored to the clipboard like a yy. |
:put (:pu) | no | Put a register below the line; :put + reads the clipboard. |
:startinsert (:start) | no | Enter insert mode. |
:nohlsearch (:noh) | yes | Clear the search highlight. |
:marks | yes | List marks. |
:delmarks (:delm) | yes | Delete marks. |
:registers (:reg) | yes | List registers. |
:colorscheme (:colo) | yes | Accepted and does nothing; the theme is a setting, and mapping it is a later step. |
:version (:ve) | yes | Show the library version. |
Not here, and not coming.
| Command | Why |
|---|---|
:! | No shell on a phone. |
:split (:sp) | No splits: one editor per screen. |
:vsplit (:vs) | No splits. |
:tabnew | Tabs are files here; :e opens one. |
:cd | The project is the directory. |
:make | No 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.
| Id | Action | Default key | Where | Kind |
|---|---|---|---|---|
project.quickOpen | Search project by filename | ⌘P | anywhere | command, may ask |
project.contentSearch | Search project by file content | ⇧⌘F | anywhere | command, may ask |
project.goToSymbolInWorkspace | Search project by symbol | ⌘T | anywhere | command, may ask |
Workspace.
| Id | Action | Default key | Where | Kind |
|---|---|---|---|---|
workspace.closeAllTabs | Close all tabs | — | anywhere | command |
workspace.switchToTab | Switch to tab by name | — | anywhere | command, may ask |
help.open | Open help | — | anywhere | command |
workspace.fileStructure | Go to symbol in file | ⇧⌘O | anywhere | command, may ask |
workspace.goToDefinition | Go to definition | — | the editor | command, may ask |
workspace.findReferences | Find references | — | the editor | command, may ask |
workspace.recentFiles | Open recent file | ⌘R | anywhere | command, may ask |
workspace.commandPalette | Show command palette | ⇧⌘P | anywhere | command, may ask |
workspace.openSettings | Go to Settings | ⌘, | anywhere | command |
workspace.goHome | Go to Home | ⌘0 | anywhere | command |
workspace.modeEditor | Switch to Editor tab | ⌘1 | anywhere | command |
workspace.modeGit | Switch to Git tab | ⌘2 | anywhere | command |
workspace.modeGitHistory | Switch to Git History tab | ⌘3 | anywhere | command |
workspace.tabSwitcher | Switch between tabs | ⌘E | anywhere | command, may ask |
workspace.nextTab | Go to next tab | ⇧⌘] | anywhere | command |
workspace.prevTab | Go to previous tab | ⇧⌘[ | anywhere | command |
workspace.closeTab | Close current tab | ⌘W | anywhere | command |
workspace.discardAndClose | Close current tab without saving | — | anywhere | command |
workspace.toggleSidebar | Toggle sidebar | ⌘B | anywhere | command |
workspace.panesSplit | Show both sidebar and editor | — | anywhere | command |
workspace.panesSidebarOnly | Show sidebar only | — | anywhere | command |
workspace.panesHideSidebar | Show editor only | — | anywhere | command |
workspace.focusSidebar | Focus sidebar | — | anywhere | command |
workspace.focusEditor | Focus editor | — | anywhere | command |
editor.toggleFullscreen | Show editor in full screen | ⌘\ | the editor | command |
workspace.viewOptions | Open view options | — | anywhere | command |
workspace.goBack | Go back | — | anywhere | command |
workspace.toggleCheatsheet | Toggle keyboard cheatsheet | — | anywhere | command |
workspace.activeFile | The active file | — | anywhere | query |
workspace.tabs | The open tabs | — | anywhere | query |
workspace.state | The workspace: project, mode, sidebar | — | anywhere | query |
Editor.
| Id | Action | Default key | Where | Kind |
|---|---|---|---|---|
editor.save | Save file | ⌘S | the editor | command |
editor.formatDocument | Format file | — | the editor | command |
editor.lintDocument | Lint file | — | the editor | command |
editor.formatSelection | Format selection | — | the editor | command |
editor.formatRange <from to> | Format a range | — | the editor | command |
editor.toggleMarkdownPreview | Toggle preview | ⌥⌘M | anywhere | command |
editor.toggleVim | Toggle Vim mode | — | anywhere | command |
editor.commentToggle | Toggle line comment | ⌘/ | the editor | command |
editor.toggleBlockComment | Toggle block comment | — | the editor | command |
editor.indent | Indent | ⌘] | the editor | command |
editor.outdent | Outdent | ⌘[ | the editor | command |
editor.indentSelection | Re-indent selection | — | the editor | command |
editor.openYankPicker | Open yank ring picker | — | the editor | command, may ask |
editor.snippetsPalette | Open snippet picker | ⌘; | anywhere | command, may ask |
editor.insertBlankLine | Insert blank line below | ⌘↵ | the editor | command |
editor.hideKeyboard | Hide keyboard | — | anywhere | command |
editor.undo | Undo | ⌘Z | the editor | command |
editor.redo | Redo | ⇧⌘Z | the editor | command |
editor.insertText <text> | Insert text at the cursor | — | the editor | command |
editor.moveCursor <direction> | Move the cursor | — | the editor | command |
editor.copy | Copy | — | the editor | command |
editor.cut | Cut | — | the editor | command |
editor.paste | Paste | — | the editor | command |
editor.focus | Focus the editor | — | the editor | command |
editor.find | Find in file | ⌘F | the editor | command |
editor.findNext | Find next match | ⌘G | the editor | command |
editor.findPrev | Find previous match | ⇧⌘G | the editor | command |
editor.selectNextOccurrence | Add next occurrence to selection | ⌘D | the editor | command |
editor.skipLastOccurrence | Skip current occurrence, add next | ⌥⌘D | the editor | command |
editor.dropLastOccurrence | Drop last cursor | ⌘U | the editor | command |
editor.addCursorAbove | Add cursor on the line above | ⌥⌘↑ | the editor | command |
editor.addCursorBelow | Add cursor on the line below | ⌥⌘↓ | the editor | command |
editor.simplifySelection | Collapse multi-cursor to single cursor | ⇧⌘U | the editor | command |
editor.splitSelectionByLine | Split selection into one cursor per line | — | the editor | command |
editor.moveLineUp | Move line up | ⌥↑ | the editor | command |
editor.moveLineDown | Move line down | ⌥↓ | the editor | command |
editor.copyLineUp | Duplicate line above | — | the editor | command |
editor.copyLineDown | Duplicate line below | — | the editor | command |
editor.deleteLine | Delete line | ⇧⌘K | the editor | command |
editor.deleteToLineStart | Delete to start of line | ⌃U | the editor | command |
editor.deleteToLineEnd | Delete to end of line | ⌃K | the editor | command |
editor.deleteWordLeft | Delete previous word | — | the editor | command |
editor.deleteWordRight | Delete next word | — | the editor | command |
editor.deleteSubwordLeft | Delete previous subword | — | the editor | command |
editor.deleteSubwordRight | Delete next subword | — | the editor | command |
editor.deleteTrailingWhitespace | Trim trailing whitespace | — | the editor | command |
editor.gotoLine | Go to line | ⌃G | the editor | command, may ask |
editor.matchBracket | Cursor to matching bracket | ⇧⌘\ | the editor | command |
editor.cursorWordLeft | Cursor to previous word | — | the editor | command |
editor.cursorWordRight | Cursor to next word | — | the editor | command |
editor.cursorSubwordLeft | Cursor to previous subword | — | the editor | command |
editor.cursorSubwordRight | Cursor to next subword | — | the editor | command |
editor.cursorSyntaxLeft | Cursor to previous syntax node | — | the editor | command |
editor.cursorSyntaxRight | Cursor to next syntax node | — | the editor | command |
editor.selectLine | Select line | ⌘L | the editor | command |
editor.selectToMatchingBracket | Select to matching bracket | ⌥⌘\ | the editor | command |
editor.selectMatchesOfSelection | Select all occurrences of selection | ⇧⌘L | the editor | command |
editor.selectAllMatches | Select all search matches | — | the editor | command |
editor.expandSelectionToSyntax | Extend selection to enclosing syntax | ⇧⌘A | the editor | command |
editor.selectSubwordLeft | Extend selection to previous subword | — | the editor | command |
editor.selectWordLeft | Extend selection to previous word | — | the editor | command |
editor.selectWordRight | Extend selection to next word | — | the editor | command |
editor.selectSubwordRight | Extend selection to next subword | — | the editor | command |
editor.selectSyntaxLeft | Extend selection to previous syntax node | — | the editor | command |
editor.selectSyntaxRight | Extend selection to next syntax node | — | the editor | command |
editor.toggleFold | Toggle folding at cursor | ⌥⌘[ | the editor | command |
editor.foldCode | Collapse code block at cursor | — | the editor | command |
editor.unfoldCode | Expand code block at cursor | — | the editor | command |
editor.foldAll | Collapse all code blocks | — | the editor | command |
editor.unfoldAll | Expand all code blocks | — | the editor | command |
editor.increaseFontSize | Increase editor font size | — | anywhere | command |
editor.decreaseFontSize | Decrease editor font size | — | anywhere | command |
editor.toggleLineNumbers | Toggle editor line numbers | — | anywhere | command |
editor.saveState | Whether the file is saved | — | anywhere | query |
editor.selection | The selection and cursor | — | the editor | query |
editor.mode | The vim mode | — | the editor | query |
editor.settings | The editor settings | — | anywhere | query |
editor.outline | The file outline | — | the editor | query |
editor.nodeAt | The syntax node at a position | — | the editor | query |
editor.diagnostics | The lint findings | — | the editor | query |
editor.fixAll | Fix all lint findings | — | the editor | command |
editor.fixDiagnostic <id> | Fix one lint finding | — | the editor | command |
editor.syntaxQuery <pattern> | Query the syntax tree | — | the editor | query |
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.
| Keys | Modes | What it does | Notes |
|---|---|---|---|
d | normal, visual | Delete | |
c | normal, visual | Change: delete, then insert | |
< | normal, visual | Shift left | |
> | normal, visual | Shift right | |
= | normal, visual | Reindent | |
g~ | normal, visual | Toggle case | |
gu | normal, visual | Lowercase | |
gU | normal, visual | Uppercase | |
gc | normal, visual | Toggle the line comment (the language's) | not in vim itself |
gC | normal, visual | Toggle the block comment (the language's): the range in place, whole lines for gCC | not in vim itself; a range that already holds a */ is wrapped as is, and may not parse |
gq | normal, visual | Hard-wrap at textwidth | |
gw | normal, visual | Hard-wrap, cursor kept | |
g? | normal, visual | Rot13 |
Edits.
| Keys | Modes | What it does | Notes |
|---|---|---|---|
x | normal, visual | Delete the character (the selection) | |
X | normal, visual | Delete the character before (visual: the lines) | |
<Del> | normal, visual | Delete the character (the selection); the library's alias of x | |
D | normal, visual | Delete to the end of the line (visual: the lines) | |
C | normal, visual | Change to the end of the line (visual: the lines) | |
s | normal, visual | Substitute the character (the selection) | |
S | normal, visual | Substitute the line (visual: the lines) | |
~ | normal, visual | Toggle the case of the character (the selection) | |
J | normal, visual | Join lines | |
gJ | normal, visual | Join lines without a space | |
p | normal, visual | Paste after (visual: over the selection) | |
P | normal, visual | Paste before (visual: over the selection) | |
r | normal, visual | Replace the character (every selected one) | |
<C-a> | normal | Increment the number under the cursor | |
<C-x> | normal | Decrement the number under the cursor | |
. | normal, visual | Repeat the last edit | a yank counts as the last edit here |
u | normal | Undo | |
u | visual | Lowercase the selection | |
U | visual | Uppercase the selection | |
<C-r> | normal, visual | Redo | |
q | normal, visual | Record a macro into a register | |
<C-u> | insert | Delete to the start of the line | |
<C-w> | insert | Delete the word before the cursor | |
<C-t> | insert | Indent the line | |
<C-d> | insert | Dedent the line | |
<C-h> | normal, insert | Backspace (h in normal mode); the app's own mapping, since the library leaves it unbound | |
]p [p | normal, visual | Paste after, before, matching the line's indent | |
@ | normal, visual | Replay a macro |
Entering insert mode, and insert mode's own keys.
| Keys | Modes | What it does | Notes |
|---|---|---|---|
i | normal | Insert before the cursor | |
a | normal | Append after the cursor | |
I | normal, visual | Insert at the first non-blank (visual: before the selection) | |
A | normal, visual | Append at the end of the line (visual: after the selection) | |
o | normal | Open a line below | |
O | normal | Open a line above | |
gi | normal | Insert where insert mode last ended | |
gI | normal | Insert at column 1 | |
R | normal, visual | Replace mode (visual: change the lines) | |
<Ins> | normal | Insert; the library's alias of i | |
<C-[> | normal, visual, insert | Esc | |
<C-c> | normal, visual, insert | Esc | |
<C-Esc> | normal, visual, insert | Esc (the iPad keyboard sends it for Ctrl+[) | |
<C-o> | insert | One normal-mode command, then back to insert |
Motions and text objects.
| Keys | Modes | What it does | Notes |
|---|---|---|---|
<C-Space> | normal, visual | Next WORD (W) | |
<C-BS> | normal, visual | Previous WORD (B) | |
<C-n> | normal, visual | Down a line (j) | |
<C-p> | normal, visual | Up a line (k) | |
<C-i> | normal, visual | Forward in the jump list | |
<C-o> | normal, visual | Back in the jump list | |
gd | normal | Go 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 l | normal, visual | Left, down, up, right | |
gj gk | normal, visual | Down, up by display line | |
w W e E b B ge gE | normal, visual | By word and WORD | |
0 ^ $ <Bar> | normal, visual | Line start, first non-blank, line end, column | |
+ - _ <CR> | normal, visual | First non-blank of the next, previous, current line | |
gg G | normal, visual | First line, last line (or the count's) | |
H M L | normal, visual | Top, middle, bottom of the screen | |
{ } ( ) | normal, visual | By paragraph and sentence | |
% | normal, visual | The matching bracket | |
f F t T ; , | normal, visual | To a character in the line, and repeat | |
' ` | normal, visual | To a mark (line, exact); m sets one | |
[ ] | normal, visual | To a bracket, mark or section | |
n N * # g* g# | normal, visual | Next and previous match; the word under the cursor | |
gn gN | normal, visual | Select the next, previous match | |
/ ? | normal, visual | Search forward, backward | |
aw iw aW iW | visual | A word, WORD (after an operator too) | |
as is ap ip | visual | A sentence, paragraph (after an operator too) | |
a( i( a[ i[ a{ i{ | visual | A bracket block (after an operator too) | |
a< i< ab ib aB iB | visual | An angle block; b and B are ( and { (after an operator too) | |
a" i" a' i' a` i` | visual | A quoted string (after an operator too) | |
at it | visual | A 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.
| Keys | Modes | What it does | Notes |
|---|---|---|---|
<C-a> | visual | Consumed, does nothing in visual mode | vim increments the selection's numbers |
<C-x> | visual | Consumed, does nothing in visual mode | vim decrements the selection's numbers |
<C-f> | normal, visual | Page down | |
<C-b> | normal, visual | Page up | |
<C-d> | normal, visual | Half a page down | |
<C-u> | normal, visual | Half a page up | |
<C-e> | normal, visual | Scroll down a line | |
<C-y> | normal, visual | Scroll up a line | |
<C-v> | normal, visual | Blockwise visual mode | |
<C-q> | normal, visual | Blockwise visual mode | |
<C-w> | normal | Nothing, and consumed (vim's window prefix) | no windows here |
<C-r> | insert | Insert a register (<C-r>a) | |
v V gv o O | normal, visual | Visual, linewise visual, reselect, swap the selection's ends | |
y Y | normal, visual | Yank (reading, allowed read-only) | |
" | normal, visual | Choose a register | |
zz zt zb z. z<CR> z- | normal, visual | Scroll the cursor line to the centre, top, bottom | |
: | normal, visual | The 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.
| Register | Holds | Clipboard | Across workspaces | Notes |
|---|---|---|---|---|
" | The unnamed register: what the last yank, delete or change wrote, whatever register it named | Every write, while Mirror unnamed register to clipboard is on | yes | |
0 | The last yank that named no register; the yank ring takes each new one | Through the unnamed register, as above | yes | |
1–9 | The last nine deletes and changes of a line or more, newest first | Through the unnamed register | yes | |
- | The last delete or change of less than a line | Through the unnamed register | yes | |
a–z | Named registers; A–Z append. Macros record here (qa … q, @a) | Never on their own; the unnamed register takes the same text, and its rule applies | yes | |
+ | The system clipboard: "+y writes it, "+p and :put + read it | Always written on "+y and "+d, switch or no switch: the register was named — so a "+d enters the yank ring as a copy would | no | Read through the host on paste, so the register itself holds only what this editor last wrote to it |
/ | The last search pattern | Never | no | Does 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 again | Never | yes | |
. | The last inserted text | Never | no | This document's: it stays with the editor that inserted |
_ | The black hole: "_d deletes without touching any register | Never | no |
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 quickjjleave insert mode;jkis 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
pyou 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. unnamedandunnamedplusare one thing here:set clipboard-=unnamedplusturns it off even ifunnamedwas set, and:set clipboard?answersunnamedfor 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.
| Keys | Mode | What it does | Switch |
|---|---|---|---|
]d | normal | To the next problem in the file; [d the previous; a count takes the n-th | Navigation |
]c | in a diff or merge viewer | To the next change; [c the previous | Navigation |
ys{motion}{char} | normal | Put a pair around what the motion covers | Surround |
yss{char} | normal | Put a pair around the line, from its first to its last non-blank; a count is that many lines | Surround |
cs{old}{new} | normal | Change the pair around the cursor to another | Surround |
ds{char} | normal | Remove the pair around the cursor | Surround |
S{char} | visual | Put a pair around the selection (in place of vim's own S) | Surround |
ie | after an operator, visual | The whole file, without the blank lines at its start and end; ae with them | More text objects |
ii | after an operator, visual | The lines indented as far as the cursor's; ai with the line above them | More text objects |
il | after an operator, visual | The line from its first to its last non-blank; al all of it | More text objects |
iq | after an operator, visual | The contents of the quoted string, whichever quote it is in; aq with the quotes | More text objects |
i, | after an operator, visual | The argument around the cursor; a, with one comma | More text objects |
it | after an operator, visual | The contents of the tag around the cursor, in any file; at with its tags | More text objects |
gr{motion} | normal | Replace what the motion covers with the register's text, the register kept; "agrw from register a | Replace with register |
grr | normal | Replace the line; a count is that many lines | Replace with register |
gr | visual | Replace the selection | Replace with register |
crs | normal | The word under the cursor in snake_case | Change case |
crc | normal | … in camelCase | Change case |
crm | normal | … in MixedCase | Change case |
cru | normal | … in UPPER_CASE | Change case |
cr- | normal | … in dash-case | Change case |
]w | normal | To the next part of an identifier (someLongName, some_long_name); [w the previous; a count repeats | Subwords |
]e | normal | To the end of the part; [e the previous end | Subwords |
iS | after an operator, visual | The part around the cursor; aS with its _ or - | Subwords |
Navigation
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 modeSand 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,randaare short for),},]and>. Any other character is used as itself on both sides (ysiw*). Afterw,W,awandaWthe 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, andbBra— 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 )leavesword); by its closing character they stay. A count reaches outward:2ds)removes the second pair of brackets around the cursor. - Tags.
dstremoves the tag pair around the cursor, andcstchanges it. To add a tag, or change something to one, type<(ort) and then the tag:ysiw<em>,cst<h3>,cs"<b>, andS<div>on a selection. The tag shows in the status bar as you type it; Backspace corrects it andEscgives 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<h3Enter 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(orda"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,uundoes it in one step, and no register is touched.Esccancels while a delimiter is waited for; the status bar shows what is pending.- With the switch on, visual
Sis surround's and no longer vim's "change the selected lines" —Vcdoes that.
More text objects
On by default. Used after an operator or in visual mode like vim's own (diw, vip):
ieis the whole file without the blank lines at its start and end,aewith them — whole lines, soyaecopies the file andvieselects it.iiis the lines indented at least as far as the cursor's line — the body of a block — andaiadds 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 noaI.ilis the line from its first to its last non-blank,alall of it; neither takes the line break.iqis the contents of a quoted string, whichever of',"and`it is in, andaqthe string with its quotes — and, unlike vim'sa", no space beside it. Before a string on the line it takes that string; between two strings it takes the second — vim's owni"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<…>— anda,takes one comma with it, soda,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.itandat, 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.d2itreaches the next tag out — a count, not a repeat:vatatstays on the tag, asva(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.
