From 9aaff61b79fbc0186aa06cef18d458134321cf51 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 15 Jun 2026 09:03:44 +0200 Subject: [PATCH] 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. --- pkg/gui/gui.go | 1 + pkg/gui/side_panels.go | 29 ++++++++++- pkg/integration/tests/test_list.go | 1 + .../tests/ui/reload_side_panels.go | 51 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 pkg/integration/tests/ui/reload_side_panels.go diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 83416c582..dfc71d642 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -357,6 +357,7 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context if didChange && reloadErr == nil { gui.c.Log.Info("User config changed - reloading") reloadErr = gui.onUserConfigLoaded() + gui.reloadSidePanels() if err := gui.resetKeybindings(); err != nil { return err } diff --git a/pkg/gui/side_panels.go b/pkg/gui/side_panels.go index 2b41eb9c1..361d54fb1 100644 --- a/pkg/gui/side_panels.go +++ b/pkg/gui/side_panels.go @@ -3,6 +3,7 @@ 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. @@ -58,7 +59,8 @@ func sidePanelContexts(contextTree *context.ContextTree) map[string]types.Contex // 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). +// 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) @@ -76,6 +78,31 @@ func (gui *Gui) moveDefaultTabsToTop() { } } +// 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 diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 3f03f5665..5565ef892 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -481,6 +481,7 @@ var tests = []*components.IntegrationTest{ ui.OpenLinkFailure, ui.PromoteTabToSidePanel, ui.RangeSelect, + ui.ReloadSidePanels, ui.ReorderSidePanels, ui.SwitchTabFromMenu, ui.SwitchTabWithPanelJumpKeys, diff --git a/pkg/integration/tests/ui/reload_side_panels.go b/pkg/integration/tests/ui/reload_side_panels.go new file mode 100644 index 000000000..9d8b77693 --- /dev/null +++ b/pkg/integration/tests/ui/reload_side_panels.go @@ -0,0 +1,51 @@ +package ui + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ReloadSidePanels = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Editing the side panel config and refocusing the window re-applies the layout live, keeping the focused panel focused", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(cfg *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateNCommits(2) + // Start with worktrees promoted to its own panel. + shell.CreateFile(".git/lazygit.yml", ` +gui: + sidePanels: + - [status] + - [files, submodules] + - [worktrees] + - [branches, remotes, tags] + - [commits, reflog] + - [stash]`) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // Worktrees is its own panel in the third position. + t.Views().Files().IsFocused(). + Press(keys.Universal.JumpToBlock[2]) + t.Views().Worktrees().IsFocused() + + // Demote worktrees back into the files panel, then refocus the window to + // trigger a live reload of the changed config. + t.Shell().UpdateFile(".git/lazygit.yml", ` +gui: + sidePanels: + - [status] + - [files, worktrees, submodules] + - [branches, remotes, tags] + - [commits, reflog] + - [stash]`) + t.FocusIn() + + // Worktrees is now a tab of the files panel. It stays focused, and is shown + // in front rather than being hidden behind the files tab (which would leave + // the panel looking unfocused). + t.Views().Worktrees().IsActiveTab().IsFocused(). + Press(keys.Universal.PrevTab) + t.Views().Files().IsActiveTab().IsFocused() + }, +})