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, notchar* p. snake_casefor functions and variables.PascalCasefor types.UPPER_CASEfor 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.hand are autogenerated from// XXXcomments bysrc/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
mallocfamily withxmalloc/xcalloc/xrealloc/xfree(src/nvim/memory.c) which abort on OOM rather than returning NULL. Use those. - Don't use bare
strdup. Usexstrduporxstrnsave. - Strings.
StringBuilder(src/nvim/lib/) is the preferred growable buffer. - Lists.
kvec.h(vendored from klib) is the default. Older code usesgarray(src/nvim/garray.c); new code should not. - Linked lists. Only when an intrusive list is genuinely required.
src/nvim/lib/queue_defs.his 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:
- Return-value errors — return
bool/int/-1. Old idiom; still pervasive inregexp.c,option.c, etc. Error *out-parameter — used by every API function. Set withapi_set_error(err, kErrorTypeValidation, "...")and checked withERROR_SET(err).- 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(functionsnlua_push_*,nlua_pop_*). - C
Object↔ msgpack-rpc:src/nvim/api/private/helpers.cand the packer/unpacker insrc/nvim/msgpack_rpc/. - Vimscript
typval_T↔Object:src/nvim/eval/typval.c(object_to_vim,vim_to_object).
When you add a new API function:
- 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. - Document with a doc-comment that the parser in
src/gen/cdoc_parser.luaunderstands. - Run
make docto regenerateruntime/doc/api.txt. - 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.luais private;runtime/lua/vim/lsp/buf.luais public. - Modules that need to be available before
VIMRUNTIMEis valid live inruntime/lua/vim/_core/and are precompiled to bytecode. Modify them and rebuild — or use--luamod-devto bypass the compile step. - New plugin modules go into the
nvimnamespace:require('nvim.foo')reads fromruntime/lua/nvim/foo.lua. Oldlua/<name>.luaplugins 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:
- Full Lua. Implement in
runtime/lua/vim/_core/vimfn.luaand register the function insrc/nvim/eval.luawithfunc_lua = true. Lua callers (vim.fn.xx()) get the fast path; Vimscript callers go through the conversion bridge. - Partial Lua. Implement in C; the C function calls into Lua via
nlua_call_vimfn/nlua_call_excmd. - Legacy C. Implement entirely in C. Add the entry to
src/nvim/eval.lua(vimfn) orsrc/nvim/ex_cmds.lua(excmd) and the function insrc/nvim/eval/funcs.cor the appropriateex_*.cfile.
A new editor event (autocmd)
- Register the name in
src/nvim/auevents.lua. Mark Nvim-only events in thenvim_specifictable. - Fire it via
aucmd_defer()from C (preferred — deferred and predictable) orvim.api.nvim_exec_autocmds()from Lua. - Define the event-data type in
runtime/lua/vim/_meta/events.lua. - 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.