Time-based key event detection for Neovim.
keyevent.nvim detects keyboard input events and classifies them according to the timing of consecutive key presses.
It provides a common event layer for plugins that want to use the time axis of key input in addition to ordinary Vim key mappings.
keyevent.nvim was originally developed as part of rush.nvim, and was separated into an independent plugin so that other plugins can use the same key event detection.
For example, a sequence such as:
j
jj
jj + hold j
can be distinguished as different events depending on the timing between the keys.
The basic idea is:
A key event is more than just the key itself. Its timing can carry meaning.
- Detects key input using Neovim's
on_key - Distinguishes clicks, taps, hold starts, and repeats
- Measures the interval between key events
- Tracks repeated key counts
- Tracks hold time
- Tracks key sequences
- Handles modifier keys
- Provides the same event information from both
on_keyand key mappings - Supports multiple event callbacks
- Provides a diagnosis command for measuring key repeat timing
Using lazy.nvim:
{
"kibi2/keyevent.nvim",
config = function()
require("keyevent").setup({
threshold = {
delta = 20,
tap = 500,
},
})
-- Uncomment the following lines to try the on_key example.
-- In insert mode, type "kj" to leave insert mode.
-- require("example.on_event")
-- Uncomment the following lines to try the keymap example.
-- Press n normally to repeat the search.
-- Press and hold n to show the search history.
-- require("example.keymap")
end,
}keyevent.nvim watches keyboard input and creates a KeyEvent for each relevant key.
A simplified sequence might look like:
j → click
j → tap
j → hold
j → repeat
j → repeat
The classification is based on the interval between consecutive key events.
For example:
j
↓
j
↓
j ───────────── hold
↓
key repeats
A KeyEvent contains information such as:
key : the key that was pressed
prev_key : the previous key
meta : modifier keys held with the key
prev_meta : modifier keys held with the previous key
interval : elapsed time since the previous key event
nt : number of consecutive taps
nh : number of hold events
nr : number of repeat events
hold_start : time when the hold started
This allows another plugin to decide what the sequence means.
keyevent.nvim does not decide what a particular key sequence should do. It only provides the event information.
The main event types are:
| Event | Meaning |
|---|---|
click |
A key press separated from the previous input |
tap |
A key pressed again within the tap interval |
repeat |
A key generated by holding the previous key |
repeat_end |
The end of a key repeat |
The exact classification depends on the configured timing thresholds and the key repeat characteristics of the system.
A plugin can register an event callback with:
require("keyevent").on_event(function(event)
-- use event here
end)Multiple callbacks can be registered.
For example, an event consumer can inspect the current key:
require("keyevent").on_event(function(event)
if event.key == "j" then
-- ...
end
end)The callback receives a KeyEvent containing the timing and sequence information.
keyevent.nvim can also be used from an expression mapping.
For example:
local KeyEvent = require("keyevent.keyevent")
vim.keymap.set("n", "n", function()
local event = KeyEvent.keymap_event("n")
if event.nr == 1 and event.nt == 2 then
return ":history /<Enter>"
elseif event.nr > 1 and event.nt == 2 then
return ""
end
return "n"
end, { expr = true })This example keeps the normal behavior of n, while using a different action when n is pressed and held.
The important point is that the mapping receives the same kind of timing information that is available to on_event().
Recent key events can be obtained with:
KeyEvent.keys(10)For example, a plugin can use the key sequence to implement its own multi-key behavior.
An example included with keyevent.nvim uses this to make:
kj
leave Insert mode.
local KeyEvent = require("keyevent.keyevent")
require("keyevent").on_event(function()
if vim.fn.mode() == "i" then
if KeyEvent.keys(2, true) == "kj" then
local keys =
vim.api.nvim_replace_termcodes(
"<BS><BS><Esc>",
true,
false,
true
)
vim.api.nvim_feedkeys(keys, "n", false)
end
end
end)Timing values are measured in milliseconds.
The classification uses several timing values, including:
- key repeat interval
- hold detection interval
- tap interval
- timing tolerance (
delta)
The timing of key repeats differs between systems and keyboard configurations.
For this reason, keyevent.nvim can obtain timing information from the operating system and also measure observed input timing.
The effective value can be determined from the available configuration and timing information.
Run:
:KeyEvent diagnosisA floating window shows the timing information detected by keyevent.nvim.
It can be used to check the timing of your keyboard and compare:
- configured values
- operating system values
- analyzer values
- measured intervals
- suggested configuration
This is especially useful when the actual key input timing differs from the local operating system settings.
For example, keyboard input may be coming through a remote input system rather than directly from the local keyboard.
Press <Esc> to close the diagnosis window.
The default configuration is designed to work with the timing information available to keyevent.nvim.
A configuration can override the timing values:
require("keyevent").setup({
threshold = {
delta = 20,
tap = 500,
},
})delta defines the tolerance used when comparing measured intervals.
tap defines the maximum interval treated as a tap.
There are two additional timing parameters:
delay— the time from pressing a key until key repeat startsinterval— the interval between repeated key events
keyevent.nvim automatically obtains these values from the operating system using the following tools:
tools/macos/keyevent-macos
tools/windows/keyevent-win.exe
tools/X11/keyevent-x11
There is currently no tool for Linux Wayland, so obtaining the values from the operating system will fail in that environment.
If the operating system query fails, keyevent.nvim estimates the values statistically from observed key input.
The automatically obtained values may also be inaccurate when the same keyboard is shared between multiple computers, because the local operating system settings may not reflect the timing of the actual keyboard input.
If tap, hold, or repeat detection does not work correctly, use the diagnosis tool to measure the actual timing and specify delay and interval explicitly in your configuration.
All timing values are in milliseconds.
Several examples are included with keyevent.nvim, ranging from basic event handling to more advanced key sequence matching.
Use on_event() to react to the generated event stream.
require("example.on_event")This example lets you leave Insert mode by typing kj.
Use keymap_event() in an expression mapping.
require("example.keymap")This example behaves like the normal n command when n is tapped, and displays the search history when n is held.
Use match() to check whether recent key events match a specified sequence.
require("example.demo_scheme")This example demonstrates how to use match() to control color scheme switching and a slideshow.
To open the help window, press H in this sequence: click, tap, click, tap. Then follow the instructions displayed in the window.
A more advanced example is rush.nvim.
It changes the cursor movement distance based on how a motion key is pressed.
For example, repeated taps and holding a key can trigger different movement behaviors.
Vim key mappings normally describe the meaning of a key based on:
- the key itself
- the current mode
- the preceding key sequence
- counts and other Vim state
keyevent.nvim adds another dimension:
the time between key presses.
This makes it possible to build key mappings where:
key
key + key
key + hold
key + repeat
have different meanings without requiring every plugin to implement its own keyboard timing detection.
rush.nvim was the original motivation for this mechanism. Its motion acceleration needs to distinguish ordinary motion, repeated taps, and held-key repeats.
By separating that mechanism into keyevent.nvim, the same event model can be used by other plugins.
- Neovim 0.10+
- A system with key repeat support
The exact timing of key events depends on:
- operating system
- keyboard
- keyboard repeat settings
- terminal
- remote input systems
Therefore, timing values may need to be adjusted for a particular environment.
Use:
:KeyEvent diagnosisto inspect the timing available in your environment.
MIT