The main panel owns the debugger UI windows, one scratch buffer per
registered view, and the registration/lifecycle API consumed by the bundled
views. Views never create their own windows or buffers; that boundary is
CI-enforced by scripts/lint.lua.
require("nvim-dapper.panel.main").setup({
placement = "bottom", -- "bottom" (default) | "right" | "float"
height = 15, -- bottom default; rows (integer) or ratio in (0, 1)
width = 60, -- right default; cols (integer) or ratio in (0, 1)
auto_open = false, -- open on session_started? off by default
splits = nil, -- flat indexed split definitions; see below
})placement:bottom,right, orfloat. Bottom setswinfixheight, right setswinfixwidth, and float is centered with a rounded border. Floats default towidth = 0.6, height = 0.4.height/width: positive numbers. Values in(0, 1)are ratios of the editor dimension; integers are absolute rows or columns.auto_open: whentrue,session_startedopens the panel if it is closed. Off by default.splits:nilor a non-empty list of flat sibling split definitions.
Omitting splits creates one split containing all registered views in
registration order. This is the default for require("nvim-dapper").setup({}).
Each configured split declares exactly one of:
{ view = "console" }
{ views = { "variables", "stack", "watches" } }view is shorthand for a one-entry views list. View ids may be configured
before views are registered; resolution happens when the panel opens. If an id
is still unregistered at open time, panel.open() raises and leaves the panel
closed.
Duplicate ids, empty views, non-string ids, unknown split keys, and legacy
layout / panes options raise during setup. View registrations must not
include pane or split ownership fields; layout assignment belongs in
panel.splits.
Each split may set size:
splits = {
{ view = "variables", size = "45%" },
{ view = "console", size = 30 },
{ view = "stack" },
}size = 30means 30 cells along the split orientation.size = "45%"means 45 percent of the available panel dimension.- omitted sizes divide the remaining cells evenly.
Numeric ratios such as 0.45, non-integer numbers, zero, negative numbers,
and malformed percent strings are rejected. If resolved sizes cannot fit when
the panel opens, the call raises and closes any panel windows it created.
Wide placements, bottom and float, arrange splits left-to-right. Tall
placements, currently right, arrange splits top-to-bottom.
Every open panel split receives a window-local statusline, replacing the
default scratch-buffer presentation. Exactly one split is the debug status
carrier:
- wide layouts (
bottomandfloat) use the leftmost split; - tall layouts (
right) use the bottom-most split.
The carrier renders require("nvim-dapper.session").current_status() with a
compact format:
IDLE
● RUNNING | Profile: Remote-Docker (✈ 192.168.1.50)
⏸ PAUSED @ auth.js:18 | Profile: Remote-Docker
✕ STOPPED | Profile: Remote-Docker
Profile metadata is shown as Profile: <name>. When a process or target hint
is available, it is rendered after the profile in parentheses, preferring
PID: <pid> over ✈ <connection> over target text. Non-carrier splits use a
single-space local statusline so they render blank instead of falling back to
the user's global statusline. The carrier uses DapperPanelActiveView, the
same highlight group used by the active view label in the winbar.
The panel refreshes all split statuslines after panel open, view lifecycle
changes, and synchronously when the panel's main-panel session subscription
receives a session event. Winbars remain split-scoped and continue to own view
switching and active-view highlighting.
local panel = require("nvim-dapper.panel.main")
panel.register_view({
id = "variables",
name = "Variables",
render = function(buf, ctx) ... end,
on_mount = function(buf, ctx) ... end,
on_unmount = function(buf, ctx) ... end,
on_focus = function(buf, ctx) ... end,
on_blur = function(buf, ctx) ... end,
on_session_event = function(buf, event, ctx) ... end,
})Required fields are id, name, and render. Duplicate ids raise.
Registration order is preserved globally for panel.list_views(),
:DebugView completion, and session-event fan-out.
Views are mounted on first activation, never at registration:
nvim_create_buf -> on_mount -> render -> nvim_win_set_buf -> on_focus
Switching within a split blurs the outgoing active view, mounts or refreshes
the incoming view, swaps the buffer with nvim_win_set_buf, updates that
split's winbar, then focuses the incoming view. Scratch buffers remain valid
across switches and panel close/open.
ctx is allocated once per view and reused across callbacks:
{ panel = panel, view_id = "variables", win = win_or_nil, session = session, split_id = 1 }ctx.win is the active split window while the view is focused and is cleared
after on_blur. ctx.split_id identifies the active split during lifecycle
callbacks.
| Function | Purpose |
|---|---|
panel.open([target_id]) |
Open all configured splits; mounts target view first when provided. |
panel.close() |
Close every split window. Buffers and view state are retained. |
panel.toggle() |
open if closed, close if open. |
panel.show_view(id) |
Switch the owning split to id; opens the panel if closed. |
| `panel.cycle_view("next" | "prev")` |
panel.request_redraw(view_id) |
Re-render now if active, mark dirty if inactive, no-op if unmounted. |
panel.current_view() |
Focused split's active id, fallback to split 1 while open, or nil. |
panel.split_view(index) |
Active view id for a 1-based split index, or nil. |
panel.is_open() |
true if any owned split window is valid. |
panel.get_win() |
Focused split window, split 1 fallback, or nil. |
panel.split_win(index) |
Window handle for a 1-based split index, or nil. |
panel.list_views([index]) |
Fresh global view order, or a split-scoped view order. |
The panel subscribes to the session manager once under
subscriber_id = "main-panel" and fans every event out to mounted views in
global registration order. Unmounted views do not receive events because they
do not yet have scratch buffers.
| Command | Behaviour |
|---|---|
:DebugPanel |
Open the panel; focuses it if already open. |
:DebugPanelToggle |
Toggle open/closed. |
:DebugPanelClose |
Close the panel. |
:DebugView <id> |
Switch to a registered view id. |
Each split window renders a split-scoped winbar. Active segments use
DapperPanelActiveView; inactive segments use DapperPanelInactiveView.
Defaults are installed with:
hi default link DapperPanelActiveView Title
hi default link DapperPanelInactiveView CommentSegments use Neovim statusline click regions and route through
require("nvim-dapper.panel.main")._winbar_click.