10 More VS Code Vim Tricks to Code Faster ⚡
Tags: vscode, vim, tutorial, productivity
Published: 2024-07-06 10:57:13

Around a year ago, I published the article “[10 VS Code Vim Tricks to Boost Your Productivity ⚡](https://dev.to/ansonh/10-vs-code-vim-tricks-to-boost-your-productivity-1b0n)**”,** which I shared various tips for the [VSCodeVim](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim) 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](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim) extension
- Basic understanding of Vim keybindings. This guide is NOT for total beginners!

---

## Table of Contents

([Previous 10 Tips](https://dev.to/ansonh/10-vs-code-vim-tricks-to-boost-your-productivity-1b0n))

- Navigation
  - [#1 - Tabs Management](#1-tabs-management)
  - [#2 - Splits Management](#2-splits-management)
  - [#3 - Multi-Cursor](#3-multicursor)
  - [#4 - Quick Search](#4-quick-search)
- Vim Features
  - [#5 - Emulated Plugins](#5-emulated-plugins)
  - [#6 - Macros](#6-macros)
- Git
  - [#7 - Peek Diff](#7-peek-diff)
  - [#8 - Revert Hunk or Selected Lines](#8-revert-hunk-or-selected-lines)
  - [#9 - Jump to Previous/Next Changes](#9-jump-to-previousnext-changes)
  - [#10 - Open File on Remote](#10-open-file-on-remote)

(Full Settings: [`settings.json`](https://gist.github.com/AnsonH/e6b5222c3f0e588253a3e8fb0d55e147) | [`keybindings.json`](https://gist.github.com/AnsonH/dd027ae954fb82124fd90849d36da8b1))

---

## Navigation

### #1 - Tabs Management

I prefix all my custom keybindings for managing [editor tabs](https://code.visualstudio.com/docs/getstarted/userinterface#_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”.

![1.1-tabs-management](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720242135/1.1-tabs_p8tlix.gif)

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:

```jsonc
{
  "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

![1.2-move-tabs](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720242712/1.2-tabs_ovtezn.gif)

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:

```jsonc
[
  {
    "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'"
  }
]
```

([🔼 Table of Contents](#table-of-contents))

### #2 - Splits Management

I prefix all splits-related keybindings with `Ctrl` key, similar to [Tip #1](#1-tabs-management) 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).

```jsonc
{
  "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](#1-tabs-management) can also close a split if it only has one tab 👇

![2.1-create-splits](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720243351/2.1-splits_e6dikp.gif)

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:

```jsonc
{
  "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"]
    }
  ]
}
```

![2.2-move-across-splits](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720244389/2.2-splits_wxwdiq.gif)

⚠️ 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`.

([🔼 Table of Contents](#table-of-contents))

### #3 - Multi-Cursor

VS Code’s [multi-cursor editing](https://code.visualstudio.com/docs/editor/codebasics#_multiple-selections-multicursor) 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`:

```jsonc
[
  {
    "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:

![3.1-multi-cursor](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720256539/3.1-multi-cursor_qdfswi.gif)

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
- `s` or `c` - 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`):

![3.2-multi-cursor-edit](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720256539/3.2-multi-cursor_zgdpha.gif)

### #4 - Quick Search

To quickly search text from files, I’ve created the `<leader>f` keybinding:

💡 `<leader>` stands for [Leader Key](https://stackoverflow.com/q/1764263/11067496), 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)

```jsonc
{
  "vim.leader": "<space>",
  "vim.normalModeKeyBindings": [
    {
      "before": ["<leader>", "f"], // Search text
      "commands": ["workbench.action.findInFiles"]
    }
  ]
}
```

![4.1-find-in-files](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720254241/4.1-search_wulu8f.gif)

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.

```jsonc
{
  "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:

![4.2-search-symbol](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720254242/4.2-symbol_mdvqk3.gif)

💡 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 👇

![4.3-search-symbol-tip](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720254242/4.3-symbol_ho5nnl.gif)

([🔼 Table of Contents](#table-of-contents))

---

## 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](https://github.com/VSCodeVim/Vim#-emulated-plugins)), I want to highlight a plugin called [vim-surround](https://github.com/VSCodeVim/Vim#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 <motion>     |
| `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](https://github.com/tpope/vim-surround).

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

![5-vim-surround](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720258618/5-vim-surround_zq0oaz.gif)

([🔼 Table of Contents](#table-of-contents))

### #6 - Macros

[Macro](https://vim.fandom.com/wiki/Macros) 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:

```jsx
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 `q` followed by `<register>` (i.e. a single letter, which is like the name of a save slot). For example, `qa` means I record a macro to a slot named `a`.
  - `<commands>` is the series of Vim keybindings you wish to perform.
  - End the recording by pressing `q`.
- To play a macro: `@<register>`.
  - `<register>` is the single letter that you just recorded your macro to. For example, `@a` will play the commands that I’ve previously recorded via `qa<commands>q`.
  - You can play the macro multiple times. For example, `4@a` plays 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:

1. Place our cursor at start of the object key name.
2. `qa` - Begin recording the macro to slot `a`.
3. `f"` - Move the cursor to the first double quote.
4. `yst,]` - A [vim-surround](https://github.com/tpope/vim-surround) keybinding that we saw from [Tip #5](#5-emulated-plugins). It means adding a `[]` bracket for the text from the current cursor’s position (first double quote) till the first comma (i.e. `t,`).
5. `j0w` - Places the cursor in an appropriate location before the macro ends. We move one line down with `j`, then move to beginning of first word with `0w`.
   - 💡 The cursor’s end position matters because it affects the repeatability of the macro.
6. `q` - Stops the macro recording.

![6.1-macro-record](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720258848/6.1-record-macro_wxd5oa.gif)

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):

![6.2-macro-play](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720258849/6.2-play-macro_rx6kiw.gif)

([🔼 Table of Contents](#table-of-contents))

---

## Git

All my custom Git keybindings are prefixed with `<leader>g`, where `g` stands for **g**it. 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](https://stackoverflow.com/q/37620729), I can press `<leader>gp` to peek the diff changes (`gp` = **g**it **p**eek).

```jsonc
{
  "vim.normalModeKeyBindings": [
    {
      "before": ["<leader>", "g", "p"], // Peek Git diff for the changed line
      "commands": ["editor.action.dirtydiff.next"]
    }
  ]
}
```

![7-peek-diff](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720245906/7-peek-diff_pzajvb.gif)

([🔼 Table of Contents](#table-of-contents))

### #8 - Revert Hunk or Selected Lines

To revert a Git hunk (i.e. discard unstaged changes) in Normal Mode:

1. Place my cursor inside the Git hunk
2. Press `<leader>gr` (`gr` = **g**it **r**evert)

```jsonc
{
  "vim.normalModeKeyBindings": [
    {
      "before": ["<leader>", "g", "r"], // Revert hunk
      "commands": ["git.revertSelectedRanges"]
    }
  ]
}
```

![8-revert-hunk-normal](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720251911/8.1-revert_mwjjcy.gif)

To discard multiple hunks at the same time, I’ve also defined the same keybinding for Visual Mode:

```jsonc
{
  "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:

![8-revert-hunk-visual](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720251797/8.2-revert_o2tind.gif)

([🔼 Table of Contents](#table-of-contents))

### #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)

```jsonc
{
  "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"]
    }
  ]
}
```

![9-jump-change](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720252105/9-jump_l3zk9g.gif)

([🔼 Table of Contents](#table-of-contents))

### #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.

1. Prerequisite: Install [GitLens extension](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
2. Add these keybindings to `settings.json`

   ```jsonc
   {
     "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"]
       }
     ]
   }
   ```

3. Move your cursor on a line you wish to open on remote, or enter Visual Mode to select multiple lines
4. Press `<leader>gg` (mnemonic: last `g` stands for **G**itHub)

To jump to a specific line on remote, use the keybinding in Normal Mode 👇

![10.1-remote-normal](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720252561/10.1-source_c5vdnq.gif)

To select multiple lines on remote, use Visual Mode 👇

![10.2-remote-visual](https://res.cloudinary.com/dwezt4vx1/image/upload/v1720252560/10.2-source_kiugif.gif)

([🔼 Table of Contents](#table-of-contents))

---

For the **full** settings code, they can be found below 👇

- [`settings.json`](https://gist.github.com/AnsonH/e6b5222c3f0e588253a3e8fb0d55e147)
- [`keybindings.json`](https://gist.github.com/AnsonH/dd027ae954fb82124fd90849d36da8b1)

(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! 🙌

- [My GitHub](https://github.com/AnsonH)
- [Personal website](https://www.ansonh.com/)