mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 10:13:41 -05:00
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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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() },
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)",
|
||||
|
||||
Reference in New Issue
Block a user