Grow shortcut trees in Visual Studio Code with Which Key

Leaving Emacs and vim for the tepid embrace of VS Code has some perks and some downsides.

The perks: Time to hack on things for fun that don’t use elisp, Vimscript or Lua.

The downsides: Retired configs. Lost nerd cred. Worse: time spent finding VS Code equivalents to Emacs or vim features. Features like Which Key.

Which Key in Emacs, Neovim and Helix

Which Key displays a shortcut menu in a pop up when you press a key:

Screenshot from Which Key in Emacs showing a list of commands with single-key shortcut options.

Pressing the key on the left (“1”) performs the action on the right (“delete-other-windows”). New users get fast shortcut discovery. Experienced users get a free memory boost and the superpower of building visual menus from trees of rapid-fire shortcut combos.

Other editors stole Which Key because it’s a great idea. There’s a Which Key extension for Neovim. The Helix editor ships with a similar thing, making it easy to experience in your terminal right now.

Let’s install and launch Helix quickly, if only to convince ourselves that the fleeting pain of manually configuring something similar in VS Code as we’ll do next is worthwhile.

# Instructions for macOS.
# Others: https://docs.helix-editor.com/install.html
brew install helix
hx

In Helix press the space key to see a Which Key-style prompt. Subsequent keypresses summon subcommand menus:

Helix editor showing Which Key feature to narrow shortcut choices through a tree of options.

Exit Helix the same way you exit vim: throw your computer into Mount Doom, or type <Escape> to enter normal mode, then :qa!<Enter> to quit all views without saving.

By now you get the idea. Which Key helps you navigate a tree of customisable keyboard shortcuts with a lens on accessible sub-branches.

Growing a personal tree of shortcuts feels smarter than memorising default keybindings. It’s also faster than searching a kitchen sink command palette as long as naming editor actions well remains unsolved by GPT-3.

Which Key for VS Code

VS Code already supports shortcut “chords” — a chain of keypress actions that trigger a command when you complete the sequence. By default on macOS ⌘K followed by ⌘C will comment out the current line in the editor (and like Emacs you keep ⌘ held down; no need to lift command between pressing k and c, hence “chord”).

There are 46 chorded options after ⌘K by default. But VS Code won’t give you hints about what they are (despite a 2016 feature request). You have to discover and remember the sequence you care about, like learning the church organ or studying Tekken combos and pretending it’s fun.

When you press ⌘K in VS Code it assumes you know what to press next, which — speaking for myself — is far too generous:

Chord prompt from VS Code showing the less than helpful text, “Command K was pressed. Waiting for second key of chord…”.

With Which Key for VS Code it doesn’t have to be this way. The extension helps VS Code users grow their own shortcut trees of mini menus with built-in hints.

I press ⌘L (for ‘list’) in VS Code to summon my root Which Key menu:

Which Key prompt from VS Code showing a list of helpful submenu options together with their shortcut keys.

I release ⌘L and press “g” to show my git commands submenu:

Custom Which Key git submenu in VS Code showing a list of possible git operations and their shortcut keys.

Helpful git commands are one more keypress away. They use mnemonic sequences that make sense to me, because it was me who chose them and not Microsoft (‘git init’ → ⌘L g i, ‘GitHub’ → ⌘L g h, and so on).

If you like the look of this stuff but don’t want to spend time configuring custom commands and shortcuts, you could install the VSpaceCode extension. It comes with a bunch of shortcuts that someone who probably isn’t you configured.

The VSpaceCode defaults are fine and they’ll be faster to start using. But you’ll end up with menu items you don’t care about, missing commands from your daily workflow, as well as mnemonics that are pretty good but perhaps not what you would have picked, simply through the gift and curse of not sharing a brain with the plugin’s authors.

For me it’s better to grow your own tree of shortcuts from scratch with the commands you really want. To build custom trees like this you don’t need VSpaceCode, just Which Key (from the same lovely authors):

  1. Install the Which Key extension. (Press ⌘P and type ext install VSpaceCode.whichkey.)

  2. Open VS Code’s Command Palette (⇧⌘P) and type enough of “Preferences: Open User Settings (JSON)” that the option appears, then select it and press enter.

    The file that opens should be named settings.json. (If it’s named keybindings.json or defaultSettings.json or Settings without the .json or anything else, close it and look for “User Settings (JSON)” from the menu again.)

    Configure your Which Key bindings tree with JSON. Some pointers:

    • For the ‘name’ values, I try to use the convention of appending ‘…’ if the action will open another prompt or submenu. The absence of the ellipsis tells me I’m about to run a command straight away instead of triggering another prompt or menu.

    • For the ‘key’ values, it’s best to use a bare key like ‘f’ over a combo like ‘cmd+f’. The latter won’t work because it conflicts with “find in file”.

    • I’ve omitted my full config to encourage you to make your own instead of copying mine.

    "whichkey.bindings": [
        {
           "key": "g",
           "name": "git…",
           "type": "bindings",
           "bindings": [
               {
               "key": "i",
               "name": "init",
               "type": "command",
               "command": "git.init"
               },
               {
               "key": "g",
               "name": "status",
               "type": "command",
               "command": "workbench.view.scm"
               },
               {
               "key": "c",
               "name": "checkout…",
               "type": "command",
               "command": "git.checkout"
               },
               {
               "key": "P",
               "name": "push",
               "type": "command",
               "command": "git.push"
               },
               {
               "key": "F",
               "name": "pull",
               "type": "command",
               "command": "git.pull"
               },
               {
               "key": "f",
               "name": "fetch",
               "type": "command",
               "command": "git.fetch"
               },
               {
               "key": "s",
               "name": "stage",
               "type": "command",
               "command": "git.stage"
               },
               {
               "key": "S",
               "name": "stage selected",
               "type": "command",
               "command": "git.stageSelectedRanges"
               },
               {
               "key": "u",
               "name": "unstage",
               "type": "command",
               "command": "git.unstage"
               },
               {
               "key": "U",
               "name": "unstage selected",
               "type": "command",
               "command": "git.unstageSelectedRanges"
               },
           ]
        },
        {
           "key": "p",
           "name": "project…",
           "type": "bindings",
           "bindings": [
               {
               "key": "o",
               "name": "open",
               "type": "command",
               "command": "workbench.action.openRecent"
               }
           ]
        },
    ],
    

    To get the command ID strings like workbench.action.openRecent:

    i. Find the command in the command palette (⇧⌘P) and click the Configure Keybinding icon:

    Visual Studio Code command palette showing the Open User Settings command.'

    ii. Copy the command ID by second-clicking the command name (or copying the text after @command: in the search filter at the top):

    Visual Studio Code keybindings list showing the Copy Command ID contextual option.'

    This works for commands from installed extensions too.

  3. Finally, bind a key to open the root Which Key menu in VS Code’s keybindings.json.

    This is separate to the settings.json config you’ve just been editing. Open it with ⇧⌘P via the command called “Preferences: Open Keyboard Shortcuts (JSON)”.

    I use ⌘L to show Which Key:

      {
        "key": "cmd+l",
        "command": "whichkey.show"
      },
    

    The shortcut works even if focus is in a non-editor pane. You get quick access to your Which Key menu throughout VS Code.