diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go index 7f34ea7dc..4a99259fd 100644 --- a/pkg/gui/context/local_commits_context.go +++ b/pkg/gui/context/local_commits_context.go @@ -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 } diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 6c8f49e1f..748355f67 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -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) diff --git a/pkg/gui/controllers/filtering_menu_action.go b/pkg/gui/controllers/filtering_menu_action.go index 7ae26c4ef..0bd7ca902 100644 --- a/pkg/gui/controllers/filtering_menu_action.go +++ b/pkg/gui/controllers/filtering_menu_action.go @@ -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 -} diff --git a/pkg/gui/controllers/helpers/app_status_helper.go b/pkg/gui/controllers/helpers/app_status_helper.go index d0bb03395..44de70546 100644 --- a/pkg/gui/controllers/helpers/app_status_helper.go +++ b/pkg/gui/controllers/helpers/app_status_helper.go @@ -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) }) } diff --git a/pkg/gui/controllers/helpers/cherry_pick_helper.go b/pkg/gui/controllers/helpers/cherry_pick_helper.go index fc96b9d1b..f82f8843a 100644 --- a/pkg/gui/controllers/helpers/cherry_pick_helper.go +++ b/pkg/gui/controllers/helpers/cherry_pick_helper.go @@ -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 { diff --git a/pkg/gui/controllers/helpers/mode_helper.go b/pkg/gui/controllers/helpers/mode_helper.go index 4947e42d1..6e5139744 100644 --- a/pkg/gui/controllers/helpers/mode_helper.go +++ b/pkg/gui/controllers/helpers/mode_helper.go @@ -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 } diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index 3db9ab9dc..d0327216c 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -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 { diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 17c1dcf30..642263d12 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -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( diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 4dcd3c209..a7466d584 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -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() }, diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go index 7c15c56ea..3b305a25d 100644 --- a/pkg/gui/popup/popup_handler.go +++ b/pkg/gui/popup/popup_handler.go @@ -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 } diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 7091d46f7..b2b924bc0 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -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 diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index deb55d6e9..845155aed 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -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)",