Open-Source Wikis

/

Neovim

/

Systems

/

TUI

neovim/neovim

TUI

Purpose

The TUI is Neovim's built-in terminal UI. It is a separate process — even when launched as plain nvim in a shell — that owns the terminal device, parses incoming escape sequences into key events, and renders the editor's redraw events as cells on screen. The same architecture lets external GUIs (Neovide, vimr, gnvim, ...) drop in as alternative consumers of the same protocol.

Directory layout

src/nvim/
├── ui_client.c                 The TUI process's main loop (server side of the split)
├── ui_client.h
├── tui/
│   ├── tui.c                   ~87k bytes. The actual draw loop.
│   ├── tui.h, tui_defs.h
│   ├── input.c                 Escape-sequence parser top half
│   ├── input.h, input_defs.h
│   ├── terminfo.c              Terminfo lookup
│   ├── terminfo.h, terminfo_defs.h, terminfo_enum_defs.h
│   ├── terminfo_builtin.h      ~67k bytes of bundled terminfo entries
│   ├── ugrid.c, ugrid.h        The internal grid representation
│   └── termkey/                Vendored libtermkey: low-level key parsing
└── ui.c, ui_compositor.c       Server-side UI multiplexer (not the TUI itself)

The TUI talks msgpack-rpc to the editor server. From the server's perspective it is just another remote UI; from the user's perspective it is "the editor".

Key abstractions

Type / function File Description
TUIData tui/tui.c The TUI's per-process state. Owns the libuv loop, the screen grid, and the input parser.
TermInput tui/input.c Wraps termkey plus a "what's-the-terminal-doing-now" state machine.
unibi_term (unibilium) Parsed terminfo entry. Used to look up CSI/ESC sequences for capabilities.
UGrid tui/ugrid.c The 2D cell grid the TUI maintains as it receives grid_line events.
tinput_* tui/input.c Escape sequence parsing — bracketed paste, focus, mouse, kitty keyboard protocol.
tui_main() tui/tui.c Entry point for the TUI thread.
ui_client_run() ui_client.c Process-level driver: spawn the server, attach the UI, run.

How it works

sequenceDiagram
    participant Term as Terminal
    participant TIN as TermInput (input.c)
    participant TUI as TUIData (tui.c)
    participant RPC as msgpack-rpc
    participant Srv as nvim server
    Term->>TIN: bytes (keys, mouse, paste)
    TIN->>TIN: termkey parses to TermKeyKey
    TIN->>RPC: nvim_input("hello")
    RPC->>Srv: dispatch
    Srv->>RPC: redraw notification (grid_line, cursor_goto, ...)
    RPC->>TUI: handle event
    TUI->>TUI: update UGrid
    TUI->>Term: write CSI + cells

The TUI runs its own libuv loop on a dedicated thread (or process — the boundary is the same code in both modes). When the user presses a key, termkey decodes it into a TermKeyKey struct, the TUI translates that to Neovim's internal key form, and nvim_input() is called via msgpack. When the server sends back redraw events, the TUI updates its UGrid and emits the corresponding terminfo-based escape sequences.

The escape sequence emission is the most subtle part of the file. Modern terminals support a wealth of features (true color, undercurl, kitty graphics, sync output) that are negotiated at startup via XTGETTCAP and CSI queries. The TUI keeps a unibi_term * handle (from unibilium) plus a set of feature flags it has detected, and chooses sequences accordingly. Look near terminfo_start and update_attrs in tui.c for the negotiation and rendering paths.

ui_client.c — splitting the process

When you run plain nvim in a terminal, the nvim binary actually:

  1. Forks (or pthreads, on platforms without fork) a server in the same process or a child process.
  2. Connects a pipe pair as the server's stdin/stdout.
  3. Calls ui_client_run() (in src/nvim/ui_client.c) on the parent side, which is just a thin loop around tui_main() and the msgpack channel.

This is why ps shows two processes for a typical Neovim session and why you can detach the TUI from the server (vim.api.nvim_ui_detach()) without killing the editor.

In --headless mode, only the server runs. In --embed mode, only the server runs and a parent process drives it via stdio. The TUI is just one possible UI client, sitting on the same protocol any third-party GUI would use.

Input parsing

tui/input.c is a state machine that handles:

  • Plain bytes / UTF-8 sequences → nvim_input("a").
  • ANSI/CSI key sequences (cursor keys, F-keys, modifiers) via termkey.
  • Mouse events (legacy + SGR + URXVT encodings).
  • Bracketed paste (ESC [ 200 ~ ... ESC [ 201 ~).
  • Focus events (ESC [ I / ESC [ O).
  • Kitty keyboard protocol (ESC [ ? u).
  • Terminal size changes (SIGWINCH on Unix, console events on Windows).

tui/termkey/ is a vendored copy of the libtermkey keyboard-decoder library. The TUI integrates its parser tables into the libuv loop.

Terminfo

tui/terminfo.c and terminfo_builtin.h ship with bundled terminfo entries for common terminals so Neovim works without a system terminfo database. At runtime the TUI calls into unibilium to parse the entry and uses the parsed entry to look up sequences. The bundled set covers xterm, xterm-256color, tmux, screen, linux, iterm2, alacritty, kitty, plus several variants — see terminfo_enum_defs.h.

Integration points

  • src/nvim/ui.c — the server-side UI multiplexer. Calls every attached UI's handlers (TUI included) when the editor wants to draw. Independent of msgpack.
  • src/nvim/api/ui.c — the server-side RPC bridge. Marshals UI events to remote UIs.
  • src/nvim/ui_compositor.c — composites floating windows over the base grid before sending events to UIs that don't support multigrid.
  • runtime/lua/vim/ui_attach — a Lua-side hook that lets a plugin observe the same redraw stream the TUI sees.

Entry points for modification

  • A new escape-sequence parse: extend tui/input.c and the relevant termkey table.
  • A new emitted feature: usually one branch in update_attrs, tui_grid_line, or a new tui_* handler in tui.c.
  • A new bundled terminfo entry: regenerate terminfo_builtin.h from the script in src/gen/.
  • Most third-party GUI work doesn't touch this directory at all — it lives in a separate repo and consumes the public UI protocol described in API and msgpack-rpc and runtime/doc/api-ui-events.txt.

Key source files

File Purpose
src/nvim/tui/tui.c The draw loop: receive UI events, update the grid, emit escape sequences
src/nvim/tui/input.c Escape-sequence and key parsing
src/nvim/tui/terminfo.c Lookup against the bundled or system terminfo database
src/nvim/tui/terminfo_builtin.h ~67 KB of bundled terminfo entries
src/nvim/tui/ugrid.c The TUI-side cell grid representation
src/nvim/tui/termkey/ Vendored libtermkey
src/nvim/ui_client.c TUI process driver — spawns the server, attaches as a UI
src/nvim/ui.c Server-side UI multiplexer (not the TUI proper)
src/nvim/api/ui.c Server-side RPC bridge for UI events
src/nvim/ui_compositor.c Compose floats for non-multigrid UIs

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

TUI – Neovim wiki | Factory