If you are here just for the destination without the journey, I created a plugin, that you can find here↗.
If you are like me, you like efficiency, and when it comes to text editors Vim is one of the most efficient there is. Why? Vim motions. Vim makes it stupidly easy to move around once you get used to it. You move left, down, right and up via hjkl, you can jump a word forward using w, backward with b and a lot more but that’s not what this post is about.
Despite this supposed greatness of vim, I used VSCode for several years before switching, because one thing was holding me back - Colemak MOD-DH, an alternative English layout, that tries to solve the inefficiencies of QWERTY. Sadly it is not as popular and most software, vim included, does not expect you to be using it.
Just imagine trying to move via hjkl while using the above layout, not ideal. My initial solution was to just remap the most important keys and learn to live with the rest being a bit messed up, remapping things as I go whenever I deemed it necessary.
But then I found the langmap
option:
This option allows switching your keyboard into a special language
mode. When you are typing text in Insert mode the characters are
inserted directly. When in Normal mode the 'langmap' option takes
care of translating these special characters to the original meaning
of the key.
Which sounds exactly like what I wanted, so I removed my previous mappings,
set the langmap
option in my config and was happy for maybe like 10 minutes
before trying to use a mapping containing CTRL. Turns out langmap
does not work
with alt or control, on top of that plugins don’t expect you to use this feature,
that maybe like three people know about, therefore mappings set by them sometimes
stop working and sometimes do completely different things.
I was so close to greatness and I didn’t intend to give up. The only thing left to do was remap all of vim’s default mappings, all of them.
Typing all the mappings by hand was out of question and having a huge for loop run every time vim started also didn’t feel right, thus I settled on generating the mappings with python and then writing them into a .vim file.
We define a dictionary containing all the keys, necessary to translate between Colemak MOD-DH and QWERTY, plus a small helper function, so we can also translate uppercase characters.
|
|
Different modes have different mappings and the mappings sometimes get more
complex than just “j
goes down”. I created another dictionary with keys being
modes and elements lists of tuples containing mapping and a list of characters
to apply it to. For example ("<C-W>g{}", ["f", "F", "t", "T"])
will format the
string "<C-W>g{}"
with f, F, t and T, resulting in:
As a convenience, if we omit the tuple and just have a string, the string will
get formatted for all the characters, e.g. "<C-W>{}"
remaps several of the
window commands.
For more complex mappings we can use a lambda like this:
After around 45 lines of mappings we can run the following:
|
|
gen_mappings
checks if lhs
is a string or a lambda, loops through the set we
supplied and then passes the formatted mappings to this function:
|
|
To avoid clashing mappings, mappings that take too long to resolve because of
timeout
and similar issues, we check if we already mapped the rhs
and if we
didn’t, we map it to '<Nop>'
, disabling it.
In the big dictionary of mappings there are some, that break things, most
notably remapping "<C-{}>"
for insert mode turns the Enter key into Backspace
because of how terminal emulators handle input, the i
key, that’s remapped to
l
will wait for timeout in visual mode and some more shenanigans. To fix this
we simply append few more lines to the file before writing it.
The final result is a .vim file with around 800 lines of noremap
commands. To
use this in our config we can do call colemak_dh#setup()
or
vim.fn['colemak_dh#setup']()
if using Lua.
I also tried doing this in pure Lua, but I did some quick non-scientific
benchmarks and sourcing an empty Lua file takes around 3.5ms on my machine, while
sourcing the 800 noremap
calls takes 1ms.