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 pos = config.opts.sidebar == "right" and "right" or "left"
-- Console height: min(5 rows, 15% of available vertical space). Caps
-- at 5 rows — Console is ancillary; the sidebar panes and source area
-- deserve most of the vertical space. On very tall terminals the 15%
-- calc would dominate, so the min() clamp keeps Console compact.
local top_bar_rows = 2
local bottom_bar_rows = 2
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
-- Console height: fixed 5 rows. Dynamic sizing via min(5, 15%) was
-- producing the right number on first enter but dapui's internal
-- layout tracker was letting Console grow back up on re-enter,
-- eventually filling the column. A hard-coded size plus a post-open
-- `winfixheight = true` pin (see pin_dapui_sizes) stops the drift.
local console_size = config.opts.console_height or 5
-- 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
@@ -137,7 +131,11 @@ local function define_highlights()
-- seam between the bar and the surrounding panes.
hl(0, "TurboDebugBar", { default = true, link = "Normal" })
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, "TurboDebugBarBrand", { default = true, link = "Title" })
hl(0, "TurboDebugBarReady", { default = true, link = "DiagnosticHint" })
@@ -525,27 +523,30 @@ local function open_bars()
render_bars()
end
-- After dapui has created its vertical splits (sidebar etc.), the bars
-- may be constrained to the non-sidebar column. `wincmd K` / `wincmd J`
-- re-layout the window tree with the bar window spanning full editor
-- width at the top / bottom respectively, pushing everything else into
-- the middle rows.
local function force_bars_full_width()
local caller_win = vim.api.nvim_get_current_win()
if sbar_win and vim.api.nvim_win_is_valid(sbar_win) then
pcall(vim.api.nvim_set_current_win, sbar_win)
pcall(vim.cmd, "wincmd K")
pcall(vim.api.nvim_win_set_height, sbar_win, 2)
-- Pin every dapui pane's height so layout drift from subsequent wincmd
-- rearrangements (or from our own bar manipulations) can't mess with
-- the sizing. Console is the main offender — without winfixheight it
-- absorbs freed rows on re-entry and eventually fills the column.
local function pin_dapui_sizes()
local pane_fts = {
dapui_stacks = true, dapui_scopes = true, dapui_watches = true,
dapui_breakpoints = true, dapui_console = true, ["dap-repl"] = true,
}
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
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
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
local function close_bars()
@@ -1064,12 +1065,15 @@ function M.enter()
set_debug_chrome()
ensure_dapui()
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()
-- dapui creates full-height vertical splits for the sidebar. Without
-- this step the bars end up constrained to the non-sidebar column.
-- `wincmd K`/`J` rearranges the window tree so each bar spans full
-- editor width at the top/bottom, pushing everything else in-between.
force_bars_full_width()
-- Pin dapui pane heights so re-entry / resize doesn't let Console
-- absorb freed rows.
pin_dapui_sizes()
-- second install pass after dapui has created its buffers
vim.schedule(function()
@@ -1077,7 +1081,8 @@ function M.enter()
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
end
reposition_bars()
pin_dapui_sizes()
render_bars()
end)
ensure_vt()
@@ -1095,10 +1100,14 @@ function M.exit()
pcall(vim.api.nvim_del_augroup_by_name, "TurboDebugModalKeys")
clear_all_modal_keys()
clear_debug_chrome()
close_bars()
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
close_bars()
if vt_initialized then require("nvim-dap-virtual-text").disable() end
end