Zed: Keymap

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.

Zed: Navigation

This post is a part of my "Zed is the Future?" series


Despite its name, I would argue that most time in a text editor is actually not spent editing. Instead, it is spent navigating between and within files that you are working with. Let's explore how to efficiently navigate with Zed!

As a quick aside, I intend this post to be a living lab notebook that I use to share my progress on the topic.

Setup

I will mostly focus on the keyboard-centric navigation, as this is my preferred setup. I use Zed Vim mode and shortcuts below rely on it (by the way, you can find all available shortcuts in Vim mode by opening a Command Palette and typing vim: open default keymap).

To add a bit of structure, I will distinguish between two different types of navigation:

  • Local navigation: Moving and jumping within a single buffer to specific lines, words or characters
  • Global navigation: Moving between different files and searching across an entire project workspace

Local navigation

Vim motions

Local navigation is a bread and butter of Vim Motions - the key shortcuts in Vim Normal mode that move the cursor to specific locations in the text.

I won't describe them in detail, but there is an excellent guided tour and a cheatsheet that lists the most useful ones. Suffice to say that Zed implements them quite faithfully. There are also motions specific to Zed Treesitter integration that are useful for navigating structured file formats, e.g., code or JSON.

Here are the ones that I find most useful:

  • w/b: move to the beginning of the next/previous word
  • CTRL + g: move to a specific line number
  • /, then n/N: search for a specific text in the buffer and cycle through the results forwards/backwards
  • gg/G: move to the beginning/end of the file

When I'm using Vim, I feel there is a gap that needs to be filled between very efficient navigation within a single line with f motion and a bit more clumsy general search with /. This gap is usually filled for me by EasyMotion-style movement (specifically flash.nvim) that allows to move to any visible character with just a few letter keypresses. Unfortunately, Zed does not have a built-in equivalent yet, but this is a very popular feature request.

Symbol outline

Given the native Treesitter and LSP integration, symbol navigation feels quite reliable.

  • CTRL + SHIFT + o (or g s in Vim): list and search symbols in the buffer
  • CTRL + SHIFT + b: open and focus the symbol outline panel
  • CTRL + SHIFT + f: opens a workspace-wide text search panel where you can type any text to find its occurrences across all files
  • g /: automatically searches for the currently selected text across all files in the workspace
  • CTRL + p: opens a fuzzy search dialog that lets you quickly find and open any file by name in the workspace
  • g d: go to definition of the symbol under cursor (e.g. a function name or variable)
  • g S: search for any symbol (functions, classes, etc.) across all files in the workspace