WilliamHsieh
overlook.nvim
Lua

Stackable popups for Neovim. Peek around the code without overlooking the bigger picture.

Last updated Jul 5, 2026
139
Stars
11
Forks
4
Issues
0
Stars/day
Attention Score
69
Language breakdown
Lua 99.6%
Makefile 0.4%
โ–ธ Files click to expand
README

overlook.nvim

Explore without losing context. Stackable, editable floating popups for Neovim.

Demo

https://github.com/user-attachments/assets/ac784f7e-e4ad-45be-b2f5-60e8318c8089

The Problem

You know the frustration: you're deep in a function, need to check a definition, so you jump to it... and now you've lost your place. Or you use a peek feature but can't fix that typo you just spotted.

The Solution

overlook.nvim creates stackable floating popups that are actual buffers - edit them, save them, navigate from them. Build a visual trail of your code exploration without ever losing where you started.

Key Features

  • ๐Ÿ” Peek at definitions - View LSP definitions, marks, or any location in floating windows
  • โœ๏ธ Actually editable - Spot a bug? Fix it right there in the popup and :w to save
  • ๐Ÿ“š Visual stack navigation - See your entire exploration path as cascading popups
  • ๐Ÿ”„ Undo your exploration - Accidentally closed a popup? Bring it back with restore_popup()
  • ๐ŸชŸ Popup promotion - Found something important? Convert any popup to a split/tab
  • ๐ŸŽฏ Window-local stacks - Each window maintains its own popup stack for parallel exploration

Why overlook.nvim?

The core philosophy is simple: popups are buffers. Everything else follows from that one decision. Because a popup is a real buffer in a real window, it is editable and saveable; every keybinding, plugin, and picker you already have works inside it; it can host the next peek (which is why popups stack); and when a quick glance turns into real work, it can be promoted to a split or tab. There is nothing special to learn - it behaves exactly like any other window.

Real-world use cases:

  • Trace through call chains: Peek definition โ†’ find another reference โ†’ peek again โ†’ you now have a visual stack showing your exploration path
  • Fix as you explore: Reviewing a function and spot a typo? Fix it in the popup and save - no context switching needed
  • Visual debugging: Build a breadcrumb trail of function calls while stepping through the code

What makes it different:

  • All your keybindings work normally in popups
  • Switch buffers inside popups - :bnext, <C-^>, telescope/fzf all work
  • Popups automatically offset and resize to stay readable when stacked
  • Full undo/redo support for your exploration history

Requirements

  • Neovim >= 0.11 (CI tests against 0.11.x and nightly)
  • An LSP client only for peekdefinition() - peekcursor() and peek_mark() need nothing extra
  • No external dependencies

Installation

Using lazy.nvim:

{
  "WilliamHsieh/overlook.nvim",
  opts = {},

-- Optional: set up common keybindings keys = { { "<leader>pd", function() require("overlook.api").peek_definition() end, desc = "Overlook: Peek definition" }, { "<leader>pc", function() require("overlook.api").close_all() end, desc = "Overlook: Close all popup" }, { "<leader>pu", function() require("overlook.api").restore_popup() end, desc = "Overlook: Restore popup" }, }, }

Quick Start

  • Install the plugin with your favorite package manager
  • Add a keybinding for peek_definition()
  • Navigate to any symbol and trigger the peek
  • Edit the popup content if needed
  • Press q to close or continue exploring
That's it! No complex setup required.

Configuration

overlook.nvim works out of the box with sensible defaults - opts = {} is all you need. Pass a table to setup() to override only the pieces you care about. The example below sets non-default values to illustrate the shape:

require("overlook").setup({
  -- Overrides (not defaults) - shown to illustrate the shape
  ui = {
    border = "single",  -- default is "rounded"
    size_ratio = 0.8,   -- default is 0.65
  },

-- Run custom logic when the last popup in a stack closes onstackempty = function() -- Your custom logic here end, })

The full, always-current list of options and their defaults lives in :h overlook-config - it is generated from the source annotations, so it can't drift out of sync.

Usage

Essential keybindings

vim.keymap.set("n", "<leader>pd", require("overlook.api").peek_definition, { desc = "Peek definition" })
vim.keymap.set("n", "<leader>pp", require("overlook.api").peek_cursor, { desc = "Peek cursor" })
vim.keymap.set("n", "<leader>pu", require("overlook.api").restore_popup, { desc = "Restore last popup" })
vim.keymap.set("n", "<leader>pU", require("overlook.api").restoreallpopups, { desc = "Restore all popups" })
vim.keymap.set("n", "<leader>pc", require("overlook.api").close_all, { desc = "Close all popups" })
vim.keymap.set("n", "<leader>pf", require("overlook.api").switch_focus, { desc = "Switch focus" })
vim.keymap.set("n", "<leader>ps", require("overlook.api").openinsplit, { desc = "Open popup in split" })
vim.keymap.set("n", "<leader>pv", require("overlook.api").openinvsplit, { desc = "Open popup in vsplit" })
vim.keymap.set("n", "<leader>pt", require("overlook.api").openintab, { desc = "Open popup in tab" })
vim.keymap.set("n", "<leader>po", require("overlook.api").openinoriginal_window, { desc = "Open popup in current window" })

API Functions

Check :h overlook-api for more details.

  • peek_definition() - Peek at the LSP definition under cursor
  • peek_cursor() - Create a popup at current cursor position
  • peek_mark() - Prompt for a mark and peek at its location
  • restore_popup() - Restore the last closed popup
  • restoreallpopups() - Restore all closed popups
  • close_all() - Close all overlook popups
  • switch_focus() - Switch focus between popups and root window
  • openinsplit() - Promote popup to horizontal split
  • openinvsplit() - Promote popup to vertical split
  • openintab() - Promote popup to new tab
  • openinoriginal_window() - Replace current window with popup content

Writing a custom peek source

A peek source is just a function: gather whatever context you need, build an options table, and call require("overlook.window").open_popup(...). There is no registration step - once you have the function, bind it to a key exactly like any built-in.

local function peekalternatefile()
  local alt = vim.fn.bufnr("#") -- the alternate buffer
  if alt == -1 then
    return vim.notify("Overlook: no alternate buffer", vim.log.levels.WARN)
  end
  require("overlook.window").open_popup({
    target_bufnr = alt,
    lnum = 1,
    col = 1,
    title = vim.fn.bufname(alt),
  })
end

vim.keymap.set("n", "<leader>pa", peekalternatefile, { desc = "Peek alternate file" })

Async sources (LSP, pickers) work the same way - they just call open_popup later from their own callback. See :h overlook-peek for the full contract and overlook.peek.* for the built-in examples.

Development

  • make test - run the plenary test suite
  • make vimdoc - regenerate doc/ from the source annotations (never edit doc/*.txt by hand)
  • stylua - format Lua code

Acknowledgments

Special thanks to the lspsaga.nvim project for the original peekdefinition implementation that inspired this plugin.

๐Ÿ”— More in this category

ยฉ 2026 GitRepoTrend ยท WilliamHsieh/overlook.nvim ยท Updated daily from GitHub