Awesome
๐ฅ Which Key
WhichKey helps you remember your Neovim keymaps, by showing available keybindings in a popup as you type.
โจ Features
- ๐ Key Binding Help: show available keybindings in a popup as you type.
- โจ๏ธ Modes: works in normal, insert, visual, operator pending, terminal and command mode. Every mode can be enabled/disabled.
- ๐ ๏ธ Customizable Layouts: choose from
classic
,modern
, andhelix
presets or customize the window. - ๐ Flexible Sorting: sort by
local
,order
,group
,alphanum
,mod
,lower
,icase
,desc
, ormanual
. - ๐จ Formatting: customizable key labels and descriptions
- ๐ผ๏ธ Icons: integrates with mini.icons and nvim-web-devicons
- โฑ๏ธ Delay: delay is independent of
timeoutlen
- ๐ Plugins: built-in plugins for marks, registers, presets, and spelling suggestions
- ๐ Operators, Motions, Text Objects: help for operators, motions and text objects
- ๐ Hydra Mode: keep the popup open until you hit
<esc>
โก๏ธ Requirements
- Neovim >= 0.9.0
- for proper icons support:
- mini.icons (optional)
- nvim-web-devicons (optional)
- a Nerd Font (optional)
๐ฆ Installation
Install the plugin with your package manager:
lazy.nvim
{
"folke/which-key.nvim",
event = "VeryLazy",
opts = {
-- your configuration comes here
-- or leave it empty to use the default settings
-- refer to the configuration section below
},
keys = {
{
"<leader>?",
function()
require("which-key").show({ global = false })
end,
desc = "Buffer Local Keymaps (which-key)",
},
},
}
โ๏ธ Configuration
[!important] Make sure to run
:checkhealth which-key
if something isn't working properly
WhichKey is highly configurable. Expand to see the list of all the default options below.
<details><summary>Default Options</summary> <!-- config:start -->---@class wk.Opts
local defaults = {
---@type false | "classic" | "modern" | "helix"
preset = "classic",
-- Delay before showing the popup. Can be a number or a function that returns a number.
---@type number | fun(ctx: { keys: string, mode: string, plugin?: string }):number
delay = function(ctx)
return ctx.plugin and 0 or 200
end,
---@param mapping wk.Mapping
filter = function(mapping)
-- example to exclude mappings without a description
-- return mapping.desc and mapping.desc ~= ""
return true
end,
--- You can add any mappings here, or use `require('which-key').add()` later
---@type wk.Spec
spec = {},
-- show a warning when issues were detected with your mappings
notify = true,
-- Enable/disable WhichKey for certain mapping modes
modes = {
n = true, -- Normal mode
i = true, -- Insert mode
x = true, -- Visual mode
s = true, -- Select mode
o = true, -- Operator pending mode
t = true, -- Terminal mode
c = true, -- Command mode
},
plugins = {
marks = true, -- shows a list of your marks on ' and `
registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
-- the presets plugin, adds help for a bunch of default keybindings in Neovim
-- No actual key bindings are created
spelling = {
enabled = true, -- enabling this will show WhichKey when pressing z= to select spelling suggestions
suggestions = 20, -- how many suggestions should be shown in the list?
},
presets = {
operators = true, -- adds help for operators like d, y, ...
motions = true, -- adds help for motions
text_objects = true, -- help for text objects triggered after entering an operator
windows = true, -- default bindings on <c-w>
nav = true, -- misc bindings to work with windows
z = true, -- bindings for folds, spelling and others prefixed with z
g = true, -- bindings for prefixed with g
},
},
---@type wk.Win
win = {
-- don't allow the popup to overlap with the cursor
no_overlap = true,
-- width = 1,
-- height = { min = 4, max = 25 },
-- col = 0,
-- row = math.huge,
-- border = "none",
padding = { 1, 2 }, -- extra window padding [top/bottom, right/left]
title = true,
title_pos = "center",
zindex = 1000,
-- Additional vim.wo and vim.bo options
bo = {},
wo = {
-- winblend = 10, -- value between 0-100 0 for fully opaque and 100 for fully transparent
},
},
layout = {
width = { min = 20 }, -- min and max width of the columns
spacing = 3, -- spacing between columns
align = "left", -- align columns left, center or right
},
keys = {
scroll_down = "<c-d>", -- binding to scroll down inside the popup
scroll_up = "<c-u>", -- binding to scroll up inside the popup
},
---@type (string|wk.Sorter)[]
--- Add "manual" as the first element to use the order the mappings were registered
--- Other sorters: "desc"
sort = { "local", "order", "group", "alphanum", "mod", "lower", "icase" },
---@type number|fun(node: wk.Node):boolean?
expand = 1, -- expand groups when <= n mappings
-- expand = function(node)
-- return not node.desc -- expand all nodes without a description
-- end,
---@type table<string, ({[1]:string, [2]:string}|fun(str:string):string)[]>
replace = {
key = {
function(key)
return require("which-key.view").format(key)
end,
-- { "<Space>", "SPC" },
},
desc = {
{ "<Plug>%((.*)%)", "%1" },
{ "^%+", "" },
{ "<[cC]md>", "" },
{ "<[cC][rR]>", "" },
{ "<[sS]ilent>", "" },
{ "^lua%s+", "" },
{ "^call%s+", "" },
{ "^:%s*", "" },
},
},
icons = {
breadcrumb = "ยป", -- symbol used in the command line area that shows your active key combo
separator = "โ", -- symbol used between a key and it's label
group = "+", -- symbol prepended to a group
ellipsis = "โฆ",
--- See `lua/which-key/icons.lua` for more details
--- Set to `false` to disable keymap icons
---@type wk.IconRule[]|false
rules = {},
-- use the highlights from mini.icons
-- When `false`, it will use `WhichKeyIcon` instead
colors = true,
-- used by key format
keys = {
Up = "๏ข ",
Down = "๏ฃ ",
Left = "๏ ",
Right = "๏ก ",
C = "๓ฐด ",
M = "๓ฐต ",
S = "๓ฐถ ",
CR = "๓ฐ ",
Esc = "๓ฑท ",
ScrollWheelDown = "๓ฑ ",
ScrollWheelUp = "๓ฑ ",
NL = "๓ฐ ",
BS = "โซ",
Space = "๓ฑ ",
Tab = "๓ฐ ",
},
},
show_help = true, -- show a help message in the command line for using WhichKey
show_keys = true, -- show the currently pressed key and its label as a message in the command line
-- Which-key automatically sets up triggers for your mappings.
-- But you can disable this and setup the triggers yourself.
-- Be aware, that triggers are not needed for visual and operator pending mode.
triggers = true, -- automatically setup triggers
disable = {
-- disable WhichKey for certain buf types and file types.
ft = {},
bt = {},
-- disable a trigger for a certain context by returning true
---@type fun(ctx: { keys: string, mode: string, plugin?: string }):boolean?
trigger = function(ctx)
return false
end,
},
debug = false, -- enable wk.log in the current directory
}
<!-- config:end -->
</details>
โจ๏ธ Setup
WhichKey automatically gets the descriptions of your keymaps from the desc
attribute of the keymap. So for most use-cases, you don't need to do anything else.
However, the mapping spec is still useful to configure group descriptions and mappings that don't really exist as a regular keymap.
[!WARNING] The mappings spec changed in
v3
, so make sure to only use the newadd
method if you updated your existing mappings.
Mappings can be added as part of the config opts.spec
, or can be added later
using require("which-key").add()
.
wk.add()
can be called multiple times from anywhere in your config files.
A mapping has the following attributes:
- [1]: (
string
) lhs (required) - [2]: (
string|fun()
) rhs (optional): when present, it will create the mapping - desc: (
string|fun():string
) description (required for non-groups) - group: (
string|fun():string
) group name (optional) - mode: (
string|string[]
) mode (optional, defaults to"n"
) - cond: (
boolean|fun():boolean
) condition to enable the mapping (optional) - hidden: (
boolean
) hide the mapping (optional) - icon: (
string|wk.Icon|fun():(wk.Icon|string)
) icon spec (optional) - any other option valid for
vim.keymap.set
. These are only used for creating mappings.
When desc
, group
, or icon
are functions, they are evaluated every time
the popup is shown.
local wk = require("which-key")
wk.add({
{ "<leader>f", group = "file" }, -- group
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find File", mode = "n" },
{ "<leader>fb", function() print("hello") end, desc = "Foobar" },
{ "<leader>fn", desc = "New File" },
{ "<leader>f1", hidden = true }, -- hide this keymap
{
-- Nested mappings are allowed and can be added in any order
-- Most attributes can be inherited or overridden on any level
-- There's no limit to the depth of nesting
mode = { "n", "v" }, -- NORMAL and VISUAL mode
{ "<leader>q", "<cmd>q<cr>", desc = "Quit" }, -- no need to specify mode since it's inherited
{ "<leader>w", "<cmd>w<cr>", desc = "Write" },
}
})
๐จ Icons
[!note] For full support, you need to install either mini.icons or nvim-web-devicons
There's multiple ways to set icons for your keymaps:
- if you use lazy.nvim, then some icons will be autodetected for keymaps belonging to certain plugins.
- custom rules to decide what icon to use
- in your mapping spec, you can specify what icon to use at any level, so at the node for
<leader>g
for example, to apply to all git keymaps.
The icon
attribute of a mapping can be a string
, which will be used as the actual icon,
or an wk.Icon
object, which can have the following attributes:
icon
(string
): the icon to use (optional)hl
(string
): the highlight group to use for the icon (optional)color
(string
): the color to use for the icon (optional) valid colors are:azure
,blue
,cyan
,green
,grey
,orange
,purple
,red
,yellow
cat
(string
): the category of the icon (optional) valid categories are:file
,filetype
,extension
name
(string
): the name of the icon in the specified category (optional)
[!TIP] If you'd rather not use icons, you can disable them by setting
opts.icons.rules
tofalse
.
๐ Usage
When the WhichKey popup is open, you can use the following key bindings (they are also displayed at the bottom of the screen):
- hit one of the keys to open a group or execute a key binding
<esc>
to cancel and close the popup<bs>
go up one level<c-d>
scroll down<c-u>
scroll up
๐ Hydra Mode
Hydra mode is a special mode that keeps the popup open until you hit <esc>
.
-- Show hydra mode for changing windows
require("which-key").show({
keys = "<c-w>",
loop = true, -- this will keep the popup open until you hit <esc>
})
๐ฅ Plugins
Four built-in plugins are included with WhichKey.
Presets
Built-in key binding help for motions
, text-objects
, operators
, windows
, nav
, z
and g
and more.
Marks
Shows a list of your buffer local and global marks when you hit ` or '
Registers
Shows a list of your buffer local and global registers when you hit " in NORMAL mode, or <c-r>
in INSERT mode.
Spelling
When enabled, this plugin hooks into z=
and replaces the full-screen spelling suggestions window by a list of suggestions within WhichKey.
๐จ Colors
The table below shows all the highlight groups defined for WhichKey with their default link.
<!-- colors:start -->Highlight Group | Default Group | Description |
---|---|---|
WhichKey | Function | |
WhichKeyBorder | FloatBorder | Border of the which-key window |
WhichKeyDesc | Identifier | description |
WhichKeyGroup | Keyword | group name |
WhichKeyIcon | @markup.link | icons |
WhichKeyIconAzure | Function | |
WhichKeyIconBlue | DiagnosticInfo | |
WhichKeyIconCyan | DiagnosticHint | |
WhichKeyIconGreen | DiagnosticOk | |
WhichKeyIconGrey | Normal | |
WhichKeyIconOrange | DiagnosticWarn | |
WhichKeyIconPurple | Constant | |
WhichKeyIconRed | DiagnosticError | |
WhichKeyIconYellow | DiagnosticWarn | |
WhichKeyNormal | NormalFloat | Normal in th which-key window |
WhichKeySeparator | Comment | the separator between the key and its description |
WhichKeyTitle | FloatTitle | Title of the which-key window |
WhichKeyValue | Comment | values by plugins (like marks, registers, etc) |