mini.keymap documentation
Generated from the main
branch of ‘mini.nvim’
mini.keymap Special key mappings
MIT License Copyright (c) 2025 Evgeni Chasnovski
Module
Features:
Map keys to perform configurable multi-step actions: if condition for step one is true - execute step one action, else check step two, and so on until falling back to executing original keys. This is usually referred to as “smart” keys (like “smart tab”). See MiniKeymap.map_multistep().
There are many built-in steps targeted for Insert mode mappings of special keys like <Tab>, <S-Tab>, <CR>, and <BS>:
Navigate and accept popupmenu-completion. Useful for mini.completion.
Navigate and expand mini.snippets.
Execute <CR> and <BS> respecting mini.pairs.
Jump before/after current tree-sitter node.
Jump before opening and after closing characters (brackets and quotes).
Increase/decrease indent when cursor is inside of it.
Delete all whitespace to the left (“hungry backspace”).
Navigate vim.snippet.
Navigate and accept in hrsh7th/nvim-cmp completion.
Navigate and accept in Saghen/blink.cmp completion.
Navigate and expand L3MON4D3/LuaSnip snippets.
Execute <CR> and <BS> respecting windwp/nvim-autopairs.
Map keys as “combo”: each key acts immediately plus execute extra action if all are typed within configurable delay between each other. See MiniKeymap.map_combo(). Some of the common use cases include:
Map insertable keys (like “jk”, “kj”) in Insert and Command-line mode to exit into Normal mode.
Fight against bad habits of pressing the same navigation key by showing a notification if there are too many of them pressed in a row.
Sources with more details:
Setup
This module doesn’t need setup, but it can be done to improve usability. Setup with require('mini.keymap').setup({})
(replace {}
with your config
table). It will create global Lua table MiniKeymap
which you can use for scripting or manually (with :lua MiniKeymap.*
).
See MiniKeymap.config for config
structure and default values.
This module doesn’t have runtime options, so using vim.b.minikeymap_config
will have no effect here.
Comparisons
-
Mostly similar to MiniKeymap.map_combo() with a different approach to creating mappings.
Mostly targeted for Insert mode mappings as pressed keys get removed automatically after typed. This module allows more general cases while requiring explicit removal of keys (usually via explicit
<BS><BS>
).
-
Similar general idea as in
'jump_{after,before}_tsnode'
steps of MiniKeymap.map_multistep().Works only with enabled tree-sitter parser. This module provides fallback via ‘jump_after_close’ and ‘jump_before_open’ that work without tree-sitter parser.
‘tabout.nvim’ has finer control of how the tree-sitter node movement is done, while this module has “jump outside of current node” behavior.
Disabling
To disable acting in mappings, set vim.g.minikeymap_disable
(globally) or vim.b.minikeymap_disable
(for a buffer) to true
. Considering high number of different scenarios and customization intentions, writing exact rules for disabling module’s functionality is left to user. See mini.nvim-disabling-recipes for common recipes.
Examples
Multi-step
See MiniKeymap.map_multistep() for a general description of how multi-step mappings work and what built-in steps are available.
Setup that works well with mini.completion and mini.pairs:
local map_multistep = require('mini.keymap').map_multistep
('i', '<Tab>', { 'pmenu_next' })
map_multistep('i', '<S-Tab>', { 'pmenu_prev' })
map_multistep('i', '<CR>', { 'pmenu_accept', 'minipairs_cr' })
map_multistep('i', '<BS>', { 'minipairs_bs' }) map_multistep
Use <Tab> / <S-Tab> to also navigate and expand mini.snippets:
local map_multistep = require('mini.keymap').map_multistep
local tab_steps = { 'minisnippets_next','minisnippets_expand','pmenu_next' }
('i', '<Tab>', tab_steps)
map_multistep
local shifttab_steps = { 'minisnippets_prev', 'pmenu_prev' }
('i', '<S-Tab>', shifttab_steps) map_multistep
An extra smart <Tab> and <S-Tab>:
local map_multistep = require('mini.keymap').map_multistep
-- NOTE: this will never insert tab, press <C-v><Tab> for that
local tab_steps = {
'minisnippets_next', 'minisnippets_expand', 'pmenu_next',
'jump_after_tsnode', 'jump_after_close',
}
('i', '<Tab>', tab_steps)
map_multistep
local shifttab_steps = {
'minisnippets_prev', 'pmenu_prev',
'jump_before_tsnode', 'jump_before_open',
}
('i', '<S-Tab>', shifttab_steps) map_multistep
Navigation in active vim.snippet session also requires mapping in Select-mode:
local map_multistep = require('mini.keymap').map_multistep
({ 'i', 's' }, '<Tab>', { 'vimsnippet_next', 'pmenu_next' })
map_multistep({ 'i', 's' }, '<S-Tab>', { 'vimsnippet_prev', 'pmenu_prev' }) map_multistep
Combos
See MiniKeymap.map_combo() for a general description of what is a combo and more caveats about its usage.
All combos require their left hand side keys to be typed relatively quickly. To adjust the delay between keys, add { delay = 500 }
(use custom value) as fourth argument.
“Better escape” to Normal mode
Leave into Normal-mode without having to reach for <Esc> key:
-- Support most common modes. This can also contain 't', but would
-- only mean to press `<Esc>` inside terminal.
local mode = { 'i', 'c', 'x', 's' }
require('mini.keymap').map_combo(mode, 'jk', '<BS><BS><Esc>')
-- To not have to worry about the order of keys, also map "kj"
require('mini.keymap').map_combo(mode, 'kj', '<BS><BS><Esc>')
-- Escape into Normal mode from Terminal mode
require('mini.keymap').map_combo('t', 'jk', '<BS><BS><C-\\><C-n>')
require('mini.keymap').map_combo('t', 'kj', '<BS><BS><C-\\><C-n>')
Fix previous spelling mistake
Fix previous spelling mistake (see [s and z=) without manually leaving Insert mode:
local action = '<BS><BS><Esc>[s1z=gi<Right>'
require('mini.keymap').map_combo('i', 'kk', action)
Hide search highlighting
Use double <Esc><Esc> to execute :nohlsearch. Although this can also be done with nmap <Esc> <Cmd>nohl<CR>
, the combo approach also exists and can be used to free <Esc> mapping in Normal mode for something else.
local action = function() vim.cmd('nohlsearch') end
require('mini.keymap').map_combo({ 'n','i','x','c' }, '<Esc><Esc>', action)
setup()
MiniKeymap.setup
({config})
Module setup
Parameters
{config} (table|nil)
Module config table. See MiniKeymap.config.
Usage
require('mini.keymap').setup({}) -- replace {} with your config table
-- needs `keymap` field present
config
MiniKeymap.config
Defaults
MiniKeymap.config = {}
map_multistep()
MiniKeymap.map_multistep
({mode}, {lhs}, {steps}, {opts})
Map multi-step action
Mapping of a multi-step action is an expression mapping (:map-expression). Executing a multi-step action is essentially:
Check condition for step one. If
true
- execute step one action and stop.Check condition for step two, and so on.
If there is no more steps, fall back to returning mapped key.
For better user experience there are many built-in steps mostly designed to create Insert mode “smart” mappings of <Tab>, <S-Tab>, <CR>, and <BS>. Available built-in steps (“For key” is a suggestion, any can be used):
┌─────────────────────┬────────────────┬──────────────────────────┬─────────┐
│ Step name │ Condition │ Action │ For key │
├─────────────────────┴────────────────┴──────────────────────────┴─────────┤
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ |ins-completion-menu| ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ pmenu_next │ Pmenu visible │ Select next (as <C-n>) │ <Tab> │
│ pmenu_prev │ Pmenu visible │ Select prev (as <C-p>) │ <S-Tab> │
│ pmenu_accept │ Item selected │ Accept (as <C-y>) │ <CR> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ |mini.snippets| ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ minisnippets_next │ Session active │ Jump to next tabstop │ <Tab> │
│ minisnippets_prev │ Session active │ Jump to prev tabstop │ <S-Tab> │
│ minisnippets_expand │ Can expand │ Expand snippet at cursor │ <Tab> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ |mini.pairs| ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ minipairs_cr │ Module set up │ <CR> respecting pairs │ <CR> │
│ minipairs_bs │ Module set up │ <BS> respecting pairs │ <BS> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ Jump around in Insert mode ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ jump_after_tsnode │ TS parser │ Jump after node end │ <Tab> │
│ jump_before_tsnode │ TS parser │ Jump before node start │ <S-Tab> │
│ jump_after_close │ Insert mode │ Jump after )]}"'` │ <Tab> │
│ jump_before_open │ Insert mode │ Jump before ([{"'` │ <S-Tab> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ Work with whitespace ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ increase_indent │ Is on indent │ Increase indent │ <Tab> │
│ decrease_indent │ Is on indent │ Decrease indent │ <S-Tab> │
│ hungry_bs │ Space to left │ Delete all space to left │ <BS> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ |vim.snippet| ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ vimsnippet_next │ Session active │ Jump to next tabstop │ <Tab> │
│ vimsnippet_prev │ Session active │ Jump to prev tabstop │ <S-Tab> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ 'hrsh7th/nvim-cmp' ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ cmp_next │ Menu visible │ Select next item │ <Tab> │
│ cmp_prev │ Menu visible │ Select prev item │ <S-Tab> │
│ cmp_accept │ Item selected │ Accept selected item │ <CR> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ 'Saghen/blink.cmp' ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ blink_next │ Menu visible │ Select next item │ <Tab> │
│ blink_prev │ Menu visible │ Select prev item │ <S-Tab> │
│ blink_accept │ Item selected │ Accept selected item │ <CR> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ 'L3MON4D3/LuaSnip' ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ luasnip_next │ Session active │ Jump to next tabstop │ <Tab> │
│ luasnip_prev │ Session active │ Jump to prev tabstop │ <S-Tab> │
│ luasnip_expand │ Can expand │ Expand snippet at cursor │ <Tab> │
├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ 'windwp/nvim-autopairs' ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
│ nvimautopairs_cr │ Module present │ <CR> respecting pairs │ <CR> │
│ nvimautopairs_bs │ Module present │ <BS> respecting pairs │ <BS> │
└─────────────────────┴────────────────┴──────────────────────────┴─────────┘
Notes:
Executing action has limitations of :map-expression (like not allowed text or buffer changes, etc.). To execute complex lua code, use vim.schedule() inside action, return the code as string in :map-cmd format, or return a function to be later executed. See usage examples.
Some mapped keys (like <Tab>, <CR>) might require disabling smart presets in plugins (like ‘nvim-cmp’, ‘blink-cmp’, ‘nvim-autopairs’).
Parameters
{mode} (string|table)
Same as for vim.keymap.set().
{lhs} (string)
Same as for vim.keymap.set().
{steps} (table)
Array of steps. Each step can be a string with the name of built-in step or a table with two callable methods (will be called without arguments):
<condition> - return
true
if the action should be executed.<action> - action to be executed if <condition> returns
true
. For more flexibility, it can also return a value which can be:String - will be returned as expression output. Can be something like
"<Tab>"
(treat as <Tab> key) or"<Cmd>lua vim.notify('Hello')<CR>"
. Should not escape keycodes (i.e. return “<Tab>” and not “). To undo already done escape, use keytrans().Function - will be executed as if
"<Cmd>lua f()<CR>"
, but does not need to create a global function for that.false
- do not stop going through steps.
{opts} (table|nil)
Same as for vim.keymap.set().
Usage
See MiniKeymap-examples for practical examples.
Some illustrative examples:
_G.log = {}
local steps = {}
steps[1] = {
condition = function() table.insert(_G.log, 'C1'); return _G.cond1 end,
-- Compute and return keys. Will be emulated as pressed.
action = function() table.insert(_G.log, 'A1'); return 'hello' end,
}
steps[2] = {
condition = function() table.insert(_G.log, 'C2'); return _G.cond2 end,
-- Perform action immediately, return `false` to keep asking other steps
action = function() table.insert(_G.log, 'A2'); return false end,
}
steps[3] = {
condition = function() table.insert(_G.log, 'C3'); return _G.cond3 end,
-- Perform action later (to overcom expression mapping limitations)
action = function()
table.insert(_G.log, 'A3_1')
return function() table.insert(_G.log, 'A3_2') end
end,
}
-- Make Insert mode <Tab> mapping
require('mini.keymap').map_multistep('i', '<Tab>', steps)
-- Pressing <Tab> inserts fallback `\t`; logs C1+C2+C3
_G.cond1, _G.cond2, _G.cond3 = false, false, false
-- Pressing <Tab> inserts `hello`; logs C1+A1
_G.cond1, _G.cond2, _G.cond3 = true, false, false
-- Pressing <Tab> inserts nothing; logs C1+C2+A2+C3+A3_1+A3_2
_G.cond1, _G.cond2, _G.cond3 = false, true, true
gen_step
MiniKeymap.gen_step
Generate step for multi-step mappings
This is a table with function elements. Call to actually get a step.
gen_step.search_pattern()
MiniKeymap.gen_step.search_pattern
({pattern}, {flags}, {opts})
Search pattern step
Use search() to jump to pattern match. Possibly adjust final position to be just to the right of the match (useful in Insert mode).
Parameters
{pattern} (string)
Same as for search().
{flags} (string|nil)
Same as for search().
{opts} (table|nil)
Options. Possible fields:
Return
(table)
Step which searches pattern.
Usage
Built-in MiniKeymap.map_multistep() steps “jump_after_close” and “jump_before_open” use this, but only in Insert mode.
Steps that jump before/after all consecutive brackets in several modes:
local keymap = require('mini.keymap')
local tab_step_insert = keymap.gen_step.search_pattern(
-- Need to use 'c' flag and 'after' side for robust "chaining"
[=[[)\]}]\+]=], 'ceW', { side = 'after' }
)
keymap.map_multistep('i', '<Tab>', { tab_step_insert })
local tab_step = keymap.gen_step.search_pattern([=[[)\]}]\+]=], 'eW')
keymap.map_multistep({ 'n', 'x' }, '<Tab>', { tab_step })
local stab_step = keymap.gen_step.search_pattern([=[[(\[{]\+]=], 'bW')
keymap.map_multistep({ 'i', 'n', 'x' }, '<S-Tab>', { stab_step })
map_combo()
MiniKeymap.map_combo
({mode}, {lhs}, {action}, {opts})
Map combo post action
Create a combo: sequence of keys where each acts immediately plus execute an extra action if all are typed within configurable delay between each other.
Example for Insert mode “better escape” jk
combo with <BS><BS><Esc>
action:
Press
j
. It is visible immediately without any side effects.Quickly (no more than default 200 ms after) press
k
. This triggers the action which is equivalent to typing <BS><BS> (delete already presentjk
) and <Esc> to exit into Normal mode.
Notes:
IMPORTANT! Combo is not a regular mapping but a separate key tracking with vim.on_key(). This is important as combos will not be visible and can not be managed as regular mappings. Instead each combo is associated with a dedicated namespace (named for human readability). However, it is not really expected to manage them on the fly after they are created.
String action is executed with nvim_input(), i.e. emulated keys will respect custom mappings.
Different combos are tracked and act independent of each other. For example, if there are combos for
jjk
andjk
keys, fast typingjjk
will execute both.Neovim>=0.11 is recommended due to vim.on_key() improvement to allow watching for keys as they are typed and not as if coming from mappings. For example, this matters when creating a
jk
combo for Visual mode while also havingxnoremap j gj
style of remaps. On Neovim<0.11 the fix is to usegjgk
as combo’s left hand side.Each combo adds very small but non-zero overhead on each keystroke. Usually about 1-3 microseconds (i.e. 0.001-0.003 ms), which should be fast enough for most setups. For a “normal, real world” coding session with a total of ~20000 keystrokes it results in extra ~40ms of overhead for a single created combo. Create many combos with caution.
Parameters
{mode} (string|table)
String or array of string mode id (like “n”, “i”, etc.). Array of several modes is more performant than several single mode combos.
{lhs} (string|table)
String with tracked key sequence or an array of tracked keys (one element - one key).
{action} (string|function)
Action to perform after key sequence is detected. If string, treated as keys and emulated with nvim_input(). If function, executed in vim.schedule(). Can return string keys which will be emulated.
{opts} (table|nil)
Options. Possible fields:
- <delay>
(number)
- delay in milliseconds within which keys should be pressed to detect a key sequence. Default: 200.
Usage
See MiniKeymap-examples for practical examples.
Some illustrative examples:
local map_combo = require('mini.keymap').map_combo
-- In Insert mode pressing `x` followed by `x` within 1 second logs 'A'
-- and emulates extra pressing of `yy`
_G.log = {}
local action = function() table.insert(_G.log, 'A'); return 'yy' end
('i', 'xx', action, { delay = 1000 }) map_combo