Open-Source Wikis

/

Neovim

/

Systems

/

Autocmds and editor events

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 surface

Key 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 callbacks

The 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:

  1. Looks up the event's AutoCmdVec.
  2. For each registered autocmd, checks whether its pattern matches the file name.
  3. Runs each matching AutoCmd in registration order.
  4. Restores any state mutated by callbacks (e.g., the cursor position can be restored after BufRead if :doautocmd ... ++nested is 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_refLuaRef 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 LuaRef in AutoCmd. Invoked via nlua_call_ref.
  • Vimscript callbackscmd is stored as a string and run via do_cmdline_cmd.
  • Pattern matching — uses match_file_pat in autocmd.c, which handles glob plus <buffer>, <buffer=N>.
  • Mode eventsModeChanged is fired from state.c whenever the global State changes.
  • API — every nvim_* function that mutates buffer text triggers TextChanged* via the change tracker.

Entry points for modification

  • Add a new event. Edit src/nvim/auevents.lua, mark nvim_specific if it's not in Vim, fire it via aucmd_defer(), document it in runtime/doc/autocmd.txt. Define the data shape in runtime/lua/vim/_meta/events.lua.
  • Hook an existing event. Use nvim_create_autocmd from a plugin. From C, call apply_autocmds() (synchronous) or aucmd_defer() (preferred).
  • Inspect what's registered. nvim_get_autocmds returns 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.

Autocmds and editor events – Neovim wiki | Factory