From 228090232f8b627dbac20d4ea3e3b4e2eb3ad334 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Aug 2026 10:08:17 +0200 Subject: [PATCH] Let the main section show the secondary pane alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main section shows one pane or two, which was enough while the second pane only ever accompanied the first. It is about to have to show the second one by itself: the working tree's staged changes are moving there for good, and a file with nothing but staged changes has only that side to show — it should have the whole section rather than sit under an empty pane. So which panes are shown becomes a three-way answer, derived from which of them the render has content for. Behaviour-preserving: nothing renders into the secondary pane alone yet. Co-authored-by: Claude Opus 5 (1M context) --- .../helpers/window_arrangement_helper.go | 51 ++++++++----------- .../helpers/window_arrangement_helper_test.go | 2 +- pkg/gui/gui.go | 10 ++-- pkg/gui/main_panels.go | 21 ++++++-- pkg/gui/types/common.go | 4 +- pkg/gui/types/rendering.go | 13 +++++ 6 files changed, 60 insertions(+), 41 deletions(-) diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper.go b/pkg/gui/controllers/helpers/window_arrangement_helper.go index 5379e9c09..7428d17e3 100644 --- a/pkg/gui/controllers/helpers/window_arrangement_helper.go +++ b/pkg/gui/controllers/helpers/window_arrangement_helper.go @@ -60,7 +60,7 @@ type WindowArrangementArgs struct { ContentHeightForWindow func(window string) int // Whether the main panel is split (as is the case e.g. when a file has both // staged and unstaged changes) - SplitMainPanel bool + MainPanes types.MainPanes // The current screen mode (normal, half, full) ScreenMode types.ScreenMode // The content shown on the bottom left of the screen when showing a loader @@ -103,7 +103,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, ContentHeightForWindow: func(window string) int { return self.windowHelper.GetContextForWindow(window).TotalContentHeight() }, - SplitMainPanel: repoState.GetSplitMainPanel(), + MainPanes: repoState.GetMainPanes(), ScreenMode: repoState.GetScreenMode(), AppStatus: appStatus, InformationStr: informationStr, @@ -215,36 +215,27 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { } func mainSectionChildren(args WindowArrangementArgs) []*boxlayout.Box { - // if we're not in split mode we can just show the one main panel. Likewise if - // the main panel is focused and we're in full-screen mode - if !args.SplitMainPanel || (args.ScreenMode == types.SCREEN_FULL && args.CurrentWindow == "main") { - return []*boxlayout.Box{ - { - Window: "main", - Weight: 1, - }, + mainPane := &boxlayout.Box{Window: "main", Weight: 1} + secondaryPane := &boxlayout.Box{Window: "secondary", Weight: 1} + + switch args.MainPanes { + case types.MainPaneOnly: + return []*boxlayout.Box{mainPane} + case types.SecondaryPaneOnly: + return []*boxlayout.Box{secondaryPane} + case types.BothMainPanes: + // In full-screen mode the focused one takes the whole section anyway. + if args.ScreenMode == types.SCREEN_FULL { + if args.CurrentWindow == "main" { + return []*boxlayout.Box{mainPane} + } + if args.CurrentWindow == "secondary" { + return []*boxlayout.Box{secondaryPane} + } } } - if args.CurrentWindow == "secondary" && args.ScreenMode == types.SCREEN_FULL { - return []*boxlayout.Box{ - { - Window: "secondary", - Weight: 1, - }, - } - } - - return []*boxlayout.Box{ - { - Window: "main", - Weight: 1, - }, - { - Window: "secondary", - Weight: 1, - }, - } + return []*boxlayout.Box{mainPane, secondaryPane} } func getMidSectionWeights(args WindowArrangementArgs) (int, int) { @@ -382,7 +373,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box { } func splitMainPanelSideBySide(args WindowArrangementArgs) bool { - if !args.SplitMainPanel { + if args.MainPanes != types.BothMainPanes { return false } diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go index 365d7f104..2235d7fb4 100644 --- a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go +++ b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go @@ -35,7 +35,7 @@ func TestGetWindowDimensions(t *testing.T) { // Each panel shows its first tab by default; for the special-cased // panels (status, stash) the view name matches the window name. ActiveViewForWindow: func(window string) string { return window }, - SplitMainPanel: false, + MainPanes: types.MainPaneOnly, ScreenMode: types.SCREEN_NORMAL, AppStatus: "", InformationStr: "information", diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 3f6233fa7..6739a2175 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -231,7 +231,7 @@ type GuiRepoState struct { Model *types.Model Modes *types.Modes - SplitMainPanel bool + MainPanes types.MainPanes SearchState *types.SearchState // Lets us not load everything at once. Written and read from refresh @@ -321,12 +321,12 @@ func (self *GuiRepoState) GetSearchState() *types.SearchState { return self.SearchState } -func (self *GuiRepoState) SetSplitMainPanel(value bool) { - self.SplitMainPanel = value +func (self *GuiRepoState) SetMainPanes(value types.MainPanes) { + self.MainPanes = value } -func (self *GuiRepoState) GetSplitMainPanel() bool { - return self.SplitMainPanel +func (self *GuiRepoState) GetMainPanes() types.MainPanes { + return self.MainPanes } func (gui *Gui) onSwitchToNewRepo(startArgs appTypes.StartArgs, contextKey types.ContextKey) error { diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go index 566993a0f..988e5df0b 100644 --- a/pkg/gui/main_panels.go +++ b/pkg/gui/main_panels.go @@ -112,6 +112,8 @@ func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) { if opts.Main != nil { gui.RefreshMainView(opts.Main, opts.Pair.Main) + } else { + opts.Pair.Main.GetView().Clear() } if opts.Secondary != nil { @@ -134,7 +136,20 @@ func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) { } } - gui.splitMainPanel(opts.Secondary != nil) + gui.setMainPanes(mainPanesFor(opts)) +} + +// mainPanesFor says which panes the given render occupies: the one it has content for, +// or both when it has content for both. +func mainPanesFor(opts types.RefreshMainOpts) types.MainPanes { + switch { + case opts.Secondary == nil: + return types.MainPaneOnly + case opts.Main == nil: + return types.SecondaryPaneOnly + default: + return types.BothMainPanes + } } // clampDiffSelectionToContent brings the focused main view's selection back onto the @@ -298,8 +313,8 @@ func (gui *Gui) mainContextForView(view *gocui.View) *context.MainContext { return nil } -func (gui *Gui) splitMainPanel(splitMainPanel bool) { - gui.State.SplitMainPanel = splitMainPanel +func (gui *Gui) setMainPanes(panes types.MainPanes) { + gui.State.MainPanes = panes } // reApplySearch runs a search the view holds again over the content a render has just diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 1603df9bb..1baf6b3f9 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -461,8 +461,8 @@ type IRepoStateAccessor interface { SetScreenMode(ScreenMode) InSearchPrompt() bool GetSearchState() *SearchState - SetSplitMainPanel(bool) - GetSplitMainPanel() bool + SetMainPanes(MainPanes) + GetMainPanes() MainPanes GetMergeOrRebaseStartedInLazygit() bool SetMergeOrRebaseStartedInLazygit(bool) } diff --git a/pkg/gui/types/rendering.go b/pkg/gui/types/rendering.go index 0d9baf395..f59a535ba 100644 --- a/pkg/gui/types/rendering.go +++ b/pkg/gui/types/rendering.go @@ -13,6 +13,19 @@ func NewMainContextPair(main Context, secondary Context) MainContextPair { return MainContextPair{Main: main, Secondary: secondary} } +// MainPanes says which of the two panes of the main section are shown. Most content +// takes the main pane alone; content with two sides to it — the working tree's +// unstaged and staged changes, a commit's diff and the patch built from it — takes +// both; and content whose only side is the second one takes the secondary pane alone, +// so that it has the whole section rather than sitting under an empty pane. +type MainPanes int + +const ( + MainPaneOnly MainPanes = iota + BothMainPanes + SecondaryPaneOnly +) + type MainViewPairs struct { Normal MainContextPair MergeConflicts MainContextPair