687 lines · 38.9 KB · Markdown
RawDownload

title: Vim Mode - Zed description: Full Vim emulation in Zed with motions, text objects, visual mode, macros, and Zed-specific extensions.

Vim Mode

Zed includes a Vim emulation layer. This page covers enabling and disabling vim mode, key bindings, Zed-specific features, and configuration options.

Zed's vim mode design

Vim mode replicates the behavior of motions and commands where it makes sense and uses Zed-specific functionality where Zed's approach is better. The goal is a familiar experience that works out of the box without requiring configuration.

This includes support for semantic navigation, multiple cursors, or other features usually provided by plugins like surrounding text.

So, Zed's vim mode does not replicate Vim one-to-one, but it meshes Vim's modal design with Zed's modern features to provide a more fluid experience. It's also configurable, so you can add your own key bindings or override the defaults.

Core differences

There are four types of features in vim mode that use Zed's core functionality, leading to some differences in behavior:

  1. Motions: vim mode uses Zed's semantic parsing to tune the behavior of motions per language. For example, in Rust, jumping to matching bracket with % works with the pipe character |. In JavaScript, w considers $ to be a word character.
  2. Visual block selections: vim mode uses Zed's multiple cursor to emulate visual block selections, making block selections a lot more flexible. For example, anything you insert after a block selection updates on every line in real-time, and you can add or remove cursors anytime.
  3. Macros: vim mode uses Zed's recording system for vim macros. So, you can capture and replay more complex actions, like autocompletion.
  4. Search and replace: vim mode uses Zed's search system, so, the syntax for regular expressions is slightly different compared to Vim. Head to the Regex differences section for details.

Note: The foundations of Zed's vim mode should already cover many use cases, and we're always looking to improve it. If you find missing features that you rely on in your workflow, please file an issue on GitHub.

Enabling and disabling vim mode

When you first open Zed, you'll see a checkbox on the welcome screen that allows you to enable vim mode.

If you missed this, you can toggle vim mode on or off anytime by opening the command palette and using the workspace command {#action workspace::ToggleVimMode}.

Note: This command toggles the following property in your user settings:

{
  "vim_mode": true
}

Zed-specific features

Zed is built on a modern foundation that (among other things) uses Tree-sitter and language servers to understand the content of the file you're editing and supports multiple cursors out of the box.

Vim mode has several "core Zed" key bindings that will help you make the most of Zed's specific feature set.

Language server

The following commands use the language server to help you navigate and refactor your code.

Command Default Shortcut
Go to definition g d
Go to declaration g D
Go to type definition g y
Go to implementation g I
Rename (change definition) c d
Go to All references to the current word g A
Find symbol in current file g s
Find symbol in entire project g S
Go to next diagnostic g ] or ] d
Go to previous diagnostic g [ or [ d
Show inline error (hover) g h
Open the code actions menu g .

Git

Command Default Shortcut
Go to next git change ] c
Go to previous git change [ c
Expand diff hunk d o
Toggle staged d O
Stage and next (in diff view) d u
Unstage and next (in diff view) d U
Restore change d p

Tree-sitter

Tree-sitter is the parser Zed uses to understand the structure of your code. Zed provides motions that change the current cursor position, and text objects that can be used as the target of actions.

Command Default Shortcut
Go to next/previous method ] m / [ m
Go to next/previous method end ] M / [ M
Go to next/previous section ] ] / [ [
Go to next/previous section end ] [ / [ ]
Go to next/previous comment ] /, ] * / [ /, [ *
Select a larger syntax node [ x
Select a smaller syntax node ] x
Text Objects Default Shortcut
Around a class, definition, etc. a c
Inside a class, definition, etc. i c
Around a function, method etc. a f
Inside a function, method, etc. i f
A comment g c
An argument, or list item, etc. i a
An argument, or list item, etc. (including trailing comma) a a
Around an HTML-like tag a t
Inside an HTML-like tag i t
The current indent level, and one line before and after a I
The current indent level, and one line before a i
The current indent level i i

Note that the definitions for the targets of the [m family of motions are the same as the boundaries defined by af. The targets of the [[ are the same as those defined by ac, though if there are no classes, then functions are also used. Similarly gc is used to find [ /. g c

The definition of functions, classes and comments is language dependent, and support can be added to extensions by adding a [textobjects.scm]. The definition of arguments and tags operates at the Tree-sitter level, but looks for certain patterns in the parse tree and is not currently configurable per language.

Multi cursor

These commands help you manage multiple cursors in Zed.

Command Default Shortcut
Add a cursor selecting the next copy of the current word g l
Add a cursor selecting the previous copy of the current word g L
Add a cursor at the end of every line in the current visual selection g A
Add a cursor at the first character of every line in the current visual selection g I
Add a visual selection for every copy of the current word g a
Skip latest word selection, and add next g >
Skip latest word selection, and add previous g <

Pane management

These commands open new panes or jump to specific panes.

Command Default Shortcut
Open a project-wide search g /
Open the current search excerpt g <space>
Open the current search excerpt in a split <ctrl-w> <space>
Go to definition in a split <ctrl-w> g d
Go to type definition in a split <ctrl-w> g D

In insert mode

The following commands help you bring up Zed's completion menu, request a suggestion from GitHub Copilot, or open the inline AI assistant without leaving insert mode.

Command Default Shortcut
Open the completion menu ctrl-x ctrl-o
Request GitHub Copilot suggestion (requires GitHub Copilot to be configured) ctrl-x ctrl-c
Open the inline AI assistant (requires a configured assistant) ctrl-x ctrl-a
Open the code actions menu ctrl-x ctrl-l
Hides all suggestions ctrl-x ctrl-z

Supported plugins

Zed's vim mode includes features commonly provided by plugins in the Vim ecosystem:

  • You can surround text objects with ys (yank surround), change surrounding with cs, and delete surrounding with ds.
  • You can comment and uncomment selections with gc in visual mode and gcc in normal mode.
  • The project panel supports many shortcuts modeled after the Vim plugin netrw: navigation with hjkl, open file with o, open file in a new tab with t, etc.
  • You can add key bindings to your keymap to navigate "camelCase" names. Head down to the Optional key bindings section to learn how.
  • You can use gR to do ReplaceWithRegister.
  • You can use cx for vim-exchange functionality. Note that it does not have a default binding in visual mode, but you can add one to your keymap (refer to the optional key bindings section).
  • You can navigate to indent depths relative to your cursor with the indent wise plugin [-, ]-, [+, ]+, [=, ]=.
  • You can select quoted text with AnyQuotes and bracketed text with AnyBrackets text objects. Zed also provides MiniQuotes and MiniBrackets which offer alternative selection behavior based on the mini.ai Neovim plugin. See the Quote and Bracket text objects section below for details.
  • You can configure AnyQuotes, AnyBrackets, MiniQuotes, and MiniBrackets text objects for selecting quoted and bracketed text using different selection strategies. See the Any Bracket Functionality section below for details.

Any Bracket Functionality

Zed offers two different strategies for selecting text surrounded by any quote, or any bracket. These text objects are not enabled by default and must be configured in your keymap to be used.

Included Characters

Each text object type works with specific characters:

Text Object Characters
AnyQuotes/MiniQuotes Single quote ('), Double quote ("), Backtick (`)
AnyBrackets/MiniBrackets Parentheses (()), Square brackets ([]), Curly braces ({}), Angle brackets (<>)

Both "Any" and "Mini" variants work with the same character sets, but differ in their selection strategy.

AnyQuotes and AnyBrackets (Traditional Vim behavior)

These text objects implement traditional Vim behavior:

  • Selection priority: Finds the innermost (closest) quotes or brackets first
  • Fallback mechanism: If none are found, falls back to the current line
  • Character-based matching: Focuses solely on open and close characters without considering syntax
  • Vanilla Vim similarity: AnyBrackets matches the behavior of commands like ci<, ci(, etc., in vanilla Vim, including potential edge cases (like considering > in => as a closing delimiter)

MiniQuotes and MiniBrackets (mini.ai behavior)

These text objects implement the behavior of the mini.ai Neovim plugin:

  • Selection priority: Searches the current line first before expanding outward
  • Tree-sitter integration: Uses Tree-sitter queries for more context-aware selections
  • Syntax-aware matching: Can distinguish between actual brackets and similar characters in other contexts (like > in =>)

Choosing Between Approaches

  • Use AnyQuotes/AnyBrackets if you:

    • Prefer traditional Vim behavior
    • Want consistent character-based selection prioritizing innermost delimiters
    • Need behavior that closely matches vanilla Vim's text objects
  • Use MiniQuotes/MiniBrackets if you:

    • Prefer the mini.ai plugin behavior
    • Want more context-aware selections using Tree-sitter
    • Prefer current-line priority when searching

Example Configuration

To use these text objects, you need to add bindings to your keymap. Here's an example configuration that makes them available when using text object operators (i and a) or change-surrounds (cs):

{
  "context": "vim_operator == a || vim_operator == i || vim_operator == cs",
  "bindings": {
    // Traditional Vim behavior
    "q": "vim::AnyQuotes",
    "b": "vim::AnyBrackets",

    // mini.ai plugin behavior
    "Q": "vim::MiniQuotes",
    "B": "vim::MiniBrackets"
  }
}

With this configuration, you can use commands like:

  • cib - Change inside brackets using AnyBrackets behavior
  • ciB - Change inside brackets using MiniBrackets behavior
  • ciq - Change inside quotes using AnyQuotes behavior
  • ciQ - Change inside quotes using MiniQuotes behavior

Command palette

Vim mode allows you to open Zed's command palette with :. You can then type to access any usual Zed command. Additionally, vim mode adds aliases for popular Vim commands to ensure your muscle memory transfers to Zed. For example, you can write :w or :write to save the file.

Below, you'll find tables listing the commands you can use in the command palette. We put optional characters in square brackets to indicate that you can omit them.

Note: We don't emulate the full power of Vim's command line yet. In particular, commands currently do not support arguments. Please file issues on GitHub as you find things that are missing from the command palette.

File and window management

This table shows commands for managing windows, tabs, and panes. As commands don't support arguments currently, you cannot specify a filename when saving or creating a new file.

Command Description
:w[rite][!] Save the current file
:wq[!] Save the file and close the buffer
:q[uit][!] Close the buffer
:wa[ll][!] Save all open files
:wqa[ll][!] Save all open files and close all buffers
:qa[ll][!] Close all buffers
:[e]x[it][!] Close the buffer
:up[date] Save the current file
:cq Quit completely (close all running instances of Zed)
:bd[elete][!] Close the active file in all panes
:vs[plit] Split the pane vertically
:sp[lit] Split the pane horizontally
:new Create a new file in a horizontal split
:vne[w] Create a new file in a vertical split
:tabedit Create a new file in a new tab
:tabnew Create a new file in a new tab
:tabn[ext] Go to the next tab
:tabp[rev] Go to previous tab
:tabc[lose] Close the current tab
:ls Show all buffers

Note: The ! character is used to force the command to execute without saving changes or prompting before overwriting a file.

Ex commands

These ex commands open Zed's various panels and windows.

Command Default Shortcut
Open the project panel :E[xplore]
Open the collaboration panel :C[ollab]
Open the chat panel :Ch[at]
Open the AI panel :A[I]
Open the git panel :G[it]
Open the debug panel :D[ebug]
Open the notifications panel :No[tif]
Open the feedback window :fe[edback]
Open the diagnostics window :cl[ist]
Open the terminal :te[rm]
Open the extensions window :Ext[ensions]

Navigating diagnostics

These commands navigate diagnostics.

Command Description
:cn[ext] or :ln[ext] Go to the next diagnostic
:cp[rev] or :lp[rev] Go to the previous diagnostic
:cc or :ll Open the errors page

Git

These commands interact with the version control system git.

Command Description
:dif[fupdate] View the diff under the cursor (d o in normal mode)
:rev[ert] Revert the diff under the cursor (d p in normal mode)

Jump

These commands jump to specific positions in the file.

Command Description
:<number> Jump to a line number
:$ Jump to the end of the file
:/foo and :?foo Jump to next/prev line matching foo

Replacement

This command replaces text. It emulates the substitute command in vim. The substitute command uses regular expressions, and Zed uses a slightly different syntax than vim. You can learn more about Zed's syntax below, in the regex differences section. Zed will replace only the first occurrence of the search pattern in the current line. To replace all matches append the g flag.

Command Description
:[range]s/foo/bar/[g] Replace instances of foo with bar

Editing

These commands help you edit text.

Command Description
:j[oin] Join the current line
:d[elete][l][p] Delete the current line
:s[ort] [i] Sort the current selection (with i, case-insensitively)
:y[ank] Yank (copy) the current selection or line

Set

These commands modify editor options locally for the current buffer.

Command Description
:se[t] [no]wrap Lines longer than the width of the window will wrap and displaying continues on the next line
:se[t] [no]nu[mber] Print the line number in front of each line
:se[t] [no]r[elative]nu[mber] Changes the displayed number to be relative to the cursor
:se[t] [no]i[gnore]c[ase] Controls whether the buffer and project search use case-sensitive matching

Command mnemonics

Zed does not ship with any command mnemonics by default, but you can define short aliases for Zed commands using the command_aliases setting in your settings file. When you type an alias from this map in the command palette, it resolves to the mapped command.

Example Configuration

To configure command mnemonics, add the command_aliases key to your settings file. Here's an example configuration with useful mnemonics:

{
  "command_aliases": {
    "zlog": "zed::OpenLog",
    "newf": "workspace::NewFile",
    "diffs": "editor::ToggleSelectedDiffHunks",
    "crp": "workspace::CopyRelativePath",
    "cpp": "workspace::CopyPath",
    "reveal": "editor::RevealInFileManager",
    "clank": "editor::CancelLanguageServerWork"
  }
}

With this configuration, you can use commands like:

  • :zlog - Open the Zed log
  • :newf - Create a new file
  • :diffs - Toggle selected diff hunks
  • :crp - Copy the relative path to the current file
  • :cpp - Copy the full path to the current file
  • :reveal - Reveal the current file in the file manager
  • :clank - Cancel language server work

Customizing key bindings

Selecting the correct context

Zed's key bindings are evaluated only when the "context" property matches your location in the editor. For example, if you add key bindings to the "Editor" context, they will only work when you're editing a file. If you add key bindings to the "Workspace" context, they will work everywhere in Zed. Here's an example of a key binding that saves when you're editing a file:

{
  "context": "Editor",
  "bindings": {
    "ctrl-s": "workspace::Save"
  }
}

Contexts are nested, so when you're editing a file, the context is the "Editor" context, which is inside the "Pane" context, which is inside the "Workspace" context. That's why any key bindings you add to the "Workspace" context will work when you're editing a file. Here's an example:

// This key binding will work when you're editing a file. It comes built into Zed by default as the workspace: save command.
{
  "context": "Workspace",
  "bindings": {
    "ctrl-s": "workspace::Save"
  }
}

Contexts are expressions. They support boolean operators like && (and) and || (or). For example, you can use the context "Editor && vim_mode == normal" to create key bindings that only work when you're editing a file and you're in vim's normal mode.

Vim mode adds several contexts to the "Editor" context:

Operator Description
VimControl Indicates that vim keybindings should work. Currently an alias for vim_mode == normal || vim_mode == visual || vim_mode == operator, but the definition may change over time
vim_mode == normal Normal mode
vim_mode == visual Visual mode
vim_mode == insert Insert mode
vim_mode == replace Replace mode
vim_mode == waiting Waiting for an arbitrary key (e.g., after typing f or t)
vim_mode == operator Waiting for another binding to trigger (e.g., after typing c or d)
vim_operator Set to none unless vim_mode == operator, in which case it is set to the current operator's default keybinding (e.g., after typing d, vim_operator == d)

Note: Contexts are matched only on one level at a time. So it is possible to use the expression "Editor && vim_mode == normal", but "Workspace && vim_mode == normal" will never match because we set the vim context at the "Editor" level.

Useful contexts for vim mode key bindings

Here's a template with useful vim mode contexts to help you customize your vim mode key bindings. You can copy it and integrate it into your user keymap.

[
  {
    "context": "VimControl && !menu",
    "bindings": {
      // Put key bindings here if you want them to work in normal & visual mode.
    }
  },
  {
    "context": "vim_mode == normal && !menu",
    "bindings": {
      // "shift-y": ["workspace::SendKeystrokes", "y $"] // Use neovim's yank behavior: yank to end of line.
    }
  },
  {
    "context": "vim_mode == insert",
    "bindings": {
      // "j k": "vim::NormalBefore" // In insert mode, make jk escape to normal mode.
    }
  },
  {
    "context": "EmptyPane || SharedScreen",
    "bindings": {
      // Put key bindings here (in addition to the context above) if you want them to
      // work when no editor exists.
      // "space f": "file_finder::Toggle"
    }
  }
]

Note: If you would like to emulate Vim's map commands (nmap, etc.), you can use the action workspace::SendKeystrokes in the correct context.

Optional key bindings

By default, you can navigate between the different files open in the editor with shortcuts like ctrl+w followed by one of hjkl to move to the left, down, up, or right, respectively.

But you cannot use the same shortcuts to move between all the editor docks (the terminal, project panel, agent panel, ...). If you want to use the same shortcuts to navigate to the docks, you can add the following key bindings to your user keymap.

{
  "context": "Dock",
  "bindings": {
    "ctrl-w h": "workspace::ActivatePaneLeft",
    "ctrl-w l": "workspace::ActivatePaneRight",
    "ctrl-w k": "workspace::ActivatePaneUp",
    "ctrl-w j": "workspace::ActivatePaneDown"
    // ... or other keybindings
  }
}

Subword motion, which allows you to navigate and select individual words in camelCase or snake_case, is not enabled by default. To enable it, add these bindings to your keymap.

{
  "context": "VimControl && !menu && vim_mode != operator",
  "bindings": {
    "w": "vim::NextSubwordStart",
    "b": "vim::PreviousSubwordStart",
    "e": "vim::NextSubwordEnd",
    "g e": "vim::PreviousSubwordEnd"
  }
}

Lines 1–500 of 687.