neovim/neovim
Autocmds and editor events
Purpose
Autocmds (autocommands, "editor events") are the primary plugin-extensibility mechanism. A user or plugin registers a callback to fire on a named event (BufRead, TextChanged, LspAttach, ...), the editor fires the event at the appropriate point, and matching callbacks run. This subsystem is also the public face of "let plugins react to editor state" — much of the Lua stdlib (vim.lsp, vim.treesitter, vim.diagnostic) is wired into the editor exclusively through autocmds.
Directory layout
src/nvim/
├── autocmd.c (~82k bytes) Registration, dispatch, pattern matching
├── autocmd.h, autocmd_defs.h
├── auevents.lua (~9k bytes) The list of every event name
└── api/autocmd.c (~29k bytes) The nvim_*_autocmd API surfaceKey abstractions
| Type / function | File | Description |
|---|---|---|
AutoPat |
autocmd_defs.h |
A pattern-bound autocmd registration. Has a list of AutoCmds. |
AutoCmd |
autocmd_defs.h |
One registration: pattern, command/callback, flags, group. |
AutoCmdVec |
autocmd_defs.h |
The flat vector of all autocmds for a given event. |
apply_autocmds |
autocmd.c |
Fire an event synchronously. Iterates matching AutoCmds. |
aucmd_defer |
autocmd.c |
Fire an event "deferred", at a safe point in the event loop. |
nvim_create_autocmd |
api/autocmd.c |
Public API. The Lua-side workhorse. |
How it works
sequenceDiagram
participant Plugin
participant API as api/autocmd.c
participant Auto as autocmd.c
participant Editor as Editor core
Plugin->>API: nvim_create_autocmd("BufRead", {pattern, callback})
API->>Auto: au_register
Auto->>Auto: append to AutoPat list
Note over Editor: later, file is read
Editor->>Auto: apply_autocmds(EVENT_BUFREAD, fname, ...)
Auto->>Auto: walk AutoPat list, match patterns
Auto->>Plugin: invoke matching callbacksThe flow has two halves: registration (one-time, via the API) and dispatch (called by the editor at well-known points).
Events
Every event has a name (e.g. BufRead) and an integer id (EVENT_BUFREAD). The set of valid events is defined in src/nvim/auevents.lua:
local M = {}
M.events = {
-- list of event names (alphabetized)
'BufAdd', 'BufDelete', 'BufEnter', ...
'CmdlineEnter', 'CmdlineLeave', ...
'CursorHold', 'CursorMoved', ...
'LspAttach', 'LspDetach', 'LspProgress', 'LspRequest', 'LspTokenUpdate', ...
'TextChanged', 'TextChangedI', 'TextChangedP', ...
}
M.nvim_specific = { -- events not present in Vim
'TermRequest', 'ChanInfo', 'ChanOpen', 'DiagnosticChanged', ...
}The build picks up this file via src/gen/gen_events.lua and produces the C tables (event_names[], the EVENT_* enum) consumed by autocmd.c.
Patterns and groups
An autocmd is registered with a pattern (file glob) and optional group. Patterns are matched against the buffer name at fire time using the same matcher as :autocmd. Groups are an organizational unit: nvim_create_augroup("MyPlugin", {clear=true}) returns an id, and any autocmds created with that group are isolated — clear=true deletes prior registrations for the group, which is the standard way for a plugin to be reloadable.
Firing
The editor fires an event by calling apply_autocmds(event_id, fname, ...). The function:
- Looks up the event's
AutoCmdVec. - For each registered autocmd, checks whether its pattern matches the file name.
- Runs each matching
AutoCmdin registration order. - Restores any state mutated by callbacks (e.g., the cursor position can be restored after
BufReadif:doautocmd ... ++nestedis in play).
The dispatch is synchronous. If the callback is a Vim command, it's run via do_cmdline_cmd. If it's a Lua function (Neovim addition), it's called via nlua_call_ref — LuaRef is one of the API types.
Deferred events
A deferred event (aucmd_defer()) doesn't fire immediately; it queues onto the main event loop. This was added to fix a class of crashes where firing autocmds during a critical section (e.g., from inside a redraw) led to reentrancy bugs.
runtime/doc/dev_arch.txt makes the recommendation explicit:
Where possible, new editor events should be implemented using
aucmd_defer()(and where possible, old events migrate to this), so they are processed in a predictable manner, which avoids crashes.
The pattern: from the C site that wants to fire the event, call aucmd_defer(EVENT_FOO, fname, data) and return; the editor will dispatch when it next pumps the loop in a safe state.
API surface
The user-facing Lua API:
-- Register an autocmd
vim.api.nvim_create_autocmd({'BufRead', 'BufNewFile'}, {
pattern = '*.lua',
group = vim.api.nvim_create_augroup('MyPlugin', { clear = true }),
callback = function(args)
-- args.id, args.event, args.match, args.buf, args.file, args.data
end,
})
-- Fire one
vim.api.nvim_exec_autocmds('User', { pattern = 'MyPluginRefresh', data = ... })
-- Delete
vim.api.nvim_clear_autocmds({ group = 'MyPlugin' })The C-side counterparts live in src/nvim/api/autocmd.c. Argument validation and dispatch into autocmd.c are uniform; the dispatcher generates the wrappers.
For Vimscript, the legacy :autocmd syntax in src/nvim/ex_docmd.c ends up in the same au_register underneath.
Common events
| Event | When it fires |
|---|---|
BufRead / BufReadPost |
After reading a file into a buffer. |
BufWrite / BufWritePost |
Before/after writing a buffer to a file. |
BufEnter / BufLeave |
Window-current buffer changed. |
BufAdd, BufNew |
New buffer added to the buffer list. |
TextChanged, TextChangedI, TextChangedP |
After buffer text changes. The variants distinguish modes. |
CursorMoved, CursorMovedI |
Cursor moved without leaving the mode. |
CursorHold, CursorHoldI |
After 'updatetime' ms with no input. |
InsertEnter / InsertLeave |
Insert-mode boundaries. |
ModeChanged |
Any mode change. The pattern matches <old>:<new>. |
FileType |
When 'filetype' is set. |
LspAttach / LspDetach / LspProgress / LspRequest |
Lifecycle of an LSP client on a buffer. |
DiagnosticChanged |
vim.diagnostic.set has updated diagnostics. |
User |
Custom; only fired by nvim_exec_autocmds. |
The full list is in src/nvim/auevents.lua and documented in runtime/doc/autocmd.txt.
Integration points
- Lua callbacks — registered as
LuaRefinAutoCmd. Invoked vianlua_call_ref. - Vimscript callbacks —
cmdis stored as a string and run viado_cmdline_cmd. - Pattern matching — uses
match_file_patinautocmd.c, which handles glob plus<buffer>,<buffer=N>. - Mode events —
ModeChangedis fired fromstate.cwhenever the globalStatechanges. - API — every
nvim_*function that mutates buffer text triggersTextChanged*via the change tracker.
Entry points for modification
- Add a new event. Edit
src/nvim/auevents.lua, marknvim_specificif it's not in Vim, fire it viaaucmd_defer(), document it inruntime/doc/autocmd.txt. Define the data shape inruntime/lua/vim/_meta/events.lua. - Hook an existing event. Use
nvim_create_autocmdfrom a plugin. From C, callapply_autocmds()(synchronous) oraucmd_defer()(preferred). - Inspect what's registered.
nvim_get_autocmdsreturns the registry. Useful in tests.
Key source files
| File | Purpose |
|---|---|
src/nvim/autocmd.c |
Registration, dispatch, pattern matching |
src/nvim/auevents.lua |
List of every event name |
src/nvim/autocmd_defs.h |
AutoPat, AutoCmd, group structs |
src/nvim/api/autocmd.c |
Public API |
src/gen/gen_events.lua |
Generator: turns auevents.lua into C tables |
runtime/doc/autocmd.txt |
User-facing docs (regenerated) |
runtime/lua/vim/_meta/events.lua |
Lua type stubs for event-data |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.