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.themes
If the preview is focused, the preview mode is added:
vim.selector.preview
These 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.json
Each 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-mode
set-mode
The 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.normal
set-mode other.mode
→ does not affect vim.*
modesremove-mode
If 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 j
jj
→ 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": ["vim-select-text-object", "vim-word-inner", false, true, "<#text_object.count>"],
"<?-count>i{": ["vim-select-surrounding", "vim-surround-{-inner", false, true, "<#text_object.count>"]
}
}
#
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 "vim-select-text-object \"vim-word-inner\" false true 2" 3
<*>
) change the default mode after a keybinding finishes to the tagged state instead of the start state.