Files
lazygit/pkg/gui/side_panels.go
Stefan Haller 9aaff61b79 Re-apply the side panel config on a live config reload
When the config file changes and lazygit regains focus it reloads the
config, but the side panel window assignments, default views, tab
strips, and z-order were only ever set up on repo entry, so a changed
sidePanels wouldn't take effect until restart. Re-apply it from the
reload path: reassign windows and default views and restore each panel's
default tab.

The focused panel needs care: resetting it to its default tab would
leave the focused tab hidden behind that default tab, so the panel looks
unfocused even though its tab is selected. Re-focus the current context
so its tab stays shown and highlighted; only when the new config hides
the focused panel entirely do we move focus to the default side panel.
Tab strips are already refreshed via configureViewProperties.
2026-06-23 14:15:18 +02:00

129 lines
4.7 KiB
Go

package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
// sidePanelViewNames maps each gui.sidePanels name to the gocui view it controls.
// A panel's window name is the name of its first tab, so for a panel's first tab
// this also gives the default view of its window. The keys must match
// config.ValidSidePanelTabs (enforced by a test).
var sidePanelViewNames = map[string]string{
"status": "status",
"files": "files",
"worktrees": "worktrees",
"submodules": "submodules",
"branches": "localBranches",
"remotes": "remotes",
"tags": "tags",
"commits": "commits",
"reflog": "reflogCommits",
"stash": "stash",
}
// sidePanelTabTitles maps each gui.sidePanels name to the title shown on its tab.
func (gui *Gui) sidePanelTabTitles() map[string]string {
tr := gui.c.Tr
return map[string]string{
"status": tr.StatusTitle,
"files": tr.FilesTitle,
"worktrees": tr.WorktreesTitle,
"submodules": tr.SubmodulesTitle,
"branches": tr.LocalBranchesTitle,
"remotes": tr.RemotesTitle,
"tags": tr.TagsTitle,
"commits": tr.CommitsTitle,
"reflog": tr.ReflogCommitsTitle,
"stash": tr.StashTitle,
}
}
// sidePanelContexts maps each gui.sidePanels name to the context it controls.
func sidePanelContexts(contextTree *context.ContextTree) map[string]types.Context {
return map[string]types.Context{
"status": contextTree.Status,
"files": contextTree.Files,
"worktrees": contextTree.Worktrees,
"submodules": contextTree.Submodules,
"branches": contextTree.Branches,
"remotes": contextTree.Remotes,
"tags": contextTree.Tags,
"commits": contextTree.LocalCommits,
"reflog": contextTree.ReflogCommits,
"stash": contextTree.Stash,
}
}
// applySidePanelConfig (re)assigns each side context's window and resets each
// window's default view from the current gui.sidePanels config. It runs against
// the current repo's contexts, so gui.State must already be set. We call it on
// every repo entry (a repo's per-repo config can differ from the previous one's)
// and on a live config reload.
func (gui *Gui) applySidePanelConfig() {
contextTree := gui.State.Contexts
gui.assignSidePanelWindows(contextTree)
gui.State.WindowViewNameMap = gui.initialWindowViewNameMap(contextTree)
}
// moveDefaultTabsToTop brings each panel's first configured tab to the top of
// its window, so the configured default tab is the one shown when a panel hasn't
// been focused yet (the view z-order is otherwise set from a fixed list that
// need not match the configured tab order).
func (gui *Gui) moveDefaultTabsToTop() {
contexts := sidePanelContexts(gui.State.Contexts)
for _, panel := range gui.c.UserConfig().Gui.SidePanels {
gui.helpers.Window.MoveToTopOfWindow(contexts[panel[0]])
}
}
// reloadSidePanels re-applies the side panel config to the current repo after a
// live config reload: it reassigns windows and default views, restores each
// panel's default tab, and keeps the focused panel in a consistent state.
func (gui *Gui) reloadSidePanels() {
gui.applySidePanelConfig()
gui.moveDefaultTabsToTop()
// applySidePanelConfig reset every window to show its first configured tab,
// which would leave the focused tab hidden behind its panel's default tab
// (the panel would look unfocused even though its tab is selected). Re-focus
// the current context so its tab stays shown and highlighted. If the new
// config has hidden the focused panel entirely, move focus to the default
// side panel instead.
current := gui.c.Context().Current()
if current.GetKind() != types.SIDE_CONTEXT {
return
}
if lo.Contains(gui.helpers.Window.SideWindows(), current.GetWindowName()) {
gui.c.Context().Activate(current, types.OnFocusOpts{})
} else {
gui.c.Context().Push(gui.defaultSideContext(), types.OnFocusOpts{})
}
}
// assignSidePanelWindows sets each side context's window name from the config so
// that contexts grouped into one panel share a window (the window name being the
// panel's first tab). Side panels the user hasn't listed get their own window
// name; since the layout produces no dimensions for those windows, their views
// stay hidden rather than overlapping a visible panel.
func (gui *Gui) assignSidePanelWindows(contextTree *context.ContextTree) {
contexts := sidePanelContexts(contextTree)
assigned := make(map[string]bool, len(contexts))
for _, panel := range gui.c.UserConfig().Gui.SidePanels {
windowName := panel[0]
for _, name := range panel {
contexts[name].SetWindowName(windowName)
assigned[name] = true
}
}
for name, ctx := range contexts {
if !assigned[name] {
ctx.SetWindowName(name)
}
}
}