Offer commit and find-fixup-base in the focused main view

The focused main view now plays the role the staging panel used to,
so the working-tree commands the staging panel offered — commit (and
its variants) and find-base-commit-for-fixup — need to be reachable
there as well.

Gate them to when the focused main view actually shows the working-tree
diff (DiffMainViewTypeStaging), which is exactly the role the staging
panel filled. Over a commit's or stash's diff these commands would
operate on the working tree, unrelated to what's on screen, so the keys
are a no-op there and the bindings don't clutter its keybinding menu.

The gate is re-checked on each press rather than captured at
registration time, since one keybinding set serves the main view over
every panel. That requires reading the panel beneath the main view from
GetKeybindings (to decide whether to show the descriptions), which runs
for off-stack contexts too — at startup and during cheatsheet
generation — so a panic-safe IsInStack guards the NextInStack lookup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-06 09:33:32 +02:00
parent 140cc03fd1
commit 97b1b4cf7f
3 changed files with 86 additions and 0 deletions

View file

@ -373,3 +373,15 @@ func (self *ContextMgr) NextInStack(c types.Context) types.Context {
panic("context not in stack")
}
// IsInStack reports whether the given context is somewhere in the context stack.
// Unlike NextInStack it never panics, so it's safe to call before dereferencing
// the stack from a caller that might run with the context off-stack.
func (self *ContextMgr) IsInStack(c types.Context) bool {
self.RLock()
defer self.RUnlock()
return lo.ContainsBy(self.ContextStack, func(other types.Context) bool {
return other.GetKey() == c.GetKey()
})
}

View file

@ -45,6 +45,11 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
// line in the branch's pull request).
selectionShown := self.context.GetView().Highlight
// The commit and find-fixup-base commands act on the working tree, so — like the
// old staging panel, which was always the working-tree diff — we only surface them
// while the focused main view plays that role (see workingTreeAction).
stagingMode := self.diffMainViewType() == types.DiffMainViewTypeStaging
var enterDescription string
var editDescription string
var editTooltip string
@ -59,6 +64,21 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
openPullRequestTooltip = "Open a browser at the selected line in the diff of the current branch's pull request, so that you can comment on it. Only works for local branches that have a pull request on GitHub."
}
var commitDescription string
var commitTooltip string
var commitWithoutHookDescription string
var commitWithEditorDescription string
var findBaseCommitDescription string
var findBaseCommitTooltip string
if stagingMode {
commitDescription = self.c.Tr.Commit
commitTooltip = self.c.Tr.CommitTooltip
commitWithoutHookDescription = self.c.Tr.CommitChangesWithoutHook
commitWithEditorDescription = self.c.Tr.CommitChangesWithEditor
findBaseCommitDescription = self.c.Tr.FindBaseCommitForFixup
findBaseCommitTooltip = self.c.Tr.FindBaseCommitForFixupTooltip
}
return []*types.Binding{
{
Keys: opts.GetKeys(opts.Config.Universal.TogglePanel),
@ -112,6 +132,28 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Description: openPullRequestDescription,
Tooltip: openPullRequestTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Files.CommitChanges),
Handler: self.workingTreeAction(self.c.Helpers().WorkingTree.HandleCommitPress),
Description: commitDescription,
Tooltip: commitTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Files.CommitChangesWithoutHook),
Handler: self.workingTreeAction(self.c.Helpers().WorkingTree.HandleWIPCommitPress),
Description: commitWithoutHookDescription,
},
{
Keys: opts.GetKeys(opts.Config.Files.CommitChangesWithEditor),
Handler: self.workingTreeAction(self.c.Helpers().WorkingTree.HandleCommitEditorPress),
Description: commitWithEditorDescription,
},
{
Keys: opts.GetKeys(opts.Config.Files.FindBaseCommitForFixup),
Handler: self.workingTreeAction(self.c.Helpers().FixupHelper.HandleFindBaseCommitForFixupPress),
Description: findBaseCommitDescription,
Tooltip: findBaseCommitTooltip,
},
{
Keys: opts.GetKeys(opts.Config.Main.ToggleSelectHunk),
Handler: self.toggleSelectHunk,
@ -383,6 +425,37 @@ func (self *MainViewController) focusedMainViewActions() types.FocusedMainViewAc
return sidePanelContext.GetFocusedMainViewActions()
}
// diffMainViewType reports what the side panel beneath the focused main view makes its
// primary action mean — staging (the working tree), patch-building, or nothing — or
// DiffMainViewTypeNone when this pane isn't focused or has no diff panel beneath it. The
// IsInStack guard is essential: NextInStack panics for a context that isn't in the stack,
// and GetKeybindings (which calls this) runs for off-stack panes too — at startup and
// during cheatsheet generation, where the stack is empty.
func (self *MainViewController) diffMainViewType() types.DiffMainViewType {
if !self.c.Context().IsInStack(self.context) {
return types.DiffMainViewTypeNone
}
if diffContext, ok := self.c.Context().NextInStack(self.context).(types.DiffMainViewContext); ok {
return diffContext.GetDiffMainViewType()
}
return types.DiffMainViewTypeNone
}
// workingTreeAction wraps a working-tree command (commit, find-fixup-base) so it runs
// only while the focused main view shows the working-tree diff — the role the staging
// panel used to play, and the only place these commands mean anything. Over any other
// diff (a commit, a stash) the key is a no-op, so committing can't be triggered by
// accident while browsing history. Re-checks on each press rather than capturing the
// state at keybinding-registration time.
func (self *MainViewController) workingTreeAction(action func() error) func() error {
return func() error {
if self.diffMainViewType() != types.DiffMainViewTypeStaging {
return nil
}
return action()
}
}
// primaryAction acts on the selected diff line(s) — a single line, a range, or a hunk —
// delegating to the side panel beneath the focused main view, since what the action means
// is the panel's business: the working tree stages, while commits toggle the selection

View file

@ -441,6 +441,7 @@ type IContextMgr interface {
CurrentSide() Context
CurrentPopup() []Context
NextInStack(context Context) Context
IsInStack(c Context) bool
IsCurrent(c Context) bool
IsCurrentOrParent(c Context) bool
ForEach(func(Context))