Architecture

NeoVim’s architecture splits Vim’s monolith into three layers: a C event loop core, a MsgPack RPC protocol, and Lua runtime scripting. Every UI (terminal, GUI, remote) is a client speaking the same protocol.

The Big Picture

flowchart TD
    subgraph Clients
        tui[Terminal UI]
        gui[GUI Client]
        remote[Remote Client]
    end

    rpc[MsgPack RPC]

    subgraph Core["NeoVim Core (C)"]
        api[API Layer]
        loop[Event Loop]
        buffers[Buffer and Window Mgmt]
        lua[Lua Runtime]
        vimscript[Vimscript Eval]
    end

    tui --> rpc
    gui --> rpc
    remote --> rpc
    rpc --> api
    api --> loop
    api --> buffers
    api --> lua
    api --> vimscript
  • Every UI is just a client. The terminal UI is not special from the protocol’s perspective.
  • MsgPack RPC is the seam between clients and the core.
  • The API layer is the stable entry point into the event loop, buffer state, and scripting runtimes.

Event Loop

The core runs a single-threaded event loop built on libuv. All I/O (terminal input, RPC messages, child processes, file watchers) feeds through this loop.

Source: src/nvim/event/
Key files:
  loop.c / loop.h       - Main loop structure
  multiqueue.c          - Priority event queues
  defs.h                - Event types and structs
  rstream.c / wstream.c - Read/write streams
  process.c             - Child process management
  signal.c              - Signal handling
  socket.c              - Unix/TCP socket I/O

The loop processes events from a MultiQueue - a priority-ordered set of event queues. This avoids the threading complexity that made Vim’s async story painful.

Gotcha: NeoVim is single-threaded for editor operations. Lua code runs on the main thread. Long-running Lua blocks the editor. Use vim.system() or vim.uv (libuv bindings) for async work.

MsgPack RPC

Every NeoVim instance is an RPC server. The API is defined in src/nvim/api/*.c and exposed over MsgPack-RPC. This is how GUIs, plugins, and remote clients communicate.

Source: src/nvim/msgpack_rpc/
Key files:
  channel.c      - RPC channel management
  packer.c       - MsgPack serialization
  unpacker.c     - MsgPack deserialization
  server.c       - Listen on sockets

The protocol carries three message types:

TypeDirectionPurpose
RequestClient -> NeoVimCall an API function, get a response
ResponseNeoVim -> ClientReturn value or error
NotificationEither directionFire-and-forget event

You can connect to any running NeoVim instance:

# Start NeoVim listening on a socket
nvim --listen /tmp/nvim.sock

# From another process, connect and call API
nvim --server /tmp/nvim.sock --remote-send ':echo "hello"<CR>'

Programmatically from Python, Lua, or any language with a MsgPack library:

# pip install pynvim
import pynvim
nvim = pynvim.attach('socket', path='/tmp/nvim.sock')
nvim.command('echo "hello from Python"')
buf = nvim.current.buffer
print(buf[:])  # Read all lines

API Layer

The API (src/nvim/api/) is the single source of truth for all editor operations. It has ~30 files covering:

FileScope
buffer.cBuffer operations (get/set lines, options, marks)
window.cWindow layout, cursor, options
tabpage.cTab page management
vim.cGlobal operations (commands, eval, options)
extmark.cVirtual text, highlights, decorations
autocmd.cAutocommand creation and management
command.cUser command creation
options.cOption get/set
ui.cUI protocol events

API functions are auto-generated into dispatch tables by scripts in src/gen/. When you call vim.api.nvim_buf_set_lines() from Lua, it goes through the same dispatch as a remote RPC call.

The C/Lua Bridge

LuaJIT is embedded in the C core. The bridge lives in src/nvim/lua/ (17 files):

Source: src/nvim/lua/
Key files:
  executor.c   - Lua state management, pcall wrappers
  converter.c  - C <-> Lua type conversion
  treesitter.c - Tree-sitter bindings for Lua
  spell.c      - Spell checking bindings
  secure.c     - Sandboxed execution

The Lua runtime provides the vim.* namespace - a comprehensive API surface that wraps both the C API and higher-level abstractions. See Lua API for details.

Code Generation

NeoVim generates significant code at build time. Lua scripts in src/gen/ produce:

GeneratorOutput
gen_api_dispatch.luaAPI function dispatch tables
gen_eval.luaVimscript function bindings
gen_options.luaOption definitions and metadata
gen_lsp.luaLSP protocol types from spec
gen_help_html.luaHTML help documentation
gen_declarations.luaHeader declarations
gen_char_blob.luaEmbedded Lua bytecode

This means the LSP types, API dispatch, and Vimscript bindings are always in sync with the C source.

UI Protocol

The TUI (Terminal UI) at src/nvim/tui/ is just one UI client. It connects to the core the same way an external GUI would, through the UI protocol - a stream of redraw events:

sequenceDiagram
    participant Client as UI Client
    participant Core as NeoVim Core

    Client->>Core: Attach UI over MsgPack RPC
    Core-->>Client: grid_resize / grid_line / grid_scroll
    Core-->>Client: hl_attr_define / hl_group_set
    Core-->>Client: mode_change / mode_info_set
    Core-->>Client: cmdline_show / cmdline_pos
    Core-->>Client: popupmenu_show / popupmenu_select
    Core-->>Client: msg_show / msg_clear
  • The core does not render pixels directly for external GUIs.
  • It emits redraw events; the client decides how to present them.

External GUIs (Neovide, nvim-qt, firenvim) consume these same events. This is why NeoVim can run headless and be controlled entirely over RPC.

Vim Relationship

NeoVim started as a Vim fork. The relationship today:

AspectVimNeoVim
ArchitectureMonolithic, single-processEvent loop + RPC server
ScriptingVimscript (+ Vim9script)Vimscript + Lua (first-class)
UIBuilt into the binaryProtocol-based, external GUIs
AsyncLimited (jobs, timers)libuv event loop, channels
LSPNone built-inFull client built-in
Tree-sitterNoneBuilt-in parsing and highlighting
DefaultsMinimalSensible defaults (syntax on, filetype on, etc.)

NeoVim still processes Vim patches for the shared editing core (buffer management, motions, text objects). The divergence is in everything around that core: UI, scripting, async, and tooling integration.