vimtricks.wiki Concise Vim tricks, one at a time.

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.api functions directly inside it; use vim.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

Next

How do I use a count with text objects to operate on multiple text objects at once?