@@ -1,12 +1,6 @@
[
{
- "context": "ProjectPanel || Editor",
- "bindings": {
- "ctrl-6": "pane::AlternateFile"
- }
- },
- {
- "context": "Editor && VimControl && !VimWaiting && !menu",
+ "context": "VimControl && !menu",
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
@@ -198,20 +192,21 @@
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w space": "editor::OpenExcerptsSplit",
"ctrl-w g space": "editor::OpenExcerptsSplit",
- "-": "pane::RevealInProjectPanel"
+ "-": "pane::RevealInProjectPanel",
+ "ctrl-6": "pane::AlternateFile"
}
},
{
- // escape is in its own section so that it cancels a pending count.
- "context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
+ "context": "VimControl && VimCount",
"bindings": {
- "escape": "editor::Cancel",
- "ctrl-[": "editor::Cancel"
+ "0": ["vim::Number", 0]
}
},
{
- "context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
+ "context": "vim_mode == normal",
"bindings": {
+ "escape": "editor::Cancel",
+ "ctrl-[": "editor::Cancel",
".": "vim::Repeat",
"c": ["vim::PushOperator", "Change"],
"shift-c": "vim::ChangeToEndOfLine",
@@ -255,12 +250,48 @@
"] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",
- "[ c": "editor::GoToPrevHunk"
+ "[ c": "editor::GoToPrevHunk",
+ "g c c": "vim::ToggleComments"
}
},
{
- "context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
+ "context": "vim_mode == visual",
"bindings": {
+ "u": "vim::ConvertToLowerCase",
+ "U": "vim::ConvertToUpperCase",
+ "o": "vim::OtherEnd",
+ "shift-o": "vim::OtherEnd",
+ "d": "vim::VisualDelete",
+ "x": "vim::VisualDelete",
+ "shift-d": "vim::VisualDeleteLine",
+ "shift-x": "vim::VisualDeleteLine",
+ "y": "vim::VisualYank",
+ "shift-y": "vim::VisualYank",
+ "p": "vim::Paste",
+ "shift-p": ["vim::Paste", { "preserveClipboard": true }],
+ "s": "vim::Substitute",
+ "shift-s": "vim::SubstituteLine",
+ "shift-r": "vim::SubstituteLine",
+ "c": "vim::Substitute",
+ "~": "vim::ChangeCase",
+ "*": ["vim::MoveToNext", { "partialWord": true }],
+ "#": ["vim::MoveToPrev", { "partialWord": true }],
+ "ctrl-a": "vim::Increment",
+ "ctrl-x": "vim::Decrement",
+ "g ctrl-a": ["vim::Increment", { "step": true }],
+ "g ctrl-x": ["vim::Decrement", { "step": true }],
+ "shift-i": "vim::InsertBefore",
+ "shift-a": "vim::InsertAfter",
+ "shift-j": "vim::JoinLines",
+ "r": ["vim::PushOperator", "Replace"],
+ "ctrl-c": ["vim::SwitchMode", "Normal"],
+ "escape": ["vim::SwitchMode", "Normal"],
+ "ctrl-[": ["vim::SwitchMode", "Normal"],
+ ">": "vim::Indent",
+ "<": "vim::Outdent",
+ "i": ["vim::PushOperator", { "Object": { "around": false } }],
+ "a": ["vim::PushOperator", { "Object": { "around": true } }],
+ "g c": "vim::ToggleComments",
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
@@ -268,89 +299,52 @@
}
},
{
- "context": "Editor && VimCount && vim_mode != insert",
+ "context": "vim_mode == insert",
"bindings": {
- "0": ["vim::Number", 0]
- }
- },
- {
- "context": "Editor && vim_operator == c",
- "bindings": {
- "c": "vim::CurrentLine",
- "d": "editor::Rename" // zed specific
- }
- },
- {
- "context": "Editor && vim_mode == normal && vim_operator == c",
- "bindings": {
- "s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
- }
- },
- {
- "context": "Editor && vim_operator == d",
- "bindings": {
- "d": "vim::CurrentLine"
- }
- },
- {
- "context": "Editor && vim_operator == gu",
- "bindings": {
- "g u": "vim::CurrentLine",
- "u": "vim::CurrentLine"
- }
- },
- {
- "context": "Editor && vim_operator == gU",
- "bindings": {
- "g shift-u": "vim::CurrentLine",
- "shift-u": "vim::CurrentLine"
- }
- },
- {
- "context": "Editor && vim_operator == g~",
- "bindings": {
- "g ~": "vim::CurrentLine",
- "~": "vim::CurrentLine"
- }
- },
- {
- "context": "Editor && vim_mode == normal && vim_operator == d",
- "bindings": {
- "s": ["vim::PushOperator", "DeleteSurrounds"]
- }
- },
- {
- "context": "Editor && vim_operator == y",
- "bindings": {
- "y": "vim::CurrentLine"
- }
- },
- {
- "context": "Editor && vim_mode == normal && vim_operator == y",
- "bindings": {
- "s": ["vim::PushOperator", { "AddSurrounds": {} }]
+ "escape": "vim::NormalBefore",
+ "ctrl-c": "vim::NormalBefore",
+ "ctrl-[": "vim::NormalBefore",
+ "ctrl-x": null,
+ "ctrl-x ctrl-o": "editor::ShowCompletions",
+ "ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
+ "ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
+ "ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
+ "ctrl-x ctrl-z": "editor::Cancel",
+ "ctrl-w": "editor::DeleteToPreviousWordStart",
+ "ctrl-u": "editor::DeleteToBeginningOfLine",
+ "ctrl-t": "vim::Indent",
+ "ctrl-d": "vim::Outdent",
+ "ctrl-r": ["vim::PushOperator", "Register"]
}
},
{
- "context": "Editor && vim_operator == ys",
+ "context": "vim_mode == replace",
"bindings": {
- "s": "vim::CurrentLine"
+ "escape": "vim::NormalBefore",
+ "ctrl-c": "vim::NormalBefore",
+ "ctrl-[": "vim::NormalBefore",
+ "backspace": "vim::UndoReplace",
+ "tab": "vim::Tab",
+ "enter": "vim::Enter"
}
},
{
- "context": "Editor && vim_operator == >",
+ "context": "vim_mode == waiting",
"bindings": {
- ">": "vim::CurrentLine"
+ "tab": "vim::Tab",
+ "enter": "vim::Enter"
}
},
{
- "context": "Editor && vim_operator == <",
+ "context": "vim_mode == operator",
"bindings": {
- "<": "vim::CurrentLine"
+ "escape": "vim::ClearOperators",
+ "ctrl-c": "vim::ClearOperators",
+ "ctrl-[": "vim::ClearOperators"
}
},
{
- "context": "Editor && VimObject",
+ "context": "vim_operator == a || vim_operator == i || vim_operator == cs",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
@@ -375,100 +369,64 @@
}
},
{
- "context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
+ "context": "vim_operator == c",
"bindings": {
- "u": "vim::ConvertToLowerCase",
- "U": "vim::ConvertToUpperCase",
- "o": "vim::OtherEnd",
- "shift-o": "vim::OtherEnd",
- "d": "vim::VisualDelete",
- "x": "vim::VisualDelete",
- "shift-d": "vim::VisualDeleteLine",
- "shift-x": "vim::VisualDeleteLine",
- "y": "vim::VisualYank",
- "shift-y": "vim::VisualYank",
- "p": "vim::Paste",
- "shift-p": ["vim::Paste", { "preserveClipboard": true }],
- "s": "vim::Substitute",
- "shift-s": "vim::SubstituteLine",
- "shift-r": "vim::SubstituteLine",
- "c": "vim::Substitute",
- "~": "vim::ChangeCase",
- "*": ["vim::MoveToNext", { "partialWord": true }],
- "#": ["vim::MoveToPrev", { "partialWord": true }],
- "ctrl-a": "vim::Increment",
- "ctrl-x": "vim::Decrement",
- "g ctrl-a": ["vim::Increment", { "step": true }],
- "g ctrl-x": ["vim::Decrement", { "step": true }],
- "shift-i": "vim::InsertBefore",
- "shift-a": "vim::InsertAfter",
- "shift-j": "vim::JoinLines",
- "r": ["vim::PushOperator", "Replace"],
- "ctrl-c": ["vim::SwitchMode", "Normal"],
- "escape": ["vim::SwitchMode", "Normal"],
- "ctrl-[": ["vim::SwitchMode", "Normal"],
- ">": "vim::Indent",
- "<": "vim::Outdent",
- "i": ["vim::PushOperator", { "Object": { "around": false } }],
- "a": ["vim::PushOperator", { "Object": { "around": true } }]
+ "c": "vim::CurrentLine",
+ "d": "editor::Rename", // zed specific
+ "s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
}
},
{
- "context": "Editor && vim_mode == normal && !VimWaiting",
+ "context": "vim_operator == d",
"bindings": {
- "g c c": "vim::ToggleComments"
+ "d": "vim::CurrentLine",
+ "s": ["vim::PushOperator", "DeleteSurrounds"]
}
},
{
- "context": "Editor && vim_mode == visual",
+ "context": "vim_operator == gu",
"bindings": {
- "g c": "vim::ToggleComments"
+ "g u": "vim::CurrentLine",
+ "u": "vim::CurrentLine"
}
},
{
- "context": "Editor && vim_mode == insert",
+ "context": "vim_operator == gU",
"bindings": {
- "escape": "vim::NormalBefore",
- "ctrl-c": "vim::NormalBefore",
- "ctrl-[": "vim::NormalBefore",
- "ctrl-x": null,
- "ctrl-x ctrl-o": "editor::ShowCompletions",
- "ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
- "ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
- "ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
- "ctrl-x ctrl-z": "editor::Cancel",
- "ctrl-w": "editor::DeleteToPreviousWordStart",
- "ctrl-u": "editor::DeleteToBeginningOfLine",
- "ctrl-t": "vim::Indent",
- "ctrl-d": "vim::Outdent",
- "ctrl-r": ["vim::PushOperator", "Register"]
+ "g shift-u": "vim::CurrentLine",
+ "shift-u": "vim::CurrentLine"
}
},
{
- "context": "Editor && vim_mode == replace",
+ "context": "vim_operator == g~",
"bindings": {
- "escape": "vim::NormalBefore",
- "ctrl-c": "vim::NormalBefore",
- "ctrl-[": "vim::NormalBefore",
- "tab": "vim::Tab",
- "enter": "vim::Enter",
- "backspace": "vim::UndoReplace"
+ "g ~": "vim::CurrentLine",
+ "~": "vim::CurrentLine"
}
},
{
- "context": "Editor && vim_mode != replace && VimWaiting",
+ "context": "vim_operator == y",
"bindings": {
- "tab": "vim::Tab",
- "enter": "vim::Enter",
- "escape": ["vim::SwitchMode", "Normal"],
- "ctrl-[": ["vim::SwitchMode", "Normal"]
+ "y": "vim::CurrentLine",
+ "s": ["vim::PushOperator", { "AddSurrounds": {} }]
}
},
{
- "context": "Editor && vim_mode == insert && VimWaiting",
+ "context": "vim_operator == ys",
"bindings": {
- "escape": "vim::NormalBefore",
- "ctrl-[": "vim::NormalBefore"
+ "s": "vim::CurrentLine"
+ }
+ },
+ {
+ "context": "vim_operator == >",
+ "bindings": {
+ ">": "vim::CurrentLine"
+ }
+ },
+ {
+ "context": "vim_operator == <",
+ "bindings": {
+ "<": "vim::CurrentLine"
}
},
{
@@ -508,7 +466,8 @@
"x": "project_panel::RevealInFileManager",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
- "-": "project_panel::SelectParent"
+ "-": "project_panel::SelectParent",
+ "ctrl-6": "pane::AlternateFile"
}
},
{
@@ -85,36 +85,22 @@ Finally, Vim mode's search and replace functionality is backed by Zed's. This me
## Custom key bindings
You can edit your personal key bindings with `:keymap`.
-For vim-specific shortcuts, you may find the following template a good place to start:
+For vim-specific shortcuts, you may find the following template a good place to start.
+
+> **Note:** We made some breaking changes in Zed version `0.145.0`. For older versions, see [the previous version of this document](https://github.com/zed-industries/zed/blob/c67aeaa9c58619a58708722ac7d7a78c75c29336/docs/src/vim.md#L90).
```json
[
{
- "context": "Editor && (vim_mode == normal || vim_mode == visual) && !VimWaiting && !menu",
+ "context": "VimControl && !menu",
"bindings": {
// put key-bindings here if you want them to work in normal & visual mode
}
},
{
- "context": "Editor && vim_mode == normal && !VimWaiting && !menu",
- "bindings": {
- // put key-bindings here if you want them to work only in normal mode
- // "down": ["workspace::SendKeystrokes", "4 j"]
- // "up": ["workspace::SendKeystrokes", "4 k"]
- }
- },
- {
- "context": "Editor && vim_mode == visual && !VimWaiting && !menu",
+ "context": "vim_mode == insert",
"bindings": {
- // visual, visual line & visual block modes
- }
- },
- {
- "context": "Editor && vim_mode == insert && !menu",
- "bindings": {
- // put key-bindings here if you want them to work in insert mode
- // e.g.
- // "j j": "vim::NormalBefore" // remap jj in insert mode to escape.
+ // "j k": "vim::NormalBefore" // remap jk in insert mode to escape.
}
},
{
@@ -122,7 +108,6 @@ For vim-specific shortcuts, you may find the following template a good place to
"bindings": {
// put key-bindings here (in addition to above) if you want them to
// work when no editor exists
- // e.g.
// "space f": "file_finder::Toggle"
}
}
@@ -133,20 +118,15 @@ If you would like to emulate vim's `map` (`nmap` etc.) commands you can bind to
You can see the bindings that are enabled by default in vim mode [here](https://github.com/zed-industries/zed/blob/main/assets/keymaps/vim.json).
-The details of the context are a little out of scope for this doc, but suffice to say that `menu` is true when a menu is open (e.g. the completions menu), `VimWaiting` is true after you type `f` or `t` when weβre waiting for a new key (and you probably donβt want bindings to happen). Please reach out on [GitHub](https://github.com/zed-industries/zed) if you want help making a key bindings work.
+#### Contexts
-### Examples
+Zed's keyboard bindings are evaluated only when the `"context"` matches the location you are in on the screen. Locations are nested, so when you're editing you're in the `"Workspace"` location is at the top, containing a `"Pane"` which contains an `"Editor"`. Contexts are matched only on one level at a time. So it is possible to combine `Editor && vim_mode == normal`, but `Workspace && vim_mode == normal` will never match because we set the vim context at the `Editor` level.
-Binding `jk` to exit insert mode and go to normal mode:
+Vim mode adds several contexts to the `Editor`:
-```
-{
- "context": "Editor && vim_mode == insert && !menu",
- "bindings": {
- "j k": ["vim::SwitchMode", "Normal"]
- }
-}
-```
+* `vim_mode` is similar to, but not identical to, the current mode. It starts as one of `normal`, `visual`, `insert` or `replace` (depending on your mode). If you are mid-way through typing a sequence, `vim_mode` will be either `waiting` if it's waiting for an arbitrary key (for example after typing `f` or `t`), or `operator` if it's waiting for another binding to trigger (for example after typing `c` or `d`).
+* `vim_operator` is set to `none` unless `vim_mode == operator` in which case it is set to the current operator's default keybinding (for example after typing `d`, `vim_operator == d`).
+* `"VimControl"` indicates that vim keybindings should work. It is currently an alias for `vim_mode == normal || vim_mode == visual || vim_mode == operator`, but the definition may change over time.
### Restoring some sense of normality
@@ -155,7 +135,7 @@ that you can't live without. You can restore them to their defaults by copying t
```
{
- "context": "Editor && !VimWaiting && !menu",
+ "context": "Editor && !menu",
"bindings": {
"ctrl-c": "editor::Copy", // vim default: return to normal mode
"ctrl-x": "editor::Cut", // vim default: increment
@@ -304,7 +284,7 @@ Subword motion is not enabled by default. To enable it, add these bindings to yo
```json
{
- "context": "Editor && VimControl && !VimWaiting && !menu",
+ "context": "VimControl && !menu",
"bindings": {
"w": "vim::NextSubwordStart",
"b": "vim::PreviousSubwordStart",
@@ -318,7 +298,7 @@ Surrounding the selection in visual mode is also not enabled by default (`shift-
```json
{
- "context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
+ "context": "vim_mode == visual",
"bindings": {
"shift-s": [
"vim::PushOperator",