Picker & marginalia

The picker is one vertico-style fuzzy finder that works over every source. You type :picker <source>, the picker walks that source's candidate set, and the same query line, ranking, preview, and keymap apply no matter what you picked over — files, grep hits, buffers, symbols, registers, commands. Recently-used candidates float to the top automatically across every source.

Each row can carry marginalia: typed, colour-coded annotation columns (a file's size and mtime, a command's keybinding and latency class, a grep hit's path:line:col) that live beside the matchable text rather than baked into it. Rows that contain source code — grep hits, :picker lines, :picker outline — render that code in the buffer's own syntax colours.

Status: shipped — all first-party sources below, marginalia across every source, syntax-highlighted previews for lines / outline / grep, and frecency (MRU) ranking with disk persistence. LSP-backed pickers (references, symbols, code actions) live in lsp.md; the theme picker lives in themes.md. Plugin-contributed sources are Phase 7.


Quick reference

Opening a picker

CommandOpens
:picker <source> [args]The named source (see the table below). <Tab> after :picker lists sources.
:files [root]File picker (alias for :picker files [root])
:recentRecently-edited files (alias for :picker recent)
:buffers / :bBuffer switcher (fuzzy picker; :ls is the static text listing)
:colorschemeTheme picker, with live preview (see themes.md)

Inside an open picker

KeyAction
(type)Filter the candidate list (fuzzy match)
<C-n> / <Down> / <Tab>Move selection down
<C-p> / <Up> / <S-Tab>Move selection up
<CR>Accept the selected candidate
<Esc> / <C-c>Dismiss (nothing happens; state restored)
<C-s>Accept, opening the file in a horizontal split
<C-v>Accept, opening the file in a vertical split
<C-t>Accept, opening the file in a new tab
<BS>Delete the last query character
<C-u>Clear the query

<C-s> / <C-v> / <C-t> only change where a file-opening accept lands; for sources that don't open a file (registers, commands, …) they behave like <CR>.


The sources

Every id below is valid as :picker <id>. Sources marked "active buffer" read the buffer you were in when you opened the picker.

SourceLists<CR> doesArgs
filesFiles under the workspace rootOpen the file[root] — directory to walk (default: document's parent / cwd)
recentRecently-edited files (MRU)Open the file
buffersEvery open bufferSwitch to it
linesLines of the active bufferJump to the line
outlineTree-sitter symbols in the active bufferJump to the symbol
grepLive recursive text search (rg / ag / grep)Jump to the hit[pattern] — seeds the prompt; omit to start empty
jumpsPosition-history ring (jump list + mark ring, newest first)Jump to the entry
marksVim marksJump to the mark (same as `)
registersVim registers (unnamed, numbered, named)Paste the register at the cursor
commandsThe ex-command paletteInvoke the command
snippetsSnippets for the active buffer's languageExpand the snippet at the cursor
colorschemeRegistered themes (live preview)Commit the theme— (usually reached via bare :colorscheme)

:picker grep is a live source: it re-runs the search backend as you type and streams hits in. It jumps to a single chosen hit — for a persistent, editable multibuffer of all matches, use :search instead.

Tab completion inside :picker

  • :picker <Tab> lists every source id, each with its one-line summary as marginalia.
  • :picker files <Tab> (and other sources that take an arg) completes the argument through the same machinery :e <Tab> uses — e.g. path completion for files.

Type to narrow. Matching is fuzzy: characters must appear in order, but not adjacently, and the matched characters are highlighted in each row. Ranking blends the fuzzy-match score with a frecency bonus (see MRU ranking), so a strong match wins but recent picks break ties.

Live preview. Moving the selection previews the candidate. For lines, grep, and outline the preview scrolls the underlying buffer to (and centres) the target line, so you see the result in context before committing. For colorscheme the whole editor recolours to the highlighted theme; <Esc> restores exactly what you had. Previews never touch the file on disk — dismissing leaves everything as it was.

Where accepts land. By default a picked buffer or location opens in the active pane. :set picker.result.display=split-h (or split-v) makes every picker accept open in a new sibling pane instead; the per-accept <C-s> / <C-v> / <C-t> chords override it for one pick.


Marginalia

Marginalia are the typed, colour-coded columns to the right of a row's matchable text. Instead of a source hand-padding metadata into one flat string, each source emits typed segments and the renderer resolves each segment's colour from the theme. The row's display (the part fuzzy match scores against) stays clean; the metadata rides alongside.

What each picker surfaces:

SourceMatchable textMarginalia columns
commandscommand namebound keybinding · argument hint · doc summary · latency class
bufferspathbuffer id (#N) · status ( active / + dirty) · kind
filespathpermissions · size · mtime (eza-style)
recentpathpermissions · size · mtime
grepmatched line (preview)path:line:col
jumpsbuffer labelline:col · source tag (auto / mark / plugin / 'x)
outlinesymbol nameline number
linesline textline number
marksmark nameline:col
registersregister contentsregister name ("a, "0, …)
snippetsprefix (trigger)snippet name (kind) · description

Unbound commands, un-stattable paths, and other missing values simply leave the cell blank — no placeholder, no error.

Marginalia is the same mechanism the insert-mode completion popup uses for its annotation column; see completion.md for that surface.

Colouring the columns

Every column resolves through the theme under the completion.annotation.* namespace, so :colorscheme recolours picker marginalia and buffer syntax in lockstep. The slots (with default dark tones):

SlotDefaultUsed for
completion.annotation.location.pathdimgrep hit path
completion.annotation.location.lineyellowline number
completion.annotation.location.coldimcolumn number
completion.annotation.status.dirtyreddirty-buffer +
completion.annotation.status.activegreenactive-buffer
completion.annotation.latency.reflexgreen[reflex] commands
completion.annotation.latency.displayblue[display] commands
completion.annotation.latency.backgroundorange[background] commands
completion.annotation.argssubtextcommand argument hint
completion.annotation.buffer-iddimbuffer #N
completion.annotation.registerpurpleregister / mark name

The file-metadata columns (permissions, size, mtime) in the files and recent pickers reuse the permission / size / mtime slots documented in filetree-oil.md. Picker rows don't carry a file-type icon column today — icons appear in the file tree and multibuffer headers, not here.


Syntax-highlighted previews

Rows that contain source code render it in the buffer's real syntax colours rather than one flat tone:

  • :picker lines and :picker outline colour the active buffer's line/symbol text using the tree it has already parsed — no extra parsing, no cost on the UI thread.
  • :picker grep colours each hit's preview by detecting the file's language from its extension and parsing just that one line, off the UI thread alongside the search.

The colours come from the same syntax.* theme slots as the editor, so :colorscheme recolours previews too. Where the fuzzy match overlaps a syntax span, the match highlight wins — you always see what matched first. Files with no supported grammar fall back to a plain single-colour preview.


MRU ranking

The picker keeps a per-source frecency history (recency × frequency): candidates you accept float toward the top next time, decaying over days. This is a property of the picker itself — every source gets it for free, including future plugin sources, as long as the candidate has a stable identity (a file path, a buffer, a command, a register, a mark). Coordinate-only picks (a specific grep line, a jump entry) have no stable identity and are not ranked by history.

The index persists to ~/.cache/lattice/ (or the $XDG_CACHE_HOME equivalent) between sessions. It's a derived cache, not config — if it's ever corrupt the picker discards it and starts fresh rather than refusing to open.

Configuration

OptionDefaultMeaning
picker.mru.enabledtrueFrecency ranking on/off. Off = rank by pure match score.
picker.mru.recency-half-life-days7Days for a pick's recency weight to halve.
picker.mru.cap-per-namespace1000Max history entries per source before the lowest-frecency one is evicted.
picker.mru.persisttrueWrite the index to disk between runs.
picker.displayminibufferminibuffer (vertico-style, list above the : line) or popup (centred overlay).
picker.result.displayactive-paneWhere an accepted buffer/location opens (active-pane, split-h, split-v).
picker.grep.backendautoGrep binary: auto picks the first of rg / ag / grep on PATH; or force one by name.
picker.grep.max-hits2000Cap on hits surfaced per :picker grep.

Set any of these with :set <name>=<value>, or edit them through :customize. See options.md.


Several features open the same picker over their own sources:

  • LSPgr (references), :lsp-symbols (document symbols), :lsp-workspace-symbol, :lsp-code-action. See lsp.md.
  • Themes — bare :colorscheme opens the live-preview theme picker. See themes.md.
  • Buffers:b opens the buffer switcher; :ls / :buffers print a flat listing. See buffers.md.

They all share this keymap, ranking, and marginalia machinery — the muscle memory carries across.


Not yet

  • Plugin-contributed sources. The source trait is designed to be mirrored over WASM in Phase 7; today all sources are first-party.
  • A theme colour swatch in the colorscheme picker (the live full-editor preview ships; a per-row colour chip does not).
  • Keybinding marginalia for non-command pickers (e.g. showing the open-in-split chord on file rows).