Automatically add kbd-tags to Your Posts with a Single Regex
Tags: writing, productivity, blogging, vscode
Published: 2020-12-05 10:00:17

# Introduction

Until about a year ago, I had the same approach to regular expressions as most developers: Doing trial and error until the damn thing was working.

Then came [KeyCombiner](https://keycombiner.com) and with it some very specific challenges around parsing text that contains keyboard shortcuts. One of these challenges is writing blog posts about KeyCombiner:
- [A Collection of all Keyboard Shortcuts I use](https://tkainrad.dev/posts/a-collection-of-all-keyboard-shortcuts-i-use/)
- [Learning all VSCode shortcuts evolved my developing habits](https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/)
- [How I learned 50 new keyboard shortcuts in 42 minutes](https://tkainrad.dev/posts/how-i-learned-50-new-keyboard-shortcuts-in-42-minutes/)

These posts contain *a lot* of keyboard shortcuts that should be highlighted via \<kbd\> tags. By now, these tags are supported by most static site generators and blogging platforms, such as DEV.to. However, typing them in manually is extremely tedious. Initially, I felt quite smart when I added a user snippet in VSCode:

```json
"kbd": {
    "scope": "markdown",
    "prefix": "kbd",
    "body": [
        "<kbd>$0</kbd>"
    ],
    "description": "Insert HTML kbd tags"
},
```

This lets me type `kbd`, press <kbd>ctrl</kbd>+<kbd>space</kbd> and hit <kbd>enter</kbd> to put my cursor in-between an opening and a closing `<kbd>` tag. However, it does not help with copy-pasted text, which is an essential use case for me. I write the first draft of my posts in Notion, as described in [my post about personal knowledge management](https://tkainrad.dev/posts/managing-my-personal-knowledge-base/). Even if this were not an issue, it's still quite a lot of keys to hit for just adding two tags. Another solution would be to write a script in some interpreted language and do all kinds of fancy things to this text. The problem is, this script will likely end up like many single-purpose scripts do: Abandoned and buggy.

So, I needed a better, more timeless solution: Regular Expressions.  
Fear not, I won't try to teach you general regex syntax. Instead, I will show how to use it for this particular use case.
I will explain in detail how and why this works, but you can also just copy-paste [the final regex](#final-regex) and apply it for yourself. One of the best tools for putting the regex to work is Visual Studio Code, but any editor that supports regex-based replace operations will do.


# How-To

Suppose we have the following text:

> Modifier keys, such as ctrl, shift, alt, and cmd do nothing by themselves but can be used in combination with other keys. To paste without the original formatting, use ctrl+shift+v. Debugging is controlled via f5, f6, f10, and f11. 


What we want to have is this:

> Modifier keys, such as <kbd>ctrl</kbd>, <kbd>shift</kbd>, <kbd>alt</kbd>, and <kbd>cmd</kbd>  do nothing by themselves, but can be used in combination with other keys. To paste without the original formatting, use <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>v</kbd>. Debugging is controlled via <kbd>f5</kbd>, <kbd>f6</kbd>, <kbd>f10</kbd>, and <kbd>f11</kbd>. 


The next sections will describe how we get there.

## Modifiers and F-keys

We can already go pretty far with this regex that uses or operators (`|`) to match a predefined set of strings:

```
(f1|f2|f3|f4|f5|f6|f7|f8|f9|f10|f11|f12|backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta)
```

Putting the expression in brackets `()` means that it is a capture group. Capture groups are an essential concept when working with regular expressions. One of their greatest features is that we get to reference them by number in the replacement string:

```html
<kbd>$1</kbd>
```

This means we will replace a match of the first capture group with itself (`$1`) surround by the desired tags.

The above text will become:

> Modifier keys, such as <kbd>ctrl</kbd>, <kbd>shift</kbd>, <kbd>alt</kbd>, and <kbd>cmd</kbd>  do nothing by themselves, but can be used in combination with other keys. To paste without the original formatting, use <kbd>ctrl</kbd>+<kbd>shift</kbd>+v. Debugging is controlled via <kbd>f5</kbd>, <kbd>f6</kbd>, <kbd>f10</kbd>, and <kbd>f11</kbd>. 


This is a good start, but we are not quite there yet. Non-modifier keys that are part of key combinations do not yet have the proper tags. We will deal with them in the next section. But before, we can shorten our expression a bit, by switching out the f-keys with a single sub-expression: `f([2-9]|1[0-2]?)`. This will match any `f`-character followed by either `2-9` or a sequence of `1` and `0-2`. It is equal to typing out the f-keys, but a little more fun. So we end up with this expression:


```
(f([2-9]|1[0-2]?)|backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta)
```


## Regular keys in combinations

Matching regular keys is a bit more tricky. We cannot just match any `v` characters in our text. One approach is to match any single alphanumeric character that is preceded by a `+`-sign. To do this, we need to use something called *Lookarounds*, ore more specifically, a *Lookbehind*: `(?<=\+)`
The nice thing about lookarounds is that they do not increase the matched character sequence. Instead, they specify what must (or must not) come before or after the matched part, which is very useful when applying replacement operations. Adding this to our expression results in the following:


```brackets
((?<=\+)[\w\d]{1}|f([2-9]|1[0-2]?)|backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta)
```

Using this expression, we end up with the desired text already:

> Modifier keys, such as <kbd>ctrl</kbd>, <kbd>shift</kbd>, <kbd>alt</kbd>, and <kbd>cmd</kbd>  do nothing by themselves, but can be used in combination with other keys. To paste without the original formatting, use <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>v</kbd>. Debugging is controlled via <kbd>f5</kbd>, <kbd>f6</kbd>, <kbd>f10</kbd>, and <kbd>f11</kbd>. 

However, we shouldn't celebrate too early. There are still some issues and special cases to take care of:

1) What happens if a modifier appears within another word, such as *ins* in _**ins**ert_?
2) What happens if we apply this to text that already has some `<kbd>` tags?

The answer to both questions is clear: Terrible things will happen. We will have to go a bit deeper.


## Avoiding False Positives

Now that we already know the *Lookaround* concept, this is relatively easy. We will ensure that the expression only matches occurrences surrounded by one of these characters: `[\+ .,\n]`.

```
(?<=[\+ .,\n])(backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta|f([2-9]|1[0-2]?)|(?<=\+)[\w\d]{1})(?=[\+ .,\n])
```


## Achieving Idempotence

An idempotent operation is one that can be applied more than once without changing the outcome. To achieve idempotence, we will make use of another advantage of our beloved lookarounds: They can be stacked one after another. All we have to do is make sure that our matches are not already preceded by a `<kbd>` tag, using a negative *Lookbehind*:

```
(?<!<kbd>)(?<=[\+ .,\n])(backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta|f([2-9]|1[0-2]?)|(?<=\+)[\w\d]{1})(?=[\+ .,\n])
```

We could add a negative *Lookahead* for the closing tag, too, but it is not needed.

## Final regex

That's it. We have a regular expression that we can use in VSCode and other editors to automatically surround all occurences of keys with `<kbd>` tags. To use it, search for this expression with the *Use Regular Expression* (<kbd>alt</kbd>+<kbd>r</kbd>)-toggle enabled:

```
(?<!<kbd>)(?<=[\+ .,\n])(backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta|f([2-9]|1[0-2]?)|(?<=\+)[\w\d]{1})(?=[\+ .,\n])
```

Then, replace all occurrences  string:

```html
<kbd>$1</kbd>
```

If you want to go one step further, you could even apply this in a build pipeline, e.g., after pushing to GitHub Pages or GitLab Pages. Personally, I like to do it myself in VSCode. These are very satisfying 30 seconds of work before publishing a new blog post.


# Conclusion

Hopefully, I could help a bit to improve the bad reputation of regular expressions. But I will let you in on a little secret. Just like everyone else, I have a hard time remembering the syntax.
The good thing is, I have a superpower to overcome this: KeyCombiner's instant shortcut lookup. Whenever I press <kbd>super</kbd>+<kbd>c</kbd>, I have instant access to the shortcuts of the active application, all other shortcuts that I like to use, and even regex syntax:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/i/r70tc7ozkzu36s0nhhav.gif" alt="Instant regex lookup with KeyCombiner's free to use desktop app.">
  <figcaption>Instant shortcut lookup of KeyCombiner's free to use [desktop app](https://keycombiner.com/desktop/).</figcaption>
</figure>