This post is a part of my "Zed is the Future?" series
If your are a keyboard-heavy user, one of the first things you start tweaking in a text editor are keyboard shortcuts.
Coming from Neovim, I came to expect full programmability of my key mappings. However, Zed takes a different approach that steers more towards the zero-configuration philosophy that has become popular lately. Zed's keymap is configured through a collection of JSON files with the list of bindings and contexts where they are active:
[
{
"context": "Dock || Terminal || VimControl"
"bindings": {
"ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
},
},
{
"context": "Editor && VimControl && !VimWaiting && !menu"
"bindings": {
", q": "pane::CloseActiveItem",
"n": "search::SelectNextMatch",
"shift-n": "search::SelectPrevMatch"
},
}
]
The JSON-based approach offers several advantages:
- Auto-completion and validation tools can provide instant feedback while editing the config
- Keybindings are easily shareable as simple JSON snippets
- Finding specific mappings is straightforward with basic text search
- Future format migrations can be handled smoothly with automated tooling
However, this comes with several drawbacks:
- The JSON format can be verbose and repetitive, making keybindings more cumbersome to define
- As everything is defined in a single file with little organization, it becomes hard to explore and maintain the mappings
- Customizing default bindings requires copying the existing ones, as there's no native way to extend them systematically
Alternatives
I've been wondering if there are alternatives that would be a middle-ground between the full Turing-complete configuration and the plain JSON files.
So I've built a tool to generate the keymap from a more structured and concise definition written in Jsonnet configuration language.
Here is the excerpt from my keymap:
local lib = import 'keymap.lib.jsonnet';
local ctx = lib.ctx;
local map = lib.map;
local leader = 'space';
local local_leader = ',';
std.flattenArrays([
// You can split the keymap into multiple files.
(import 'window.lib.jsonnet')(prefix='space w'),
// Definitions can take parameters for customization.
// For example, I copied default Zed Vim 'g' bindings and
// added ability to replace prefix `g` with any other key.
(import 'actions.lib.jsonnet')(prefix='g'),
[
// Commonly used contexts can be defined in a single place
// as the library functions.
ctx.hub({
'ctrl-h': ['workspace::ActivatePaneInDirection', 'Left'],
'ctrl-l': ['workspace::ActivatePaneInDirection', 'Right'],
'ctrl-k': ['workspace::ActivatePaneInDirection', 'Up'],
'ctrl-j': ['workspace::ActivatePaneInDirection', 'Down'],
}),
ctx.vim_normal({
n: 'search::SelectNextMatch',
'shift-n': 'search::SelectPrevMatch',
[local_leader + ' q']: 'pane::CloseActiveItem',
}),
// When multiple bindings start with the same key,
// they can be grouped together with `map.hydra`.
ctx.vim_normal(map.hydra(leader, {
'/': 'workspace::NewSearch',
',': 'tab_switcher::Toggle',
})),
],
// You can still use the standard keymap definition syntax.
{
context: 'Workspace',
bindings: {
'shift-ctrl-r': ['task::Spawn', { task_name: 'Update keymap' }],
},
},
])
This config can be fed into jsonnet
tool to generate your familiar keymap.json
:
jsonnet keymap.jsonnet -o keymap.json
I'm still exploring the ergonomics of this approach, but it does address my configuration needs for now. The tooling for using it still has a few rough edges though - you need to manually invoke the keymap update after you change the Jsonnet file. Also, error reporting could be improved to provide more context when something goes wrong.
I plan to polish it further and share it with the community to gather some feedback.