Core Principle

When an external process needs to inspect a live Neovim session, it must compose its queries out of pure read-only APIs (nvim_list_wins, nvim_win_get_buf, nvim_buf_get_name, nvim_win_get_width/height, vim.fn.win_screenpos, nvim_get_hl). Never :normal, never mode switches, never zooming. And when mapping a Neovim window to outer terminal coordinates, use vim.fn.win_screenpos (absolute, 1-indexed, includes tabline offset) and not nvim_win_get_position (editor-area-relative, excludes tabline).

Why This Matters

Live Neovim sessions are user-owned state. A read feature (preview, status, picker) that mutates mode to scrape contents is a latent user-facing bug. Neovim’s API already exposes enough inspection primitives that mutation is never required; reaching for :normal or window-zoom is a sign you haven’t found the right read API yet. And mixing up the two coordinate systems silently produces off-by-tabline captures that look right for tabline-less Neovim setups and break otherwise.

Evidence/Examples

  • Terminal mode is sticky over RPC: calling nvim_exec2(":normal ...") against a window in terminal mode fails with E5108: Can't re-enter normal mode from terminal mode. Any design that wants this is wrong.
  • Mutation leaks even on failure: an RPC that zooms a terminal window before failing can leave the user’s input state corrupted (keystrokes producing garbage until restart).
  • win_screenpos vs nvim_win_get_position: win_screenpos returns absolute 1-indexed screen coords including tabline, nvim_win_get_position returns {row, col} relative to the editor area. The latter is off by the tabline height (and more if there’s a winbar/command line) when used as outer-terminal coordinates.
  • nvim_get_hl(0, {name="Normal"}).bg returns the background as a packed 24-bit integer, not a hex string. Decompose via math.floor(bg/65536)%256, math.floor(bg/256)%256, bg%256 to get R/G/B.

Implications

  • External previewers/inspectors for Neovim should treat the session as strictly read-only. If a design requires mode switching to work, redesign it.
  • When feeding Neovim window geometry to tools that speak in absolute screen coordinates (tmux capture-pane, external renderers), reach for win_screenpos first. Only use nvim_win_get_position when you already know you’re working in editor-area coordinates.
  • Treat any highlight-group color fetched via nvim_get_hl as an integer to be decomposed, not a string to be used directly.

Questions

  • Is there a single read-only RPC for “give me the rendered contents of this terminal window as a rectangular block of cells with styling”, instead of the current geometry-plus-external-capture approach?
  • Does winbar or floating window overlays perturb win_screenpos in ways the tabline case doesn’t expose?