Custom Key Mappings
Define custom vim key mappings, leader keys, and mode-specific bindings in @replit/codemirror-vim.
@replit/codemirror-vim provides a programmatic API for defining vim-style key mappings. These work the same way as :map and :noremap Ex commands but are called from JavaScript/TypeScript.
Core Mapping API
All mapping functions are methods on the Vim object.
import { Vim } from "@replit/codemirror-vim";Vim.map()
Creates a recursive key mapping. If {rhs} is itself a mapped sequence, the mapping is expanded.
Vim.map(lhs: string, rhs: string, mode?: string): voidVim.noremap()
Creates a non-recursive key mapping. The {rhs} is interpreted as raw keys, not expanded through other mappings.
Vim.noremap(lhs: string, rhs: string, mode?: string): voidVim.unmap()
Removes a previously defined mapping.
Vim.unmap(lhs: string, mode?: string): voidMode Parameter
The optional mode parameter restricts the mapping to a specific vim mode.
| Value | Mode |
|---|---|
"normal" | normal mode |
"insert" | insert mode |
"visual" | visual mode |
| (omitted) | all modes |
Key Notation
Key sequences use vim-style notation for special keys.
| Notation | Key |
|---|---|
<Esc> | Escape |
<CR> | Enter / Return |
<Tab> | Tab |
<Space> | Space |
<BS> | Backspace |
<C-w> | Ctrl+w |
<C-d> | Ctrl+d |
<A-x> | Alt+x |
<S-Tab> | Shift+Tab |
<Leader> | Leader key (configurable, default is \) |
Practical Examples
Escape from Insert Mode with jk
A common mapping to avoid reaching for the Escape key.
Vim.map("jk", "<Esc>", "insert");Map Leader Key Combinations
Set a leader key and define leader-based shortcuts.
// Set leader to space
Vim.map("<Space>", "<Leader>");
// Leader+w to write
Vim.map("<Leader>w", ":w<CR>");
// Leader+q to quit
Vim.map("<Leader>q", ":q<CR>");Window-like Shortcuts in Insert Mode
// Ctrl+s to save from insert mode
Vim.noremap("<C-s>", "<Esc>:w<CR>a", "insert");Remap Navigation Keys
// Make H and L go to start/end of line
Vim.noremap("H", "^", "normal");
Vim.noremap("L", "$", "normal");Mapping to Ex Commands
Mappings that include : enter command-line mode and can execute Ex commands.
// Clear search highlighting with leader+h
Vim.map("<Leader>h", ":nohlsearch<CR>");
// Sort selection with leader+s
Vim.map("<Leader>s", ":sort<CR>", "visual");Move Lines Up and Down
Vim.noremap("<A-j>", ":m .+1<CR>", "normal");
Vim.noremap("<A-k>", ":m .-2<CR>", "normal");Setting the Leader Key
The leader key is a configurable prefix used in custom mappings. The default leader is \ (backslash).
To change the leader key:
Vim.map("<Space>", "<Leader>");After this, any mapping that uses <Leader> will respond to Space followed by the next key.
Warning
Setting the leader key with Vim.map must happen before defining mappings that use <Leader>. Otherwise, those earlier mappings will still use the old leader key.
Removing Mappings
Remove mappings when they are no longer needed.
// Remove the jk mapping in insert mode
Vim.unmap("jk", "insert");
// Remove a normal mode mapping
Vim.unmap("H", "normal");Recursive vs Non-Recursive Mappings
Use Vim.noremap() in most cases.
Vim.map()-- the right-hand side is expanded through other existing mappings, which can cause infinite loops if not carefulVim.noremap()-- the right-hand side is literal keystrokes, preventing unintended chaining
// Safe: noremap prevents recursive expansion
Vim.noremap("j", "gj", "normal");
Vim.noremap("k", "gk", "normal");
// Dangerous with map: if gj were also mapped, this could loop
// Vim.map("j", "gj", "normal"); // avoid this patternTip
Following the same best practice as in a .vimrc, prefer noremap / nnoremap / inoremap / vnoremap over map / nmap / imap / vmap unless you specifically need recursive expansion.
Vim and CodeMirror keymap precedence
Vim handles its mapped keys and lets unhandled events reach other CodeMirror keymaps. Putting vim() before the shared editor extensions does not disable those later bindings: an unbound key can still trigger searchKeymap or completion commands.
On macOS, Vim does not normally bind Cmd+D. CodeMirror's searchKeymap binds Mod-d to selectNextOccurrence; during a visual selection this can select another occurrence and scroll the viewport, including wrapping to an earlier match. The applied fix in zudo-text PR #910, tracked by issue #874, consumes that key only when Vim is enabled:
import { keymap } from "@codemirror/view";
import { searchKeymap } from "@codemirror/search";
import { vim } from "@replit/codemirror-vim";
const vimEnabled = true;
const extensions = [
...(vimEnabled
? [vim(), keymap.of([{ key: "Mod-d", run: () => true }])]
: []),
keymap.of(searchKeymap),
];The no-op returns true to mark the event handled and stop later CodeMirror bindings. preventDefault: true only requests suppression of the browser's default action; it does not replace a successful command handler. Keep the blocker before the shared search keymap, at the same precedence; a shared map wrapped in Prec.high can otherwise outrank it.
Mod-d means Cmd+D on macOS and Ctrl+D on Linux/Windows. With this ordering, Vim handles its native Ctrl+D half-page scroll first on non-Mac platforms. With Vim disabled, the blocker is absent and CodeMirror's normal select-next-occurrence command remains available. Check normal, visual and visual-block modes on macOS, native Ctrl+D on non-Mac, and the Vim-disabled editor when adopting the pattern.
If another shortcut behaves unexpectedly, inspect the entire active keymap stack: Mod-Alt-g, Mod-Shift-l and completion-keymap Escape are useful places to look. Block only a confirmed conflict, rather than swallowing all unbound keys.
Reference: CodeMirror KeyBinding.