diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index de3bdbe9b..bcdc0edfc 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -154,7 +154,14 @@ func (gui *Gui) layout(g *gocui.Gui) error { if err != nil && !errors.Is(err, gocui.ErrUnknownView) { return err } - view.Visible = gui.helpers.Window.GetViewNameForWindow(context.GetWindowName()) == context.GetViewName() + // A transient view is visible if it is the view its window is currently + // showing — but only if that window is part of the layout at all. For a + // window without dimensions, setViewFromDimensions parks the view at full + // screen size in the background, so making it visible would cover all + // windows below it. + _, windowHasDimensions := viewDimensions[context.GetWindowName()] + view.Visible = windowHasDimensions && + gui.helpers.Window.GetViewNameForWindow(context.GetWindowName()) == context.GetViewName() } if gui.PrevLayout.Information != informationStr { diff --git a/pkg/gui/side_panels.go b/pkg/gui/side_panels.go index 361d54fb1..8b327b197 100644 --- a/pkg/gui/side_panels.go +++ b/pkg/gui/side_panels.go @@ -125,4 +125,13 @@ func (gui *Gui) assignSidePanelWindows(contextTree *context.ContextTree) { ctx.SetWindowName(name) } } + + // The transient contexts take over the window of the context they are + // drilled into from, but they need a valid initial window before their + // first use. Assign the window hosting branches or commits, respectively; + // unlike e.g. remotes, those tabs can't be hidden, so their windows are + // always part of the layout. + contextTree.RemoteBranches.SetWindowName(contextTree.Branches.GetWindowName()) + contextTree.SubCommits.SetWindowName(contextTree.Branches.GetWindowName()) + contextTree.CommitFiles.SetWindowName(contextTree.LocalCommits.GetWindowName()) } diff --git a/pkg/gui/side_panels_test.go b/pkg/gui/side_panels_test.go index b1240c357..24813b4ae 100644 --- a/pkg/gui/side_panels_test.go +++ b/pkg/gui/side_panels_test.go @@ -28,3 +28,23 @@ func TestSidePanelLookupsCoverAllValidTabs(t *testing.T) { assert.Equal(t, want, sortedKeys(gui.sidePanelTabTitles())) assert.Equal(t, want, sortedKeys(sidePanelContexts(gui.contextTree()))) } + +// The transient contexts must end up in windows that exist under the configured +// panel layout, or their views would be laid out for a window that is never +// shown. +func TestAssignSidePanelWindowsCoversTransientContexts(t *testing.T) { + gui := NewDummyGui() + gui.c.UserConfig().Gui.SidePanels = []config.SidePanel{ + {"worktrees", "branches", "remotes"}, + {"files"}, + {"tags", "commits"}, + {"stash"}, + } + + contextTree := gui.contextTree() + gui.assignSidePanelWindows(contextTree) + + assert.Equal(t, "worktrees", contextTree.RemoteBranches.GetWindowName()) + assert.Equal(t, "worktrees", contextTree.SubCommits.GetWindowName()) + assert.Equal(t, "tags", contextTree.CommitFiles.GetWindowName()) +} diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index abf13073e..8213d5159 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -484,6 +484,8 @@ var tests = []*components.IntegrationTest{ tag.Reset, tag.ResetToDuplicateNamedBranch, ui.Accordion, + ui.BranchesNotFirstTab, + ui.CommitsNotFirstTab, ui.DisableSwitchTabWithPanelJumpKeys, ui.EmptyMenu, ui.HideSidePanel, diff --git a/pkg/integration/tests/ui/branches_not_first_tab.go b/pkg/integration/tests/ui/branches_not_first_tab.go new file mode 100644 index 000000000..47e8a4fd5 --- /dev/null +++ b/pkg/integration/tests/ui/branches_not_first_tab.go @@ -0,0 +1,31 @@ +package ui + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var BranchesNotFirstTab = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "With gui.sidePanels grouping branches behind another tab, no ghost view must appear over the side panels", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.SidePanels = []config.SidePanel{ + {"worktrees", "branches", "remotes"}, + {"files"}, + {"commits", "tags"}, + {"stash"}, + } + }, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("one") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // The remote branches and sub-commits views are only shown after + // drilling into a remote or a branch; at startup both must be hidden, + // or they'd cover the side panels. + t.Views().RemoteBranches(). + IsInvisible() + t.Views().SubCommits().IsInvisible() + }, +}) diff --git a/pkg/integration/tests/ui/commits_not_first_tab.go b/pkg/integration/tests/ui/commits_not_first_tab.go new file mode 100644 index 000000000..505aa4307 --- /dev/null +++ b/pkg/integration/tests/ui/commits_not_first_tab.go @@ -0,0 +1,29 @@ +package ui + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CommitsNotFirstTab = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "With gui.sidePanels grouping commits behind another tab, no ghost view must appear over the side panels", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.SidePanels = []config.SidePanel{ + {"branches", "worktrees", "remotes"}, + {"files"}, + {"tags", "commits"}, + {"stash"}, + } + }, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("one") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // The commit files view is only shown after drilling into a commit; at + // startup it must be hidden, or it'd cover the side panels. + t.Views().CommitFiles(). + IsInvisible() + }, +})