How do I register a Lua callback that fires before every keystroke to react to keyboard input?
Answer
vim.on_key()
Explanation
vim.on_key(callback, namespace) registers a Lua function that runs before Neovim processes each keystroke. This is the foundation for features that need to react to specific key presses — such as automatically clearing search highlights when you start moving, or toggling relative line numbers while holding a motion key.
How it works
-- Register a namespace to uniquely identify this handler
local ns = vim.api.nvim_create_namespace("my_handler")
vim.on_key(function(char)
-- char is the raw key sequence as a string
-- runs before Neovim processes the key
end, ns)
To remove the handler later, call vim.on_key(nil, ns) with the same namespace.
Example
Automatically clear search highlighting whenever you press a non-search key in normal mode:
local ns = vim.api.nvim_create_namespace("auto_hlsearch")
vim.on_key(function(char)
if vim.fn.mode() == "n" then
local search_keys = { "n", "N", "*", "#", "/", "?" }
local key = vim.fn.keytrans(char)
vim.opt.hlsearch = vim.tbl_contains(search_keys, key)
end
end, ns)
This keeps hlsearch on only while actively searching, and turns it off the moment you press any other key.
Tips
- The callback fires in a fast-event context — avoid calling most
vim.apifunctions directly inside it; usevim.schedule()for any API calls that are not safe in fast contexts vim.fn.keytrans(char)converts the raw character byte sequence to a human-readable key name like"<CR>"or"n"- Use separate namespaces for unrelated handlers so they can be enabled/disabled independently