fix: stable sidebar + console sizing across toggle cycles

The "console fills the whole column" + "sidebar panes all fucked up on
re-entry" bug traces to three interacting problems:

1. I was creating bars BEFORE dapui.open, then using `wincmd K/J` to
   force them to full width. That cascade recomputed every window
   height. dapui windows without winfixheight absorbed freed space
   unpredictably, and the drift accumulated on every toggle.

2. M.exit was closing our bars BEFORE dapui. When our bars closed,
   their 4 rows were freed; dapui grabbed those rows into Console
   and remembered the new size. Next open → Console ballooned.

3. Console height was coming out right on first entry but dapui's
   internal size tracker was letting it grow back on re-open.

Fixes:

- Open bars AFTER dapui.open. `:topleft split` and `:botright split`
  natively produce full-width horizontal splits above/below whatever
  layout already exists — no wincmd K/J rearrangement needed.
- Close dapui BEFORE close_bars in M.exit so dapui doesn't see our
  bars disappearing and grow to fill the void.
- New pin_dapui_sizes(): after dapui.open, iterate dapui panes and
  set winfixheight = true on all of them. Console gets an explicit
  set_height to config.opts.console_height (default 5) first. Panes
  are now locked — wincmd/resize can't mess with them.
- Console height: fixed 5 rows (config.opts.console_height), no
  more dynamic min(5, 15% of avail) which was producing unstable
  values on re-entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-04-12 14:31:01 -06:00
1 parent 32c7e465c8
commit 4f5fecfb34
1 file changed
+49 -40
+49 -40
View File
@@ -81,18 +81,12 @@ local ICON = {
local function build_default_layouts() local function build_default_layouts()
local pos = config.opts.sidebar == "right" and "right" or "left" local pos = config.opts.sidebar == "right" and "right" or "left"
-- Console height: min(5 rows, 15% of available vertical space). Caps -- Console height: fixed 5 rows. Dynamic sizing via min(5, 15%) was
-- at 5 rows — Console is ancillary; the sidebar panes and source area -- producing the right number on first enter but dapui's internal
-- deserve most of the vertical space. On very tall terminals the 15% -- layout tracker was letting Console grow back up on re-enter,
-- calc would dominate, so the min() clamp keeps Console compact. -- eventually filling the column. A hard-coded size plus a post-open
local top_bar_rows = 2 -- `winfixheight = true` pin (see pin_dapui_sizes) stops the drift.
local bottom_bar_rows = 2 local console_size = config.opts.console_height or 5
local statusline_rows = vim.o.laststatus > 0 and 1 or 0
local cmdline_rows = math.max(1, vim.o.cmdheight)
local avail = vim.o.lines - top_bar_rows - bottom_bar_rows - statusline_rows - cmdline_rows
if avail < 10 then avail = 10 end
local console_size = math.min(5, math.floor(avail * 0.15))
if console_size < 3 then console_size = 3 end -- absolute floor so it's usable
-- Sidebar width: ideal 40 cols, never less than 25, never more than 1/3 -- Sidebar width: ideal 40 cols, never less than 25, never more than 1/3
-- of tty width. Scales down gracefully on narrow terminals, stops at 40 -- of tty width. Scales down gracefully on narrow terminals, stops at 40
@@ -137,7 +131,11 @@ local function define_highlights()
-- seam between the bar and the surrounding panes. -- seam between the bar and the surrounding panes.
hl(0, "TurboDebugBar", { default = true, link = "Normal" }) hl(0, "TurboDebugBar", { default = true, link = "Normal" })
hl(0, "TurboDebugBarSeparator", { default = true, link = "FloatBorder" }) hl(0, "TurboDebugBarSeparator", { default = true, link = "FloatBorder" })
hl(0, "TurboDebugBarCtrl", { default = true, link = "StatusLine" }) -- Control labels: link to Normal so the text blends with the bar's
-- Normal-bg surface. StatusLine bg was creating a visible color patch
-- behind each label. The key letter still pops via TurboDebugBarKey
-- (Special fg, bold), and icons carry their own emoji color.
hl(0, "TurboDebugBarCtrl", { default = true, link = "Normal" })
hl(0, "TurboDebugBarStatus", { default = true, link = "Comment" }) hl(0, "TurboDebugBarStatus", { default = true, link = "Comment" })
hl(0, "TurboDebugBarBrand", { default = true, link = "Title" }) hl(0, "TurboDebugBarBrand", { default = true, link = "Title" })
hl(0, "TurboDebugBarReady", { default = true, link = "DiagnosticHint" }) hl(0, "TurboDebugBarReady", { default = true, link = "DiagnosticHint" })
@@ -525,27 +523,30 @@ local function open_bars()
render_bars() render_bars()
end end
-- After dapui has created its vertical splits (sidebar etc.), the bars -- Pin every dapui pane's height so layout drift from subsequent wincmd
-- may be constrained to the non-sidebar column. `wincmd K` / `wincmd J` -- rearrangements (or from our own bar manipulations) can't mess with
-- re-layout the window tree with the bar window spanning full editor -- the sizing. Console is the main offender — without winfixheight it
-- width at the top / bottom respectively, pushing everything else into -- absorbs freed rows on re-entry and eventually fills the column.
-- the middle rows. local function pin_dapui_sizes()
local function force_bars_full_width() local pane_fts = {
local caller_win = vim.api.nvim_get_current_win() dapui_stacks = true, dapui_scopes = true, dapui_watches = true,
if sbar_win and vim.api.nvim_win_is_valid(sbar_win) then dapui_breakpoints = true, dapui_console = true, ["dap-repl"] = true,
pcall(vim.api.nvim_set_current_win, sbar_win) }
pcall(vim.cmd, "wincmd K") for _, buf in ipairs(vim.api.nvim_list_bufs()) do
pcall(vim.api.nvim_win_set_height, sbar_win, 2) if vim.api.nvim_buf_is_valid(buf) then
local ft = vim.bo[buf].filetype
if pane_fts[ft] then
for _, win in ipairs(vim.fn.win_findbuf(buf)) do
if vim.api.nvim_win_is_valid(win) then
if ft == "dapui_console" then
pcall(vim.api.nvim_win_set_height, win, config.opts.console_height or 5)
end
pcall(function() vim.wo[win].winfixheight = true end)
end
end
end
end
end end
if cbar_win and vim.api.nvim_win_is_valid(cbar_win) then
pcall(vim.api.nvim_set_current_win, cbar_win)
pcall(vim.cmd, "wincmd J")
pcall(vim.api.nvim_win_set_height, cbar_win, 2)
end
if vim.api.nvim_win_is_valid(caller_win) then
pcall(vim.api.nvim_set_current_win, caller_win)
end
render_bars()
end end
local function close_bars() local function close_bars()
@@ -1064,12 +1065,15 @@ function M.enter()
set_debug_chrome() set_debug_chrome()
ensure_dapui() ensure_dapui()
require("dapui").open() require("dapui").open()
-- Create bars AFTER dapui.open so :topleft split / :botright split
-- create full-width splits above/below whatever dapui built. Doing it
-- before dapui meant dapui's sidebar-vsplit could extend through the
-- bar rows, and my old `wincmd K/J` compensation caused layout drift
-- on re-entry (Console ballooning). Opening after dapui avoids both.
open_bars() open_bars()
-- dapui creates full-height vertical splits for the sidebar. Without -- Pin dapui pane heights so re-entry / resize doesn't let Console
-- this step the bars end up constrained to the non-sidebar column. -- absorb freed rows.
-- `wincmd K`/`J` rearranges the window tree so each bar spans full pin_dapui_sizes()
-- editor width at the top/bottom, pushing everything else in-between.
force_bars_full_width()
-- second install pass after dapui has created its buffers -- second install pass after dapui has created its buffers
vim.schedule(function() vim.schedule(function()
@@ -1077,7 +1081,8 @@ function M.enter()
for _, buf in ipairs(vim.api.nvim_list_bufs()) do for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf) then M._install_for_buf(buf) end if vim.api.nvim_buf_is_loaded(buf) then M._install_for_buf(buf) end
end end
reposition_bars() pin_dapui_sizes()
render_bars()
end) end)
ensure_vt() ensure_vt()
@@ -1095,10 +1100,14 @@ function M.exit()
pcall(vim.api.nvim_del_augroup_by_name, "TurboDebugModalKeys") pcall(vim.api.nvim_del_augroup_by_name, "TurboDebugModalKeys")
clear_all_modal_keys() clear_all_modal_keys()
clear_debug_chrome() clear_debug_chrome()
close_bars()
help.close() help.close()
-- Close dapui FIRST so dapui's windows tear down into a state we
-- control. Closing our bars first would let dapui's remaining panes
-- expand into the freed rows, which dapui then "remembers" and
-- breaks the next open's layout.
if dapui_initialized then require("dapui").close() end if dapui_initialized then require("dapui").close() end
close_bars()
if vt_initialized then require("nvim-dap-virtual-text").disable() end if vt_initialized then require("nvim-dap-virtual-text").disable() end
end end