Open-Source Wikis

/

Neovim

/

Systems

/

Lua bridge

neovim/neovim

Lua bridge

Purpose

The Lua bridge is the C glue that makes LuaJIT a first-class scripting environment for Neovim. It hosts the vim.* namespace, converts data between Lua values, Vimscript typvals, and API Objects, and ships C bindings for performance-critical capabilities (treesitter, xdiff, base64, libuv via vim.uv). The Lua functions written on top of this bridge — the stdlib — live in runtime/lua/vim/; that is covered separately under Features.

Directory layout

src/nvim/lua/
├── executor.c (~70k bytes)     Lua state setup, error handling, call helpers
├── executor.h
├── converter.c (~44k bytes)    typval ↔ Object ↔ Lua conversions
├── converter.h
├── treesitter.c (~50k bytes)   tree-sitter language/parser/tree/node bindings
├── treesitter.h
├── stdlib.c (~21k bytes)       Misc Lua-side helpers (vim.iconv, vim.spell.check, ...)
├── stdlib.h
├── api_wrappers.c              Auto-generated Lua wrappers for nvim_* (registered)
├── base64.c, base64.h          vim.base64
├── xdiff.c, xdiff.h            vim.diff (libxdiff binding)
├── secure.c, secure.h          The 'secure' modeline mode for Lua
└── spell.c, spell.h            vim.spell helpers

Key abstractions

Type / function File Description
lua_State * LuaJIT The single editor-wide Lua state. Created in nlua_init.
nlua_init executor.c Boot the Lua state, register vim, load _core/init.lua.
nlua_call_ref, nlua_call_ref_with executor.c Invoke a LuaRef (registry handle) with arguments.
nlua_call executor.c Fast-path call from Lua to a Lua-implemented vimfn.
nlua_push_* / nlua_pop_* converter.c Convert between Lua and Object.
LuaRef api/private/defs.h An integer key into the Lua registry. The "function pointer" the API exposes.

How it works

The Lua state is created once at startup. _core/init.lua is run synchronously to install the vim.* namespace; everything else is loaded lazily. There is one state per editor instance — no separate sandboxes.

graph LR
    subgraph C["C side"]
        Editor[Editor core]
        API[api/*.c]
    end
    subgraph Lua["Lua side"]
        VIM[vim.*]
        Plugins[user plugins]
    end
    Editor -->|nlua_call_ref| Lua
    API -->|generated wrappers<br/>vim.api.*| Lua
    VIM -->|vim.api.nvim_*| C
    VIM -->|vim.fn.*| C
    Plugins --> VIM
    Plugins --> C

A handful of patterns recur:

  • vim.api.<name> — bound by api_wrappers.c (generated by gen_api_dispatch.lua). Each API function gets a Lua-callable shim that pops args, converts to Object, calls the C implementation, and pushes the result back.
  • vim.fn.<name> — calls a Vimscript built-in. Goes through nlua_call_vimfn in executor.c. For Lua-implemented vimfns there is a fast path that skips the typval conversion.
  • vim.uv — exposed by luv. The same uv_loop_t that drives the editor; Lua callbacks must coordinate with the editor (via vim.schedule) when they touch editor state.
  • Calls from C into Lua — keep a LuaRef (integer registry key) in C, invoke via nlua_call_ref. The decoration provider, nvim_create_autocmd callback, and vim.on_key use this.

Conversions

converter.c is where the bulk of the bridge work happens. The three conversion paths and where they live:

From To Function
Lua → Object API nlua_pop_Object
Object → Lua API nlua_push_Object
typval → Lua vimfn nlua_push_typval
Lua → typval vimfn nlua_pop_typval
typval → Object eval ↔ API bridge object_to_vim (in eval/typval.c)

Edge cases the converter handles:

  • Lua tables that are arrays vs maps — decided by checking lua_objlen and key types.
  • Functions and userdata in arrays — fail with a clear error.
  • NaN and infinity in floats — preserved.
  • Cycles — detected and rejected.
  • vim.NIL — a sentinel for nil that survives roundtrips through the converter (Lua's nil would otherwise drop dict keys).

executor.c

The biggest file in lua/. It does:

  • Lua state lifecycle (init, teardown).
  • Error formatting — Lua errors are caught, formatted with the call stack, and surfaced via :messages and the log.
  • The pcall-wrapping helpers (nlua_pcall, nlua_pcall_with_msg).
  • The "fast event" Lua callback path used by decoration providers and other api-fast Lua callbacks.
  • Lua's print is overridden to go through :echo so output works in --headless.

treesitter.c

50 KB of bindings between the C tree-sitter library and Lua. Exposes vim.treesitter._parser, _tree, _node, _query, _query_cursor. The Lua-side wrappers in runtime/lua/vim/treesitter/ implement the higher-level abstractions (LanguageTree, the highlighter, query linter).

The C side is responsible for:

  • Loading parser shared objects (tree-sitter-lua.so, etc.).
  • Memory ownership: parsers and trees are reference-counted Lua userdata.
  • Edit propagation: when the buffer changes, the language tree's parser is told the byte range.
  • Query execution and capture iteration.

stdlib.c

Smaller miscellaneous bindings used by the Lua stdlib:

  • vim.spell.check — wraps the spell engine.
  • vim.iconv — wraps libiconv.
  • A handful of cryptographic-strength helpers (vim.uri_*, decode/encode).
  • vim.regex — the binding to the editor's regex engine.

xdiff.c

Binding to libxdiff (vendored under src/xdiff/) used by vim.diff for two-way diffs.

Integration points

  • API — every nvim_* is callable from Lua via vim.api.*. The wrappers are generated.
  • Vimscriptvim.fn.<name>(args) invokes a vimfn. vim.cmd("...") runs a command line.
  • Autocmdsnvim_create_autocmd accepts a Lua function as callback. Internally stored as a LuaRef.
  • Decoration providers — Lua callbacks invoked during redraw. Must be api-fast.
  • Treesitter — entirely C-backed; runtime/lua/vim/treesitter/ is the user-facing API. See Treesitter.
  • :lua, :luado, :luafile — Ex commands that drop into the bridge.
  • -l flagnvim -l script.lua args runs a Lua file as a script and exits.

Entry points for modification

  • Add a new C-implemented Lua function. Define a static int lua_foo(lua_State *L) in an appropriate *.c, register in the namespace setup (in _core/init.lua for vim.* or in executor.c#nlua_state_init for built-ins). Adding via runtime/lua/vim/... in pure Lua is preferred unless you need C performance or access to the editor core.
  • Add a new C-Lua data conversion. Edit converter.c. Tests live in test/functional/lua/api_spec.lua and test/functional/lua/buffer_spec.lua.
  • Improve treesitter bindings. treesitter.c plus the user-facing wrappers under runtime/lua/vim/treesitter/.

Key source files

File Purpose
src/nvim/lua/executor.c State setup, error handling, call helpers
src/nvim/lua/converter.c typval ↔ Object ↔ Lua conversions
src/nvim/lua/treesitter.c Tree-sitter C-level bindings
src/nvim/lua/stdlib.c Misc helpers (vim.iconv, vim.spell, ...)
src/nvim/lua/api_wrappers.c Generated Lua wrappers for the API
src/nvim/lua/xdiff.c vim.diff
src/nvim/lua/base64.c vim.base64
runtime/lua/vim/_core/init.lua Lua-side bootstrap (the first module loaded into the state)

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

Lua bridge – Neovim wiki | Factory