QMK firmware – Add Support for VisualStudio Code

Introduction

In a previous post, I showed my Keebio Iris split keyboard that runs QMK. I am still amazed at the breadth of things one can do with QMK. In this post, I will examine how to work with editor groups in visual studio code. Conceptually, things should work for other editors/IDE’s as well.

Editor Groups in Visual Studio

Take a look at VS code. On the left, there is the file tree, called Explorer in Visual Studio Code. On the right we have the editing area. At the bottom of that editing area, there is a single Terminal. Looking at the editors, we see that they we have a left pane and a right pane. Each editor group contains 2 open files. Visual Studio Code would say we have two editor groups, with 2 editors each. I shall use the VS Code terminology in this article. Having 2 active editors in different groups can be very helpful. As a coder, I might place UnitTest code on the left editor group, and the code under test on the right editor group.

Things I want to do, that normally involve the mouse.

1. Move between editor groups (View: Focus next editor group) – has to work for Terminal Too.

Lets say I am on editor 1, and I now want to work in editor 4. To do this with a mouse is simple, click on editor 4 and start typing. Want to go back to editor 1? Just click on it. Do this with the keyboard? Hit F1, and type ‘next editor group’ and hit enter. That does the trick.

2. Open a specific editor within my selected editor group. (View: Focus next/previous editor in Group)

Scenario here is that I am on Editor 1, and want to start editing Editor2. VS Code treats editor groups in kind of a segregated way. Once you are in an editor in a specific group, using the keyboard to do the switch is possible only within that group. On the mac, the shortcut for this is Ctrl + Tab. That brings up a useful dialog which I can’t easily screenshot. It shows a list of files and paths of files open in the CURRENT editor group. Its worth noting that ctrl+1, ctrl+2, … also work, and focus the editor of choice in the editor group.

3. Split editor into new Editor group (View:Split Editor) (should work for to split terminal window)

Here, its useful to find the icon that lets you do this with the mouse.

Pictured above is the Top Right of VSCode. You want the icon that looks like an open book, next to the …, second last icon from the right. Doing this with the keyboard is easy, just hit Cmd + \, or F1 followed by ‘Split editor’. You want View: Split Editor. A small annoyance is that this creates a duplicate of the current editor in the new editor group. This is usually not what I want. Normally, I want an editor from the first editor group to MOVE (not copy) to the next editor group. There is a shortcut to do this automatically, but this hits an Electron Bug, that causes the terminal to beep.

4. Move an editor from one group to the next/previous group (View: Move editor to next group)

As mentioned, suppose I want to move editor to the next group?  It turns out this is easy, but not that intuitive with the mouse. Click and keep the left mouse button down, drag the editor tab to the next group. Depending on where you go with the mouse, VSCode will offer to either just move it to the editor group of choice, or create a new editor group. Just be sure to drag to the centre of the editor group; this will perform the move.  With this technique, you can also split an editor into a new editor group. The keyboard way? F1, then Move editor to next group. Assign a shortcut if you like.

5. Increase the width/height of the current editor group (Increase Editor Width, Increase Editor Height)

With the mouse, this is easy and intuitive. Hover over the split, the mouse cursor changes – drag to the right or left to resize. With the keyboard, its F1 and Increase Editor Width/ Decrease Editor Width, Increase Editor Height, Decrease Editor hight.

6. Increase/decrease width of terminal (Terminal: Resize Terminal Left, Right)

This is related to item 5, however its a different command in Visual Studio code. Doing it with the mouse, with the keyboard, its F1 Resize Terminal Left/Right.

The problem with the keyboard is remembering shortcuts.

The easiest thing is of course not to bother with keyboard commands. Why bother with the keyboard if something is easy/intuitive with the mouse, but hard with the keyboard? I am trying to be more keyboard-focused in my computer use. Every reach for the mouse is a distracting mini-context switch, and I can avoid those by keeping my hands on the keyboard. Once learned, and internalized, using the keyboard feels like controlling the computer with your mind. One ends up thinking about what one wants to do, and the fingers and muscle memory do the rest. This takes practice, of course, but yields a more pleasant and more productive way to work. Its also good for avoiding repetitive stress injuries.

A helpful metaphor

I found it helpful to think of the above actions in terms of the arrow keys. I have laid these out in a quad like fashion here, as opposed to an inverted T layout. That detail is a personal preference.

Assigning keyboard shortcuts in VSCode

In order to do the keyboard thing, I try to avoid the F1 commands. These take a lot of typing to work, which is slow. I also re-assign any default keyboard shortcuts that involve things like chording. (Ctrl +K followed by Ctrl + →) is an example. On my mac, I have chosen Control Option(Alt) Command (Windows GUI key) +#, where # is a number from 1 to 9,  When assigning or invoking, hold down all 4 keys at once.  The actual shortcuts don’t matter that much, except that one needs to pick a combination that is not already in use.

Desired end-state with the custom ‘arrow’ keys

Note that I am using layers in QMK, in my setup, I use a MO(ADJUST), momentary layer switch.  That just means that I press one key on my keyboard which switches the keyboard to a new layer. So when that special layer switch key is held, the Adjust layer activates – but the switch layer key must be pressed during the entire time one uses the the layer. So hitting Adjust Layer + ‘f’ in QWERTY mode, corresponds to Ed →, Similarily, Adjust + ‘s’ invokes Ed ←

Moving the focus between editor groups (left, right, up,down)

Holding down the Adjust Layer key, and pressing f, gets me Ed →. If I am in the first editor group, this teleports me to the next editor group. The Left, Up, Down keys work in a similar way.  This is super intuitive. Most times, I will only have a single vertical split and only use Ed → and Ed ←

Moving an editor to the next/previous, upper, lower group.

This is the scenario when I want to move an editor to an already existing editor group. For instance move the editor 1 to the editor group that has editor 3 and editor 4. Here, I add a modifier to the same conceptual arrow keys, the Cmd key. Hold down ADJUST + Cmd + arrow key, and the editor moves to the appropriate group.

Resizing the editor/terminal (left, right, up, down)

Holding down ADJUST + CTRL + Ed arrow key allows me to do the resize. VS Code has a context-aware keyboard shortcut system. If you assign the same shortcut to widen the terminal, VS code will use the correct one depending on the editing context. If the focus is in the Terminal, it invokes the terminal make wider command. Otherwise it invokes the editor make wider command.

Splitting editors, selecting editors within a group

The existing shortcuts, as described above work.

Ease of learning

This re-use of the Ed arrow keys makes it easy to learn the functionality and remember the shortcut required to achive the desired function.

Enter QMK

I want to discuss how I have achieved this with my keyboard layout code. Since I know C and want flexibility, coding the layout and functionality are really the only option. Its a bit of a learning curve, even if you know C already. Looking at other user’s work is sometimes very helpful. While my layout is currently not in the repo, I will try to share the essence of how to accomplish this. I am not going to rehash the setup that is needed to cross-compile and flash your keyboard as this is documented elsewhere.

First, define your layers. Mine look like this:

#include QMK_KEYBOARD_H
#include "secrets.h"
#include "keymap_combo.h"

enum iris_layers {
  _COLEMAK,
  _QWERTY,
  _LOWER,
  _RAISE,
  _ADJUST,
};

Next, define custom keycodes

enum custom_keycodes {
  COLEMAK = SAFE_RANGE,
  QWERTY,
  LOWER,
  RAISE,
  ADJUST,
  FIND_G, // VSCode Replace in files Shift+Cmd+H
  FIND_L, // VSCode Replace in this file Option+Cmd+F
  SIG,
  KC_OCPRN,
  KC_OCBRC,
  KC_OCCBR,
  KC_OCDQUO,
  KC_OCQUOT,
  LEFT,
  RIGHT,
  TOP,
  BOTTOM,
  PATH,
  ALT_TAB
};

Note the LEFT, RIGHT, TOP, BOTTOM, these are the custom keycodes that will be sent when pressed with Adjust.

We define KC_ADJU as a momentary switch to layer, when held.

Here the Adjust layer:

On line 226-228 you can see the pictoral representation, while the actual mapping happens on lines 239, 241 and 243.

Handling the user-defined keys

If I compile this right now, the layer adjust would work. QMK then sends the TOP, LEFT, RIGHT and BOTTOM custom keycodes. QMK however, would not know what to do with them. This is where the process_record_user() callback comes in.

But lets look at the code that handles the keycodes of interest

case LEFT: {
    static bool left_registered;
    if (record->event.pressed) {
        if (mod_state & MOD_MASK_GUI) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("-"))));
            set_mods(mod_state);
        }
        else if (mod_state & MOD_MASK_CTRL) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("4"))));
            set_mods(mod_state);
        }
        else
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("8"))));
        left_registered = true;
        return false;
    } else {
        if (left_registered) {
            unregister_code(key_to_keycode_for_default_layer(LEFT));
            left_registered = false;
            return false;
        }
    }
    return true;
}
case RIGHT: {
    static bool right_registered;
    if (record->event.pressed) {
        if (mod_state & MOD_MASK_GUI) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("="))));
            set_mods(mod_state);
        }
        else if (mod_state & MOD_MASK_CTRL) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("1"))));
            set_mods(mod_state);
        }
        else
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("7"))));
        right_registered = true;
        return false;
    } else {
        if (right_registered) {
            unregister_code(key_to_keycode_for_default_layer(BOTTOM));
            right_registered = false;
            return false;
        }
    }
    return true;
}
case TOP: {
    static bool top_registered;
    if (record->event.pressed) {
        if (mod_state & MOD_MASK_GUI) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("["))));
            set_mods(mod_state);
        }
        else if (mod_state & MOD_MASK_CTRL) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("3"))));
            set_mods(mod_state);
        }
        else
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("0"))));
        top_registered = true;
        return false;
    } else {
        if (top_registered) {
            unregister_code(key_to_keycode_for_default_layer(TOP));
            top_registered = false;
            return false;
        }
    }
    return true;
}
case BOTTOM: {
    static bool bottom_registered;
    if (record->event.pressed) {
        if (mod_state & MOD_MASK_GUI) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("]"))));
            set_mods(mod_state);
        }
        else if (mod_state & MOD_MASK_CTRL) {
            clear_mods();
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("2"))));
            set_mods(mod_state);
        }
        else
            SEND_STRING(SS_LGUI(SS_LCTL(SS_LALT("9"))));
        bottom_registered = true;
        return false;
    } else {
        if (bottom_registered) {
            unregister_code(key_to_keycode_for_default_layer(TOP));
            bottom_registered = false;
            return false;
        }
    }
    return true;
}

If we look at the BOTTOM case, one can immediatly see how this works. If  we press MOD_MASK_GUI (this is Cmd on mac), we ‘unpress’ it first. We then send cmd+ctrl+alt+]. This invokes our VS code command for Move editor to editor group below command.  The other mod states invoke separate vscode shortcuts. There is a bit of house keeping in the last else, that handles things when we release the key. There is some horrible duplication in this code, and that duplicated code uses up precious bytes of eeprom storage.

Conclusion

QMK paired with VSCode shortcuts with a smart multi-mode key assignment makes working without mouse and keyboard only much more pleasant.

 

Leave a Reply