You can map key combinations to commands that perform actions in the editor. These bindings are defined per input mode (like vim.normal or vscode) and set up mostly in various keybindings.json files.
At any given time, a combination of modes is active. Here’s how mode resolution works:
vim.insert or vim.completion) are added depending on the state of the focused view.Example input mode stack when using Vim keybindings:
vim.completion ← top (from `text.completion-mode`) -|
vim.insert ← added dynamically | These come from the active editor
vim ← from `text.modes` -|
vim.base ← bottom (from `editor.base-modes`) - This one is always active
| Setting | Purpose |
|---|---|
editor.base-modes |
Always-active modes, regardless of focus |
text.modes |
Modes active when a text editor is focused. These are changed dynamically when using Vim keybindings |
text.default-mode |
Default mode added to text.modes for a text editor |
text.completion-mode |
Mode added when the completion window is visible |
editor.command-line-mode-low |
Input mode added during command-line mode (low priority) |
editor.command-line-mode-high |
Input mode added during command-line mode (high priority) |
editor.command-line-result-mode-low |
Mode active during command-line result (low) |
editor.command-line-result-mode-high |
Mode active during command-line result (high) |
terminal.base-mode |
Always-active mode when terminal is focused |
terminal.default-mode |
Optional additional terminal mode |
selector.base-mode |
Always-active mode when a selector popup is open |
When a selector popup is open, the following rules apply:
vim.selector) is always added.A scope-specific mode is added:
themes → vim.selector.themesIf the preview is focused, the preview mode is added:
vim.selector.previewThese examples assume using Vim keybindings, with the modes defined like this
| Context | Active Input Modes (Bottom → Top) |
| —————————————– | ————————————————————————————————– |
| Selector popup for themes (preview not focused) | vim.base, vim.selector, vim.selector.themes |
| Selector popup for themes (preview focused) | vim.base, vim.selector, vim.selector.preview |
| Text editor in normal mode | vim.base, vim, vim.normal |
| Text editor in insert mode | vim.base, vim, vim.insert |
| Text editor in insert mode and completions open | vim.base, vim, vim.insert, vim.completion |
| Terminal in normal mode | vim.base, terminal, normal |
| Command-line in insert mode + completions | vim.base, vim, vim.insert, vim.command-line-low, vim.completion, vim.command-line-high |
Use the extra-settings key to change which keybinding scheme to use (Vim is the default):
// app://config/settings.json
{
"extra-settings": ["app://config/settings-vim.json"]
}
This loads modes like vim.base, vim, vim.normal, etc.
To switch to a different scheme (e.g., VSCode-style), replace it with:
"extra-settings": ["app://config/settings-vscode.json"]
You can define your own keybindings and create new input modes. Check out the default keybindings for a lot more examples
Create or edit the following file to add your own keybindings:
~/.nev/keybindings.json%HOME%/.nev/keybindings.jsonEach top-level key represents an input mode, and the object under it defines key-to-command mappings.
To bind <LEADER>m in Vim Normal mode and <C-m> in VSCode mode to maximize the current view:
// ~/.nev/keybindings.json
{
"vscode": {
"<C-m>": ["toggle-maximize-view"]
},
"vim.normal": {
"<LEADER>m": ["toggle-maximize-view"]
}
}
{
"vim.base": {
"<C-w>h": ["focus-view-left"]
},
"vim": {
":": ["command-line"]
},
"vim.normal": {
"a": ["vim.insert-mode", "right"],
"i": ["vim.insert-mode"]
},
"vim.insert": {
"<C-w>": ["vim.delete-word-back"],
"<C-u>": ["vim.delete-line-back"]
},
"vim.selector": {
// selector global keybindings
},
"vim.selector.themes": {
// keybindings specific to theme selector
},
"vscode.base": {
// VSCode-style base bindings
},
"vscode": {
// VSCode-style editor bindings
}
}
This example shows how to create a custom vim mode ( vim.my-mode).
Add the following to ~/.nev/settings.json to define how your custom mode should behave:
{
// Whether to allow character input to be inserted when not bound to commands
"input.vim.my-mode.handle-inputs": true, // default: false
// Whether to allow keybindings to trigger commands in this mode
"input.vim.my-mode.handle-actions": true, // default: true
// If true, prevents any commands from lower-priority modes from being executed
"input.vim.my-mode.consume-all-actions": false, // default: false
// If true, prevents any text input from being handled by lower modes
"input.vim.my-mode.consume-all-input": false, // default: false
// Controls how the cursor moves for certain commands
// "last" moves only the selection end, "first" moves the start, "both" moves both
"editor.text.cursor.movement.vim.my-mode": "last",
// Makes the cursor appear as a block (true) or a line (false)
"editor.text.cursor.wide.vim.my-mode": true
}
Extend your keybindings.json to define the behavior of your new mode:
// ~/.nev/keybindings.json
{
"vim.my-mode": {
"<ESCAPE>": ["set-mode", "vim.normal"], // Exit to normal mode
"x": ["undo"] // Example: bind 'x' to undo
},
"vim.normal": {
"<C-i>": ["set-mode", "vim.my-mode"] // Enter your custom mode
}
}
set-mode and remove-modeset-modeThe command set-mode adds a new mode to the active text editor and removes other modes with the same prefix.
For example:
set-mode vim.normal → removes vim.insert, adds vim.normalset-mode other.mode → does not affect vim.* modesremove-modeIf you want to manually remove a mode without replacing it:
["remove-mode", "other.mode"]
Use angle brackets for special keys:
<ENTER>, <ESCAPE>, <SPACE>, <BACKSPACE>, <TAB>, <DELETE><LEFT>, <RIGHT>, <UP>, <DOWN><HOME>, <END>, <PAGE_UP>, <PAGE_DOWN><F1> to <F12>Modifiers use the following abbreviations:
C = ControlS = ShiftA = AltExample:
"<C-HOME>": ["command"] // CTRL+HOME
"<CS-a>": ["command"] // CTRL+SHIFT+a
Note: Uppercase letters (e.g. "A") are treated as SHIFT+a.
Keybindings can be multi-key sequences, like "d<text_object>" or "<SPACE><C-g>". The editor uses a state machine for each mode that processes each key in sequence.
⚠️ Don’t bind keys that are prefixes of other bindings in the same mode:
"a": ["command-a"],
"aa": ["command-aa"] // This will never be triggered because "a" triggers first
In insert mode or other input-consuming modes, bindings like "jj" can coexist with normal typing:
"vim.insert": {
"jj": ["set-mode", "vim.normal"]
}
Behavior depends on timing:
j → delay passed → inserts jjj → quick press → exits to normal modejk → j inserted, k handled normallyConfigure the delay with:
"editor.insert-input-delay": 300 // milliseconds
* Modifier)Use * to allow repeating the last part of a keybinding:
"vim.normal": {
"<C-w><*-f>-": ["change-font-size", -1],
"<C-w><*-f>+": ["change-font-size", 1]
}
After pressing <C-w>f, you can press + or - repeatedly without restarting the sequence.
Submodes are used to compose complex keybindings using reusable parts (like Vim-style motions and text objects).
{
"#count": {
"<-1-9><o-0-9>": [""]
},
"vim#text_object": {
"<?-count>iw": ["(count* <#text_object.count>) (vim.word-inner) (inclusive)"],
"i{": ["(surround \"{\" \"}\" true)"],
}
}
# in the mode name indicates a sub modetext_object submode can be used in any mode starting with vim,
the count submode can be used in any mode.<?-count> = optional count prefix<#text_object.count> = pass captured count as numeric argument"vim.normal": {
"<?-count>d<text_object>": ["vim.delete-move <text_object> <#count>"],
"<?-count>c<text_object>": ["vim.change-move <text_object> <#count>"]
}
This results in bindings like:
3d2iw → vim.delete-move "(count* 2) (vim.word-inner) (inclusive)" 3<*>) change the default mode after a keybinding finishes to the tagged state instead of the start state.