Configuring VSCodeVim
Tags: vscode, tutorial, productivity, tooling
Published: 2024-05-19 22:37:43

I decided to write this article because recently I have spent a long time learning and configuring VSCode. My new workplace has a strict software policy, making it my only option.
Configuring VSCode up to my liking turned out to be harder and more time consuming than expected. Maybe this article will help others and save someone their time.

## 1. About the article

This article is mostly for people who:

- want to try Vim but are not yet ready to jump head first and switch
- are already using VsCodeVim and are looking for configuration suggestions
- are interested in how far can you push the VSCode to be Vim like 

It contains information about the basic setup and configuration process of [VSCodeVim](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim) as well as "keybinding conditions", tips, tricks and personal configuration.

> ** TLDR **
>
> 1. Check Anson Heung's [article](https://dev.to/ansonh/10-vs-code-vim-tricks-to-boost-your-productivity-1b0n) if you want a shorter version with most important keybindings and settings.
> 2. Instal VSCodeVim and create files `settings.json` and `keybindings.json` in `User` [directory](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations) if necessary.
> 3. Jump directly to section 5 of this article or check my GitHub repo for personal config.

## 2. Prerequisites

First, the VSCodeVim plugin has to be installed and files `settings.json` and `keybindings.json` have to exist in the `User` [directory](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations).

The easiest way to get to those files is to click the "gear icon" of the extension and select option "Extension keyboard shortcuts" or "Extension settings". In both cases a new setting tab will open. Clicking "Open Settings (JSON)" should allow editing appropriate files.

It is also crucial to understand the naming convention.
![official-vs-code-layout-image](https://code.visualstudio.com/assets/docs/getstarted/userinterface/hero.png)

> Note!  
> Image and more information can be found [here](https://code.visualstudio.com/docs/getstarted/userinterface)

Activity bar is comprised of "Views". By default there are five views:

- Explorer
- Search
- Source Control
- Run and Debug
- Extensions

Editor group is comprised of "tabs".  
A small code outline on the right side is called "minimap".  
A fragmented path below editor groups is called "breadcrumbs".

## 3. More on settings.json and keybindings.json

Among people I have worked with, most have never configured their VSCode that is why a short introduction could be useful.

- Settings.json is a general VSCode configuration file and representation of all setting changes. It should contain information about setting changes, theme changes, extension setting changes and most importantly for this article VSCodeVim configuration and keybindings.
- Keybindings.json is a general VSCode configuration file that holds information about keybindings changed by the user.

> Note!  
> I try to follow a rule - if a key binding is related to Vim, or performs similar action (for example uses a `Leader` key), then it should be placed in `settings.json`. If not then in `keybindings.json`.

A new override in settings.json can be a simple one-liner or a list:

```json
// rest of configuration...
"vim.leader": "<Space>",
"vim.normalModeKeyBindingsNonRecursive": [
  { "before": ["<S-h>"], "commands": [":bprevious"] },
  { "before": ["<S-l>"], "commands": [":bnext"] },
],
// rest of configuration...
```

A new keybinding definition in keybindings.json usually looks like this:

```json
// rest of configuration...
{
  "key": "ctrl+shift+q",
  "command": "workbench.action.closeWindow"
  // "when": " _condition for keybinding to be available_ "
},
// rest of configuration...
```

## 4. VSCodeVim configuration

Some of the most popular Vim settings do not require VSCodeVim, for example:

```json
  "editor.lineNumbers": "relative",       // show relative line numbers
  "editor.cursorSurroundingLines": 8,     // scroll offset, number of lines
  "editor.suggest.insertMode": "replace", // what should happen on selecting a suggestion
```

Some are available only after plugin installation:

```json
  "vim.leader": "<Space>",             // set Vim leader (key that indicates start of keybinding in Vim)
  "vim.hlsearch": true,                // show all matches of the most recent search
  "vim.inccommand": "replace",         // a behavior (visual feedback) of substitute command
```

The easiest way to find the setting is to start typing, a suggestion window should pop out and show all the related options. Alternatively, all the settings provided by VSCodeVim can be found in the [official documentation](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim) under the "VSCodeVim settings" section.

Some settings, like those listed above can be declared as is, but keybindings declared in `settings.json` have to be defined per mode.

```json
  "vim.visualModeKeyBindings": [
    { "before": ["<"], "commands": ["editor.action.outdentLines"] },
    { "before": [">"], "commands": ["editor.action.indentLines"] },
  ],
```

The definition above will allow to indent or remove indentation **in visual mode**.

> Note!  
> If you are not familiar with the concept of modes in Vim, check [this Wikibooks page](https://en.wikibooks.org/wiki/Learning_the_vi_Editor/Vim/Modes).

Those are modes and list names that are available:

- normal -> "vim.normalModeKeyBindings"
- visual -> "vim.visualModeKeyBIndings"
- insert -> "vim.insertModeKeyBindings"
- operator pending mode -> "vim.operatorPendingModeKeyBindings"
- normal, non recursive -> "vim.normalModeKeyBindingsNonRecursive"
- visual, non recursive -> "vim.visualModeKeyBindingsNonRecursive"
- insert, non recursive -> "vim.insertModeKeyBindingsNonRecursive"
- operator pending mod, non recursive -> "vim.operatorPendingModeKeyBindingsNonRecursive"

> Note!  
> If this is you first encounter with "operator pending mode" check [this short article](https://learnvimscriptthehardway.stevelosh.com/chapters/15.html)
> The non recursive version helps, when you try to swap keys. Check [key remapping section](https://github.com/VSCodeVim/Vim?tab=readme-ov-file#key-remapping) for more info and examples.

## 5. Getting rid of the mouse

The feeling of being forced to stop typing and reach for the mouse just to perform one click has to be one of the most infuriating. Therefore it is crucial to eliminate them as quickly as possible. Most important actions are:

> Note!  
> In the code sections below, "before" can be changed to anything you want. Used keybindings are the ones I am using.

### 5.1 Focusing, switching and moving groups and tabs

The commands for switching between opened groups are:

```json
// settings.json

{
  "before": ["leader", "h"],
  "commands": ["workbench.action.focusLeftGroup"]
},
{
  "before": ["leader", "j"],
  "commands": ["workbench.action.focusBelowGroup"]
},
{
  "before": ["leader", "k"],
  "commands": ["workbench.action.focusAboveGroup"]
},
{
  "before": ["leader", "l"],
  "commands": ["workbench.action.focusRightGroup"]
}
// VSCode has a command for switching between groups:
// { "key": "ctrl+1", "command": "workbench.action.focusFirstEditorGroup" }
```

The command ids for moving groups are:

- "workbench.action.moveActiveEditorGroupLeft"
- "workbench.action.moveActiveEditorGroupBelow"
- "workbench.action.moveActiveEditorGroupAbove"
- "workbench.action.moveActiveEditorGroupRight"
- "workbench.action.moveEditorToFirstGroup"
- "workbench.action.moveEditorToLastGroup"
- "workbench.action.moveEditorToNextGroup"
- "workbench.action.moveEditorToPreviousGroup"
- "workbench.action.moveEditorLeftInGroup"
- "workbench.action.moveEditorRightInGroup"

The commands for switching tabs are:

```json
// settings.json

// switch between tabs:
{ "before": ["<S-h>"], "commands": ["workbench.action.nextEditor"] },
{ "before": ["<S-l>"], "commands": ["workbench.action.previousEditor"] },
// or Vim's ":bprevious" and ":bnext"

// switch between tabs in the same group:
{ "before": ["<S-h>"], "commands": ["workbench.action.nextEditorInGroup"] },
{ "before": ["<S-l>"], "commands": ["workbench.action.previousEditorInGroup"] },
// VSCode has a shortcut for opening a specific tab in group
// { "key": "alt+1", "command": "workbench.action.openEditorAtIndex1" }
```

> Note!  
> Special pages like the visual version of `settings.json` prevent switching between tabs. Besides using `openEditorAtIndex` I have not yet found a way to override it.

### 5.2 Fixing problems

A typo, type mismatch or a missing import, this popup allows performing quick actions to fix most of the problems

```json
{
	// keybindings.json
	"key": "ctrl+.",
	"command": "editor.action.quickFix",
	"when": "editorHasCodeActionsProvider && textInputFocus && !editorReadonly"
}
```

To limit quickFix action to only a normal mode, it could be moved to a `settings.json` file inside "workbench.action.moveEditorLeftInGroup: list, like so:

```json
// settings.json
"vim.normalModeKeyBindings": [
  // ...
  {
    // "before": ...
    "commands": ["editor.action.quickFix"]
  }
  // ...
],
```

### 5.2 Cycling thru popup lists

Unfortunately VSCode uses multiple unrelated suggestion boxes that all require to be configured separately. For example, a "quick fix" window/widget/popup, displays multiple options but is not considered a "suggestWidgetMultipleSuggestions" and is in no way related with "quickOpen".

> Note!  
> There is a good chance that my configuration is missing a popup window, but even after some heavy use I have yet to encounter one I would not be able to cycle through.
> If you know a simpler way of configuring this or any other action please leave a comment.

```json
{
  "key": "ctrl+n",
  "command": "selectNextSuggestion",
  "when": "editorTextFocus && suggestWidgetMultipleSuggestions && suggestWidgetVisible"
},
{
  "key": "ctrl+p",
  "command": "selectPrevSuggestion",
  "when": "editorTextFocus && suggestWidgetMultipleSuggestions && suggestWidgetVisible"
},
{
  "key": "ctrl+n",
  "command": "selectNextCodeAction",
  "when": "codeActionMenuVisible"
},
{
  "key": "ctrl+p",
  "command": "selectPrevCodeAction",
  "when": "codeActionMenuVisible"
},
{
  "key": "ctrl+n",
  "command": "workbench.action.quickOpenSelectNext",
  "when": "inQuickOpen"
},
{
  "key": "ctrl+p",
  "command": "workbench.action.quickOpenSelectPrevious",
  "when": "inQuickOpen"
}
```

By default an option can be selected using Enter or Tab key but additional shortcut can be added

```json
{
	"key": "ctrl+y",
	"command": "acceptSelectedSuggestion"
	// or "command": "acceptSelectedCodeAction"
	// or "command": "workbench.action.acceptSelectedQuickOpenItem"
	// "when": ...
}
```

### 5.3 Scrolling popups

Sometimes the popups are longer or wider than the window they are displayed in. There is a popular trick that allows to perform a scroll without using a mouse:

```json
{
  "key": "h",
  "command": "editor.action.scrollLeftHover",
  "when": "editorHoverFocused"
},
{
  "key": "j",
  "command": "editor.action.scrollDownHover",
  "when": "editorHoverFocused"
},
{
  "key": "k",
  "command": "editor.action.scrollUpHover",
  "when": "editorHoveredFocused"
},
{
  "key": "l",
  "command": "editor.action.scrollRightHover",
  "when": "editorHoverFocused"
}
```

### 5.4 Performing LSP actions

LSP actions are common tasks like checking the type of the variable, API definition or opening the implementation.

```json
"vim.normalModeKeyBindingsNonRecursive": [
  // rest of keybindings...
  // Go to Definition
  { "before": ["g", "d"], "commands": ["editor.action.goToDefinition"] },
  // Peek Definition
  { "before": ["g", "p", "d"], "commands": ["editor.action.peekDefinition"] },
  // Show Hover
  { "before": ["g", "h"], "commands": ["editor.action.showDefinitionPreviewHover"] },
  // Go to Implementations
  { "before": ["g", "i"], "commands": ["editor.action.goToImplementation"] },
  // Peek Implementations
  { "before": ["g", "p", "i"], "commands": ["editor.action.peekImplementation"] },
  // Go to References
  { "before": ["g", "r"], "commands": ["editor.action.referenceSearch.trigger"] },
  // Go to Type Definition
  { "before": ["g", "t"], "commands": ["editor.action.goToTypeDefinition"] },
  // Peek Type Definition
  { "before": ["g", "p", "t"], "commands": ["editor.action.peekTypeDefinition"] },
  // rest of keybindings...
],
```

### 5.5 Search and replace

By default keybindings "ctrl+f" opens up "find widget" that allows searching in the current file. "ctrl+h" extends that functionality by ability to replace matches with provided value. The widget can be easily toggled with an extra "when condition" and the keybindings for "next match" and "previous match" can be overridden:

```json
{
  "key": "ctrl+h",
  "command": "actions.find",
  "when": "editorFocus && editorIsOpen"
},
{
  "key": "ctrl+h",
  "command": "closeFindWidget",
  "when": "editorFocus && editorIsOpen && findWidgetVisible"
},
{
  "key": "ctrl+shift+h",
  "command": "editor.action.startFindReplaceAction",
  "when": "editorFocus && editorIsOpen"
},
{
  "key": "ctrl+n",
  "command": "editor.action.nextMatchFindAction",
  "when": "editorFocus && findWidgetVisible"
},
{
  "key": "ctrl+p",
  "command": "editor.action.previousMatchFindAction",
  "when": "editorFocus && findWidgetVisible"
},
```

## 6. Terminal

A quick access to a build in terminal will save a lot of time and mouse movement. The most important keybindings are:

```json
{
  "key": "ctrl+shift+t",
  "command": "workbench.action.togglePanel"
},
{
  "key": "ctrl+shift+n",
  "command": "workbench.action.terminal.new",
  "when": "terminalIsOpen && terminalFocus"
},
{
  "key": "ctrl+n",
  "command": "workbench.action.terminal.focusNext",
  "when": "terminalIsOpen && terminalFocus"
},
{
  "key": "ctrl+p",
  "command": "workbench.action.terminal.focusPrevious",
  "when": "terminalIsOpen && terminalFocus"
},
{
  "key": "ctrl+q",
  "command": "workbench.action.terminal.kill",
  "when": "terminalIsOpen && terminalFocus"
},
```

## 7. Activity bar - treating activities like modes

Most of the time Explorer is the only activity view needed for work, but then the workflow is broken the moment non standard action like committing changes has to be made. With the already long list of custom keybindings and most short (two keys) combinations already taken, few options remain.  
Instead of cramming new ones into "global scope" or adding a prerequisite of another shortcut, an activity view can be treated similarly to a Vim mode. For example, all actions related to file operations could be made accessible only when "Explorer" view is opened while actions related to Git, when "Source control view" is:

```json
// EXPLORER
{
  "key": "ctrl+shift+e",
  "command": "workbench.view.explorer"
},
{
  "key": "n",
  "command": "explorer.newFile",
  "when": "filesExplorerFocus && !inputFocus"
},
{
  "key": "shift+n",
  "command": "explorer.newFolder",
  "when": "filesExplorerFocus && !inputFocus"
},
{
  "key": "x",
  "command": "filesExplorer.cut",
  "when": "filesExplorerFocus && !inputFocus"
},
{
  "key": "p",
  "command": "filesExplorer.paste",
  "when": "filesExplorerFocus && !inputFocus"
},
{
  "key": "d",
  "command": "deleteFile",
  "when": "filesExplorerFocus && !inputFocus"
},
// GIT
{
  "command": "workbench.view.scm",
  "key": "ctrl+shift+g"
},
{
  "key": "ctrl+s",
  "command": "git.stage",
  "when": "activeViewlet == 'workbench.view.scm' && sideBarFocus"
},
{
  "key": "ctrl+u",
  "command": "git.unstage",
  "when": "activeViewlet == 'workbench.view.scm' && sideBarFocus"
},
{
  "key": "ctrl+c",
  "command": "git.commitAllSigned",
  "when": "activeViewlet == 'workbench.view.scm' && sideBarFocus"
},
{
  "key": "ctrl+p",
  "command": "git.push",
  "when": "activeViewlet == 'workbench.view.scm' && sideBarFocus"
},
```

Condition used in code above should help override global scoped keybindings and give access to file and Git related keybindings only when the "File explorer" or "Source control" **is the active and focus view**. In the case of file explorer there is also a need to check if the user is not in the middle of inputting details.

> Note!  
> Condition "filesExplorerFocus" works the same as "activeViewlet == 'workbench.view.explorer'", VSCodeVim just provides a simple shorthand.

## 8. Finding actions and conditions

The hardest part of configuring VSCodeVim is the sheer amount of IDs, names and available conditions. Besides looking online, the best option is playing around in the keyboard shortcut window. An action can usually be found by simply searching with a keyword.  
The condition can be found by clicking on keybinding and selecting option "Change When Expression" (or using vi motions and shortcut) and trying different keywords. Some rules are obvious, some, like the ones related to widgets and popups, less so.
It is also worth testing different combinations with the use of "Record Keys" (button on the right side of the search bar) and copying shortcuts definitions.

## 9. Prettier and spellchecker

A lot of editing and fixing mistakes can be done automatically.
[Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) can be set either as a default or a language formatter and made to perform code formatting on save:

```json
// settings.json
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,

// Settings appalled only when working with TypeScript
"[typescript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},

"prettier.enable": true,

// Prettier configuration
"prettier.useTabs": true,
```

[CodeSpellChecker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) works out of the box and allows correcting mistakes with the use of "quickFix" menu.

## 10. Closing thoughts

VSCode with the help of VSCodeVim and a lot of keybinding re-configuration is surprisingly comfortable and makes for a nice bridge into using Vim full time.  
You can find my personal configuration [here](https://github.com/Ritek/VSCodeVim).  
Thank you for reading and please leave a suggestion in the comments with your tips and tricks.