Open-Source Wikis

/

Neovim

/

Features

/

UI providers (`vim.ui`)

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

runtime/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.snippet choice 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) -- list

Used 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 (Screen in test/functional/ui/screen.lua does 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 --> Attach

vim.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 through vim.ui.select.
  • Snippets — choice nodes use vim.ui.select.
  • Quickfix and oldfiles — recently migrated to use vim.ui.select so plugin pickers get used here too (commit 7c4845ff46, fix(ui): z=, tselect with async vim.ui.select).
  • :open — the Ex command bridges to vim.ui.open.
  • vim.ui.img — used by markdown and LSP image previews.

Entry points for modification

  • Replace vim.ui.select for the whole editor. Override the function in init.lua. Common plugins ship with such overrides built in.
  • Add a new vim.ui primitive. Define it on the namespace in runtime/lua/vim/ui.lua plus the implementation in _core/ui.lua (or ui2.lua for newer components). Document in runtime/doc/lua.txt.
  • Hook into redraw. vim.ui_attach(ns, {ext_messages = true}, fn) is the entry point; the C-side machinery lives in src/nvim/ui.c and src/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.

UI providers (`vim.ui`) – Neovim wiki | Factory