Overview
Relevant Files
README.mdsrc/nvim/main.csrc/nvim/main.hruntime/doc/dev_arch.txt
Neovim is a modern refactoring of Vim that prioritizes extensibility, maintainability, and plugin ecosystem development. The project aggressively refactors Vim's codebase to enable advanced UIs, support multiple programming languages through RPC, and maximize developer contributions.
Core Design Philosophy
Neovim separates concerns into distinct subsystems that communicate through well-defined interfaces. The architecture emphasizes:
- Decoupled UI: The built-in TUI is just one possible UI; external UIs connect via MessagePack-RPC
- Async-first: Event-driven architecture using libuv for non-blocking I/O
- Lua-first scripting: First-class Lua support alongside Vimscript
- API-driven: Comprehensive RPC API for programmatic access to editor state
Main Subsystems
Loading diagram...
Event Loop (src/nvim/event/): Built on libuv, manages all asynchronous operations including timers, I/O, and signals. The main loop never blocks indefinitely.
Editor State (src/nvim/): Core editing functionality including buffers, windows, tabs, marks, and undo/redo. Managed through the normal mode command processor.
UI Layer (src/nvim/tui/, src/nvim/ui*.c): Handles rendering and input. The built-in TUI connects to the server via RPC; external UIs do the same.
RPC Server (src/nvim/msgpack_rpc/): MessagePack-RPC implementation enabling remote procedure calls from any language.
Scripting (src/nvim/lua/, src/nvim/eval/): Lua and Vimscript execution engines with full access to editor APIs.
Startup Sequence
The main() function in src/nvim/main.c orchestrates initialization:
- Early init: Parse arguments, initialize globals, set up paths
- Event init: Create the main event loop and initialize subsystems
- Configuration: Load vimrc, plugins, and user settings
- UI setup: Start the built-in TUI or connect to remote UI
- Main loop: Enter
normal_enter()which never returns
Key Architectural Patterns
Multiqueue: Events are queued in priority order (fast events, regular events, thread events) and processed at appropriate times.
Channels: Abstract communication endpoints for RPC, processes, terminals, and stdio. Each channel has its own event queue.
Decorations: Extensible system for adding visual annotations (highlights, virtual text) without modifying core buffer storage.
Extmarks: Persistent markers in buffer text that survive edits, enabling plugins to track positions reliably.
Architecture & Core Subsystems
Relevant Files
src/nvim/event/loop.h&src/nvim/event/loop.csrc/nvim/event/multiqueue.csrc/nvim/msgpack_rpc/channel.csrc/nvim/api/private/dispatch.csrc/nvim/lua/executor.csrc/nvim/event/defs.h
Neovim's architecture is built around an event-driven, non-blocking event loop that coordinates all asynchronous operations. The system uses a hierarchical queue structure to manage events from multiple sources while maintaining responsiveness.
Event Loop Foundation
The core is Loop (in src/nvim/event/loop.h), which wraps libuv's event loop (uv_loop_t). The loop manages:
-
Three event queues with different priorities:
events: Main application queue for regular eventsfast_events: High-priority events processed immediately after I/O, before returning to editor modethread_events: Thread-safe queue for cross-thread communication
-
libuv integration: Handles timers, signals, I/O streams, and process management via
uv_run()with configurable modes (blocking, non-blocking, or timeout-based) -
Recursion prevention: Tracks recursion depth to prevent re-entering
uv_run(), which would cause a fatal abort
MultiQueue Hierarchy
The MultiQueue system (in src/nvim/event/multiqueue.c) implements a parent-child queue hierarchy that enables selective event processing:
Main Loop
↓
Event Loop (parent queue)
↓
├─ Channel 1 (child queue)
├─ Channel 2 (child queue)
├─ Job 1 (child queue)
└─ Job 2 (child queue)
When a child queue receives an event, a link node is pushed to the parent. This allows the main loop to:
- Process events from all sources when idle
- Focus on a single source when needed (e.g.,
rpcrequestblocks on a specific channel,jobwaitblocks on a job)
RPC & Channel Integration
Channels (in src/nvim/msgpack_rpc/channel.c) represent communication endpoints: sockets, pipes, jobs, or internal connections. Each channel:
- Has its own child
MultiQueueattached tomain_loop.events - Runs an unpacker to deserialize MessagePack-RPC messages
- Dispatches requests via
request_event()handler, which invokes API methods throughdispatch.c
The ch_before_blocking_events queue is a special child queue processed just before the main loop blocks for input, enabling async-safe event handling.
Event Processing Flow
- Main loop calls
loop_poll_events()with a timeout - libuv processes I/O, timers, and signals via
uv_run() - Fast events are processed immediately after
uv_run()returns - Regular events are consumed by the editor state machine in
state_enter()when aK_EVENTkey is generated - Blocking operations (like
rpcrequest) useLOOP_PROCESS_EVENTS_UNTIL()macro to poll a specific queue until a condition is met
Key Design Principles
- Non-blocking: The loop never blocks indefinitely; timeouts prevent stalls
- Hierarchical: Child queues enable focusing on specific event sources without losing others
- Thread-safe:
thread_eventsanduv_async_tallow safe cross-thread event scheduling - Responsive: Fast events bypass the editor state machine for immediate processing
Event Loop & Asynchronous Processing
Relevant Files
src/nvim/event/loop.h– Loop structure and core APIsrc/nvim/event/loop.c– Loop initialization, polling, and event schedulingsrc/nvim/event/multiqueue.h– Multi-level queue macros and interfacesrc/nvim/event/multiqueue.c– Queue hierarchy implementationsrc/nvim/event/defs.h– Event and watcher type definitionssrc/nvim/event/stream.h,src/nvim/event/proc.h– Stream and process abstractions
Overview
Neovim's event loop is built on libuv, a cross-platform asynchronous I/O library. The system manages concurrent operations like process execution, network communication, timers, and signal handling without blocking the editor. Events flow through a hierarchical queue system that allows selective processing based on context.
Core Architecture
The event loop consists of three main components:
-
Loop Structure (
struct loop): Wraps libuv'suv_loop_tand manages three event queues:events: Main application queue (processed by editor state machine)fast_events: Immediate events (processed before returning fromloop_poll_events())thread_events: Thread-safe queue for cross-thread communication
-
MultiQueue Hierarchy: A parent-child queue system enabling selective event processing. When an event is pushed to a child queue, a link node is automatically added to the parent queue. This allows the main loop to focus on specific event sources (e.g., a particular job or RPC channel) while temporarily blocking others.
-
Event Model: Events are simple structures containing a handler function pointer and up to 10 void pointers for arguments. The
event_create()macro constructs events, andmultiqueue_put()enqueues them.
Event Processing Flow
Loading diagram...
The loop_poll_events() function runs libuv once with a timeout, then processes all fast events. Regular events are handled by the application layer. Thread-safe scheduling uses loop_schedule_fast() (immediate) or loop_schedule_deferred() (queued).
Key Patterns
Thread-Safe Scheduling: Use uv_mutex_lock() to protect access to thread_events. The uv_async_t handle wakes the loop when events arrive from other threads.
Selective Processing: The multiqueue hierarchy enables focusing on specific event sources. For example, jobwait() temporarily polls only a job's queue instead of the main event loop.
Recursion Prevention: The loop->recursive counter prevents re-entering uv_run(), which would cause undefined behavior.
Graceful Shutdown: loop_close() iterates with UV_RUN_NOWAIT to drain pending callbacks and close handles before destroying the loop.
Watchers and Streams
Specialized watchers (signals, timers, sockets) and streams (read/write pipes) integrate with the event system via their own MultiQueue pointers. When these emit events, they push to their private queues, which propagate to parent queues through the hierarchy.
API & RPC Communication
Relevant Files
src/nvim/msgpack_rpc/channel.csrc/nvim/msgpack_rpc/channel.hsrc/nvim/msgpack_rpc/server.csrc/nvim/api/private/dispatch.csrc/nvim/api/private/dispatch.hsrc/nvim/msgpack_rpc/packer.csrc/nvim/msgpack_rpc/unpacker.c
Neovim exposes a MessagePack-RPC API that allows external clients to communicate with the editor programmatically. This system handles bidirectional communication through channels, serialization/deserialization of messages, and routing of API calls to appropriate handlers.
Channel Architecture
Channels are the fundamental communication abstraction. Each channel has a unique ID and can operate in different modes:
- RPC Channels: Support full request-response and notification patterns
- Stream Types: Stdio, socket, job, or internal (for embedders)
- Client Types: Remote, MsgpackRpc, UI, Embedder, Host, or Plugin
The channel system maintains a global map of active channels and manages their lifecycle. When a channel is created, it's assigned a unique ID starting from 3 (IDs 1-2 are reserved for stdio and stderr).
Message Protocol
Neovim uses the MessagePack-RPC specification with three message types:
- Requests (type 0):
[0, request_id, method_name, args]- Expect a response - Responses (type 1):
[1, request_id, error, result]- Reply to a request - Notifications (type 2):
[2, method_name, args]- Fire-and-forget events
Arguments are always packed as arrays. Errors are represented as [error_type, error_message] or nil for success.
Serialization & Deserialization
The packer and unpacker handle MessagePack encoding/decoding:
- Packer (
packer.c): Converts Neovim objects (strings, arrays, dicts, handles) to binary MessagePack format - Unpacker (
unpacker.c): Parses incoming MessagePack data into structured messages using a state machine
Special handling exists for Neovim-specific types like buffer/window/tabpage handles, which use MessagePack extension types.
Request Dispatch
When a request arrives, the dispatch system routes it to the correct handler:
MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(
const char *name, size_t name_len, Error *error)
The method_handlers table (generated at build time) maps method names to handler functions. Each handler has metadata:
name: Method identifierfn: Handler function pointerfast: Whether safe to execute immediately in the event loopret_alloc: Whether return value needs manual deallocation
Execution Model
Handlers are executed in two modes:
- Fast handlers: Execute immediately in the event loop (e.g.,
nvim_get_mode) - Deferred handlers: Queued as events for safe execution later (most API calls)
Special cases like nvim_ui_try_resize are queued to multiple event queues for coordinated handling.
Event Broadcasting
Notifications can be broadcast to all RPC channels or sent to a specific channel:
bool rpc_send_event(uint64_t id, const char *name, Array args)
When id is 0, the event is serialized once and sent to all connected RPC clients.
Call Stack & Synchronous Calls
For synchronous RPC calls from Neovim to external clients, a call stack tracks pending requests:
typedef struct {
uint32_t request_id;
bool returned, errored;
Object result;
ArenaMem result_mem;
} ChannelCallFrame;
The event loop processes events until the response arrives, enabling blocking API calls to remote services.
Error Handling
Errors are propagated through the RPC protocol. For notifications with errors, an nvim_error_event is sent instead of a response. Channel errors (parse failures, write failures) trigger channel closure with diagnostic logging.
Loading diagram...
Buffer & Window Management
Relevant Files
src/nvim/buffer.c– Buffer lifecycle and managementsrc/nvim/buffer.h– Buffer API and enumssrc/nvim/buffer_defs.h– Buffer and window structuressrc/nvim/window.c– Window creation and manipulationsrc/nvim/window.h– Window API and constantssrc/nvim/api/buffer.c– Public API for bufferssrc/nvim/api/window.c– Public API for windows
Core Data Structures
Buffers and windows are the fundamental abstractions in Neovim. A buffer (buf_T) represents file content in memory, while a window (win_T) is a viewport into a buffer. Multiple windows can display the same buffer, and each window maintains its own cursor position and view state.
Buffer Structure (buf_T):
b_fnum– unique buffer number (handle)b_ml– memline structure containing the actual text linesb_nwindows– count of windows displaying this bufferb_next,b_prev– doubly-linked list of all buffersb_changed– modification flagb_wininfo– per-window state (cursor, topline) for each window that has viewed this buffer
Window Structure (win_T):
w_buffer– pointer to the buffer being displayedw_cursor– cursor position within the bufferw_topline,w_leftcol– viewport offsetsw_prev,w_next– linked list of windows in current tabw_frame– layout frame for split management
Buffer Lifecycle
Buffers transition through distinct states:
- Never Loaded (
BF_NEVERLOADED) – buffer created but file not yet read - Not Loaded (
b_ml.ml_mfp == NULL) – file contents freed from memory - Hidden (
b_nwindows == 0) – loaded but not displayed in any window - Normal – loaded and visible in at least one window
Key Operations:
buflist_new()– create or reuse a buffer entryopen_buffer()– load file contents into memory (opens memfile)buf_ensure_loaded()– ensure buffer is loaded, loading if necessaryclose_buffer()– unload or delete a buffer with three modes:DOBUF_UNLOAD– free file contents, keep buffer entryDOBUF_DEL– unload and mark unlisted (:bdel)DOBUF_WIPE– completely remove buffer (:bwipeout)
Window Management
Windows are organized hierarchically using frames (frame_T). Each frame is either a leaf (containing a window) or a branch (containing child frames). This tree structure enables complex split layouts.
Window Operations:
win_split()– create new window by splitting current windowwin_set_buf()– change which buffer a window displayswin_enter_ext()– make a window current with autocommand supportswitch_win()/restore_win()– temporarily switch windows for operations
Buffer-Window Interaction
The relationship is tracked bidirectionally:
- Each buffer maintains
b_nwindowscount andb_wininfovector storing per-window state - Each window holds a pointer to its buffer (
w_buffer) - When a buffer is displayed in a window,
b_nwindowsincrements - When a window closes,
b_nwindowsdecrements; if it reaches zero, the buffer becomes hidden
Critical Invariant: The last listed buffer cannot be unloaded. If you attempt to close the final buffer, Neovim creates an empty replacement buffer instead.
API Access
The public API (nvim_buf_* and nvim_win_* functions) provides safe access to buffers and windows through handles. Handles are validated before use, and operations respect buffer locks to prevent deletion during autocommand execution.
Lua Integration & Scripting
Relevant Files
src/nvim/lua/executor.c– Core Lua execution enginesrc/nvim/lua/executor.h– Executor API and type definitionssrc/nvim/lua/converter.c– Lua <-> Neovim type conversionsrc/nvim/lua/converter.h– Converter function declarationssrc/nvim/lua/stdlib.c– Standard library bindings (regex, spell, json, etc.)src/nvim/lua/treesitter.c– Tree-sitter integration for syntax trees
Overview
Neovim embeds a Lua interpreter to enable scripting and plugin development. The Lua integration layer bridges C and Lua, providing access to Neovim's API, event loop, and built-in utilities. All Lua code runs within a single global Lua state managed by the executor.
Architecture
Loading diagram...
Initialization
The Lua interpreter is initialized in nlua_init() during Neovim startup:
- Create Lua state –
luaL_newstate()creates a new interpreter - Load standard libraries –
luaL_openlibs()adds Lua's built-in functions - Initialize vim namespace – Populates
vim.api,vim.uv,vim.schedule, etc. - Register built-in modules – Preloads
vim.inspect,vim.regex,vim.json, and others - Set up event loop integration – Connects Lua callbacks to Neovim's libuv event loop
The global Lua state is stored in global_lstate and accessed via get_global_lstate().
Execution Modes
nlua_exec() – Execute arbitrary Lua code with arguments and return value conversion. Used by nvim_exec_lua() API and :lua command.
nlua_typval_eval() – Evaluate Lua expressions for luaeval(). Wraps code as local _A=select(1,...) return (...) to support single-expression evaluation.
nlua_exec_file() – Load and execute Lua files. Calls Lua's loadfile() to respect user overrides.
ex_lua() – Command handler for :lua and :lua ={expr} commands.
Type Conversion
The converter (converter.c) translates between Lua and Neovim types:
- Lua tables – Converted to Neovim dicts or arrays based on key structure
- Lua numbers – Mapped to integers or floats
- Lua strings – Converted to Neovim strings with UTF-8 validation
- Lua functions – Stored as
LuaRef(reference IDs) for later invocation - Neovim objects – Wrapped as Lua userdata with metatables
Special handling exists for vim.type_idx and vim.val_idx to preserve type information across conversions.
Standard Library
The stdlib module registers C functions accessible from Lua:
vim.regex(pattern)– Create regex objects using Neovim's regex enginevim.json.encode/decode– JSON serialization via cJSONvim.mpack.encode/decode– MessagePack serializationvim.spell.*– Spell checking functionsvim.base64.encode/decode– Base64 encodingvim.iconv(str, from, to)– Character encoding conversionvim.lpeg– Lua Parsing Expression Grammar library
Tree-sitter Integration
Tree-sitter bindings (treesitter.c) expose syntax tree parsing:
vim.treesitter.language.add()– Register language parsersvim.treesitter.get_parser()– Create parser for a bufferparser:parse()– Parse buffer content into syntax treetree:root()– Access root node of syntax treenode:child_count(),node:type()– Navigate and inspect nodesvim.treesitter.query.parse()– Create Tree-sitter queries for pattern matching
Error Handling
Lua errors are caught via nlua_pcall(), which uses debug.traceback for stack traces. Errors are converted to Neovim error messages via nlua_error(). In fast callbacks (libuv), errors are queued to the main event loop to avoid reentrancy issues.
Threading
Lua threads are created on-demand via nlua_thread_acquire_vm(). Each thread gets its own Lua state initialized with nlua_init_state(thread=true). Thread-local state excludes non-threadsafe functions like regex and spell checking.
UI, Rendering & Terminal
Relevant Files
src/nvim/ui.c– Core UI system and remote UI managementsrc/nvim/ui.h– UI public API and definitionssrc/nvim/tui/tui.c– Terminal UI implementationsrc/nvim/tui/tui.h– TUI definitionssrc/nvim/drawscreen.c– Screen redraw orchestrationsrc/nvim/drawline.c– Line-level rendering logicsrc/nvim/grid.c– Grid data structure and manipulationsrc/nvim/grid_defs.h– Grid structure definitionssrc/nvim/ui_compositor.c– Multi-grid composition and blendingsrc/nvim/terminal.c– Terminal emulator integration (libvterm)
Neovim's rendering system is a multi-layered architecture that separates content generation from display. The system supports both traditional single-grid rendering and modern multi-grid mode for floating windows and UI extensions.
Core Architecture
The rendering pipeline flows through three main layers:
-
Redraw Orchestration (
drawscreen.c): Determines what needs to be redrawn based on buffer changes and window state. Uses priority levels (UPD_CLEAR,UPD_NOT_VALID,UPD_VALID) to optimize updates. -
Line Rendering (
drawline.c): Converts buffer content into screen cells, applying syntax highlighting, decorations, folds, and other visual features. Handles text wrapping, virtual columns, and double-width characters. -
Grid Management (
grid.c,grid_defs.h): MaintainsScreenGridstructures that store rendered content as UTF-8 cells with attributes. Each cell can contain composing characters, and double-width characters are tracked.
Grid System
The ScreenGrid structure is the fundamental rendering unit:
struct ScreenGrid {
schar_T *chars; // UTF-8 text cells
sattr_T *attrs; // Highlight attributes per cell
colnr_T *vcols; // Virtual column tracking
size_t *line_offset; // Optimized line access via rotation
int rows, cols; // Dimensions
int zindex; // Stacking order
bool valid; // Validity flag
};
The default_grid (handle 1) represents the main editor surface. In multi-grid mode, floating windows and UI elements get their own grids with different z-indices for proper composition.
Terminal UI (TUI)
The TUI layer (tui.c) bridges Neovim's grid system to terminal output:
- Terminfo Integration: Uses terminfo database to emit terminal control sequences for cursor movement, colors, and attributes.
- Buffering: Accumulates output in a buffer (
OUTBUF_SIZE) before flushing to minimize system calls. - Invalid Region Tracking: Maintains a vector of dirty rectangles to redraw only changed areas.
- Mouse & Focus: Handles terminal mouse events and focus reporting via escape sequences.
The TUI manages a UGrid (uncompressed grid) for efficient cell-by-cell updates and tracks terminal capabilities like RGB color support and scroll region manipulation.
UI Compositor
The compositor (ui_compositor.c) handles multi-grid rendering by:
- Composing multiple grids into a single display based on z-index
- Blending floating windows with background content
- Managing message grid positioning and scrolling
- Coordinating updates across all active grids
Terminal Emulator
The terminal emulator (terminal.c) uses libvterm to provide a VT220/xterm-compatible terminal inside Neovim buffers:
- Decouples terminal state from display via abstract callbacks
- Synchronizes vterm screen updates to buffer content every 10ms
- Manages scrollback by storing invisible lines in the buffer above visible content
- Supports full terminal features: colors, mouse, keyboard input, and ANSI sequences
Rendering Flow
Loading diagram...
Key Concepts
Dirty Tracking: Instead of redrawing the entire screen, Neovim marks regions as invalid and only redraws those areas. The invalid_regions vector in TUI tracks rectangles needing updates.
Virtual Columns: The vcols[] array maps screen columns to buffer positions, enabling accurate mouse click handling and fold column interaction.
Attribute Caching: Highlight attributes are stored per-cell and compared during updates to minimize redundant escape sequence emission.
Throttling: Grids can be marked as throttled to defer compositor updates, useful during rapid changes or animations.
Syntax Highlighting & Decorations
Relevant Files
src/nvim/syntax.csrc/nvim/syntax.hsrc/nvim/highlight.csrc/nvim/highlight_group.csrc/nvim/decoration.csrc/nvim/extmark.c
Neovim's syntax highlighting and decoration system combines traditional regex-based syntax patterns with a modern extensible marks (extmarks) and decoration provider architecture. This enables both built-in language syntax support and dynamic plugin-driven highlighting.
Syntax Highlighting Architecture
The syntax system is built on three core components:
Syntax Patterns (synpat_T in syntax.c) define how text is matched and highlighted. Each pattern stores:
- A compiled regex program (
sp_prog) for matching text - A highlight group ID (
sp_syn_match_id) to apply when matched - Offset information for match and highlight regions
- Flags controlling behavior (e.g.,
HL_CONTAINED,HL_ONELINE,HL_CONCEAL)
State Items (stateitem_T) track the current highlighting state as the syntax engine processes a line. They maintain:
- Match positions and highlight boundaries
- The active highlight group and its attributes
- Containment and nextgroup relationships
- Concealment character substitutions
Highlight Groups (managed in highlight_group.c) are named collections of visual attributes (colors, bold, underline, etc.). Each group has a unique ID and can link to other groups for inheritance.
Rendering Pipeline
Loading diagram...
The get_syntax_attr() function is the core entry point. Called once per line via syntax_start(), it processes each column and returns the highlight attribute ID for that position. The syntax engine maintains a stack of active states to handle nested regions (e.g., comments within strings).
Decorations & Extmarks
Decorations provide a plugin-friendly layer for dynamic highlighting, virtual text, and signs. They work through:
Extmarks (extmark.c) are positioned markers in buffers that can carry decoration data. Each extmark has:
- A namespace ID (for plugin isolation)
- A unique ID within that namespace
- Start and end positions (supporting ranges)
- Associated
DecorInlinedata
Decoration Data includes:
- Highlight information (
DecorSignHighlight) - Virtual text (
DecorVirtText) for inline or end-of-line text - Virtual lines for above/below line content
- Concealment characters and spell-check flags
The decor_range_add_sh() function integrates decorations into the rendering state, converting highlight IDs to attribute entries and managing priority-based layering.
Highlight Attribute System
Attributes are cached in attr_entries (a set in highlight.c) to avoid redundant computation. Each entry combines:
- RGB/terminal colors
- Text attributes (bold, italic, underline, etc.)
- Semantic kind information (for LSP integration)
- Blending and URL metadata
The get_attr_entry() function returns a unique attribute ID for any color/style combination, enabling efficient screen rendering.
Decoration Providers
Plugins can register decoration providers to dynamically generate highlights per-line or per-range. The provider lifecycle:
- Start callback invoked at redraw begin
- Line callback for each visible line (receives window, buffer, row)
- Range callback for specific regions (optional)
Providers call nvim_buf_set_extmark() to place decorations, which are immediately rendered if the provider is active during drawing.
Key Concepts
- Syncing: The syntax engine can resync to known states (e.g., line start) to recover from regex timeouts, controlled by
HL_SYNC_HEREandHL_SYNC_THEREflags. - Transparency: Transparent groups (
HL_TRANSP) inherit attributes from contained groups rather than applying their own. - Concealment: Text can be hidden and replaced with a single character via
HL_CONCEALflag. - Fold Levels: Syntax patterns can define fold regions using
HL_FOLD. - Namespace Isolation: Highlight groups can be private to a namespace, enabling per-plugin color schemes.
Vimscript Evaluation & Commands
Relevant Files
src/nvim/eval.csrc/nvim/eval.hsrc/nvim/eval/executor.csrc/nvim/eval/funcs.csrc/nvim/ex_docmd.csrc/nvim/ex_cmds.c
Vimscript evaluation and command execution form the core of Neovim's scripting engine. The system handles both expression evaluation (:echo, :let, etc.) and command dispatch through a hierarchical architecture.
Command Execution Pipeline
Commands flow through do_cmdline() which orchestrates the entire execution:
- Parsing:
do_one_cmd()parses the command line, handling modifiers (:silent,:noautocmd, etc.) - Dispatch: Commands are looked up in the
cmdnamestable and dispatched to handler functions - Execution: Built-in commands call their registered handler (e.g.,
ex_echo,ex_let) - User Commands: Custom commands are expanded and re-executed via
do_ucmd()
The command stack (cstack_T) tracks nested :if/:while/:for blocks for control flow.
Expression Evaluation Hierarchy
Expressions are evaluated through a precedence-based recursive descent parser:
eval0() → Top level (handles errors, nextcmd)
↓
eval1() → Ternary operator (? :)
↓
eval2() → Logical OR (||)
↓
eval3() → Logical AND (&&)
↓
eval4() → Comparison operators (==, !=, <, >, etc.)
↓
eval5() → Addition/Subtraction/Concatenation (+, -, .)
↓
eval6() → Multiplication/Division/Modulo (*, /, %)
↓
eval7() → Unary operators (!, -, +)
↓
eval8() → Subscripts and function calls ([...], (...))
↓
eval9() → Atomic values (literals, variables, nested expressions)
Each level handles its operators and delegates to the next level for higher precedence.
Type System
All values are represented as typval_T structures:
typedef struct {
VarType v_type; // VAR_NUMBER, VAR_STRING, VAR_LIST, VAR_DICT, etc.
VarLockStatus v_lock; // VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED
union {
varnumber_T v_number;
float_T v_float;
char *v_string;
list_T *v_list;
dict_T *v_dict;
partial_T *v_partial; // Closure
blob_T *v_blob;
} vval;
} typval_T;
Evaluation Context
The evalarg_T structure carries evaluation state:
typedef struct {
int eval_flags; // EVAL_EVALUATE or EVAL_SKIP
LineGetter eval_getline;
void *eval_cookie;
char *eval_tofree;
} evalarg_T;
The EVAL_SKIP flag enables parse-only mode (used in :if conditions before execution).
Key Entry Points
eval0()- Top-level expression evaluation with error handlingeval_to_bool()- Evaluate expression as booleaneval_to_string_eap()- Evaluate expression as stringeval_to_number()- Evaluate expression as numberex_execute()- Handle:executecommandex_eval()- Handle:evalcommand
Operator Execution
Binary operators are handled by eexe_mod_op() for compound assignments (+=, -=, etc.). Type coercion follows Vimscript rules: numbers convert to floats when mixed, strings convert to numbers in numeric context.
Build System & Testing
Relevant Files
CMakeLists.txt- Main build configurationMakefile- High-level build targetsBUILD.md- Comprehensive build documentationruntime/doc/dev_test.txt- Testing framework guidetest/functional/testnvim.lua- Functional test utilitiescmake/RunTests.cmake- Test execution script
Build System Overview
Neovim uses CMake as its primary build system with a Makefile wrapper for convenience. The build process is split into two phases: dependency building (in cmake.deps/) and main project building. By default, dependencies are bundled and statically linked, but can be disabled with USE_BUNDLED=OFF.
Key build types:
- Debug: Full debug info, minimal optimizations (default)
- Release: Maximum optimizations, no debug info
- RelWithDebInfo: Optimized with debug symbols (recommended for releases)
Quick Build
make CMAKE_BUILD_TYPE=RelWithDebInfo
sudo make install
The build outputs to build/bin/nvim. Use VIMRUNTIME=runtime ./build/bin/nvim to run without installing.
Build Configuration
CMake caches settings in build/CMakeCache.txt. When changing CMAKE_BUILD_TYPE or CMAKE_INSTALL_PREFIX, run make distclean first. Key options:
CMAKE_INSTALL_PREFIX- Installation directory (default:/usr/local)ENABLE_LTO- Link-time optimization (default: ON, except MinGW)ENABLE_ASAN_UBSAN,ENABLE_MSAN,ENABLE_TSAN- Sanitizers (mutually exclusive)USE_BUNDLED- Use bundled dependencies (default: ON)
Testing Framework
Neovim has three test suites:
- Unit Tests (
test/unit/) - Compiled as shared libraries, loaded via LuaJIT FFI - Functional Tests (
test/functional/) - RPC-driven integration tests, fast (<30ms per test) - Legacy Tests (
test/old/testdir/) - Vim compatibility tests
Run tests with:
make test # All tests
make unittest # Unit tests only
make functionaltest # Functional tests only
make oldtest # Legacy tests
Test Structure
Tests use Busted (Lua test runner) and follow this layout:
*_spec.lua- Actual test filestestutil.lua- Common utilitiestestnvim.lua- RPC session utilitiespreload.lua- Busted helpers
Each functional test spawns a fresh Nvim process (10-30ms) via RPC, discarded after completion. Use t.eq() for assertions and screen:expect() for UI testing (auto-waits).
Linting & Formatting
make lint # Run all linters
make lintlua # Lua linting (luacheck + stylua)
make lintsh # Shell script linting
make format # Format code
make formatlua # Format Lua files
Lua formatting uses stylua with .styluaignore and .stylua2.toml for special cases.
Debugging Builds
Enable verbose output: cmake --build build --verbose
For include chain analysis:
echo '#include "./src/nvim/buffer.h"' | \
clang -I.deps/usr/include -Isrc -std=c99 -P -E -H - 2>&1 >/dev/null | \
grep -v /usr/
Use local.mk for custom build targets (listed in .gitignore).