Decouple hiding the working tree state from blocking input

Blocking keyboard input and hiding the working tree state mode are two
separate concerns; they were fused into one helper because every caller
so far wanted both. A caller that blocks input for something other than a
rebase would then hide the "Rebasing" indicator for the duration of its
operation, which has nothing to do with it.

Make it an explicit option instead, so blocking input on its own doesn't
imply anything about the modes on display.
This commit is contained in:
Stefan Haller 2026-08-05 07:15:04 +02:00
parent f4968f6839
commit e54cb4bf42
7 changed files with 106 additions and 36 deletions

View file

@ -339,7 +339,10 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
HandleConfirm: func() error {
commits := self.c.Model().Commits
selectedLineIdx := self.c.Contexts().LocalCommits.GetSelectedLineIdx()
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.RebasingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.RebasingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
var filePaths []string
selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes)

View file

@ -85,30 +85,32 @@ func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.
// WithWaitingStatusBlockingInput is like WithWaitingStatus, but it also blocks
// keyboard input for the whole duration of the operation: keys the user presses
// while it runs are buffered and replayed against the post-operation state (see
// gocui.BeginBlockingEvents). Use it for operations that manipulate an
// in-progress rebase or otherwise rewrite commits, where a racing keypress
// would target the wrong commit or todo.
// gocui.BeginBlockingEvents). Use it for operations whose following keypress
// depends on the state they produce, e.g. ones that manipulate an in-progress
// rebase or otherwise rewrite commits, where a racing keypress would target the
// wrong commit or todo.
//
// Must be called on the UI thread: the block is begun synchronously here, before
// the operation is dispatched to a worker, so no keypress can slip through in
// between.
func (self *AppStatusHelper) WithWaitingStatusBlockingInput(message string, f func(gocui.Task) error) {
func (self *AppStatusHelper) WithWaitingStatusBlockingInput(opts types.WaitingStatusOpts, f func(gocui.Task) error) {
self.c.GocuiGui().BeginBlockingEvents()
// Hide the rebasing-mode indicator (and its reset button) while we drive the
// rebase ourselves; it reflects the transient on-disk state and would
// otherwise flash on for the duration of the operation.
self.modeHelper.SetSuppressWorkingTreeStateMode(true)
if opts.HideWorkingTreeState {
self.modeHelper.SetSuppressWorkingTreeStateMode(true)
}
self.c.OnWorker(func(task gocui.Task) error {
// End the block and restore the mode indicator once the operation and its
// refresh have applied their UI updates: OnUIThread queues this after the
// refresh's model bounces and Then (which RefreshFromWorker has already
// enqueued by the time f returns), so the replayed keys act on the
// refreshed state and any resulting rebase state shows correctly.
// refreshed state and any resulting working tree state shows correctly.
defer self.c.OnUIThread(func() error {
self.modeHelper.SetSuppressWorkingTreeStateMode(false)
if opts.HideWorkingTreeState {
self.modeHelper.SetSuppressWorkingTreeStateMode(false)
}
return self.c.GocuiGui().EndBlockingEvents()
})
return self.WithWaitingStatusImpl(message, f, task)
return self.WithWaitingStatusImpl(opts.Message, f, task)
})
}

View file

@ -85,7 +85,10 @@ func (self *CherryPickHelper) Paste() error {
HandleConfirm: func() error {
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
cherryPickedCommits := self.getData().CherryPickedCommits
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.CherryPickingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.CherryPickingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.CherryPick)
if mustStash {

View file

@ -743,7 +743,10 @@ func (self *LocalCommitsController) squashDown(selectedCommits []*models.Commit,
HandleConfirm: func() error {
commits := self.c.Model().Commits
self.selectRebaseResultCommit(startIdx)
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.SquashingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.SquashingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
return self.interactiveRebase(commits, todo.Squash, startIdx, endIdx)
})
@ -767,7 +770,10 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
OnPress: func() error {
commits := self.c.Model().Commits
self.selectRebaseResultCommit(startIdx)
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.FixingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.FixingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
return self.interactiveRebase(commits, todo.Fixup, startIdx, endIdx)
})
@ -780,7 +786,10 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
OnPress: func() error {
commits := self.c.Model().Commits
self.selectRebaseResultCommit(startIdx)
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.FixingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.FixingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage)
return self.interactiveRebaseWithFlag(commits, todo.Fixup, startIdx, endIdx, "-C")
})
@ -891,7 +900,10 @@ func (self *LocalCommitsController) handleReword(summary string, description str
self.c.Tr.RewordingStatus, nil, nil)
}
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.RewordingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.RewordingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
err := self.c.Git().Rebase.RewordCommit(commits, selectedIdx, summary, description)
if err != nil {
return err
@ -977,7 +989,10 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
if !isMerge {
self.selectRebaseResultCommit(startIdx)
}
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.DroppingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.DroppingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DropCommit)
if isMerge {
return self.dropMergeCommit(commits, startIdx)
@ -1002,7 +1017,10 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start
commits := self.c.Model().Commits
if !commits[endIdx].IsMerge() {
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.RebasingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.RebasingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "")
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err, types.RefreshOptions{BatchUIUpdates: true})
@ -1024,7 +1042,10 @@ func (self *LocalCommitsController) quickStartInteractiveRebase() error {
func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
commitsToEdit []*models.Commit,
) error {
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.RebasingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.RebasingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.EditCommit)
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash())
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
@ -1164,7 +1185,10 @@ func (self *LocalCommitsController) move(
}
commits := self.c.Model().Commits
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.MovingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.MovingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
if offset > 0 {
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
} else {
@ -1209,7 +1233,10 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
selectedIdx := self.context().GetView().SelectedLineIdx()
handleCommit = func() error {
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.AmendingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.AmendingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
err := self.c.Git().Rebase.AmendTo(commits, selectedIdx)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
@ -1271,7 +1298,10 @@ func (self *LocalCommitsController) amendAttribute(_ []*models.Commit, start, en
}
func (self *LocalCommitsController) resetAuthor(commits []*models.Commit, start, end int) error {
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.AmendingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.AmendingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
if err := self.c.Git().Rebase.ResetCommitAuthor(commits, start, end); err != nil {
return err
@ -1287,7 +1317,10 @@ func (self *LocalCommitsController) setAuthor(commits []*models.Commit, start, e
Title: self.c.Tr.SetAuthorPromptTitle,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
HandleConfirm: func(value string) error {
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.AmendingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.AmendingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
if err := self.c.Git().Rebase.SetCommitAuthor(commits, start, end, value); err != nil {
return err
@ -1307,7 +1340,10 @@ func (self *LocalCommitsController) addCoAuthor(commits []*models.Commit, start,
Title: self.c.Tr.AddCoAuthorPromptTitle,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
HandleConfirm: func(value string) error {
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.AmendingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.AmendingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.AddCommitCoAuthor)
if err := self.c.Git().Rebase.AddCommitCoAuthor(commits, start, end, value); err != nil {
return err
@ -1341,7 +1377,10 @@ func (self *LocalCommitsController) revert(commits []*models.Commit, start, end
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
mustStash := helpers.IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.RevertingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.RevertingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
if mustStash {
if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForReverting); err != nil {
return err
@ -1392,7 +1431,10 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
selectedIdx := self.context().GetSelectedLineIdx()
commits := self.c.Model().Commits
branches := self.c.Model().Branches
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.CreatingFixupCommitStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.CreatingFixupCommitStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
if err := self.c.Git().Commit.CreateFixupCommit(commit.Hash()); err != nil {
return err
}
@ -1500,7 +1542,10 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
selectedIdx := self.context().GetSelectedLineIdx()
commits := self.c.Model().Commits
branches := self.c.Model().Branches
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.CreatingFixupCommitStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.CreatingFixupCommitStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
if err := self.c.Git().Commit.CreateAmendCommit(originalSubject, summary, description, includeFileChanges); err != nil {
return err
}
@ -1561,7 +1606,10 @@ func (self *LocalCommitsController) squashFixupsImpl(commit *models.Commit, reba
// up by that many rows to stay on the same commit. Compute the target as an
// absolute index now, on the current list.
targetIdx := self.context().GetSelectedLineIdx() - selectionOffset
return self.c.WithWaitingStatusBlockingInput(self.c.Tr.SquashingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{
Message: self.c.Tr.SquashingStatus,
HideWorkingTreeState: true,
}, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(

View file

@ -832,8 +832,8 @@ func NewGui(
return nil
},
func(message string, f func(gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
func(message string, f func(gocui.Task) error) {
gui.helpers.AppStatus.WithWaitingStatusBlockingInput(message, f)
func(opts types.WaitingStatusOpts, f func(gocui.Task) error) {
gui.helpers.AppStatus.WithWaitingStatusBlockingInput(opts, f)
},
func(message string, kind types.ToastKind) { gui.helpers.AppStatus.Toast(message, kind) },
func() string { return gui.Views.Prompt.TextArea.GetContent() },

View file

@ -19,7 +19,7 @@ type PopupHandler struct {
currentContextFn func() types.Context
createMenuFn func(types.CreateMenuOptions) error
withWaitingStatusFn func(message string, f func(gocui.Task) error)
withWaitingStatusBlockingInputFn func(message string, f func(gocui.Task) error)
withWaitingStatusBlockingInputFn func(opts types.WaitingStatusOpts, f func(gocui.Task) error)
toastFn func(message string, kind types.ToastKind)
getPromptInputFn func() string
inDemo func() bool
@ -35,7 +35,7 @@ func NewPopupHandler(
currentContextFn func() types.Context,
createMenuFn func(types.CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func(gocui.Task) error),
withWaitingStatusBlockingInputFn func(message string, f func(gocui.Task) error),
withWaitingStatusBlockingInputFn func(opts types.WaitingStatusOpts, f func(gocui.Task) error),
toastFn func(message string, kind types.ToastKind),
getPromptInputFn func() string,
inDemo func() bool,
@ -76,8 +76,8 @@ func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) e
return nil
}
func (self *PopupHandler) WithWaitingStatusBlockingInput(message string, f func(gocui.Task) error) error {
self.withWaitingStatusBlockingInputFn(message, f)
func (self *PopupHandler) WithWaitingStatusBlockingInput(opts types.WaitingStatusOpts, f func(gocui.Task) error) error {
self.withWaitingStatusBlockingInputFn(opts, f)
return nil
}

View file

@ -171,7 +171,7 @@ type IPopupHandler interface {
// Shows a popup prompting the user for input.
Prompt(opts PromptOpts)
WithWaitingStatus(message string, f func(gocui.Task) error) error
WithWaitingStatusBlockingInput(message string, f func(gocui.Task) error) error
WithWaitingStatusBlockingInput(opts WaitingStatusOpts, f func(gocui.Task) error) error
Menu(opts CreateMenuOptions) error
Toast(message string)
ErrorToast(message string)
@ -179,6 +179,20 @@ type IPopupHandler interface {
GetPromptInput() string
}
type WaitingStatusOpts struct {
// The message shown alongside the spinner while the operation runs.
Message string
// When set, the working tree state mode (the yellow
// "Rebasing"/"Merging"/"Cherry-picking"/"Reverting" indicator, along with
// its abort button) stays hidden until the operation is done. Set it for
// operations that drive such a state themselves: the state they leave on
// disk while they run is transient, so surfacing it would flash the
// indicator on and offer to abort a sequence that lazygit is in the middle
// of running.
HideWorkingTreeState bool
}
type ToastKind int
const (