Install Now

neovim/neovim

Neovim Core Architecture & Development

Last updated on Dec 18, 2025 (Commit: c08139d)

Overview

Relevant Files
  • README.md
  • src/nvim/main.c
  • src/nvim/main.h
  • runtime/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:

  1. Early init: Parse arguments, initialize globals, set up paths
  2. Event init: Create the main event loop and initialize subsystems
  3. Configuration: Load vimrc, plugins, and user settings
  4. UI setup: Start the built-in TUI or connect to remote UI
  5. 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.c
  • src/nvim/event/multiqueue.c
  • src/nvim/msgpack_rpc/channel.c
  • src/nvim/api/private/dispatch.c
  • src/nvim/lua/executor.c
  • src/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 events
    • fast_events: High-priority events processed immediately after I/O, before returning to editor mode
    • thread_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., rpcrequest blocks on a specific channel, jobwait blocks 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 MultiQueue attached to main_loop.events
  • Runs an unpacker to deserialize MessagePack-RPC messages
  • Dispatches requests via request_event() handler, which invokes API methods through dispatch.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

  1. Main loop calls loop_poll_events() with a timeout
  2. libuv processes I/O, timers, and signals via uv_run()
  3. Fast events are processed immediately after uv_run() returns
  4. Regular events are consumed by the editor state machine in state_enter() when a K_EVENT key is generated
  5. Blocking operations (like rpcrequest) use LOOP_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_events and uv_async_t allow 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 API
  • src/nvim/event/loop.c – Loop initialization, polling, and event scheduling
  • src/nvim/event/multiqueue.h – Multi-level queue macros and interface
  • src/nvim/event/multiqueue.c – Queue hierarchy implementation
  • src/nvim/event/defs.h – Event and watcher type definitions
  • src/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:

  1. Loop Structure (struct loop): Wraps libuv's uv_loop_t and manages three event queues:

    • events: Main application queue (processed by editor state machine)
    • fast_events: Immediate events (processed before returning from loop_poll_events())
    • thread_events: Thread-safe queue for cross-thread communication
  2. 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.

  3. 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, and multiqueue_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.c
  • src/nvim/msgpack_rpc/channel.h
  • src/nvim/msgpack_rpc/server.c
  • src/nvim/api/private/dispatch.c
  • src/nvim/api/private/dispatch.h
  • src/nvim/msgpack_rpc/packer.c
  • src/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:

  1. Requests (type 0): [0, request_id, method_name, args] - Expect a response
  2. Responses (type 1): [1, request_id, error, result] - Reply to a request
  3. 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 identifier
  • fn: Handler function pointer
  • fast: Whether safe to execute immediately in the event loop
  • ret_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 management
  • src/nvim/buffer.h – Buffer API and enums
  • src/nvim/buffer_defs.h – Buffer and window structures
  • src/nvim/window.c – Window creation and manipulation
  • src/nvim/window.h – Window API and constants
  • src/nvim/api/buffer.c – Public API for buffers
  • src/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 lines
  • b_nwindows – count of windows displaying this buffer
  • b_next, b_prev – doubly-linked list of all buffers
  • b_changed – modification flag
  • b_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 displayed
  • w_cursor – cursor position within the buffer
  • w_topline, w_leftcol – viewport offsets
  • w_prev, w_next – linked list of windows in current tab
  • w_frame – layout frame for split management

Buffer Lifecycle

Buffers transition through distinct states:

  1. Never Loaded (BF_NEVERLOADED) – buffer created but file not yet read
  2. Not Loaded (b_ml.ml_mfp == NULL) – file contents freed from memory
  3. Hidden (b_nwindows == 0) – loaded but not displayed in any window
  4. Normal – loaded and visible in at least one window

Key Operations:

  • buflist_new() – create or reuse a buffer entry
  • open_buffer() – load file contents into memory (opens memfile)
  • buf_ensure_loaded() – ensure buffer is loaded, loading if necessary
  • close_buffer() – unload or delete a buffer with three modes:
    • DOBUF_UNLOAD – free file contents, keep buffer entry
    • DOBUF_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 window
  • win_set_buf() – change which buffer a window displays
  • win_enter_ext() – make a window current with autocommand support
  • switch_win() / restore_win() – temporarily switch windows for operations

Buffer-Window Interaction

The relationship is tracked bidirectionally:

  • Each buffer maintains b_nwindows count and b_wininfo vector storing per-window state
  • Each window holds a pointer to its buffer (w_buffer)
  • When a buffer is displayed in a window, b_nwindows increments
  • When a window closes, b_nwindows decrements; 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 engine
  • src/nvim/lua/executor.h – Executor API and type definitions
  • src/nvim/lua/converter.c – Lua <-> Neovim type conversion
  • src/nvim/lua/converter.h – Converter function declarations
  • src/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:

  1. Create Lua stateluaL_newstate() creates a new interpreter
  2. Load standard librariesluaL_openlibs() adds Lua's built-in functions
  3. Initialize vim namespace – Populates vim.api, vim.uv, vim.schedule, etc.
  4. Register built-in modules – Preloads vim.inspect, vim.regex, vim.json, and others
  5. 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 engine
  • vim.json.encode/decode – JSON serialization via cJSON
  • vim.mpack.encode/decode – MessagePack serialization
  • vim.spell.* – Spell checking functions
  • vim.base64.encode/decode – Base64 encoding
  • vim.iconv(str, from, to) – Character encoding conversion
  • vim.lpeg – Lua Parsing Expression Grammar library

Tree-sitter Integration

Tree-sitter bindings (treesitter.c) expose syntax tree parsing:

  • vim.treesitter.language.add() – Register language parsers
  • vim.treesitter.get_parser() – Create parser for a buffer
  • parser:parse() – Parse buffer content into syntax tree
  • tree:root() – Access root node of syntax tree
  • node:child_count(), node:type() – Navigate and inspect nodes
  • vim.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 management
  • src/nvim/ui.h – UI public API and definitions
  • src/nvim/tui/tui.c – Terminal UI implementation
  • src/nvim/tui/tui.h – TUI definitions
  • src/nvim/drawscreen.c – Screen redraw orchestration
  • src/nvim/drawline.c – Line-level rendering logic
  • src/nvim/grid.c – Grid data structure and manipulation
  • src/nvim/grid_defs.h – Grid structure definitions
  • src/nvim/ui_compositor.c – Multi-grid composition and blending
  • src/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:

  1. 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.

  2. 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.

  3. Grid Management (grid.c, grid_defs.h): Maintains ScreenGrid structures 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.c
  • src/nvim/syntax.h
  • src/nvim/highlight.c
  • src/nvim/highlight_group.c
  • src/nvim/decoration.c
  • src/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 DecorInline data

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:

  1. Start callback invoked at redraw begin
  2. Line callback for each visible line (receives window, buffer, row)
  3. 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_HERE and HL_SYNC_THERE flags.
  • 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_CONCEAL flag.
  • 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.c
  • src/nvim/eval.h
  • src/nvim/eval/executor.c
  • src/nvim/eval/funcs.c
  • src/nvim/ex_docmd.c
  • src/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:

  1. Parsing: do_one_cmd() parses the command line, handling modifiers (:silent, :noautocmd, etc.)
  2. Dispatch: Commands are looked up in the cmdnames table and dispatched to handler functions
  3. Execution: Built-in commands call their registered handler (e.g., ex_echo, ex_let)
  4. 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 (&amp;&amp;)
  ↓
eval4()  → Comparison operators (==, !=, &lt;, &gt;, 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 handling
  • eval_to_bool() - Evaluate expression as boolean
  • eval_to_string_eap() - Evaluate expression as string
  • eval_to_number() - Evaluate expression as number
  • ex_execute() - Handle :execute command
  • ex_eval() - Handle :eval command

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 configuration
  • Makefile - High-level build targets
  • BUILD.md - Comprehensive build documentation
  • runtime/doc/dev_test.txt - Testing framework guide
  • test/functional/testnvim.lua - Functional test utilities
  • cmake/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:

  1. Unit Tests (test/unit/) - Compiled as shared libraries, loaded via LuaJIT FFI
  2. Functional Tests (test/functional/) - RPC-driven integration tests, fast (<30ms per test)
  3. 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 files
  • testutil.lua - Common utilities
  • testnvim.lua - RPC session utilities
  • preload.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).