Fix transitions of entering and exiting filtering mode (e.g. by path or author) (#5897)

Entering or leaving filtering mode switched the screen mode and the
focused panel immediately, then reloaded the commit list in the
background. The result was an unfiltered list presented in the layout
that says "you are filtering", with nothing to say that anything was
still happening — and in a big repo that state can last seconds.

This is a regression in 0.64.0 (more specifically, from #5790): before
that we stopped blocking the UI thread on refreshes, the reload happened
before any of it, so the two always agreed; the price was a frozen UI
for the duration.

Do neither: reload on a worker, so the UI stays live, and hold back
everything the user can see of the change until the new lists are ready,
so they still land together in one frame. A waiting status says what is
going on in the meantime, and blocking input means the keys pressed
while it runs arrive after the change rather than acting on a list that
is about to be replaced.
This commit is contained in:
Stefan Haller
2026-08-05 17:49:00 +02:00
committed by GitHub
12 changed files with 217 additions and 93 deletions
+7 -1
View File
@@ -324,7 +324,13 @@ func (self *LocalCommitsViewModel) GetCommits() []*models.Commit {
}
func shouldShowGraph(c *ContextCommon) bool {
if c.Modes().Filtering.Active() {
// Whether we can draw a graph is a property of the commit list we have
// loaded, not of the filtering mode: turning filtering on or off only
// reaches the screen when the reloaded list does, and until then the graph
// has to keep matching the list that is still on display. Drawing one for a
// filtered list is also ruinously slow, because none of the commits in it
// are connected to each other, so no pipe ever terminates.
if c.Model().CommitsWereFilteredAtLastRefresh {
return false
}
@@ -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)
+4 -36
View File
@@ -3,7 +3,6 @@ package controllers
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -42,7 +41,7 @@ func (self *FilteringMenuAction) Call() error {
menuItems = append(menuItems, &types.MenuItem{
Label: fmt.Sprintf("%s '%s'", self.c.Tr.FilterBy, fileName),
OnPress: func() error {
return self.setFilteringPath(fileName)
return self.c.Helpers().Mode.SetFilteringPath(fileName)
},
Tooltip: tooltip,
})
@@ -52,7 +51,7 @@ func (self *FilteringMenuAction) Call() error {
menuItems = append(menuItems, &types.MenuItem{
Label: fmt.Sprintf("%s '%s'", self.c.Tr.FilterBy, author),
OnPress: func() error {
return self.setFilteringAuthor(author)
return self.c.Helpers().Mode.SetFilteringAuthor(author)
},
Tooltip: tooltip,
})
@@ -65,7 +64,7 @@ func (self *FilteringMenuAction) Call() error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(),
Title: self.c.Tr.EnterFileName,
HandleConfirm: func(response string) error {
return self.setFilteringPath(response)
return self.c.Helpers().Mode.SetFilteringPath(response)
},
})
@@ -81,7 +80,7 @@ func (self *FilteringMenuAction) Call() error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
Title: self.c.Tr.EnterAuthor,
HandleConfirm: func(response string) error {
return self.setFilteringAuthor(response)
return self.c.Helpers().Mode.SetFilteringAuthor(response)
},
})
@@ -99,34 +98,3 @@ func (self *FilteringMenuAction) Call() error {
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.FilteringMenuTitle, Items: menuItems})
}
func (self *FilteringMenuAction) setFilteringPath(path string) error {
self.c.Modes().Filtering.Reset()
self.c.Modes().Filtering.SetPath(path)
return self.setFiltering()
}
func (self *FilteringMenuAction) setFilteringAuthor(author string) error {
self.c.Modes().Filtering.Reset()
self.c.Modes().Filtering.SetAuthor(author)
return self.setFiltering()
}
func (self *FilteringMenuAction) setFiltering() error {
self.c.Modes().Filtering.SetSelectedCommitHash(self.c.Contexts().LocalCommits.GetSelectedCommitHash())
repoState := self.c.State().GetRepoState()
if repoState.GetScreenMode() == types.SCREEN_NORMAL {
repoState.SetScreenMode(types.SCREEN_HALF)
}
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
self.c.Refresh(types.RefreshOptions{Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(), Then: func() error {
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().LocalCommits.HandleFocus(types.OnFocusOpts{})
return nil
}})
return nil
}
@@ -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.SetSuppressRebasingMode(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.SetSuppressRebasingMode(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)
})
}
@@ -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 {
+94 -20
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
@@ -12,12 +13,12 @@ import (
type ModeHelper struct {
c *HelperCommon
diffHelper *DiffHelper
patchBuildingHelper *PatchBuildingHelper
cherryPickHelper *CherryPickHelper
mergeAndRebaseHelper *MergeAndRebaseHelper
bisectHelper *BisectHelper
suppressRebasingMode bool
diffHelper *DiffHelper
patchBuildingHelper *PatchBuildingHelper
cherryPickHelper *CherryPickHelper
mergeAndRebaseHelper *MergeAndRebaseHelper
bisectHelper *BisectHelper
suppressWorkingTreeStateMode bool
}
func NewModeHelper(
@@ -130,7 +131,7 @@ func (self *ModeHelper) Statuses() []ModeStatus {
},
{
IsActive: func() bool {
return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState().Any()
return !self.suppressWorkingTreeStateMode && self.c.Git().Status.WorkingTreeState().Any()
},
InfoLabel: func() string {
workingTreeState := self.c.Git().Status.WorkingTreeState()
@@ -182,16 +183,39 @@ func (self *ModeHelper) ExitFilterMode() error {
return self.ClearFiltering()
}
func (self *ModeHelper) SetFilteringPath(path string) error {
return self.setFiltering(func() {
self.c.Modes().Filtering.SetPath(path)
})
}
func (self *ModeHelper) SetFilteringAuthor(author string) error {
return self.setFiltering(func() {
self.c.Modes().Filtering.SetAuthor(author)
})
}
func (self *ModeHelper) setFiltering(setFilter func()) error {
return self.changeFiltering(
func() {
// Whatever we were filtering by before is replaced, not added to
self.c.Modes().Filtering.Reset()
setFilter()
self.c.Modes().Filtering.SetSelectedCommitHash(
self.c.Contexts().LocalCommits.GetSelectedCommitHash())
},
func() {
self.c.Contexts().LocalCommits.SetSelection(0)
},
)
}
func (self *ModeHelper) ClearFiltering() error {
selectedCommitHash := self.c.Contexts().LocalCommits.GetSelectedCommitHash()
self.c.Modes().Filtering.Reset()
if self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_HALF {
self.c.State().GetRepoState().SetScreenMode(types.SCREEN_NORMAL)
}
self.c.Refresh(types.RefreshOptions{
Scope: ScopesToRefreshWhenFilteringModeChanges(),
Then: func() error {
return self.changeFiltering(
self.c.Modes().Filtering.Reset,
func() {
// Find the commit that was last selected in filtering mode, and select it again after refreshing
if !self.c.Contexts().LocalCommits.SelectCommitByHash(selectedCommitHash) {
// If we couldn't find it (either because no commit was selected
@@ -200,12 +224,62 @@ func (self *ModeHelper) ClearFiltering() error {
// before we entered filtering
self.c.Contexts().LocalCommits.SelectCommitByHash(self.c.Modes().Filtering.GetSelectedCommitHash())
}
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
return nil
},
)
}
// changeFiltering applies a change to the filtering mode: setFilter mutates the
// mode, then the views whose contents depend on the filter are reloaded, and
// selectCommit puts the selection where it belongs in the reloaded commit list.
//
// Reloading the commit list can take seconds in a big repo, so it happens on a
// worker with a waiting status. Everything the user can see of the change waits
// for it: the screen mode, the focused panel and the reloaded lists all land in
// the same frame, from the refresh's Then, rather than framing an unfiltered
// list as if it were the filtered one. Until then the pre-change state stays on
// screen, and it stays consistent, because the only thing that has changed
// behind it is the filter that the reload is in the middle of applying. The one
// thing that can't wait is the mode indicator in the information panel: the
// filter has to be set before the reload can use it, so the indicator leads the
// lists by however long the reload takes.
//
// Input is blocked for the duration: the keys the user presses arrive after the
// change, which is where they meant them to go, and it keeps a second filter
// change from racing this one — they would both refresh with whichever filter
// happened to be set when their git commands ran.
func (self *ModeHelper) changeFiltering(setFilter func(), selectCommit func()) error {
setFilter()
filtering := self.c.Modes().Filtering.Active()
message := lo.Ternary(filtering, self.c.Tr.ApplyingFilterStatus, self.c.Tr.RemovingFilterStatus)
return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{Message: message}, func(gocui.Task) error {
self.c.RefreshFromWorker(types.RefreshOptions{
Scope: ScopesToRefreshWhenFilteringModeChanges(),
BatchUIUpdates: true,
Then: func() error {
repoState := self.c.State().GetRepoState()
if filtering {
if repoState.GetScreenMode() == types.SCREEN_NORMAL {
repoState.SetScreenMode(types.SCREEN_HALF)
}
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
} else if repoState.GetScreenMode() == types.SCREEN_HALF {
repoState.SetScreenMode(types.SCREEN_NORMAL)
}
selectCommit()
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
// The list we just selected in has nothing to do with the one
// that was showing, so wherever it was scrolled to says nothing
// about where the selection now is. PostRefreshUpdate leaves the
// scroll position alone, so ask for it separately.
self.c.Contexts().LocalCommits.FocusLine(true)
return nil
},
})
return nil
})
return nil
}
// Stashes really only need to be refreshed when filtering by path, not by author, but it's too much
@@ -219,6 +293,6 @@ func ScopesToRefreshWhenFilteringModeChanges() []types.RefreshableView {
}
}
func (self *ModeHelper) SetSuppressRebasingMode(value bool) {
self.suppressRebasingMode = value
func (self *ModeHelper) SetSuppressWorkingTreeStateMode(value bool) {
self.suppressWorkingTreeStateMode = value
}
@@ -817,6 +817,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit(captured capturedCommitState,
self.c.Model().BisectInfo = bisectInfo
self.c.Model().Commits = commits
self.c.Model().CommitsWereFilteredAtLastRefresh = captured.filterPath != "" || captured.filterAuthor != ""
self.RefreshAuthors(commits)
self.c.Model().WorkingTreeStateAtLastCommitRefresh = workingTreeState
if checkedOutRef != nil {
+64 -16
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(
+2 -2
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() },
+4 -4
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
}
+16 -1
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 (
@@ -349,6 +363,7 @@ type Model struct {
BisectInfo *git_commands.BisectInfo
WorkingTreeStateAtLastCommitRefresh models.WorkingTreeState
CommitsWereFilteredAtLastRefresh bool
RemoteBranches []*models.RemoteBranch
Tags []*models.Tag
+4
View File
@@ -441,6 +441,8 @@ type TranslationSet struct {
ResettingStatus string
CreatingFixupCommitStatus string
MovingCommitsToNewBranchStatus string
ApplyingFilterStatus string
RemovingFilterStatus string
CommitFiles string
SubCommitsDynamicTitle string
CommitFilesDynamicTitle string
@@ -1598,6 +1600,8 @@ func EnglishTranslationSet() *TranslationSet {
ResettingStatus: "Resetting",
CreatingFixupCommitStatus: "Creating fixup commit",
MovingCommitsToNewBranchStatus: "Moving commits to new branch",
ApplyingFilterStatus: "Applying filter",
RemovingFilterStatus: "Removing filter",
CommitFiles: "Commit files",
SubCommitsDynamicTitle: "Commits (%s)",
CommitFilesDynamicTitle: "Diff files (%s)",