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 + cellsThe 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:
- Forks (or pthreads, on platforms without fork) a server in the same process or a child process.
- Connects a pipe pair as the server's stdin/stdout.
- Calls
ui_client_run()(insrc/nvim/ui_client.c) on the parent side, which is just a thin loop aroundtui_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 supportmultigrid.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.cand the relevanttermkeytable. - A new emitted feature: usually one branch in
update_attrs,tui_grid_line, or a newtui_*handler intui.c. - A new bundled terminfo entry: regenerate
terminfo_builtin.hfrom the script insrc/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.