Add stacked panes

This commit is contained in:
muon 2026-03-08 15:19:42 +00:00
parent 9433fc31ea
commit 5e84e6aba8

View file

@ -152,7 +152,7 @@
modes = { modes = {
locked = { locked = {
fg = color.base03; fg = color.base03;
hint = "󱁐:󰌌 hl:󰿵 1-9:󰓩 n:󰤻 w:󰅙 ^ud:󱕷"; hint = "󱁐:󰌌 hl:󰹳 ^ud:󰹹 1-9:󰓩 n:󰤻 s:󱇳 w:󰅙 t:󰓩 p:󱃔";
}; };
normal = { normal = {
fg = color.base0D; fg = color.base0D;
@ -207,6 +207,39 @@ in {
end) end)
end end
-- tab_is_zoomed: true if any pane in the current tab is zoomed.
-- Used by move_vertical to decide whether to carry zoom forward.
local function tab_is_zoomed(pane)
for _, info in ipairs(pane:tab():panes_with_info()) do
if info.is_zoomed then return true end
end
return false
end
-- move_vertical: move focus Up/Down.
-- If any pane in the tab is currently zoomed (zoom-mode is "on" for this
-- tab), zoom the destination pane too stacked-panel behaviour.
-- Otherwise just move focus plainly.
local function move_vertical(direction)
return wezterm.action_callback(function(window, pane)
local neighbour = pane:tab():get_pane_direction(direction)
if neighbour == nil then return end
if tab_is_zoomed(pane) then
-- unzoom_on_switch_pane unzooms the current pane when we move;
-- then SetPaneZoomState(true) zooms the newly active pane.
window:perform_action(
act.Multiple({
act.ActivatePaneDirection(direction),
act.SetPaneZoomState(true),
}),
pane
)
else
window:perform_action(act.ActivatePaneDirection(direction), pane)
end
end)
end
-- Status bar -- Status bar
-- Per-mode accent colour and hint text, generated from Nix/stylix palette -- Per-mode accent colour and hint text, generated from Nix/stylix palette
@ -251,6 +284,51 @@ in {
return tab.active_pane.title return tab.active_pane.title
end) end)
-- format-tab-title: show "rank/total title" when the active tab is in
-- stacked (zoom) mode; otherwise show plain " title ".
-- Returning a cell table gives us explicit padding and colours;
-- returning a plain string loses the retro tab bar's built-in spacing.
local tab_bg_active = "${color.base0D}"
local tab_fg_active = "${color.base00}"
local tab_bg_inactive = "${color.base01}"
local tab_fg_inactive = "${color.base03}"
wezterm.on("format-tab-title", function(tab, _tabs, panes, _config, _hover, _max_width)
local title = (tab.tab_title and tab.tab_title ~= "") and tab.tab_title or tab.active_pane.title
local bg = tab.is_active and tab_bg_active or tab_bg_inactive
local fg = tab.is_active and tab_fg_active or tab_fg_inactive
-- Only compute stack position for the active tab.
-- The `panes` event parameter only contains the active pane snapshot
-- and lacks `top`, so we use wezterm.mux.get_tab():panes_with_info()
-- which returns full PaneInformation including top/is_zoomed/is_active.
if tab.is_active then
local mux_tab = wezterm.mux.get_tab(tab.tab_id)
if mux_tab then
local infos = mux_tab:panes_with_info()
table.sort(infos, function(a, b) return a.top < b.top end)
local any_zoomed = false
local active_rank, total = 0, 0
for _, p in ipairs(infos) do
total = total + 1
if p.is_active then active_rank = total end
if p.is_zoomed then any_zoomed = true end
end
if any_zoomed and total > 1 and active_rank > 0 then
title = active_rank .. "/" .. total .. " " .. title
end
end
end
return {
{ Background = { Color = bg } },
{ Foreground = { Color = fg } },
{ Text = " " .. title .. " " },
}
end)
wezterm.on("update-status", function(window, _pane) wezterm.on("update-status", function(window, _pane)
local kt = window:active_key_table() local kt = window:active_key_table()
local mode = kt or "locked" local mode = kt or "locked"
@ -290,6 +368,7 @@ in {
config.default_cursor_style = "BlinkingBar" config.default_cursor_style = "BlinkingBar"
config.hide_tab_bar_if_only_one_tab = false config.hide_tab_bar_if_only_one_tab = false
config.use_fancy_tab_bar = false config.use_fancy_tab_bar = false
config.tab_max_width = 24
config.colors = { config.colors = {
tab_bar = { tab_bar = {
@ -313,6 +392,11 @@ in {
config.scrollback_lines = 10000 config.scrollback_lines = 10000
-- Stacked panes
-- When switching away from a zoomed pane, unzoom it automatically.
-- Paired with stack_focus (Alt+j/k) this gives Zellij-style stacked panels.
config.unzoom_on_switch_pane = true
-- Key bindings -- Key bindings
-- --
-- Default mode = "locked" (all keys pass through to the shell). -- Default mode = "locked" (all keys pass through to the shell).
@ -324,7 +408,7 @@ in {
config.keys = { config.keys = {
-- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab) -- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab)
-- Alt+j/k: move focus between panes -- Alt+j/k: initial bindings (overridden below by stacked-focus variants)
${viDirKeys { ${viDirKeys {
mods = "ALT"; mods = "ALT";
indent = " "; indent = " ";
@ -340,6 +424,21 @@ in {
}} }}
-- Alt+n: new pane (split right) -- Alt+n: new pane (split right)
{ key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) }, { key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) },
-- Alt+s: split below, then zoom the new pane so stacked mode starts.
-- Two separate perform_action calls: the split runs first and focuses
-- the new pane, then the second call zooms window:active_pane() which
-- is now the new pane (not the original `pane` argument).
{ key = "s", mods = "ALT", action = wezterm.action_callback(function(window, pane)
window:perform_action(act.SplitVertical({ domain = "CurrentPaneDomain" }), pane)
window:perform_action(act.SetPaneZoomState(true), window:active_pane())
end)
},
-- Alt+j/k: move_vertical plain focus move normally, stacked zoom
-- when any pane in the tab is already zoomed (mirrors Alt+f state).
{ key = "j", mods = "ALT", action = move_vertical("Down") },
{ key = "k", mods = "ALT", action = move_vertical("Up") },
{ key = "DownArrow", mods = "ALT", action = move_vertical("Down") },
{ key = "UpArrow", mods = "ALT", action = move_vertical("Up") },
-- Alt+f: toggle zoom -- Alt+f: toggle zoom
{ key = "f", mods = "ALT", action = act.TogglePaneZoomState }, { key = "f", mods = "ALT", action = act.TogglePaneZoomState },
-- Alt+[/]: rotate panes -- Alt+[/]: rotate panes
@ -352,8 +451,24 @@ in {
-- Alt+i/o: reorder tabs -- Alt+i/o: reorder tabs
{ key = "i", mods = "ALT", action = act.MoveTabRelative(-1) }, { key = "i", mods = "ALT", action = act.MoveTabRelative(-1) },
{ key = "o", mods = "ALT", action = act.MoveTabRelative(1) }, { key = "o", mods = "ALT", action = act.MoveTabRelative(1) },
-- Alt+w: close current pane -- Alt+w: close current pane; if tab was in stacked (zoom) mode, re-zoom
{ key = "w", mods = "ALT", action = act.CloseCurrentPane({ confirm = true }) }, -- the new active pane so zoom mode persists across close.
-- confirm=false because the callback must run synchronously after close;
-- a confirm dialog would make the zoom fire before the user answers.
-- Guard: only re-zoom when there will still be panes left after close.
{ key = "w", mods = "ALT", action = wezterm.action_callback(function(window, pane)
local panes = pane:tab():panes()
local was_zoomed = tab_is_zoomed(pane)
window:perform_action(act.CloseCurrentPane({ confirm = false }), pane)
if was_zoomed and #panes > 1 then
window:perform_action(act.SetPaneZoomState(true), window:active_pane())
end
end)
},
-- Alt+p: command palette
{ key = "p", mods = "ALT", action = act.ActivateCommandPalette },
-- Alt+t: tab navigator (searchable tab list)
{ key = "t", mods = "ALT", action = act.ShowTabNavigator },
-- Alt+q: quit -- Alt+q: quit
{ key = "q", mods = "ALT", action = act.QuitApplication }, { key = "q", mods = "ALT", action = act.QuitApplication },
-- Ctrl+U/D: scroll half page (matches Alacritty) -- Ctrl+U/D: scroll half page (matches Alacritty)