Format qmk and zmk keymaps in neovim
MIT License
qmk.nvim is a 100% lua plugin for Neovim that formats QMK and ZMK keymaps, used in a large number of mechanical and hobbyist keyboards.
_
if they are to be identified as the start of a key, e.g#define _AS(keycode) &as LS(keycode) keycode
// ...etc
default_layer {
&kp TAB _AS(Q) SOME_OTHER
}
For a simple example of the following keymap
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_QWERTY] = LAYOUT_preonic_grid(
KC_1 , K2,
K3 , K4, // notice the white space
KC_5 , HERE_BE_A_LONG_KEY // and how this doesn't line up at all
)
};
Setup your layout:
local qmk = require 'qmk'
qmk.setup {
name = 'LAYOUT_preonic_grid', -- identify your layout name
comment_preview = {
keymap_overrides = {
HERE_BE_A_LONG_KEY = 'Magic', -- replace any long key codes
},
},
layout = { -- create a visual representation of your final layout
'x ^xx', -- including keys that span multple rows (with alignment left, center or right)
'_ x x', -- pad empty cells
'_ x x',
},
}
Save the file and it will automaticlly be nicely aligned, with a pretty comment string
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
// ┌───┬────────────┐
// │ 1 │ K2 │
// └───┼────┬───────┤
// │ K3 │ K4 │
// ├────┼───────┤
// │ 5 │ Magic │
// └────┴───────┘
[_QWERTY] = LAYOUT_preonic_grid(
KC_1 , K2 ,
K3 , K4 ,
KC_5 , HERE_BE_A_LONG_KEY
)
};
c
parser available (e.g through nvim-treesitter)devicetree
parser available (e.g through nvim-treesitter)
set ft=dts
setup
with your layout name and your layout configuratione.g:
use {
'codethread/qmk.nvim',
config = function()
---@type qmk.UserConfig
local conf = {
name = 'LAYOUT_preonic_grid',
layout = {
'_ x x x x x x _ x x x x x x',
'_ x x x x x x _ x x x x x x',
'_ x x x x x x _ x x x x x x',
'_ x x x x x x _ x x x x x x',
'_ x x x x x x _ x x x x x x',
}
}
require('qmk').setup(conf)
end
}
qmk.nvim takes the following configuration (---@type qmk.UserConfig
):
setting | type | descritpion |
---|---|---|
name |
string required
|
the name of your layout, for example LAYOUT_preonic_grid for the preonic keyboard, for zmk this can just be anything, it won't be used |
layout |
string[] required
|
the keyboard key layout, see Layout for more details |
variant |
qmk ,zmk
|
(default qmk ) chooses the expected hardware target |
timeout |
number |
(default 5000 ) duration of vim.notify timeout if using nvim-notify
|
auto_format_pattern |
string |
(default *keymap.c ) the autocommand file pattern to use when applying QMKFormat on save |
comment_preview |
table |
table of properties for rendering a pretty comment string of each keymap |
comment_preview.position |
top ,bottom ,inside , none
|
(default top ) control the position of the preview, set to none to disable (inside is only valid for variant=qmk ) |
comment_preview.keymap_overrides |
table<string, string> |
a dictionary of key codes to text replacements, any provided value will be merged with the existing dictionary, see key_map.lua for details |
comment_preview.symbols |
table<string, string> |
a dictionary of symbols used for the preview comment border chars see default.lua for details |
Here are some example configurations:
{
name = 'Some_layout',
layout = { { 'x', 'x' } },
auto_format_pattern = nil,
comment_preview = {
position = 'none'
}
}
setup
can be called multiple times without issue to apply different configuration options. This means you can use autocommands to apply different configurations for different boards, e.g:
local group = vim.api.nvim_create_augroup('MyQMK', {})
vim.api.nvim_create_autocmd('BufEnter', {
desc = 'Format simple keymap',
group = group,
pattern = '*simple/keymap.c', -- this is a pattern to match the filepath of whatever board you wish to target
callback = function()
require('qmk').setup({
name = 'LAYOUT_preonic_grid',
auto_format_pattern = "*simple/keymap.c",
layout = {
'_ x x x x x x _ x x x x x x',
},
})
end,
})
vim.api.nvim_create_autocmd('BufEnter', {
desc = 'Format overlap keymap',
group = group,
pattern = '*overlap/keymap.c',
callback = function()
require('qmk').setup({
name = 'LAYOUT_preonic_grid',
auto_format_pattern = "*overlap/keymap.c",
layout = {
'x x x x x',
},
})
end,
})
{
name = 'meh',
layout = { { 'x', 'x' } },
variant = 'zmk'
}
For the configuration
{
name = 'Some_layout',
layout = { 'x x' },
comment_preview = {
position = 'inside',
keymap_overrides = {
-- key codes are mapped literally against the entire key in your layout
-- longer key codes are checked first, and these will replace the value displayed in the preview
--
-- lua magic patterns must be escaped with `%`, sorry, I'll fix this one day
-- watch out for emojis as they are double width
['LSG%(KC_GRAVE%)'] = 'Next Window',
},
},
}
With keymap.c:
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_QWERTY] = Some_layout(
KC_1
,
LSG(KC_GRAVE)
)
}
Becomes:
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_QWERTY] = Some_layout(
// ┌───┬─────────────┐
// │ 1 │ Next Window │
// └───┴─────────────┘
KC_1 , LSG(KC_GRAVE)
)
}
Also if your key codes are quite long, you can define aliases in c
//Aliases for longer keycodes
#define NUMPAD TG(_NUMPAD)
for the configuration
{
name = 'LAYOUT_pretty',
layout = {
'x x x x x x x x x x x x x x x x x x',
'x x x x x x _ _ _ _ _ _ x x x x x x',
'x x x x x x _ _ _ _ _ _ x x x x x x',
'x x x x x x _ _ _ _ _ _ x x x x x x',
'x x x x x x _ _ _ _ _ _ x x x x x x',
'_ x x x x _ _ _ _ _ _ _ _ x x x x _',
'_ _ _ _ _ x x _ _ _ _ x x _ _ _ _ _',
'_ _ _ _ _ _ x _ _ _ _ x _ _ _ _ _ _',
'_ _ _ _ x x x _ _ _ _ x x x _ _ _ _',
},
}
Will format to something like:
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
// ┌──────┬────┬─────┬──────┬──────┬──────┬──────┬────┬────┬────┬─────┬──────┬──────┬──────┬──────┬──────┬────┬─────────┐
// │ esc │ f1 │ f2 │ f3 │ f4 │ f5 │ f6 │ f7 │ f8 │ f9 │ f10 │ f11 │ f12 │ pscr │ slck │ paus │ no │ QK_BOOT │
// ├──────┼────┼─────┼──────┼──────┼──────┼──────┴────┴────┴────┴─────┴──────┼──────┼──────┼──────┼──────┼────┼─────────┤
// │ = │ 1 │ 2 │ 3 │ 4 │ 5 │ │ 6 │ 7 │ 8 │ 9 │ 0 │ - │
// ├──────┼────┼─────┼──────┼──────┼──────┤ ├──────┼──────┼──────┼──────┼────┼─────────┤
// │ tab │ q │ w │ e │ r │ t │ │ y │ u │ i │ o │ p │ '\' │
// ├──────┼────┼─────┼──────┼──────┼──────┤ ├──────┼──────┼──────┼──────┼────┼─────────┤
// │ caps │ a │ s │ d │ f │ g │ │ h │ j │ k │ l │ ; │ "'" │
// ├──────┼────┼─────┼──────┼──────┼──────┤ ├──────┼──────┼──────┼──────┼────┼─────────┤
// │ lsft │ z │ x │ c │ v │ b │ │ n │ m │ , │ . │ / │ rsft │
// └──────┼────┼─────┼──────┼──────┼──────┘ └──────┼──────┼──────┼──────┼────┼─────────┘
// │ ` │ ins │ left │ rght │ │ up │ down │ [ │ ] │
// └────┴─────┴──────┴──────┼──────┬──────┐ ┌──────┬──────┼──────┴──────┴──────┴────┘
// │ lctl │ lalt │ │ rgui │ rctl │
// └──────┼──────┤ ├──────┼──────┘
// │ home │ │ pgup │
// ┌──────┬──────┼──────┤ ├──────┼──────┬──────┐
// │ bspc │ del │ end │ │ pgdn │ ent │ spc │
// └──────┴──────┴──────┘ └──────┴──────┴──────┘
[QWERTY] = LAYOUT_pretty(
KC_ESC , KC_F1 , KC_F2 , KC_F3 , KC_F4 , KC_F5 , KC_F6 , KC_F7 , KC_F8 , KC_F9 , KC_F10 , KC_F11 , KC_F12 , KC_PSCR , KC_SLCK , KC_PAUS , KC_NO , QK_BOOT,
KC_EQL , KC_1 , KC_2 , KC_3 , KC_4 , KC_5 , KC_6 , KC_7 , KC_8 , KC_9 , KC_0 , KC_MINS,
KC_TAB , KC_Q , KC_W , KC_E , KC_R , KC_T , KC_Y , KC_U , KC_I , KC_O , KC_P , KC_BSLS,
KC_CAPS , KC_A , KC_S , KC_D , KC_F , KC_G , KC_H , KC_J , KC_K , KC_L , KC_SCLN , KC_QUOT,
KC_LSFT , KC_Z , KC_X , KC_C , KC_V , KC_B , KC_N , KC_M , KC_COMM , KC_DOT , KC_SLSH , KC_RSFT,
KC_GRV , KC_INS , KC_LEFT , KC_RGHT , KC_UP , KC_DOWN , KC_LBRC , KC_RBRC ,
KC_LCTL , KC_LALT , KC_RGUI , KC_RCTL ,
KC_HOME , KC_PGUP ,
KC_BSPC , KC_DEL , KC_END , KC_PGDN , KC_ENTER , KC_SPC
)
};
A comment block can be added at the bottom of your config file (for both QMK and ZMK). Inline configs will be reparsed on every call to QMK functions, and then merged with your main config.
It must
qmk:json:start
and end with qmk:json:end
e.g.:
// clang-format off
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_QWERTY] = MY_LAYOUT(
KC_1 , KC_2 , KC_3 , BAR , KC_5
),
[_SOMETHING] = MY_LAYOUT(
KC_1 , KC_2 , FOO , KC_4 , KC_5
)
};
/*
qmk:json:start
{
"layout": [
"x x x _",
"x _ ^xx"
],
"comment_preview": {
"keymap_overrides": {
"BAR": "BUTTER",
"FOO": "😎"
}
}
}
qmk:json:end
*/
The layout
config describes your layout as expected by qmk_firmware. As qmk_firmware is simply expecting an array of key codes, the layout is pretty much up to you.
A layout
is a list of strings, where each string in the list represents a single row. Rows must all be the same width, and you'll see they visually align to what your keymap looks like.
Valid keys are
x
: indicates presence of key
: space used to separate keys (must be used, and only use single spaces)_
: indicates an empty space (e.g to split left and right, or adding padding)x^x
: a key spanning multiple slots on the keyboard, the ^
indicates alignment. (NOTE vertically sized keys, like some thumb clusters are net yet supported)
^xx
: left align across two columnsx^x
: center alignxx^
: right alignxx^xx
: center align but across three columnsthere is also a test file with a great many examples
Config:
{ layout = {
'x x',
'x x'
} }
Output:
[1] = Layout(
KC_A , KC_B,
KC_C , KC_D
)
Config:
{ layout = {
'x',
'x',
'x',
'x'
} }
Output:
[1] = Layout(
KC_A ,
KC_B ,
KC_C ,
KC_D
)
Config:
{ layout = {
'x x',
'x^x',
} }
Output:
[1] = Layout(
KC_A , KC_B,
KC_C
)
Config:
{ layout = {
'x _ x', -- we need the '_' to pad out the gaps
'xx^xx', -- this spans 3 columns, but we could keep going to 5,7,9 etc
} }
Output:
[1] = Layout(
KC_A , KC_B,
KC_C
)
QMKFormat
: format the current buffer:lua require('qmk').setup( <config> )
: setup qmk using your config (must be called before format, can be called repeatedly):lua require('qmk').format( <buf id ?> )
: format a given buffer, or the current if is not providedBufWritePre
autocmd will format buffers on save matching config.auto_format_pattern
(defaults to *keymap.c
, set to nil
to disable)Getting your layout right may be a slightly iterative process, so I recommend the following:
keymap.c
filekeymap.c
file with :lua print(vim.api.nvim_get_current_buf())
qmk.setup { .. your config }
and qmk.format(<buf id>)
luafile %
e.g:
-- scratch buffer (:enew)
local qmk = require('qmk')
qmk.setup({
name = 'Some_layout',
layout = { 'x x' },
comment_preview = {
position = 'inside'
},
})
qmk.format(43) -- the result of calling :lua print(vim.api.nvim_get_current_buf()) in my keymap.c file
I have tried to create useful errors when something is wrong with the config, layout or your current keymap, but please raise an issue if something isn't clear (or you get an QMK: E00
, as that's definitely on me).