fix: 3-row bars with both edges bright + un-squish on tabline toggle

Bars now render top+bottom bright separators (not just outward edge) so
the sbar's bottom and cbar's top read as proper chrome regardless of
what the adjacent dapui pane puts in its winbar. Fixes the "top bar's
bottom border is the wrong color" reading where the sbar's inward edge
was falling back to dapui's grey pane-title row.

Adds `ensure_bar_heights()` to un-squish bars that nvim compressed to 0
rows (not closed — just hidden) when bufferline's tabline reclaims a
row on buffer-count change. `winfixheight` is a preference, not a
guarantee; when the grid has to give up a row, nvim sometimes picks a
`winfixheight` window anyway. Wire into VimResized + WinResized so the
bar pops back on every geometry shift.

Also add `WinBar:WinBar,WinBarNC:WinBar` to the dapui winhighlight
strings (and apply it as an initial baseline in set_dapui_window_opts)
so pane titles stay bright on themes that dim WinBarNC.
This commit is contained in:
swaits committed 2026-04-12 16:04:00 -06:00
1 parent 1cdde2a177
commit c02021ad5e
1 file changed
+80 -46
+80 -46
View File
@@ -200,9 +200,17 @@ local function define_highlights()
})
end
-- winhighlight strings applied to dapui panes. `WinBar:WinBar,WinBarNC:WinBar`
-- is defensive — if the user's theme makes WinBarNC dimmer than WinBar, this
-- forces the pane's winbar row to stay bright even when the pane is
-- unfocused (nvim renders unfocused wins with WinBarNC). Nordfox happens to
-- make them identical; other themes don't.
local DAPUI_WINHL_ACTIVE = "WinBar:WinBar,WinBarNC:WinBar,WinSeparator:TurboDebugBorderActive,FloatBorder:TurboDebugBorderActive"
local DAPUI_WINHL_INACTIVE = "WinBar:WinBar,WinBarNC:WinBar,WinSeparator:TurboDebugBorderInactive,FloatBorder:TurboDebugBorderInactive"
local function setup_active_win_highlights()
local active_hl = "WinSeparator:TurboDebugBorderActive,FloatBorder:TurboDebugBorderActive"
local inactive_hl = "WinSeparator:TurboDebugBorderInactive,FloatBorder:TurboDebugBorderInactive"
local active_hl = DAPUI_WINHL_ACTIVE
local inactive_hl = DAPUI_WINHL_INACTIVE
local group = vim.api.nvim_create_augroup("TurboDebugActiveWin", { clear = true })
local function apply(win, hl)
@@ -309,19 +317,21 @@ local function render_sbar()
local pad_right = remain - pad_left
local content_line = brand_text .. string.rep(" ", pad_left) .. status_msg .. string.rep(" ", pad_right) .. state_chip
-- 2 rows: sep / content.
-- Only the outward (top) edge gets our separator. The inward (bottom)
-- edge abuts dapui's top pane, whose winbar row (pane title like
-- "🧊 Call Stack") serves as the visual boundary. Drawing our own
-- bottom separator would stack two bright rows of chrome there.
-- 3 rows: top sep / content / bottom sep. BOTH edges use the bright
-- TurboDebugBarSeparator color so the bar reads as a clearly framed
-- region regardless of what sits above or below (tabline, dapui pane,
-- source buffer — all have different chrome conventions).
vim.bo[sbar_buf].modifiable = true
vim.api.nvim_buf_set_lines(sbar_buf, 0, -1, false, { sep, content_line })
vim.api.nvim_buf_set_lines(sbar_buf, 0, -1, false, { sep, content_line, sep })
vim.bo[sbar_buf].modifiable = false
vim.api.nvim_buf_clear_namespace(sbar_buf, bar_ns, 0, -1)
pcall(vim.api.nvim_buf_set_extmark, sbar_buf, bar_ns, 0, 0, {
end_row = 0, end_col = #sep, hl_group = "TurboDebugBarSeparator",
})
pcall(vim.api.nvim_buf_set_extmark, sbar_buf, bar_ns, 2, 0, {
end_row = 2, end_col = #sep, hl_group = "TurboDebugBarSeparator",
})
-- content row
pcall(vim.api.nvim_buf_set_extmark, sbar_buf, bar_ns, 1, 0, {
end_row = 1, end_col = #brand_text, hl_group = "TurboDebugBarBrand",
@@ -401,36 +411,37 @@ local function render_cbar()
if ctrl_pad_right < 1 then ctrl_pad_right = 1 end
local content_line = string.rep(" ", ctrl_pad_left) .. controls_text .. string.rep(" ", ctrl_pad_right) .. help_text
-- 2 rows: content / sep.
-- Only the outward (bottom) edge gets our separator. The inward
-- (top) edge abuts dapui's bottom pane (Breakpoints) whose winbar
-- row acts as the visual boundary. Drawing our own top separator
-- would stack two bright rows of chrome there.
-- 3 rows: top sep / content / bottom sep. Both edges bright for a
-- visible frame, independent of what sits above (dapui Console) or
-- below (statusline/tabline).
vim.bo[cbar_buf].modifiable = true
vim.api.nvim_buf_set_lines(cbar_buf, 0, -1, false, { content_line, sep })
vim.api.nvim_buf_set_lines(cbar_buf, 0, -1, false, { sep, content_line, sep })
vim.bo[cbar_buf].modifiable = false
vim.api.nvim_buf_clear_namespace(cbar_buf, bar_ns, 0, -1)
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 1, 0, {
end_row = 1, end_col = #sep, hl_group = "TurboDebugBarSeparator",
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 0, 0, {
end_row = 0, end_col = #sep, hl_group = "TurboDebugBarSeparator",
})
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 2, 0, {
end_row = 2, end_col = #sep, hl_group = "TurboDebugBarSeparator",
})
-- control labels on row 0 (content row)
-- control labels on row 1 (content row)
local ctrl_byte_offset = ctrl_pad_left
for _, span in ipairs(ctrl_spans) do
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 0, ctrl_byte_offset + span[1], {
end_row = 0, end_col = ctrl_byte_offset + span[2], hl_group = span[3],
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 1, ctrl_byte_offset + span[1], {
end_row = 1, end_col = ctrl_byte_offset + span[2], hl_group = span[3],
})
end
for _, span in ipairs(ctrl_key_spans) do
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 0, ctrl_byte_offset + span[1], {
end_row = 0, end_col = ctrl_byte_offset + span[2], hl_group = "TurboDebugBarKey",
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 1, ctrl_byte_offset + span[1], {
end_row = 1, end_col = ctrl_byte_offset + span[2], hl_group = "TurboDebugBarKey",
})
end
local help_byte_start = #content_line - #help_text
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 0, help_byte_start, {
end_row = 0, end_col = #content_line, hl_group = "TurboDebugBarCtrl",
pcall(vim.api.nvim_buf_set_extmark, cbar_buf, bar_ns, 1, help_byte_start, {
end_row = 1, end_col = #content_line, hl_group = "TurboDebugBarCtrl",
})
cbar_zones = {}
@@ -439,12 +450,12 @@ local function render_cbar()
local byte_end = ctrl_byte_offset + z[2]
local dcol_start = vim.fn.strdisplaywidth(content_line:sub(1, byte_start))
local dcol_end = vim.fn.strdisplaywidth(content_line:sub(1, byte_end))
-- content is on buffer row 0 = window line 1
cbar_zones[#cbar_zones + 1] = { 1, dcol_start, dcol_end, z[3] }
-- content is on buffer row 1 = window line 2
cbar_zones[#cbar_zones + 1] = { 2, dcol_start, dcol_end, z[3] }
end
local help_dcol_start = vim.fn.strdisplaywidth(content_line:sub(1, help_byte_start))
local help_dcol_end = vim.fn.strdisplaywidth(content_line)
cbar_zones[#cbar_zones + 1] = { 1, help_dcol_start, help_dcol_end,
cbar_zones[#cbar_zones + 1] = { 2, help_dcol_start, help_dcol_end,
function() require("turbo-debug.help").open() end }
end
@@ -532,7 +543,7 @@ local function open_bars()
sbar_win = vim.api.nvim_get_current_win()
sbar_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_win_set_buf(sbar_win, sbar_buf)
vim.api.nvim_win_set_height(sbar_win, 2)
vim.api.nvim_win_set_height(sbar_win, 3)
setup_bar_buf(sbar_buf)
setup_bar_win(sbar_win)
vim.wo[sbar_win].winfixheight = true
@@ -551,7 +562,7 @@ local function open_bars()
cbar_win = vim.api.nvim_get_current_win()
cbar_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_win_set_buf(cbar_win, cbar_buf)
vim.api.nvim_win_set_height(cbar_win, 2)
vim.api.nvim_win_set_height(cbar_win, 3)
setup_bar_buf(cbar_buf)
setup_bar_win(cbar_win)
vim.wo[cbar_win].winfixheight = true
@@ -626,6 +637,25 @@ local function close_bars()
sbar_zones, cbar_zones = {}, {}
end
-- nvim sometimes *squishes* a bar to 0 rows rather than closing it when
-- grid pressure spikes (notably: bufferline's tabline flipping visible on
-- buffer-count change reclaims one row from somewhere, and `winfixheight`
-- is only a preference not a guarantee). The window object stays valid;
-- the content just becomes invisible. Force each bar back to 3 rows any
-- time we detect it shrunk below that.
local function ensure_bar_heights()
if sbar_win and vim.api.nvim_win_is_valid(sbar_win) then
if vim.api.nvim_win_get_height(sbar_win) < 3 then
pcall(vim.api.nvim_win_set_height, sbar_win, 3)
end
end
if cbar_win and vim.api.nvim_win_is_valid(cbar_win) then
if vim.api.nvim_win_get_height(cbar_win) < 3 then
pcall(vim.api.nvim_win_set_height, cbar_win, 3)
end
end
end
local function reposition_bars()
-- Just re-render bar content. We intentionally DON'T re-pin dapui
-- sizes here — the user may have manually resized panes, and
@@ -659,6 +689,10 @@ local function set_dapui_window_opts(win)
vim.wo[win].foldcolumn = "0"
-- make end-of-buffer `~` invisible by setting EOB fillchar to space
vim.wo[win].fillchars = "eob: "
-- Baseline winhighlight so WinBarNC:WinBar is in effect from the
-- first render (not just after the first WinEnter/WinLeave cycle).
-- setup_active_win_highlights overwrites on focus change.
vim.wo[win].winhighlight = DAPUI_WINHL_INACTIVE
end
end
@@ -1139,9 +1173,9 @@ function M.enter()
if active then M._install_for_buf(args.buf) end
end,
})
-- Editor resize: recreate any bar that got killed (shrink-below-min),
-- then re-render. dapui sizes are left alone so user manual resizes
-- stick.
-- Editor resize: recreate any bar that got killed, un-squish any that
-- got shrunk below 3 rows, then re-render. dapui sizes are left alone
-- so user manual resizes stick.
vim.api.nvim_create_autocmd("VimResized", {
group = group,
callback = function()
@@ -1154,33 +1188,33 @@ function M.enter()
cbar_win, cbar_buf = nil, nil
end
open_bars()
ensure_bar_heights()
render_bars()
end)
end,
})
-- WinResized fires on any geometry change: mouse-drags on split
-- borders, bufferline's tabline appearing/disappearing (which shifts
-- and can even KILL a bar when the grid gets too tight), `wincmd =`,
-- etc. Cheap O(1) check for missing bars — only does work when one
-- actually got nuked. Always finishes with a redraw! to clear stale
-- borders, bufferline's tabline appearing/disappearing (which
-- reclaims a row and squishes `winfixheight` bars down), `wincmd =`,
-- etc. Cheap O(1): validate and un-squish only when something
-- actually shifted. Always finishes with redraw! to clear stale
-- pixels from the shift.
vim.api.nvim_create_autocmd("WinResized", {
group = group,
callback = function()
if not active then return end
vim.schedule(function()
local need_recover = not (sbar_win and vim.api.nvim_win_is_valid(sbar_win))
or not (cbar_win and vim.api.nvim_win_is_valid(cbar_win))
if need_recover then
if not (sbar_win and vim.api.nvim_win_is_valid(sbar_win)) then
sbar_win, sbar_buf = nil, nil
end
if not (cbar_win and vim.api.nvim_win_is_valid(cbar_win)) then
cbar_win, cbar_buf = nil, nil
end
open_bars()
render_bars()
if not (sbar_win and vim.api.nvim_win_is_valid(sbar_win)) then
sbar_win, sbar_buf = nil, nil
end
if not (cbar_win and vim.api.nvim_win_is_valid(cbar_win)) then
cbar_win, cbar_buf = nil, nil
end
if not (sbar_win and cbar_win) then
open_bars()
end
ensure_bar_heights()
render_bars()
pcall(vim.cmd, "redraw!")
end)
end,