Skip to content

About

Time-based key event detection for Neovim.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

39 Commits

Folders and files

Repository files navigation

keyevent.nvim

CI codecov GitHub release License: MIT Neovim

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.

Features

  • 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_key and key mappings
  • Supports multiple event callbacks
  • Provides a diagnosis command for measuring key repeat timing

Installation

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,
}

How it works

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.

Key events

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.

Using events

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.

Using key mappings

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().

Key sequences

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

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.

Diagnosis

Run:

:KeyEvent diagnosis

A 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.

Configuration

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 starts
  • interval — 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.

Examples

Several examples are included with keyevent.nvim, ranging from basic event handling to more advanced key sequence matching.

on_event()

Use on_event() to react to the generated event stream.

require("example.on_event")

This example lets you leave Insert mode by typing kj.

keymap_event()

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.

match()

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.

rush.nvim

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.

Why?

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.

Requirements

  • Neovim 0.10+
  • A system with key repeat support

Limitations

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 diagnosis

to inspect the timing available in your environment.

License

MIT

About

Time-based key event detection for Neovim.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages