Keep the inclusion gutter visible when the secondary patch pane is focused

The inclusion gutter (the ✓ markers on the commit diff showing which lines are
in the custom patch) is painted on the Normal pane but is an affordance of the
whole focused-main-view pair. It was gated on the Normal pane specifically being
current, so tabbing to the secondary (custom-patch) pane hid it — even though
both panes are visible and you're still building the patch.

Show it whenever either pane of the focused main view holds focus, finding the
side panel beneath whichever pane is current. GetOnFocusLost now re-evaluates the
gutter (rather than unconditionally hiding) so it persists across a pane switch
but still hides when focus leaves the pair; the new context is already current by
then, so it decides correctly and doesn't flicker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-06-24 17:32:12 +02:00
parent ece5f0fb66
commit 7f66f7620d
2 changed files with 17 additions and 11 deletions

View file

@ -827,20 +827,24 @@ func (self *StagingHelper) diffLineInfoFromParsed(parsed parsedDiffLine) types.D
// the existing content — the diff is unaffected by patch membership, so it never
// re-renders.
func (self *StagingHelper) RefreshInclusionGutter() {
mainContext := self.c.Contexts().Normal
v := mainContext.GetView()
// The gutter is painted on the Normal (commit-diff) pane, but it's a focused-main-view
// affordance of the whole pair: it stays visible while either pane holds focus, so it
// doesn't vanish when you tab to the secondary custom-patch pane.
v := self.c.Contexts().Normal.GetView()
// Check focus first: the gutter only shows while the main view holds focus, and
// NextInStack below requires the context to be in the stack — which it is exactly
// when it's the current one.
// Check focus first: the gutter only shows while the focused main view holds focus, and
// NextInStack below requires the context to be in the stack — which it is exactly when
// it's the current one. The side panel is found beneath whichever pane is current.
patchBuilder := self.c.Git().Patch.PatchBuilder
focused := self.c.Context().CurrentStatic().GetKey() == mainContext.GetKey()
current := self.c.Context().CurrentStatic()
focused := current.GetKey() == self.c.Contexts().Normal.GetKey() ||
current.GetKey() == self.c.Contexts().NormalSecondary.GetKey()
if !focused || !patchBuilder.Active() {
v.SetInclusionGutter(false, nil)
return
}
sidePanel := self.c.Context().NextInStack(mainContext)
sidePanel := self.c.Context().NextInStack(current)
diffMainView, ok := sidePanel.(types.DiffMainViewContext)
if !ok || diffMainView.GetDiffMainViewType() != types.DiffMainViewTypePatchBuilding {
v.SetInclusionGutter(false, nil)

View file

@ -233,12 +233,14 @@ func (self *MainViewController) GetOnFocus() func(types.OnFocusOpts) {
}
}
// GetOnFocusLost hides the inclusion gutter when the focused main view loses focus —
// it's a focused-main-view affordance, so it shouldn't linger in the side panel's diff
// preview. A no-op when no gutter is shown.
// GetOnFocusLost re-evaluates the inclusion gutter as the focused main view loses focus.
// It's a focused-main-view affordance of the whole pair, so it must persist when tabbing
// between the two panes but hide when focus leaves the pair entirely. By the time this runs
// the new context is already current, so RefreshInclusionGutter decides correctly (and so
// the gutter doesn't flicker off-then-on across a pane switch).
func (self *MainViewController) GetOnFocusLost() func(types.OnFocusLostOpts) {
return func(types.OnFocusLostOpts) {
self.context.GetView().SetInclusionGutter(false, nil)
self.c.Helpers().Staging.RefreshInclusionGutter()
}
}