Open-Source Wikis

/

Neovim

/

How to contribute

/

Patterns and conventions

neovim/neovim

Patterns and conventions

Cross-cutting rules that the C and Lua sides both follow. The canonical references are runtime/doc/dev_style.txt, runtime/doc/dev.txt, and runtime/doc/dev_arch.txt.

C style

The hard rules are in src/uncrustify.cfg. The notable ones:

  • 2-space indent. No tabs in C source.
  • Braces on the same line for functions and control flow.
  • One declaration per line. Initialize at the point of declaration.
  • Pointer asterisk attaches to the variable: char *p, not char* p.
  • snake_case for functions and variables. PascalCase for types. UPPER_CASE for macros and enum members.
  • Function names are prefixed by the module they live in (os_, nlua_, tv_, mt_, xfree_, …) so a global symbol's home is grep-able.
  • Public-from-the-module headers are *.h. Private generated declarations are *.h.generated.h. Static-function declarations are *.c.generated.h and are autogenerated from // XXX comments by src/gen/.

make formatc will fix most violations. make lintc runs clang-tidy with the project's .clang-tidy and uncrustify in check mode. CI uses -Werror so any warning is fatal.

Allocations

  • Always check allocations. Neovim wraps malloc family with xmalloc/xcalloc/xrealloc/xfree (src/nvim/memory.c) which abort on OOM rather than returning NULL. Use those.
  • Don't use bare strdup. Use xstrdup or xstrnsave.
  • Strings. StringBuilder (src/nvim/lib/) is the preferred growable buffer.
  • Lists. kvec.h (vendored from klib) is the default. Older code uses garray (src/nvim/garray.c); new code should not.
  • Linked lists. Only when an intrusive list is genuinely required. src/nvim/lib/queue_defs.h is the implementation.
  • Strings used by the API. When returning a String (src/nvim/api/private/defs.h), always allocate; the dispatcher frees it.

Errors

C functions that can fail come in three flavors:

  1. Return-value errors — return bool/int/-1. Old idiom; still pervasive in regexp.c, option.c, etc.
  2. Error * out-parameter — used by every API function. Set with api_set_error(err, kErrorTypeValidation, "...") and checked with ERROR_SET(err).
  3. Throw via emsg()emsg("E…: …") queues an error message and sets the global error state. Used inside the editor core; the dispatcher catches the resulting state and converts to API errors when calling from RPC/Lua.

API functions must use Error *. Internal functions inside the editor use emsg so that user-facing messages are formatted consistently and respect :silent!.

The vim.api boundary

Everything that crosses C↔Lua and C↔RPC goes through Object (src/nvim/api/private/defs.h). The conversions are:

  • C Object ↔ Lua: src/nvim/lua/converter.c (functions nlua_push_*, nlua_pop_*).
  • C Object ↔ msgpack-rpc: src/nvim/api/private/helpers.c and the packer/unpacker in src/nvim/msgpack_rpc/.
  • Vimscript typval_TObject: src/nvim/eval/typval.c (object_to_vim, vim_to_object).

When you add a new API function:

  1. Write it in src/nvim/api/<area>.c. Argument types and return type must be one of the API types: Boolean, Integer, Float, String, Buffer, Window, Tabpage, Object, ArrayOf(...), DictionaryOf(...), LuaRef.
  2. Document with a doc-comment that the parser in src/gen/cdoc_parser.lua understands.
  3. Run make doc to regenerate runtime/doc/api.txt.
  4. Add a functional test under test/functional/api/.

The dispatcher and the Lua wrapper are auto-generated by src/gen/gen_api_dispatch.lua. You don't need to touch any of the dispatch tables by hand.

"api-fast" annotation

Functions marked // api-fast (or nogen + fast flag) may be called from any context, including inside libuv callbacks before the editor has finished a key. Constraints:

  • May not call os_breakcheck() directly or transitively.
  • May not yield to the user or trigger autocmds.
  • Must be reentrant.

If you can't promise these, do not annotate the function as fast. The penalty for getting it wrong is non-deterministic crashes; the dispatcher does not enforce the contract.

Lua style

The hard rules are in .stylua.toml plus the linter configs (.luacheckrc, selene.toml).

  • 2-space indent.
  • Single quotes for strings.
  • Functions are documented with LuaCATS annotations (---@param, ---@return, ---@type).
  • Internal-only modules have a leading underscore in their filename: runtime/lua/vim/lsp/_capability.lua is private; runtime/lua/vim/lsp/buf.lua is public.
  • Modules that need to be available before VIMRUNTIME is valid live in runtime/lua/vim/_core/ and are precompiled to bytecode. Modify them and rebuild — or use --luamod-dev to bypass the compile step.
  • New plugin modules go into the nvim namespace: require('nvim.foo') reads from runtime/lua/nvim/foo.lua. Old lua/<name>.lua plugins are legacy.

Adding things

Quick recipes for the most common additions. Each section in runtime/doc/dev_arch.txt has the canonical version.

A new Ex command or f_xx function

Three options, in order of preference:

  1. Full Lua. Implement in runtime/lua/vim/_core/vimfn.lua and register the function in src/nvim/eval.lua with func_lua = true. Lua callers (vim.fn.xx()) get the fast path; Vimscript callers go through the conversion bridge.
  2. Partial Lua. Implement in C; the C function calls into Lua via nlua_call_vimfn / nlua_call_excmd.
  3. Legacy C. Implement entirely in C. Add the entry to src/nvim/eval.lua (vimfn) or src/nvim/ex_cmds.lua (excmd) and the function in src/nvim/eval/funcs.c or the appropriate ex_*.c file.

A new editor event (autocmd)

  1. Register the name in src/nvim/auevents.lua. Mark Nvim-only events in the nvim_specific table.
  2. Fire it via aucmd_defer() from C (preferred — deferred and predictable) or vim.api.nvim_exec_autocmds() from Lua.
  3. Define the event-data type in runtime/lua/vim/_meta/events.lua.
  4. Document it in runtime/doc/autocmd.txt.

A new UI event

Add the prototype to src/nvim/api/ui_events.in.h. The generator (src/gen/gen_api_ui_events.lua) produces the wrapper functions and the metadata. Bump NVIM_API_LEVEL if it hasn't already been bumped this cycle.

A new option

Add an entry to src/nvim/options.lua. The generator emits the C declarations; the implementation goes in src/nvim/option.c or src/nvim/optionstr.c. The doc text goes in the same Lua entry and is rendered to runtime/doc/options.txt by make doc.

Filesystem paths

From runtime/doc/dev_arch.txt: "/ separators are used everywhere except at the edges, i.e. the separator chars are converted just-in-time, and only when absolutely needed." Windows-only conversion code lives in src/nvim/path.c and src/nvim/os/fs.c. New code should use forward slashes.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Neovim wiki | Factory