In Zed 0.162 we're shipping a number of changes to keyboard shortcuts to make them work for everyone, regardless of their keyboard layout.
The Problems
Which characters you can type easily depends on your keyboard layout. Zed's default bindings are built with the assumption that you can easily type all the characters between U+0033 (!) and U+007A (~) – the ASCII graphical range – using only the shift modifier.
This turns out to be untrue for three groups of people:
- You use a Cyrillic (or other non-Latin alphabet) keyboard that doesn't provide a-z or A-Z by default.
- You use a Japanese (or other IME-based) keyboard that doesn't type anything by default.
- You use a non-US Latin alphabet (like French, German, etc.) that doesn't provide some of the ASCII punctuation because the keys are needed for letters like ü or ß.
There was also an additional (completely self-inflected) problem which was that we bound some keyboard shortcuts to option-X
(where X is any ASCII character). This was generally fine for US English speakers who rarely used the characters that the option modifier generates, but prevented people from typing essential characters like {
or @
on some keyboards.
The Solutions
The last problem is the easiest to solve. We no longer bind shortcuts to option-X
(where X is a letter). Instead we require either command or control for alphabetic shortcuts. (see here for a list of changes).
Beyond that, we wanted Zed to feel natural for people familiar with macOS. So we've made a few changes that more closely mimic the way things work by default at an operating system level.
For IME-based input sources, we now trigger shortcuts before invoking the IME system. So, if you have a Japanese input mode enabled and type cmd-a
it will select all. If the IME system is currently active, then it gets first dibs on keyboard input. This means that you can use a
in Vim normal mode to insert; but if you're in the middle of typing something based on あ then we won't get in the way.
For non-Latin keyboards, macOS provides a Latin-based layout that is activated when the command key is held down. This layout is usually just QWERTY, so you can type cmd-ա
on an Armenian keyboard and Zed will receive cmd-a
. We take this one step further however, and use this layout for shortcuts even if command is not held down. This ensures that shortcuts bound to ctrl-a
(or in Vim mode, even just a
) will still work.
For non-US Latin keyboards, the solution is more fiddly. Although you can type the full ASCII range on these keyboards, you likely need to hold option to access some characters. Unfortunately option is meaningful in a shortcut: if you need to hold option to type [
, we can't tell if you meant to trigger cmd-[
or cmd-option-[
.
Key Equivalents
Since macOS Catalina, Apple has shipped a solution to this problem in the form of automatic shortcut localization. Under the hood this reassigns some shortcuts to key equivalents that can be typed without option. Although this happens automatically if you use SwiftUI's builtin menu components, Zed requires much tighter control over the shortcuts we use.
The way that key equivalents is assigned seems to be designed with a few constraints in mind:
- If a shortcut can be typed, it shouldn't be changed. So
cmd-+
stayscmd-+
on a QWERTZ keyboard, even though the + key is in a different place on the keyboard. - If shortcuts come in pairs, they stay in pairs. So
cmd-[
andcmd-]
becomecmd-ö
andcmd-ä
on a QWERTZ keyboard. - Otherwise, if possible, shortcuts stay on the same key as QWERTY. For example
cmd-<
andcmd->
, which cannot be used because they are the system window switching shortcuts, move tocmd-;
andcmd-:
on a QWERTZ keyboard.
Unfortunately Apple doesn't seem to publish the tables of key equivalents, and it's not clear how to programmatically extract them. So we wrote a small Swift app to find such keyboard layouts, render a SwiftUI menu containing every shortcut, and inspected the menu to see what the shortcuts had been mapped to. The resulting rules are available here.
Dead Keys
The final hold-up was handling dead keys. For example on an AZERTY keyboard, ^
is a dead key - it doesn't produce a character directly, but puts the IME into a mode where the next character is different. Typing ^ a
might input â for example. To allow binding to cmd-^
on such keyboards, we use the same approach as Chrome, and use two calls to UCKeyTranslate
- equivalent to typing ^ space
to get the effective character of the key.
Dead-keys cause one other problem, which is a small incompatibility with Vim's keyboard handling. Vim does not look at keys typed, only at characters generated. This means that to delete within quotes in Vim (typically d i "
on a QWERTY keyboard) on a Brazilian keyboard, you need to type d i " space
. The " puts you into the dead key mode, and space generates "
. We used to emulate this, but with the new code d i "
on a Brazilian keyboard triggers the shortcut before space is pressed.
Although some users have complained about the Vim version being confusing, I think we will probably (in a future version) revert to more Vim-like behavior. This is partly because the main premise of Vim mode is to avoid having to relearn how to type, and partly because it introduces an annoying inconsistency. If you are going to delete to the next quote, you still need to type d t " space
because the argument to t
is any typed character (whereas the argument to i
is shortcut...).
Next Steps
All of the above applies only to macOS. We are planning similar changes for Linux (and Windows), but the details may differ slightly to keep the app feeling platform native. On Linux, we have a few bugs in our handling of non-Latin keyboards that need to be resolved; and we have a lot to learn about interacting with the various IME subsystems. On Windows we will likely use Virtual Key Codes and not do reassignment for extended Latin layouts.
As the saying goes: PR's welcome! And please do file bugs if there are things that you think should work, but are not currently working.