diff --git a/pkg/gui/controllers/global_controller.go b/pkg/gui/controllers/global_controller.go index 1ae56068561..1f0ef95d251 100644 --- a/pkg/gui/controllers/global_controller.go +++ b/pkg/gui/controllers/global_controller.go @@ -46,7 +46,7 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type }, { Key: opts.GetKey(opts.Config.Universal.Refresh), - Handler: opts.Guards.NoPopupPanel(self.refresh), + Handler: self.refreshAllowPopupAutoClose, Description: self.c.Tr.Refresh, Tooltip: self.c.Tr.RefreshTooltip, }, @@ -172,6 +172,22 @@ func (self *GlobalController) refresh() error { return nil } +func (self *GlobalController) refreshAllowPopupAutoClose() error { + if self.c.Helpers().Confirmation.IsPopupPanelFocused() && !self.canRefreshWithPopup() { + return nil + } + + return self.refresh() +} + +func (self *GlobalController) canRefreshWithPopup() bool { + self.c.Mutexes().PopupMutex.Lock() + defer self.c.Mutexes().PopupMutex.Unlock() + + popupOpts := self.c.State().GetRepoState().GetCurrentPopupOpts() + return popupOpts != nil && popupOpts.AutoCloseCondition != types.PopupAutoCloseNone +} + func (self *GlobalController) nextScreenMode() error { return (&ScreenModeActions{c: self.c}).Next() } diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 7d4f0a96a7d..6a62b075ece 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -222,8 +222,9 @@ func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error { // PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress func (self *MergeAndRebaseHelper) PromptToContinueRebase() error { self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Continue, - Prompt: fmt.Sprintf(self.c.Tr.ConflictsResolved, self.c.Git().Status.WorkingTreeState().CommandName()), + Title: self.c.Tr.Continue, + Prompt: fmt.Sprintf(self.c.Tr.ConflictsResolved, self.c.Git().Status.WorkingTreeState().CommandName()), + AutoCloseCondition: types.PopupAutoCloseWorkingTreeStateNone, HandleConfirm: func() error { // By the time we get here, we might have unstaged changes again, // e.g. if the user had to fix build errors after resolving the @@ -240,8 +241,9 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error { root := self.c.Contexts().Files.FileTreeViewModel.GetRoot() if root.GetHasUnstagedChanges() { self.c.Confirm(types.ConfirmOpts{ - Title: self.c.Tr.Continue, - Prompt: self.c.Tr.UnstagedFilesAfterConflictsResolved, + Title: self.c.Tr.Continue, + Prompt: self.c.Tr.UnstagedFilesAfterConflictsResolved, + AutoCloseCondition: types.PopupAutoCloseWorkingTreeStateNone, HandleConfirm: func() error { self.c.LogAction(self.c.Tr.Actions.StageAllFiles) if err := self.c.Git().WorkingTree.StageAll(true); err != nil { diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index 8ebc76d161d..8470c025978 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -576,7 +576,8 @@ func (self *RefreshHelper) refreshStateFiles() error { } } - if self.c.Git().Status.WorkingTreeState().Any() && conflictFileCount == 0 && prevConflictFileCount > 0 { + workingTreeState := self.c.Git().Status.WorkingTreeState() + if workingTreeState.Any() && conflictFileCount == 0 && prevConflictFileCount > 0 { self.c.OnUIThread(func() error { return self.mergeAndRebaseHelper.PromptToContinueRebase() }) } @@ -600,6 +601,37 @@ func (self *RefreshHelper) refreshStateFiles() error { return nil } +func (self *RefreshHelper) autoClosePopupIfNeeded(workingTreeState models.WorkingTreeState) { + if !workingTreeState.None() { + return + } + + if !self.shouldAutoClosePopup(types.PopupAutoCloseWorkingTreeStateNone) { + return + } + + self.c.OnUIThread(func() error { + if !self.shouldAutoClosePopup(types.PopupAutoCloseWorkingTreeStateNone) { + return nil + } + if self.c.Context().IsCurrent(self.c.Contexts().Confirmation) { + return self.c.Contexts().Confirmation.State.OnClose() + } + if self.c.Context().IsCurrent(self.c.Contexts().Prompt) { + return self.c.Contexts().Prompt.State.OnClose() + } + return nil + }) +} + +func (self *RefreshHelper) shouldAutoClosePopup(condition types.PopupAutoCloseCondition) bool { + self.c.Mutexes().PopupMutex.Lock() + defer self.c.Mutexes().PopupMutex.Unlock() + + popupOpts := self.c.State().GetRepoState().GetCurrentPopupOpts() + return popupOpts != nil && popupOpts.AutoCloseCondition == condition +} + // the reflogs panel is the only panel where we cache data, in that we only // load entries that have been created since we last ran the call. This means // we need to be more careful with how we use this, and to ensure we're emptying @@ -712,6 +744,7 @@ func (self *RefreshHelper) refreshStatus() { } workingTreeState := self.c.Git().Status.WorkingTreeState() + self.autoClosePopupIfNeeded(workingTreeState) linkedWorktreeName := self.worktreeHelper.GetLinkedWorktreeName() repoName := self.c.Git().RepoPaths.RepoName() diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go index d8824016f48..b53135e4cb7 100644 --- a/pkg/gui/popup/popup_handler.go +++ b/pkg/gui/popup/popup_handler.go @@ -108,21 +108,17 @@ func (self *PopupHandler) Alert(title string, message string) { func (self *PopupHandler) Confirm(opts types.ConfirmOpts) { self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - HandleConfirm: opts.HandleConfirm, - HandleClose: opts.HandleClose, + Title: opts.Title, + Prompt: opts.Prompt, + HandleConfirm: opts.HandleConfirm, + HandleClose: opts.HandleClose, + AutoCloseCondition: opts.AutoCloseCondition, }) } func (self *PopupHandler) ConfirmIf(condition bool, opts types.ConfirmOpts) error { if condition { - self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - HandleConfirm: opts.HandleConfirm, - HandleClose: opts.HandleClose, - }) + self.Confirm(opts) return nil } diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 4c892508d4d..9c7cd9ab849 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -149,6 +149,13 @@ const ( ToastKindError ) +type PopupAutoCloseCondition int + +const ( + PopupAutoCloseNone PopupAutoCloseCondition = iota + PopupAutoCloseWorkingTreeStateNone +) + type CreateMenuOptions struct { Title string Prompt string // a message that will be displayed above the menu options @@ -168,6 +175,7 @@ type CreatePopupPanelOpts struct { HandleConfirmPrompt func(string) error HandleClose func() error HandleDeleteSuggestion func(int) error + AutoCloseCondition PopupAutoCloseCondition FindSuggestionsFunc func(string) []*Suggestion Mask bool @@ -184,6 +192,7 @@ type ConfirmOpts struct { FindSuggestionsFunc func(string) []*Suggestion Editable bool Mask bool + AutoCloseCondition PopupAutoCloseCondition } type PromptOpts struct { diff --git a/pkg/integration/tests/branch/rebase_conflicts_resolved_externally.go b/pkg/integration/tests/branch/rebase_conflicts_resolved_externally.go new file mode 100644 index 00000000000..0597c28afe7 --- /dev/null +++ b/pkg/integration/tests/branch/rebase_conflicts_resolved_externally.go @@ -0,0 +1,63 @@ +package branch + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" +) + +var RebaseConflictsResolvedExternally = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Resolve conflicts and continue rebase externally, verify popup auto-dismisses.", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.LocalBranchSortOrder = "recency" + }, + SetupRepo: func(shell *Shell) { + shared.MergeConflictsSetup(shell) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). + Focus(). + Lines( + Contains("first-change-branch"), + Contains("second-change-branch"), + Contains("original-branch"), + ). + SelectNextItem(). + Press(keys.Branches.RebaseBranch) + + t.ExpectPopup().Menu(). + Title(Equals("Rebase 'first-change-branch'")). + Select(Contains("Simple rebase")). + Confirm() + + t.Common().AcknowledgeConflicts() + + t.Views().Files(). + IsFocused(). + Tap(func() { + t.Shell().UpdateFile("file", shared.FirstChangeFileContent) + t.Shell().GitAdd("file") + }). + Press(keys.Universal.Refresh) + + t.ExpectPopup().Confirmation(). + Title(Equals("Continue")). + Content(Contains("All merge conflicts resolved. Continue the rebase?")) + + t.Shell().RunCommandWithEnv([]string{"git", "rebase", "--continue"}, []string{ + "GIT_EDITOR=true", + "GIT_SEQUENCE_EDITOR=true", + "EDITOR=true", + "VISUAL=true", + "GIT_TERMINAL_PROMPT=0", + }) + + t.GlobalPress(keys.Universal.Refresh) + t.Wait(500) // Allow time for the refresh to complete and the popup to auto-close + + t.Views().Files().IsFocused() + t.Views().Information().Content(DoesNotContain("Rebasing")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 4d9edaa70ab..142dbffb634 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -69,6 +69,7 @@ var tests = []*components.IntegrationTest{ branch.RebaseAndDrop, branch.RebaseCancelOnConflict, branch.RebaseConflictsFixBuildErrors, + branch.RebaseConflictsResolvedExternally, branch.RebaseCopiedBranch, branch.RebaseDoesNotAutosquash, branch.RebaseFromMarkedBase,