neovim/neovim
UI providers (vim.ui)
Purpose
vim.ui is a small set of override-able UI primitives that plugins use instead of building their own pickers, prompts, and image renderers. The defaults are simple — vim.ui.select falls back to inputlist(); vim.ui.input falls back to input(). Plugins like Telescope, fzf-lua, and Snacks can replace the implementation, and any code that uses vim.ui automatically gets the user's chosen UI.
vim.ui_attach, despite the namespace overlap, is a different feature: it lets a Lua callback observe the same redraw events the TUI sees, used for pure-Lua UI rewrites and tests. Both are documented here.
Directory layout
runtime/lua/vim/
├── ui.lua (~13k bytes) Top-level API: select, input, open, img, ...
└── ui/
├── _meta/
└── ... Specialized helpers
runtime/lua/vim/_core/
├── ui.lua Built-in defaults
├── ui2/ Newer reusable UI components
└── ui2.luaruntime/lua/vim/_core/ui.lua and ui2.lua are the implementation; the public API in runtime/lua/vim/ui.lua is a thin re-export.
Key abstractions
| Function | Description |
|---|---|
vim.ui.select(items, opts, on_choice) |
Show a chooser of items, call on_choice(item, idx). |
vim.ui.input(opts, on_confirm) |
Prompt for a string, call on_confirm(value). |
vim.ui.open(uri) |
Open uri with the platform default handler (xdg-open, open, start). |
vim.ui.img.set, del, get |
Display images at extmark positions. New in v0.12. |
vim.ui_attach(ns, opts, callback) |
Observe redraw events in Lua. |
vim.ui_detach(ns) |
Stop observing. |
vim.ui.select
The default implementation builds a numbered list and calls inputlist():
vim.ui.select({'one', 'two', 'three'}, {
prompt = 'Pick a number:',
format_item = function(item) return ' • ' .. item end,
kind = 'codeaction',
}, function(choice, idx)
-- ...
end)Plugins replace it by reassigning vim.ui.select to their own function. The contract is documented in runtime/doc/lua.txt. Notable callers in the editor:
vim.lsp.buf.code_action— pick a code action.:tselect,:oldfiles,:recover— pick a file/tag.vim.snippetchoice nodes.
The default also supports asynchronous callbacks: many of the LSP callers expect on_choice to be called from a callback rather than synchronously, so a picker that needs to query asynchronously can be a drop-in replacement.
vim.ui.input
Same idea: default is the editor's input(), plugins can replace.
vim.ui.input({ prompt = 'Name:', default = '' }, function(value)
if value then ... end
end)value == nil means the user cancelled.
vim.ui.open
Open a URI with the OS default handler:
vim.ui.open('https://neovim.io')
vim.ui.open('/tmp/file.pdf')The implementation picks a handler based on os.uname(): xdg-open on Linux, open on macOS, cmd /c start on Windows. Spawned via vim.system.
vim.ui.img
The image surface introduced in v0.12. Backends include kitty graphics, sixel, and iTerm2 inline images; the editor detects what the terminal supports at startup. Programmatic API:
local id = vim.ui.img.set({
src = '/path/to/image.png',
pos = { row, col },
-- options: size, mime, alt
})
vim.ui.img.del(id) -- remove
vim.ui.img.get(buf, opts) -- listUsed by the LSP image-preview path and by markdown-rendering plugins.
vim.ui_attach
A separate feature. vim.ui_attach(namespace, {ext_*=true}, callback) registers a Lua function to be called for every redraw event the editor produces, with the same data the TUI sees. Used to:
- Build pure-Lua UIs (a fully custom popup menu, a Lua-driven cmdline).
- Snapshot the screen for tests (
Screenintest/functional/ui/screen.luadoes exactly this). - Translate UI events for non-TUI consumers.
The flag set (ext_cmdline, ext_messages, ext_popupmenu, ...) determines which event categories are forwarded; events the consumer doesn't subscribe to render normally.
How it fits
graph TD
User[Plugin / editor code]
User -->|vim.ui.select / input| UI[vim.ui]
UI -->|default| Builtin[inputlist / input]
UI -.->|overridden| Plugin[Telescope / fzf-lua / ...]
User -->|vim.ui_attach| Attach[Render observer]
Attach --> Editor[redraw pipeline]
Editor --> Attachvim.ui is policy free — it doesn't know what a "good" picker looks like; it just gives plugins a stable handle to override. vim.ui_attach is the inverse — it lets advanced plugins observe the editor's render output so they can build a richer interface than inputlist() allows.
Integration points
- LSP — every
vim.lsp.buf.*operation that needs disambiguation goes throughvim.ui.select. - Snippets — choice nodes use
vim.ui.select. - Quickfix and oldfiles — recently migrated to use
vim.ui.selectso plugin pickers get used here too (commit7c4845ff46,fix(ui): z=, tselect with async vim.ui.select). :open— the Ex command bridges tovim.ui.open.vim.ui.img— used by markdown and LSP image previews.
Entry points for modification
- Replace
vim.ui.selectfor the whole editor. Override the function ininit.lua. Common plugins ship with such overrides built in. - Add a new
vim.uiprimitive. Define it on the namespace inruntime/lua/vim/ui.luaplus the implementation in_core/ui.lua(orui2.luafor newer components). Document inruntime/doc/lua.txt. - Hook into redraw.
vim.ui_attach(ns, {ext_messages = true}, fn)is the entry point; the C-side machinery lives insrc/nvim/ui.candsrc/nvim/api/ui.c.
Key source files
| File | Purpose |
|---|---|
runtime/lua/vim/ui.lua |
Public namespace |
runtime/lua/vim/_core/ui.lua |
Default implementations |
runtime/lua/vim/_core/ui2.lua |
Newer reusable UI components |
src/nvim/api/ui.c |
The nvim_ui_attach API and event marshalling |
src/nvim/ui.c |
Server-side multiplexer that drives vim.ui_attach |
runtime/doc/lua.txt (ui section) |
User docs |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.