💡 IMPORTANT UPDATE (2024-05-11)
Dev.to has a serious bug where GIFs cannot render properly. Please read the article in the below alternative link instead 🙏
https://gist.github.com/AnsonH/c4c9f5d3b363a51c192269efc806b8c2
Around a year ago, I published the article “10 VS Code Vim Tricks to Boost Your Productivity ⚡”, which I shared various tips for the VSCodeVim extension. Turns out that the article was a hit, so allow me to share a few more VS Code Vim tips that I have up my sleeves 😆.
Prerequisites:
- You’ve installed the VSCodeVim extension
- Basic understanding of Vim keybindings. This guide is NOT for total beginners!
Table of Contents
- Navigation
- Vim Features
- Git
(Full Settings: settings.json | keybindings.json)
Navigation
#1 - Tabs Management
I prefix all my custom keybindings for managing editor tabs with the Shift key so that it’s easy to remember. For tab navigation:
-
Shift + h- Focus the left tab -
Shift + l- Focus the right tab -
Shift + q- Close current tab
Notice how intuitive they are, since h and l keys in Vim means left and right, and q means “quit”.
(💡 Click on the GIF if it's not playing)
To define the custom keybindings, open settings.json by pressing "Ctrl+Shift+P" and search for “Preferences: Open User Settings (JSON)”. Then, add the following item:
{
"vim.normalModeKeyBindings": [
{
"before": ["H"], // Focus previous tab at the left
"commands": ["workbench.action.previousEditor"]
},
{
"before": ["L"], // Focus next tab at the right
"commands": ["workbench.action.nextEditor"]
},
{
"before": ["Q"], // Close current tab
"after": ["<C-w>", "q"]
}
]
}
As for reorganizing the order of tabs:
-
Shift + LeftArrow- Move tab leftwards -
Shift + RightArrow- Move tab rightwards
Setting up this keybinding is a bit tricky. VS Code has default keybindings where “Shift + LeftArrow” will select one character at the cursor’s left side (similar for “Shift + RightArrow”).
Actually we don’t need that since Vim’s Visual Mode can achieve the same thing via vh and vl keybindings. Thus, we need to disable VS Code’s default keybindings for Shift + Left/Right.
Open keybindings.json via “Ctrl + Shift + P” → “Preferences: Open Keyboard Shortcuts (JSON)” and append the following items:
[
{
"key": "shift+left",
"command": "-cursorColumnSelectLeft",
"when": "editorColumnSelection && textInputFocus"
},
{
"key": "shift+left",
"command": "-cursorLeftSelect",
"when": "textInputFocus && vim.mode != 'Visual'"
},
{
"key": "shift+left",
"command": "workbench.action.moveEditorLeftInGroup",
"when": "vim.mode == 'Normal'"
},
{
"key": "shift+right",
"command": "-cursorColumnSelectRight",
"when": "editorColumnSelection && textInputFocus"
},
{
"key": "shift+right",
"command": "-cursorRightSelect",
"when": "textInputFocus && vim.mode != 'Visual'"
},
{
"key": "shift+right",
"command": "workbench.action.moveEditorRightInGroup",
"when": "vim.mode == 'Normal'"
}
]
#2 - Splits Management
I prefix all splits-related keybindings with Ctrl key, similar to Tip #1 where I prefix all tabs-related keybindings with Shift key.
To create splits easily, I’ve defined 2 custom keybindings:
-
|(Shift + \) - Split tab vertically -
_- Split tab horizontally
I picked these two symbols because it looks like the split direction (| is a vertical stroke, _ is a horizontal stroke).
{
"vim.normalModeKeyBindings": [
{
"before": ["|"], // Split tab vertically
"after": ["<C-w>", "v"]
},
{
"before": ["_"], // Split tab horizontally
"after": ["<C-w>", "s"]
}
]
}
(💡 <C-w> is a Vim keybinding syntax that means Ctrl + w)
The Shift + q keybinding we saw from Tip #1 can also close a split if it only has one tab 👇
To navigate around splits, I don’t like Vim’s <C-w> h/j/k/l keybindings because I have to press Ctrl + w every time. To reduce the number of keystrokes, I use Ctrl + h/j/k/l instead:
{
"vim.normalModeKeyBindings": [
{
"before": ["<C-h>"], // Focus split window at left
"commands": ["workbench.action.focusLeftGroup"]
},
{
"before": ["<C-j>"], // Focus split window at right
"commands": ["workbench.action.focusBelowGroup"]
},
{
"before": ["<C-k>"], // Focus split window at above
"commands": ["workbench.action.focusAboveGroup"]
},
{
"before": ["<C-l>"], // Focus split window at below
"commands": ["workbench.action.focusRightGroup"]
}
]
}
⚠️ For Windows/Linux users, the Ctrl + k keybinding may not work properly. You may see this message at the bottom bar:
(Ctrl + K) was pressed. Waiting for second key of chord…
This is because VS Code has a lot of default keyboard shortcuts that starts with Ctrl + k (e.g. Ctrl+K Ctrl+S to open keyboard shortcuts). As a fallback, you can use Vim’s original keybinding of Ctrl+w k.
#3 - Multi-Cursor
VS Code’s multi-cursor editing is also supported in VS Code Vim.
Below are the built-in VS Code keybindings to enter multi-cursor mode:
- MacOS -
Cmd + d - Windows/Linux -
Ctrl + d
Unfortunately for Windows/Linux users, pressing Ctrl + d will instead scroll down the file, because it clashes with Vim’s keybinding of <C-d> for scrolling down a file. As a workaround, I remapped Ctrl + d to Alt + d to enter multi-cursor mode. To do that, append these to keybindings.json:
[
{
"key": "ctrl+d",
"command": "-editor.action.addSelectionToNextFindMatch",
"when": "editorFocus"
},
{
"key": "alt+d",
"command": "editor.action.addSelectionToNextFindMatch",
"when": "editorFocus"
}
]
Alternative to Cmd/Alt + d, VS Code Vim also has a built-in keybinding of gb to enter multi-cursor mode. Here’s a demo in MacOS for both the Cmd + d and gb keybindings:
VS Code Vim treats multi-cursor mode as a variant of Visual Mode, so you need to use Visual Mode keybindings to perform multi-cursor actions, such as:
-
Shift + i- Move to beginning of selection and enter Insert Mode -
Shift + a- Move to end of selection and enter Insert Mode -
sorc- Delete word and enter Insert Mode
For example, I need to press Shift + a in order to start typing at the end of the word (unlike in Normal Mode where I only need to type a):
#4 - Quick Search
To quickly search text from files, I’ve created the <leader>f keybinding:
💡 <leader> stands for Leader Key, which is commonly used as a prefix for user-defined shortcuts. By default it's the backslash key (\), but we commonly map it to Space key (see vim.leader option below)
{
"vim.leader": "<space>",
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "f"], // Search text
"commands": ["workbench.action.findInFiles"]
}
]
}
I also created a keybinding of <leader>s to search all symbols in a workspace. This will save you a lot of time from locating the definition of a symbol.
{
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "s"], // Search symbol
"commands": ["workbench.action.showAllSymbols"]
}
]
}
For example, I want to directly jump to a React component called SaveButton. To do that, I launch the symbol search via <leader>s and enter the component name:
💡 Tip: If you place your cursor under a word (e.g. SaveButton) and press <leader>s, VS Code will automatically fill that word in the search bar 👇
Vim Features
#5 - Emulated Plugins
VS Code Vim emulates quite a number of Vim plugins. While I’m not going to walkthrough every plugin here (see docs for the full list), I want to highlight a plugin called vim-surround.
This plugin will save you tons of time when working with surrounding characters like paranthesis, brackets, quotes, and XML tags. Here’s a quick cheatsheet:
| Surround Command | Description |
|---|---|
y s <motion> <desired> |
Add desired surround around text defined by |
d s <existing> |
Delete existing surround |
c s <existing> <desired> |
Change existing surround to desired |
S <desired> |
Surround when in visual modes (surrounds full selection) |
For a full guide, please refer to vim-surround’s GitHub README.
Examples:
-
ysw)- Add()surround around a word -
ds]- Delete the[]that surrounds the cursor -
cs)]- Changes the()surrounding to[] -
S}- Surrounds the selected text with{}block
#6 - Macros
Macro is a powerful feature in Vim that lets you record a series of commands and replay them. It’s useful to perform similar code changes for many times.
For example, let’s say I have a JavaScript object and I want to turn each value from a string to an array of strings. It’d be very tedious to manually edit line-by-line:
const data = {
item1: "one", // -> item1: ["one"]
item2: "two", // -> item2: ["two"]
...
}
Here’s the anatomy of macros:
- Record a macro:
q<register><commands>q.- Begin a macro recording with
qfollowed by<register>(i.e. a single letter, which is like the name of a save slot). For example,qameans I record a macro to a slot nameda. -
<commands>is the series of Vim keybindings you wish to perform. - End the recording by pressing
q.
- Begin a macro recording with
- To play a macro:
@<register>.-
<register>is the single letter that you just recorded your macro to. For example,@awill play the commands that I’ve previously recorded viaqa<commands>q. - You can play the macro multiple times. For example,
4@aplays the macro 4 times.
-
Back to our example. We basically want to repeat the action of adding a [] bracket around the string value. Let’s record our macro:
- Place our cursor at start of the object key name.
-
qa- Begin recording the macro to slota. -
f"- Move the cursor to the first double quote. -
yst,]- A vim-surround keybinding that we saw from Tip #5. It means adding a[]bracket for the text from the current cursor’s position (first double quote) till the first comma (i.e.t,). -
j0w- Places the cursor in an appropriate location before the macro ends. We move one line down withj, then move to beginning of first word with0w.- 💡 The cursor’s end position matters because it affects the repeatability of the macro.
-
q- Stops the macro recording.
To confirm the macro works, let’s replay it once via @a. Note how the cursor perfectly lands at the start of next line (item3), so that we can replay the macro consecutively via 4@a (i.e. play 4 times):
Git
All my custom Git keybindings are prefixed with <leader>g, where g stands for git. I highly recommend grouping related keybindings with the same prefix so that it’s easier to remember 👍.
#7 - Peek Diff
If your cursor is located inside a Git hunk, I can press <leader>gp to peek the diff changes (gp = git peek).
{
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "g", "p"], // Peek Git diff for the changed line
"commands": ["editor.action.dirtydiff.next"]
}
]
}
#8 - Revert Hunk or Selected Lines
To revert a Git hunk (i.e. discard unstaged changes) in Normal Mode:
- Place my cursor inside the Git hunk
- Press
<leader>gr(gr= git revert)
{
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "g", "r"], // Revert hunk
"commands": ["git.revertSelectedRanges"]
}
]
}
To discard multiple hunks at the same time, I’ve also defined the same keybinding for Visual Mode:
{
"vim.visualModeKeyBindings": [
{
"before": ["<leader>", "g", "r"], // Revert hunk
"commands": ["git.revertSelectedRanges"]
}
]
}
Then, I can first select multiple hunks in Visual Mode, then use <leader>gr to revert them:
#9 - Jump to Previous/Next Changes
Sometimes I have many Git changes across a long file. To quickly jump between changes:
-
<leader>gj- Jump to next Git change (j= down) -
<leader>gk- Jump to previous Git change (k= up)
{
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "g", "j"], // Jump to next Git change
"commands": ["workbench.action.editor.nextChange"]
},
{
"before": ["<leader>", "g", "k"], // Jump to previous Git change
"commands": ["workbench.action.editor.previousChange"]
}
]
}
#10 - Open File on Remote
If your Git repo has a remote upstream (e.g. GitHub, GitLab), you can easily get a sharable URL to the file you’re currently opening.
- Prerequisite: Install GitLens extension
- Add these keybindings to
settings.json
{
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "g", "g"], // Open file in GitHub
"commands": ["gitlens.openFileOnRemote"]
}
],
"vim.visualModeKeyBindings": [
{
"before": ["<leader>", "g", "g"], // Open file in GitHub
"commands": ["gitlens.openFileOnRemote"]
}
]
}
- Move your cursor on a line you wish to open on remote, or enter Visual Mode to select multiple lines
- Press
<leader>gg(mnemonic: lastgstands for GitHub)
To jump to a specific line on remote, use the keybinding in Normal Mode 👇
This also works for Visual Mode too. Try it yourself!
For the full settings code, they can be found below 👇
(I used .jsonc instead of .json in the Gists. Otherwise, the syntax highlighting will add an ugly red background to all comments since theoretically comments are not allowed in JSON 😢)
Thanks for reading. Don’t forget to like and share this post if you found it useful, cheers! 🙌

















Top comments (8)
I can't believe there is no comment!!! You are my hero!!! Thanks a lot!
Amazing work. Thanks for sharing.
Thank you Buddy Soo Much
You also can jump to next highlighted word with
*and previous with#or
You can jump to last highlight in Visual Mode with
gvor last insert mode withgoThat's really useful. However, some images/GIFs are not displaying.
Thank you so much for letting me know 🙏 Alternatively you can read my post here:
gist.github.com/AnsonH/c4c9f5d3b36...
Dev.to has been a constant source of friction for me where GIFs won't show properly...
This is actually pretty useful
help me a lot!!