1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-11-28 09:08:41 +02:00

start refactoring gui

This commit is contained in:
Jesse Duffield 2022-01-28 20:44:36 +11:00
parent fa8571e1f4
commit a90b6efded
61 changed files with 1779 additions and 1522 deletions

View File

@ -106,8 +106,8 @@ func (gui *Gui) renderAppStatus() {
})
}
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
func (gui *Gui) WithWaitingStatus(message string, f func() error) error {
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
func (gui *Gui) withWaitingStatus(message string, f func() error) error {
go utils.Safe(func() {
id := gui.statusManager.addWaitingStatus(message)
@ -119,7 +119,7 @@ func (gui *Gui) WithWaitingStatus(message string, f func() error) error {
if err := f(); err != nil {
gui.OnUIThread(func() error {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
})
}
})

View File

@ -7,6 +7,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -62,7 +64,7 @@ func (gui *Gui) refreshBranches() {
branches, err := gui.Git.Loaders.Branches.Load(reflogCommits)
if err != nil {
_ = gui.surfaceError(err)
_ = gui.PopupHandler.Error(err)
}
gui.State.Branches = branches
@ -81,7 +83,7 @@ func (gui *Gui) handleBranchPress() error {
return nil
}
if gui.State.Panels.Branches.SelectedLineIdx == 0 {
return gui.createErrorPanel(gui.Tr.AlreadyCheckedOutBranch)
return gui.PopupHandler.ErrorMsg(gui.Tr.AlreadyCheckedOutBranch)
}
branch := gui.getSelectedBranch()
gui.logAction(gui.Tr.Actions.CheckoutBranch)
@ -111,16 +113,16 @@ func (gui *Gui) handleCopyPullRequestURLPress() error {
branchExistsOnRemote := gui.Git.Remote.CheckRemoteBranchExists(branch.Name)
if !branchExistsOnRemote {
return gui.surfaceError(errors.New(gui.Tr.NoBranchOnRemote))
return gui.PopupHandler.Error(errors.New(gui.Tr.NoBranchOnRemote))
}
url, err := hostingServiceMgr.GetPullRequestURL(branch.Name, "")
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.logAction(gui.Tr.Actions.CopyPullRequestURL)
if err := gui.OSCommand.CopyToClipboard(url); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.raiseToast(gui.Tr.PullRequestURLCopiedToClipboard)
@ -129,16 +131,12 @@ func (gui *Gui) handleCopyPullRequestURLPress() error {
}
func (gui *Gui) handleGitFetch() error {
if err := gui.createLoaderPanel(gui.Tr.FetchWait); err != nil {
return err
}
go utils.Safe(func() {
err := gui.fetch()
gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.PopupHandler.WithLoaderPanel(gui.Tr.FetchWait, func() error {
if err := gui.fetch(); err != nil {
_ = gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
})
return nil
}
func (gui *Gui) handleForceCheckout() error {
@ -146,15 +144,15 @@ func (gui *Gui) handleForceCheckout() error {
message := gui.Tr.SureForceCheckout
title := gui.Tr.ForceCheckoutBranch
return gui.ask(askOpts{
title: title,
prompt: message,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.ForceCheckoutBranch)
if err := gui.Git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
_ = gui.surfaceError(err)
_ = gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
})
}
@ -180,7 +178,7 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
gui.State.Panels.Commits.LimitCommits = true
}
return gui.WithWaitingStatus(waitingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(waitingStatus, func() error {
if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
@ -190,52 +188,52 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
// offer to autostash changes
return gui.ask(askOpts{
return gui.PopupHandler.Ask(popup.AskOpts{
title: gui.Tr.AutoStashTitle,
prompt: gui.Tr.AutoStashPrompt,
handleConfirm: func() error {
Title: gui.Tr.AutoStashTitle,
Prompt: gui.Tr.AutoStashPrompt,
HandleConfirm: func() error {
if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + ref); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
onSuccess()
if err := gui.Git.Stash.Pop(0); err != nil {
if err := gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
return err
}
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI})
},
})
}
if err := gui.surfaceError(err); err != nil {
if err := gui.PopupHandler.Error(err); err != nil {
return err
}
}
onSuccess()
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI})
})
}
func (gui *Gui) handleCheckoutByName() error {
return gui.prompt(promptOpts{
title: gui.Tr.BranchName + ":",
findSuggestionsFunc: gui.getRefsSuggestionsFunc(),
handleConfirm: func(response string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.BranchName + ":",
FindSuggestionsFunc: gui.getRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
gui.logAction("Checkout branch")
return gui.handleCheckoutRef(response, handleCheckoutRefOptions{
onRefNotFound: func(ref string) error {
return gui.ask(askOpts{
title: gui.Tr.BranchNotFoundTitle,
prompt: fmt.Sprintf("%s %s%s", gui.Tr.BranchNotFoundPrompt, ref, "?"),
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.BranchNotFoundTitle,
Prompt: fmt.Sprintf("%s %s%s", gui.Tr.BranchNotFoundPrompt, ref, "?"),
HandleConfirm: func() error {
return gui.createNewBranchWithName(ref)
},
})
@ -260,11 +258,11 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error {
}
if err := gui.Git.Branch.New(newBranchName, branch.Name); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.Panels.Branches.SelectedLineIdx = 0
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleDeleteBranch() error {
@ -278,7 +276,7 @@ func (gui *Gui) deleteBranch(force bool) error {
}
checkedOutBranch := gui.getCheckedOutBranch()
if checkedOutBranch.Name == selectedBranch.Name {
return gui.createErrorPanel(gui.Tr.CantDeleteCheckOutBranch)
return gui.PopupHandler.ErrorMsg(gui.Tr.CantDeleteCheckOutBranch)
}
return gui.deleteNamedBranch(selectedBranch, force)
}
@ -298,19 +296,19 @@ func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) err
},
)
return gui.ask(askOpts{
title: title,
prompt: message,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.DeleteBranch)
if err := gui.Git.Branch.Delete(selectedBranch.Name, force); err != nil {
errMessage := err.Error()
if !force && strings.Contains(errMessage, "git branch -D ") {
return gui.deleteNamedBranch(selectedBranch, true)
}
return gui.createErrorPanel(errMessage)
return gui.PopupHandler.ErrorMsg(errMessage)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{BRANCHES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
},
})
}
@ -321,11 +319,11 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
}
if gui.Git.Branch.IsHeadDetached() {
return gui.createErrorPanel("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
return gui.PopupHandler.ErrorMsg("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
}
checkedOutBranchName := gui.getCheckedOutBranch().Name
if checkedOutBranchName == branchName {
return gui.createErrorPanel(gui.Tr.CantMergeBranchIntoItself)
return gui.PopupHandler.ErrorMsg(gui.Tr.CantMergeBranchIntoItself)
}
prompt := utils.ResolvePlaceholderString(
gui.Tr.ConfirmMerge,
@ -335,10 +333,10 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
},
)
return gui.ask(askOpts{
title: gui.Tr.MergingTitle,
prompt: prompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.MergingTitle,
Prompt: prompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.Merge)
err := gui.Git.Branch.Merge(branchName, git_commands.MergeOpts{})
return gui.handleGenericMergeCommandResult(err)
@ -367,7 +365,7 @@ func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
checkedOutBranch := gui.getCheckedOutBranch().Name
if selectedBranchName == checkedOutBranch {
return gui.createErrorPanel(gui.Tr.CantRebaseOntoSelf)
return gui.PopupHandler.ErrorMsg(gui.Tr.CantRebaseOntoSelf)
}
prompt := utils.ResolvePlaceholderString(
gui.Tr.ConfirmRebase,
@ -377,10 +375,10 @@ func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
},
)
return gui.ask(askOpts{
title: gui.Tr.RebasingTitle,
prompt: prompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.RebasingTitle,
Prompt: prompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RebaseBranch)
err := gui.Git.Rebase.RebaseBranch(selectedBranchName)
return gui.handleGenericMergeCommandResult(err)
@ -395,13 +393,13 @@ func (gui *Gui) handleFastForward() error {
}
if !branch.IsTrackingRemote() {
return gui.createErrorPanel(gui.Tr.FwdNoUpstream)
return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoUpstream)
}
if !branch.RemoteBranchStoredLocally() {
return gui.createErrorPanel(gui.Tr.FwdNoLocalUpstream)
return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoLocalUpstream)
}
if branch.HasCommitsToPush() {
return gui.createErrorPanel(gui.Tr.FwdCommitsToPush)
return gui.PopupHandler.ErrorMsg(gui.Tr.FwdCommitsToPush)
}
action := gui.Tr.Actions.FastForwardBranch
@ -413,19 +411,21 @@ func (gui *Gui) handleFastForward() error {
"to": branch.Name,
},
)
go utils.Safe(func() {
_ = gui.createLoaderPanel(message)
return gui.PopupHandler.WithLoaderPanel(message, func() error {
if gui.State.Panels.Branches.SelectedLineIdx == 0 {
_ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true})
} else {
gui.logAction(action)
err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{BRANCHES}})
if err != nil {
_ = gui.PopupHandler.Error(err)
}
_ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
}
return nil
})
return nil
}
func (gui *Gui) handleCreateResetToBranchMenu() error {
@ -444,13 +444,13 @@ func (gui *Gui) handleRenameBranch() error {
}
promptForNewName := func() error {
return gui.prompt(promptOpts{
title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
initialContent: branch.Name,
handleConfirm: func(newBranchName string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
InitialContent: branch.Name,
HandleConfirm: func(newBranchName string) error {
gui.logAction(gui.Tr.Actions.RenameBranch)
if err := gui.Git.Branch.Rename(branch.Name, newBranchName); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
// need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch
@ -478,10 +478,10 @@ func (gui *Gui) handleRenameBranch() error {
return promptForNewName()
}
return gui.ask(askOpts{
title: gui.Tr.LcRenameBranch,
prompt: gui.Tr.RenameBranchWarning,
handleConfirm: promptForNewName,
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.LcRenameBranch,
Prompt: gui.Tr.RenameBranchWarning,
HandleConfirm: promptForNewName,
})
}
@ -513,10 +513,10 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
prefilledName = strings.SplitAfterN(item.ID(), "/", 2)[1]
}
return gui.prompt(promptOpts{
title: message,
initialContent: prefilledName,
handleConfirm: func(response string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: message,
InitialContent: prefilledName,
HandleConfirm: func(response string) error {
gui.logAction(gui.Tr.Actions.CreateBranch)
if err := gui.Git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
return err
@ -536,7 +536,7 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
gui.State.Panels.Branches.SelectedLineIdx = 0
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
})
}

View File

@ -1,6 +1,9 @@
package gui
import "github.com/jesseduffield/lazygit/pkg/commands/models"
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
// you can only copy from one context at a time, because the order and position of commits matter
@ -143,11 +146,11 @@ func (gui *Gui) HandlePasteCommits() error {
return err
}
return gui.ask(askOpts{
title: gui.Tr.CherryPick,
prompt: gui.Tr.SureCherryPick,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.CherryPickingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.CherryPick,
Prompt: gui.Tr.SureCherryPick,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.CherryPickingStatus, func() error {
gui.logAction(gui.Tr.Actions.CherryPick)
err := gui.Git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
return gui.handleGenericMergeCommandResult(err)

View File

@ -4,6 +4,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) getSelectedCommitFileNode() *filetree.CommitFileNode {
@ -65,10 +67,10 @@ func (gui *Gui) handleCheckoutCommitFile() error {
gui.logAction(gui.Tr.Actions.CheckoutFile)
if err := gui.Git.WorkingTree.CheckoutFile(gui.State.CommitFileTreeViewModel.GetParent(), node.GetPath()); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleDiscardOldFileChange() error {
@ -78,11 +80,11 @@ func (gui *Gui) handleDiscardOldFileChange() error {
fileName := gui.getSelectedCommitFileName()
return gui.ask(askOpts{
title: gui.Tr.DiscardFileChangesTitle,
prompt: gui.Tr.DiscardFileChangesPrompt,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.DiscardFileChangesTitle,
Prompt: gui.Tr.DiscardFileChangesPrompt,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
gui.logAction(gui.Tr.Actions.DiscardOldFileChange)
if err := gui.Git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
if err := gui.handleGenericMergeCommandResult(err); err != nil {
@ -90,7 +92,7 @@ func (gui *Gui) handleDiscardOldFileChange() error {
}
}
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI})
})
},
})
@ -109,7 +111,7 @@ func (gui *Gui) refreshCommitFilesView() error {
files, err := gui.Git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.CommitFileTreeViewModel.SetParent(to)
gui.State.CommitFileTreeViewModel.SetFiles(files)
@ -133,7 +135,7 @@ func (gui *Gui) handleEditCommitFile() error {
}
if node.File == nil {
return gui.createErrorPanel(gui.Tr.ErrCannotEditDirectory)
return gui.PopupHandler.ErrorMsg(gui.Tr.ErrCannotEditDirectory)
}
return gui.editFile(node.GetPath())
@ -167,7 +169,7 @@ func (gui *Gui) handleToggleFileForPatch() error {
})
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if gui.Git.Patch.PatchManager.IsEmpty() {
@ -178,10 +180,10 @@ func (gui *Gui) handleToggleFileForPatch() error {
}
if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
return gui.ask(askOpts{
title: gui.Tr.DiscardPatch,
prompt: gui.Tr.DiscardPatchConfirm,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.DiscardPatch,
Prompt: gui.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
gui.Git.Patch.PatchManager.Reset()
return toggleTheFile()
},
@ -226,10 +228,10 @@ func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
}
if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
return gui.ask(askOpts{
title: gui.Tr.DiscardPatch,
prompt: gui.Tr.DiscardPatchConfirm,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.DiscardPatch,
Prompt: gui.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
gui.Git.Patch.PatchManager.Reset()
return enterTheFile()
},

View File

@ -12,7 +12,7 @@ func (gui *Gui) handleCommitConfirm() error {
message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
gui.State.failedCommitMessage = message
if message == "" {
return gui.createErrorPanel(gui.Tr.CommitWithoutMessageErr)
return gui.PopupHandler.ErrorMsg(gui.Tr.CommitWithoutMessageErr)
}
cmdObj := gui.Git.Commit.CommitCmdObj(message)

View File

@ -6,6 +6,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -29,7 +31,7 @@ func (gui *Gui) onCommitFocus() error {
state.LimitCommits = false
go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil {
_ = gui.surfaceError(err)
_ = gui.PopupHandler.Error(err)
}
})
}
@ -122,7 +124,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
FilterPath: gui.State.Modes.Filtering.GetPath(),
IncludeRebaseCommits: true,
RefName: gui.refForLog(),
All: gui.State.ShowWholeGitGraph,
All: gui.ShowWholeGitGraph,
},
)
if err != nil {
@ -170,7 +172,7 @@ func (gui *Gui) handleCommitSquashDown() error {
}
if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(gui.Tr.YouNoCommitsToSquash)
return gui.PopupHandler.ErrorMsg(gui.Tr.YouNoCommitsToSquash)
}
applied, err := gui.handleMidRebaseCommand("squash")
@ -181,11 +183,11 @@ func (gui *Gui) handleCommitSquashDown() error {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.Squash,
prompt: gui.Tr.SureSquashThisCommit,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.Squash,
Prompt: gui.Tr.SureSquashThisCommit,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
gui.logAction(gui.Tr.Actions.SquashCommitDown)
err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "squash")
return gui.handleGenericMergeCommandResult(err)
@ -200,7 +202,7 @@ func (gui *Gui) handleCommitFixup() error {
}
if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(gui.Tr.YouNoCommitsToSquash)
return gui.PopupHandler.ErrorMsg(gui.Tr.YouNoCommitsToSquash)
}
applied, err := gui.handleMidRebaseCommand("fixup")
@ -211,11 +213,11 @@ func (gui *Gui) handleCommitFixup() error {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.Fixup,
prompt: gui.Tr.SureFixupThisCommit,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.FixingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.Fixup,
Prompt: gui.Tr.SureFixupThisCommit,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.FixingStatus, func() error {
gui.logAction(gui.Tr.Actions.FixupCommit)
err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "fixup")
return gui.handleGenericMergeCommandResult(err)
@ -244,20 +246,20 @@ func (gui *Gui) handleRewordCommit() error {
message, err := gui.Git.Commit.GetCommitMessage(commit.Sha)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
// TODO: use the commit message panel here
return gui.prompt(promptOpts{
title: gui.Tr.LcRewordCommit,
initialContent: message,
handleConfirm: func(response string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.LcRewordCommit,
InitialContent: message,
HandleConfirm: func(response string) error {
gui.logAction(gui.Tr.Actions.RewordCommit)
if err := gui.Git.Rebase.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, response); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
})
}
@ -278,7 +280,7 @@ func (gui *Gui) handleRewordCommitEditor() error {
gui.logAction(gui.Tr.Actions.RewordCommit)
subProcess, err := gui.Git.Rebase.RewordCommitInEditor(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if subProcess != nil {
return gui.runSubprocessWithSuspenseAndRefresh(subProcess)
@ -301,7 +303,7 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
// our input or we set a lazygit client as the EDITOR env variable and have it
// request us to edit the commit message when prompted.
if action == "reword" {
return true, gui.createErrorPanel(gui.Tr.LcRewordNotSupported)
return true, gui.PopupHandler.ErrorMsg(gui.Tr.LcRewordNotSupported)
}
gui.logAction("Update rebase TODO")
@ -311,7 +313,7 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
)
if err := gui.Git.Rebase.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil {
return false, gui.surfaceError(err)
return false, gui.PopupHandler.Error(err)
}
return true, gui.refreshRebaseCommits()
@ -330,11 +332,11 @@ func (gui *Gui) handleCommitDelete() error {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.DeleteCommitTitle,
prompt: gui.Tr.DeleteCommitPrompt,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.DeleteCommitTitle,
Prompt: gui.Tr.DeleteCommitPrompt,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
gui.logAction(gui.Tr.Actions.DropCommit)
err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "drop")
return gui.handleGenericMergeCommandResult(err)
@ -361,13 +363,13 @@ func (gui *Gui) handleCommitMoveDown() error {
gui.logCommand(fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), false)
if err := gui.Git.Rebase.MoveTodoDown(index); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.Panels.Commits.SelectedLineIdx++
return gui.refreshRebaseCommits()
}
return gui.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
gui.logAction(gui.Tr.Actions.MoveCommitDown)
err := gui.Git.Rebase.MoveCommitDown(gui.State.Commits, index)
if err == nil {
@ -398,13 +400,13 @@ func (gui *Gui) handleCommitMoveUp() error {
)
if err := gui.Git.Rebase.MoveTodoDown(index - 1); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.Panels.Commits.SelectedLineIdx--
return gui.refreshRebaseCommits()
}
return gui.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
gui.logAction(gui.Tr.Actions.MoveCommitUp)
err := gui.Git.Rebase.MoveCommitDown(gui.State.Commits, index-1)
if err == nil {
@ -427,7 +429,7 @@ func (gui *Gui) handleCommitEdit() error {
return nil
}
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
gui.logAction(gui.Tr.Actions.EditCommit)
err = gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "edit")
return gui.handleGenericMergeCommandResult(err)
@ -439,11 +441,11 @@ func (gui *Gui) handleCommitAmendTo() error {
return err
}
return gui.ask(askOpts{
title: gui.Tr.AmendCommitTitle,
prompt: gui.Tr.AmendCommitPrompt,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.AmendingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.AmendCommitTitle,
Prompt: gui.Tr.AmendCommitPrompt,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.AmendingStatus, func() error {
gui.logAction(gui.Tr.Actions.AmendCommit)
err := gui.Git.Rebase.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx].Sha)
return gui.handleGenericMergeCommandResult(err)
@ -478,17 +480,17 @@ func (gui *Gui) handleCommitRevert() error {
if commit.IsMerge() {
return gui.createRevertMergeCommitMenu(commit)
} else {
return gui.ask(askOpts{
title: gui.Tr.Actions.RevertCommit,
prompt: utils.ResolvePlaceholderString(
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.Actions.RevertCommit,
Prompt: utils.ResolvePlaceholderString(
gui.Tr.ConfirmRevertCommit,
map[string]string{
"selectedCommit": commit.ShortSha(),
}),
handleConfirm: func() error {
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RevertCommit)
if err := gui.Git.Commit.Revert(commit.Sha); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.afterRevertCommit()
},
@ -497,33 +499,33 @@ func (gui *Gui) handleCommitRevert() error {
}
func (gui *Gui) createRevertMergeCommitMenu(commit *models.Commit) error {
menuItems := make([]*menuItem, len(commit.Parents))
menuItems := make([]*popup.MenuItem, len(commit.Parents))
for i, parentSha := range commit.Parents {
i := i
message, err := gui.Git.Commit.GetCommitMessageFirstLine(parentSha)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
menuItems[i] = &menuItem{
displayString: fmt.Sprintf("%s: %s", utils.SafeTruncate(parentSha, 8), message),
onPress: func() error {
menuItems[i] = &popup.MenuItem{
DisplayString: fmt.Sprintf("%s: %s", utils.SafeTruncate(parentSha, 8), message),
OnPress: func() error {
parentNumber := i + 1
gui.logAction(gui.Tr.Actions.RevertCommit)
if err := gui.Git.Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.afterRevertCommit()
},
}
}
return gui.createMenu(gui.Tr.SelectParentCommitForMerge, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.SelectParentCommitForMerge, Items: menuItems})
}
func (gui *Gui) afterRevertCommit() error {
gui.State.Panels.Commits.SelectedLineIdx++
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI, scope: []RefreshableView{COMMITS, BRANCHES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES}})
}
func (gui *Gui) handleViewCommitFiles() error {
@ -552,16 +554,16 @@ func (gui *Gui) handleCreateFixupCommit() error {
},
)
return gui.ask(askOpts{
title: gui.Tr.CreateFixupCommit,
prompt: prompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.CreateFixupCommit,
Prompt: prompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.CreateFixupCommit)
if err := gui.Git.Commit.CreateFixupCommit(commit.Sha); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
})
}
@ -583,11 +585,11 @@ func (gui *Gui) handleSquashAllAboveFixupCommits() error {
},
)
return gui.ask(askOpts{
title: gui.Tr.SquashAboveCommits,
prompt: prompt,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.SquashAboveCommits,
Prompt: prompt,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
gui.logAction(gui.Tr.Actions.SquashAllAboveFixupCommits)
err := gui.Git.Rebase.SquashAllAboveFixupCommits(commit.Sha)
return gui.handleGenericMergeCommandResult(err)
@ -606,39 +608,40 @@ func (gui *Gui) handleTagCommit() error {
}
func (gui *Gui) createTagMenu(commitSha string) error {
items := []*menuItem{
{
displayString: gui.Tr.LcLightweightTag,
onPress: func() error {
return gui.handleCreateLightweightTag(commitSha)
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.TagMenuTitle,
Items: []*popup.MenuItem{
{
DisplayString: gui.Tr.LcLightweightTag,
OnPress: func() error {
return gui.handleCreateLightweightTag(commitSha)
},
},
{
DisplayString: gui.Tr.LcAnnotatedTag,
OnPress: func() error {
return gui.handleCreateAnnotatedTag(commitSha)
},
},
},
{
displayString: gui.Tr.LcAnnotatedTag,
onPress: func() error {
return gui.handleCreateAnnotatedTag(commitSha)
},
},
}
return gui.createMenu(gui.Tr.TagMenuTitle, items, createMenuOptions{showCancel: true})
})
}
func (gui *Gui) afterTagCreate() error {
gui.State.Panels.Tags.SelectedLineIdx = 0 // Set to the top
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
}
func (gui *Gui) handleCreateAnnotatedTag(commitSha string) error {
return gui.prompt(promptOpts{
title: gui.Tr.TagNameTitle,
handleConfirm: func(tagName string) error {
return gui.prompt(promptOpts{
title: gui.Tr.TagMessageTitle,
handleConfirm: func(msg string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.TagNameTitle,
HandleConfirm: func(tagName string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.TagMessageTitle,
HandleConfirm: func(msg string) error {
gui.logAction(gui.Tr.Actions.CreateAnnotatedTag)
if err := gui.Git.Tag.CreateAnnotated(tagName, commitSha, msg); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.afterTagCreate()
},
@ -648,12 +651,12 @@ func (gui *Gui) handleCreateAnnotatedTag(commitSha string) error {
}
func (gui *Gui) handleCreateLightweightTag(commitSha string) error {
return gui.prompt(promptOpts{
title: gui.Tr.TagNameTitle,
handleConfirm: func(tagName string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.TagNameTitle,
HandleConfirm: func(tagName string) error {
gui.logAction(gui.Tr.Actions.CreateLightweightTag)
if err := gui.Git.Tag.CreateLightweight(tagName, commitSha); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.afterTagCreate()
},
@ -666,10 +669,10 @@ func (gui *Gui) handleCheckoutCommit() error {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.LcCheckoutCommit,
prompt: gui.Tr.SureCheckoutThisCommit,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.LcCheckoutCommit,
Prompt: gui.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.CheckoutCommit)
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
},
@ -679,7 +682,7 @@ func (gui *Gui) handleCheckoutCommit() error {
func (gui *Gui) handleCreateCommitResetMenu() error {
commit := gui.getSelectedLocalCommit()
if commit == nil {
return gui.createErrorPanel(gui.Tr.NoCommitsThisBranch)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoCommitsThisBranch)
}
return gui.createResetMenu(commit.Sha)
@ -689,7 +692,7 @@ func (gui *Gui) handleOpenSearchForCommitsPanel(string) error {
// we usually lazyload these commits but now that we're searching we need to load them now
if gui.State.Panels.Commits.LimitCommits {
gui.State.Panels.Commits.LimitCommits = false
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
return err
}
}
@ -701,7 +704,7 @@ func (gui *Gui) handleGotoBottomForCommitsPanel() error {
// we usually lazyload these commits but now that we're searching we need to load them now
if gui.State.Panels.Commits.LimitCommits {
gui.State.Panels.Commits.LimitCommits = false
if err := gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{COMMITS}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
return err
}
}
@ -723,12 +726,12 @@ func (gui *Gui) handleCopySelectedCommitMessageToClipboard() error {
message, err := gui.Git.Commit.GetCommitMessage(commit.Sha)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.logAction(gui.Tr.Actions.CopyCommitMessageToClipboard)
if err := gui.OSCommand.CopyToClipboard(message); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.raiseToast(gui.Tr.CommitMessageCopiedToClipboard)
@ -737,87 +740,87 @@ func (gui *Gui) handleCopySelectedCommitMessageToClipboard() error {
}
func (gui *Gui) handleOpenLogMenu() error {
return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{
{
displayString: gui.Tr.ToggleShowGitGraphAll,
onPress: func() error {
gui.State.ShowWholeGitGraph = !gui.State.ShowWholeGitGraph
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.LogMenuTitle,
Items: []*popup.MenuItem{
{
DisplayString: gui.Tr.ToggleShowGitGraphAll,
OnPress: func() error {
gui.ShowWholeGitGraph = !gui.ShowWholeGitGraph
if gui.State.ShowWholeGitGraph {
gui.State.Panels.Commits.LimitCommits = false
}
if gui.ShowWholeGitGraph {
gui.State.Panels.Commits.LimitCommits = false
}
return gui.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{COMMITS}})
})
},
},
{
displayString: gui.Tr.ShowGitGraph,
opensMenu: true,
onPress: func() error {
onSelect := func(value string) {
gui.UserConfig.Git.Log.ShowGraph = value
gui.render()
}
return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{
{
displayString: "always",
onPress: func() error {
onSelect("always")
return nil
},
},
{
displayString: "never",
onPress: func() error {
onSelect("never")
return nil
},
},
{
displayString: "when maximised",
onPress: func() error {
onSelect("when-maximised")
return nil
},
},
}, createMenuOptions{showCancel: true})
},
},
{
displayString: gui.Tr.SortCommits,
opensMenu: true,
onPress: func() error {
onSelect := func(value string) error {
gui.UserConfig.Git.Log.Order = value
return gui.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{COMMITS}})
return gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
})
}
return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{
{
displayString: "topological (topo-order)",
onPress: func() error {
return onSelect("topo-order")
},
},
{
DisplayString: gui.Tr.ShowGitGraph,
OpensMenu: true,
OnPress: func() error {
onPress := func(value string) func() error {
return func() error {
gui.UserConfig.Git.Log.ShowGraph = value
gui.render()
return nil
}
}
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.LogMenuTitle,
Items: []*popup.MenuItem{
{
DisplayString: "always",
OnPress: onPress("always"),
},
{
DisplayString: "never",
OnPress: onPress("never"),
},
{
DisplayString: "when maximised",
OnPress: onPress("when-maximised"),
},
},
},
{
displayString: "date-order",
onPress: func() error {
return onSelect("date-order")
})
},
},
{
DisplayString: gui.Tr.SortCommits,
OpensMenu: true,
OnPress: func() error {
onPress := func(value string) func() error {
return func() error {
gui.UserConfig.Git.Log.Order = value
return gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
})
}
}
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.LogMenuTitle,
Items: []*popup.MenuItem{
{
DisplayString: "topological (topo-order)",
OnPress: onPress("topo-order"),
},
{
DisplayString: "date-order",
OnPress: onPress("date-order"),
},
{
DisplayString: "author-date-order",
OnPress: onPress("author-date-order"),
},
},
},
{
displayString: "author-date-order",
onPress: func() error {
return onSelect("author-date-order")
},
},
}, createMenuOptions{showCancel: true})
})
},
},
},
}, createMenuOptions{showCancel: true})
})
}
func (gui *Gui) handleOpenCommitInBrowser() error {
@ -830,12 +833,12 @@ func (gui *Gui) handleOpenCommitInBrowser() error {
url, err := hostingServiceMgr.GetCommitURL(commit.Sha)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.logAction(gui.Tr.Actions.OpenCommitInBrowser)
if err := gui.OSCommand.OpenLink(url); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil

View File

@ -5,53 +5,12 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type createPopupPanelOpts struct {
hasLoader bool
editable bool
title string
prompt string
handleConfirm func() error
handleConfirmPrompt func(string) error
handleClose func() error
// when handlersManageFocus is true, do not return from the confirmation context automatically. It's expected that the handlers will manage focus, whether that means switching to another context, or manually returning the context.
handlersManageFocus bool
findSuggestionsFunc func(string) []*types.Suggestion
}
type askOpts struct {
title string
prompt string
handleConfirm func() error
handleClose func() error
handlersManageFocus bool
}
type promptOpts struct {
title string
initialContent string
findSuggestionsFunc func(string) []*types.Suggestion
handleConfirm func(string) error
}
func (gui *Gui) ask(opts askOpts) error {
return gui.PopupHandler.Ask(opts)
}
func (gui *Gui) prompt(opts promptOpts) error {
return gui.PopupHandler.Prompt(opts)
}
func (gui *Gui) createLoaderPanel(prompt string) error {
return gui.PopupHandler.Loader(prompt)
}
func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error {
return func() error {
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
@ -60,7 +19,7 @@ func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function f
if function != nil {
if err := function(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
}
@ -76,7 +35,7 @@ func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, func
if function != nil {
if err := function(getResponse()); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
}
@ -179,31 +138,31 @@ func (gui *Gui) prepareConfirmationPanel(
return nil
}
func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error {
func (gui *Gui) createPopupPanel(opts popup.CreatePopupPanelOpts) error {
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
opts.title,
opts.prompt,
opts.hasLoader,
opts.findSuggestionsFunc,
opts.editable,
opts.Title,
opts.Prompt,
opts.HasLoader,
opts.FindSuggestionsFunc,
opts.Editable,
)
if err != nil {
return err
}
confirmationView := gui.Views.Confirmation
confirmationView.Editable = opts.editable
confirmationView.Editable = opts.Editable
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
if opts.editable {
if opts.Editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.prompt)
textArea.TypeString(opts.Prompt)
confirmationView.RenderTextArea()
} else {
if err := gui.renderString(confirmationView, opts.prompt); err != nil {
if err := gui.renderString(confirmationView, opts.Prompt); err != nil {
return err
}
}
@ -215,7 +174,7 @@ func (gui *Gui) createPopupPanel(opts createPopupPanelOpts) error {
return gui.pushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
actions := utils.ResolvePlaceholderString(
gui.Tr.CloseConfirm,
map[string]string{
@ -226,10 +185,10 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
_ = gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.handleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
if opts.HandleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
} else {
onConfirm = gui.wrappedConfirmationFunction(opts.handlersManageFocus, opts.handleConfirm)
onConfirm = gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirm)
}
type confirmationKeybinding struct {
@ -240,8 +199,8 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
keybindingConfig := gui.UserConfig.Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
opts.handlersManageFocus,
opts.handleConfirmPrompt,
opts.HandlersManageFocus,
opts.HandleConfirmPrompt,
gui.getSelectedSuggestionValue,
)
@ -259,7 +218,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
{
viewName: "confirmation",
key: gui.getKey(keybindingConfig.Universal.Return),
handler: gui.wrappedConfirmationFunction(opts.handlersManageFocus, opts.handleClose),
handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
},
{
viewName: "confirmation",
@ -284,7 +243,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
{
viewName: "suggestions",
key: gui.getKey(keybindingConfig.Universal.Return),
handler: gui.wrappedConfirmationFunction(opts.handlersManageFocus, opts.handleClose),
handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
},
{
viewName: "suggestions",
@ -317,19 +276,3 @@ func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View)
return f()
}
}
func (gui *Gui) createErrorPanel(message string) error {
return gui.PopupHandler.Error(message)
}
func (gui *Gui) surfaceError(err error) error {
if err == nil {
return nil
}
if err == gocui.ErrQuit {
return err
}
return gui.createErrorPanel(err.Error())
}

View File

@ -174,12 +174,13 @@ func (gui *Gui) contextTree() ContextTree {
OnGetOptionsMap: gui.getMergingOptions,
},
Credentials: &BasicContext{
OnFocus: OnFocusWrapper(gui.handleCredentialsViewFocused),
OnFocus: OnFocusWrapper(gui.handleAskFocused),
Kind: PERSISTENT_POPUP,
ViewName: "credentials",
Key: CREDENTIALS_CONTEXT_KEY,
},
Confirmation: &BasicContext{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
Kind: TEMPORARY_POPUP,
ViewName: "confirmation",
Key: CONFIRMATION_CONTEXT_KEY,

View File

@ -0,0 +1,243 @@
package controllers
import (
"fmt"
"path/filepath"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// if Go let me do private struct embedding of structs with public fields (which it should)
// I would just do that. But alas.
type ControllerCommon struct {
*common.Common
IGuiCommon
}
type SubmodulesController struct {
// I've said publicly that I'm against single-letter variable names but in this
// case I would actually prefer a _zero_ letter variable name in the form of
// struct embedding, but Go does not allow hiding public fields in an embedded struct
// to the client
c *ControllerCommon
enterSubmoduleFn func(submodule *models.SubmoduleConfig) error
getSelectedSubmodule func() *models.SubmoduleConfig
git *commands.GitCommand
submodules []*models.SubmoduleConfig
}
func NewSubmodulesController(
c *ControllerCommon,
enterSubmoduleFn func(submodule *models.SubmoduleConfig) error,
git *commands.GitCommand,
submodules []*models.SubmoduleConfig,
getSelectedSubmodule func() *models.SubmoduleConfig,
) *SubmodulesController {
return &SubmodulesController{
c: c,
enterSubmoduleFn: enterSubmoduleFn,
git: git,
submodules: submodules,
getSelectedSubmodule: getSelectedSubmodule,
}
}
func (self *SubmodulesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig) []*types.Binding {
return []*types.Binding{
{
Key: getKey(config.Universal.GoInto),
Handler: self.forSubmodule(self.enter),
Description: self.c.Tr.LcEnterSubmodule,
},
{
Key: getKey(config.Universal.Remove),
Handler: self.forSubmodule(self.remove),
Description: self.c.Tr.LcRemoveSubmodule,
},
{
Key: getKey(config.Submodules.Update),
Handler: self.forSubmodule(self.update),
Description: self.c.Tr.LcSubmoduleUpdate,
},
{
Key: getKey(config.Universal.New),
Handler: self.add,
Description: self.c.Tr.LcAddSubmodule,
},
{
Key: getKey(config.Universal.Edit),
Handler: self.forSubmodule(self.editURL),
Description: self.c.Tr.LcEditSubmoduleUrl,
},
{
Key: getKey(config.Submodules.Init),
Handler: self.forSubmodule(self.init),
Description: self.c.Tr.LcInitSubmodule,
},
{
Key: getKey(config.Submodules.BulkMenu),
Handler: self.openBulkActionsMenu,
Description: self.c.Tr.LcViewBulkSubmoduleOptions,
OpensMenu: true,
},
}
}
func (self *SubmodulesController) enter(submodule *models.SubmoduleConfig) error {
return self.enterSubmoduleFn(submodule)
}
func (self *SubmodulesController) add() error {
return self.c.Prompt(popup.PromptOpts{
Title: self.c.Tr.LcNewSubmoduleUrl,
HandleConfirm: func(submoduleUrl string) error {
nameSuggestion := filepath.Base(strings.TrimSuffix(submoduleUrl, filepath.Ext(submoduleUrl)))
return self.c.Prompt(popup.PromptOpts{
Title: self.c.Tr.LcNewSubmoduleName,
InitialContent: nameSuggestion,
HandleConfirm: func(submoduleName string) error {
return self.c.Prompt(popup.PromptOpts{
Title: self.c.Tr.LcNewSubmodulePath,
InitialContent: submoduleName,
HandleConfirm: func(submodulePath string) error {
return self.c.WithWaitingStatus(self.c.Tr.LcAddingSubmoduleStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.AddSubmodule)
err := self.git.Submodule.Add(submoduleName, submodulePath, submoduleUrl)
if err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
},
})
},
})
},
})
}
func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) error {
return self.c.Prompt(popup.PromptOpts{
Title: fmt.Sprintf(self.c.Tr.LcUpdateSubmoduleUrl, submodule.Name),
InitialContent: submodule.Url,
HandleConfirm: func(newUrl string) error {
return self.c.WithWaitingStatus(self.c.Tr.LcUpdatingSubmoduleUrlStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl)
err := self.git.Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl)
if err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
},
})
}
func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error {
return self.c.WithWaitingStatus(self.c.Tr.LcInitializingSubmoduleStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule)
err := self.git.Submodule.Init(submodule.Path)
if err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
}
func (self *SubmodulesController) openBulkActionsMenu() error {
return self.c.Menu(popup.CreateMenuOptions{
Title: self.c.Tr.LcBulkSubmoduleOptions,
Items: []*popup.MenuItem{
{
DisplayStrings: []string{self.c.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(self.git.Submodule.BulkInitCmdObj().ToString())},
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.LcRunningCommand, func() error {
self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules)
err := self.git.Submodule.BulkInitCmdObj().Run()
if err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
},
},
{
DisplayStrings: []string{self.c.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(self.git.Submodule.BulkUpdateCmdObj().ToString())},
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.LcRunningCommand, func() error {
self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules)
if err := self.git.Submodule.BulkUpdateCmdObj().Run(); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
},
},
{
DisplayStrings: []string{self.c.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(self.git.Submodule.BulkDeinitCmdObj().ToString())},
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.LcRunningCommand, func() error {
self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules)
if err := self.git.Submodule.BulkDeinitCmdObj().Run(); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
},
},
},
})
}
func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) error {
return self.c.WithWaitingStatus(self.c.Tr.LcUpdatingSubmoduleStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule)
err := self.git.Submodule.Update(submodule.Path)
if err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
})
}
func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) error {
return self.c.Ask(popup.AskOpts{
Title: self.c.Tr.RemoveSubmodule,
Prompt: fmt.Sprintf(self.c.Tr.RemoveSubmodulePrompt, submodule.Name),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.RemoveSubmodule)
if err := self.git.Submodule.Delete(submodule); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES, types.FILES}})
},
})
}
func (self *SubmodulesController) forSubmodule(callback func(*models.SubmoduleConfig) error) func() error {
return func() error {
submodule := self.getSelectedSubmodule()
if submodule == nil {
return nil
}
return callback(submodule)
}
}

View File

@ -0,0 +1,13 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type IGuiCommon interface {
popup.IPopupHandler
LogAction(string)
Refresh(types.RefreshOptions) error
}

View File

@ -4,6 +4,7 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -48,15 +49,16 @@ func (gui *Gui) handleSubmitCredential() error {
return err
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleCloseCredentialsView() error {
gui.Views.Credentials.ClearTextArea()
gui.credentials <- ""
return gui.returnFromContext()
}
func (gui *Gui) handleCredentialsViewFocused() error {
func (gui *Gui) handleAskFocused() error {
keybindingConfig := gui.UserConfig.Keybinding
message := utils.ResolvePlaceholderString(
@ -69,18 +71,3 @@ func (gui *Gui) handleCredentialsViewFocused() error {
return gui.renderString(gui.Views.Options, message)
}
// handleCredentialsPopup handles the views after executing a command that might ask for credentials
func (gui *Gui) handleCredentialsPopup(cmdErr error) {
if cmdErr != nil {
errMessage := cmdErr.Error()
if strings.Contains(errMessage, "Invalid username, password or passphrase") {
errMessage = gui.Tr.PassUnameWrong
}
_ = gui.returnFromContext()
// we are not logging this error because it may contain a password or a passphrase
_ = gui.createErrorPanel(errMessage)
} else {
_ = gui.closeConfirmationPrompt(false)
}
}

View File

@ -12,7 +12,9 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -62,18 +64,18 @@ func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (s
func (gui *Gui) inputPrompt(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.prompt(promptOpts{
title: title,
initialContent: initialValue,
handleConfirm: func(str string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: title,
InitialContent: initialValue,
HandleConfirm: func(str string) error {
promptResponses[responseIdx] = str
return wrappedF()
},
@ -82,7 +84,7 @@ func (gui *Gui) inputPrompt(prompt config.CustomCommandPrompt, promptResponses [
func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
// need to make a menu here some how
menuItems := make([]*menuItem, len(prompt.Options))
menuItems := make([]*popup.MenuItem, len(prompt.Options))
for i, option := range prompt.Options {
option := option
@ -93,22 +95,22 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
}
name, err := gui.resolveTemplate(nameTemplate, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
description, err := gui.resolveTemplate(option.Description, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
value, err := gui.resolveTemplate(option.Value, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
menuItems[i] = &menuItem{
displayStrings: []string{name, style.FgYellow.Sprint(description)},
onPress: func() error {
menuItems[i] = &popup.MenuItem{
DisplayStrings: []string{name, style.FgYellow.Sprint(description)},
OnPress: func() error {
promptResponses[responseIdx] = value
return wrappedF()
},
@ -117,30 +119,30 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, labelFormat string) ([]commandMenuEntry, error) {
reg, err := regexp.Compile(filter)
if err != nil {
return nil, gui.surfaceError(errors.New("unable to parse filter regex, error: " + err.Error()))
return nil, gui.PopupHandler.Error(errors.New("unable to parse filter regex, error: " + err.Error()))
}
buff := bytes.NewBuffer(nil)
valueTemp, err := template.New("format").Parse(valueFormat)
if err != nil {
return nil, gui.surfaceError(errors.New("unable to parse value format, error: " + err.Error()))
return nil, gui.PopupHandler.Error(errors.New("unable to parse value format, error: " + err.Error()))
}
colorFuncMap := style.TemplateFuncMapAddColors(template.FuncMap{})
descTemp, err := template.New("format").Funcs(colorFuncMap).Parse(labelFormat)
if err != nil {
return nil, gui.surfaceError(errors.New("unable to parse label format, error: " + err.Error()))
return nil, gui.PopupHandler.Error(errors.New("unable to parse label format, error: " + err.Error()))
}
candidates := []commandMenuEntry{}
@ -165,7 +167,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label
err = valueTemp.Execute(buff, tmplData)
if err != nil {
return candidates, gui.surfaceError(err)
return candidates, gui.PopupHandler.Error(err)
}
entry := commandMenuEntry{
value: strings.TrimSpace(buff.String()),
@ -175,7 +177,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label
buff.Reset()
err = descTemp.Execute(buff, tmplData)
if err != nil {
return candidates, gui.surfaceError(err)
return candidates, gui.PopupHandler.Error(err)
}
entry.label = strings.TrimSpace(buff.String())
} else {
@ -193,33 +195,33 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
// Collect cmd to run from config
cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
// Collect Filter regexp
filter, err := gui.resolveTemplate(prompt.Filter, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
// Run and save output
message, err := gui.Git.Custom.RunWithOutput(cmdStr)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
// Need to make a menu out of what the cmd has displayed
candidates, err := gui.GenerateMenuCandidates(message, filter, prompt.ValueFormat, prompt.LabelFormat)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
menuItems := make([]*menuItem, len(candidates))
menuItems := make([]*popup.MenuItem, len(candidates))
for i := range candidates {
i := i
menuItems[i] = &menuItem{
displayStrings: []string{candidates[i].label},
onPress: func() error {
menuItems[i] = &popup.MenuItem{
DisplayStrings: []string{candidates[i].label},
OnPress: func() error {
promptResponses[responseIdx] = candidates[i].value
return wrappedF()
},
@ -228,10 +230,10 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand) func() error {
@ -241,7 +243,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
f := func() error {
cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if customCommand.Subprocess {
@ -252,7 +254,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
if loadingText == "" {
loadingText = gui.Tr.LcRunningCustomCommandStatus
}
return gui.WithWaitingStatus(loadingText, func() error {
return gui.PopupHandler.WithWaitingStatus(loadingText, func() error {
gui.logAction(gui.Tr.Actions.CustomCommand)
cmdObj := gui.OSCommand.Cmd.NewShell(cmdStr)
if customCommand.Stream {
@ -260,9 +262,9 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
}
err := cmdObj.Run()
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{})
return gui.refreshSidePanels(types.RefreshOptions{})
})
}
@ -291,7 +293,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
return gui.menuPromptFromCommand(prompt, promptResponses, idx, wrappedF)
}
default:
return gui.createErrorPanel("custom command prompt must have a type of 'input', 'menu' or 'menuFromCommand'")
return gui.PopupHandler.ErrorMsg("custom command prompt must have a type of 'input', 'menu' or 'menuFromCommand'")
}
}
@ -300,8 +302,8 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
}
}
func (gui *Gui) GetCustomCommandKeybindings() []*Binding {
bindings := []*Binding{}
func (gui *Gui) GetCustomCommandKeybindings() []*types.Binding {
bindings := []*types.Binding{}
customCommands := gui.UserConfig.CustomCommands
for _, customCommand := range customCommands {
@ -334,7 +336,7 @@ func (gui *Gui) GetCustomCommandKeybindings() []*Binding {
description = customCommand.Command
}
bindings = append(bindings, &Binding{
bindings = append(bindings, &types.Binding{
ViewName: viewName,
Contexts: contexts,
Key: gui.getKey(customCommand.Key),

View File

@ -28,7 +28,7 @@ func isShowingDiff(gui *Gui) bool {
func (gui *Gui) IncreaseContextInDiffView() error {
if isShowingDiff(gui) {
if err := gui.CheckCanChangeContext(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.UserConfig.Git.DiffContextSize = gui.UserConfig.Git.DiffContextSize + 1
@ -43,7 +43,7 @@ func (gui *Gui) DecreaseContextInDiffView() error {
if isShowingDiff(gui) && old_size > 1 {
if err := gui.CheckCanChangeContext(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.UserConfig.Git.DiffContextSize = old_size - 1

View File

@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/stretchr/testify/assert"
)
@ -144,8 +145,8 @@ func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
gui.Git.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
gui.PopupHandler = &TestPopupHandler{
onError: func(message string) error {
gui.PopupHandler = &popup.TestPopupHandler{
OnErrorMsg: func(message string) error {
assert.Equal(t, gui.Tr.CantChangeContextSizeError, message)
errorCount += 1
return nil
@ -166,8 +167,8 @@ func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
gui.Git.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
gui.PopupHandler = &TestPopupHandler{
onError: func(message string) error {
gui.PopupHandler = &popup.TestPopupHandler{
OnErrorMsg: func(message string) error {
assert.Equal(t, gui.Tr.CantChangeContextSizeError, message)
errorCount += 1
return nil

View File

@ -5,11 +5,13 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) exitDiffMode() error {
gui.State.Modes.Diffing = diffing.New()
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) renderDiff() error {
@ -105,31 +107,31 @@ func (gui *Gui) diffStr() string {
func (gui *Gui) handleCreateDiffingMenuPanel() error {
names := gui.currentDiffTerminals()
menuItems := []*menuItem{}
menuItems := []*popup.MenuItem{}
for _, name := range names {
name := name
menuItems = append(menuItems, []*menuItem{
menuItems = append(menuItems, []*popup.MenuItem{
{
displayString: fmt.Sprintf("%s %s", gui.Tr.LcDiff, name),
onPress: func() error {
DisplayString: fmt.Sprintf("%s %s", gui.Tr.LcDiff, name),
OnPress: func() error {
gui.State.Modes.Diffing.Ref = name
// can scope this down based on current view but too lazy right now
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
},
}...)
}
menuItems = append(menuItems, []*menuItem{
menuItems = append(menuItems, []*popup.MenuItem{
{
displayString: gui.Tr.LcEnterRefToDiff,
onPress: func() error {
return gui.prompt(promptOpts{
title: gui.Tr.LcEnteRefName,
findSuggestionsFunc: gui.getRefsSuggestionsFunc(),
handleConfirm: func(response string) error {
DisplayString: gui.Tr.LcEnterRefToDiff,
OnPress: func() error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.LcEnteRefName,
FindSuggestionsFunc: gui.getRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
gui.State.Modes.Diffing.Ref = strings.TrimSpace(response)
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
})
},
@ -137,23 +139,23 @@ func (gui *Gui) handleCreateDiffingMenuPanel() error {
}...)
if gui.State.Modes.Diffing.Active() {
menuItems = append(menuItems, []*menuItem{
menuItems = append(menuItems, []*popup.MenuItem{
{
displayString: gui.Tr.LcSwapDiff,
onPress: func() error {
DisplayString: gui.Tr.LcSwapDiff,
OnPress: func() error {
gui.State.Modes.Diffing.Reverse = !gui.State.Modes.Diffing.Reverse
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
},
{
displayString: gui.Tr.LcExitDiffMode,
onPress: func() error {
DisplayString: gui.Tr.LcExitDiffMode,
OnPress: func() error {
gui.State.Modes.Diffing = diffing.New()
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
},
},
}...)
}
return gui.createMenu(gui.Tr.DiffingMenuTitle, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.DiffingMenuTitle, Items: menuItems})
}

View File

@ -1,36 +1,41 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) handleCreateDiscardMenu() error {
node := gui.getSelectedFileNode()
if node == nil {
return nil
}
var menuItems []*menuItem
var menuItems []*popup.MenuItem
if node.File == nil {
menuItems = []*menuItem{
menuItems = []*popup.MenuItem{
{
displayString: gui.Tr.LcDiscardAllChanges,
onPress: func() error {
DisplayString: gui.Tr.LcDiscardAllChanges,
OnPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardAllChangesInDirectory)
if err := gui.Git.WorkingTree.DiscardAllDirChanges(node); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
}
if node.GetHasStagedChanges() && node.GetHasUnstagedChanges() {
menuItems = append(menuItems, &menuItem{
displayString: gui.Tr.LcDiscardUnstagedChanges,
onPress: func() error {
menuItems = append(menuItems, &popup.MenuItem{
DisplayString: gui.Tr.LcDiscardUnstagedChanges,
OnPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardUnstagedChangesInDirectory)
if err := gui.Git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
})
}
@ -41,43 +46,43 @@ func (gui *Gui) handleCreateDiscardMenu() error {
if file.IsSubmodule(submodules) {
submodule := file.SubmoduleConfig(submodules)
menuItems = []*menuItem{
menuItems = []*popup.MenuItem{
{
displayString: gui.Tr.LcSubmoduleStashAndReset,
onPress: func() error {
return gui.handleResetSubmodule(submodule)
DisplayString: gui.Tr.LcSubmoduleStashAndReset,
OnPress: func() error {
return gui.resetSubmodule(submodule)
},
},
}
} else {
menuItems = []*menuItem{
menuItems = []*popup.MenuItem{
{
displayString: gui.Tr.LcDiscardAllChanges,
onPress: func() error {
DisplayString: gui.Tr.LcDiscardAllChanges,
OnPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardAllChangesInFile)
if err := gui.Git.WorkingTree.DiscardAllFileChanges(file); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
}
if file.HasStagedChanges && file.HasUnstagedChanges {
menuItems = append(menuItems, &menuItem{
displayString: gui.Tr.LcDiscardUnstagedChanges,
onPress: func() error {
menuItems = append(menuItems, &popup.MenuItem{
DisplayString: gui.Tr.LcDiscardUnstagedChanges,
OnPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardAllUnstagedChangesInFile)
if err := gui.Git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
})
}
}
}
return gui.createMenu(node.GetPath(), menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: node.GetPath(), Items: menuItems})
}

View File

@ -3,34 +3,36 @@ package gui
import (
"io"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
)
func (gui *Gui) handleCreateExtrasMenuPanel() error {
menuItems := []*menuItem{
{
displayString: gui.Tr.ToggleShowCommandLog,
onPress: func() error {
currentContext := gui.currentStaticContext()
if gui.ShowExtrasWindow && currentContext.GetKey() == COMMAND_LOG_CONTEXT_KEY {
if err := gui.returnFromContext(); err != nil {
return err
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.CommandLog,
Items: []*popup.MenuItem{
{
DisplayString: gui.Tr.ToggleShowCommandLog,
OnPress: func() error {
currentContext := gui.currentStaticContext()
if gui.ShowExtrasWindow && currentContext.GetKey() == COMMAND_LOG_CONTEXT_KEY {
if err := gui.returnFromContext(); err != nil {
return err
}
}
}
show := !gui.ShowExtrasWindow
gui.ShowExtrasWindow = show
gui.Config.GetAppState().HideCommandLog = !show
_ = gui.Config.SaveAppState()
return nil
show := !gui.ShowExtrasWindow
gui.ShowExtrasWindow = show
gui.Config.GetAppState().HideCommandLog = !show
_ = gui.Config.SaveAppState()
return nil
},
},
{
DisplayString: gui.Tr.FocusCommandLog,
OnPress: gui.handleFocusCommandLog,
},
},
{
displayString: gui.Tr.FocusCommandLog,
onPress: gui.handleFocusCommandLog,
},
}
return gui.createMenu(gui.Tr.CommandLog, menuItems, createMenuOptions{showCancel: true})
})
}
func (gui *Gui) handleFocusCommandLog() error {

View File

@ -6,6 +6,7 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
@ -117,7 +118,7 @@ func (gui *Gui) watchFilesForChanges() {
}
// only refresh if we're not already
if !gui.State.IsRefreshingFiles {
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
_ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}
// watch for errors

View File

@ -12,6 +12,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -67,7 +69,7 @@ func (gui *Gui) filesRenderToMain() error {
gui.resetMergeStateWithLock()
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.IgnoreWhitespaceInDiffView)
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
title: gui.Tr.UnstagedChanges,
@ -76,7 +78,7 @@ func (gui *Gui) filesRenderToMain() error {
if node.GetHasUnstagedChanges() {
if node.GetHasStagedChanges() {
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.IgnoreWhitespaceInDiffView)
refreshOpts.secondary = &viewUpdateOpts{
title: gui.Tr.StagedChanges,
@ -191,7 +193,7 @@ func (gui *Gui) enterFile(opts OnFocusOpts) error {
return gui.switchToMerge()
}
if file.HasMergeConflicts {
return gui.createErrorPanel(gui.Tr.FileStagingRequirements)
return gui.PopupHandler.ErrorMsg(gui.Tr.FileStagingRequirements)
}
return gui.pushContext(gui.State.Contexts.Staging, opts)
@ -213,36 +215,36 @@ func (gui *Gui) handleFilePress() error {
if file.HasUnstagedChanges {
gui.logAction(gui.Tr.Actions.StageFile)
if err := gui.Git.WorkingTree.StageFile(file.Name); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
} else {
gui.logAction(gui.Tr.Actions.UnstageFile)
if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
}
} else {
// if any files within have inline merge conflicts we can't stage or unstage,
// or it'll end up with those >>>>>> lines actually staged
if node.GetHasInlineMergeConflicts() {
return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts)
return gui.PopupHandler.ErrorMsg(gui.Tr.ErrStageDirWithInlineMergeConflicts)
}
if node.GetHasUnstagedChanges() {
gui.logAction(gui.Tr.Actions.StageFile)
if err := gui.Git.WorkingTree.StageFile(node.Path); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
} else {
// pretty sure it doesn't matter that we're always passing true here
gui.logAction(gui.Tr.Actions.UnstageFile)
if err := gui.Git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
}
}
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
@ -273,10 +275,10 @@ func (gui *Gui) handleStageAll() error {
err = gui.Git.WorkingTree.StageAll()
}
if err != nil {
_ = gui.surfaceError(err)
_ = gui.PopupHandler.Error(err)
}
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
@ -290,7 +292,7 @@ func (gui *Gui) handleIgnoreFile() error {
}
if node.GetPath() == ".gitignore" {
return gui.createErrorPanel("Cannot ignore .gitignore")
return gui.PopupHandler.ErrorMsg("Cannot ignore .gitignore")
}
unstageFiles := func() error {
@ -306,10 +308,10 @@ func (gui *Gui) handleIgnoreFile() error {
}
if node.GetIsTracked() {
return gui.ask(askOpts{
title: gui.Tr.IgnoreTracked,
prompt: gui.Tr.IgnoreTrackedPrompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.IgnoreTracked,
Prompt: gui.Tr.IgnoreTrackedPrompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.IgnoreFile)
// not 100% sure if this is necessary but I'll assume it is
if err := unstageFiles(); err != nil {
@ -323,7 +325,7 @@ func (gui *Gui) handleIgnoreFile() error {
if err := gui.Git.WorkingTree.Ignore(node.GetPath()); err != nil {
return err
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
},
})
}
@ -335,16 +337,16 @@ func (gui *Gui) handleIgnoreFile() error {
}
if err := gui.Git.WorkingTree.Ignore(node.GetPath()); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
}
func (gui *Gui) handleWIPCommitPress() error {
skipHookPrefix := gui.UserConfig.Git.SkipHookPrefix
if skipHookPrefix == "" {
return gui.createErrorPanel(gui.Tr.SkipHookPrefixNotConfigured)
return gui.PopupHandler.ErrorMsg(gui.Tr.SkipHookPrefixNotConfigured)
}
textArea := gui.Views.CommitMessage.TextArea
@ -381,11 +383,11 @@ func (gui *Gui) prepareFilesForCommit() error {
func (gui *Gui) handleCommitPress() error {
if err := gui.prepareFilesForCommit(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if gui.State.FileTreeViewModel.GetItemsLength() == 0 {
return gui.createErrorPanel(gui.Tr.NoFilesStagedTitle)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoFilesStagedTitle)
}
if len(gui.stagedFiles()) == 0 {
@ -403,7 +405,7 @@ func (gui *Gui) handleCommitPress() error {
prefixReplace := commitPrefixConfig.Replace
rgx, err := regexp.Compile(prefixPattern)
if err != nil {
return gui.createErrorPanel(fmt.Sprintf("%s: %s", gui.Tr.LcCommitPrefixPatternError, err.Error()))
return gui.PopupHandler.ErrorMsg(fmt.Sprintf("%s: %s", gui.Tr.LcCommitPrefixPatternError, err.Error()))
}
prefix := rgx.ReplaceAllString(gui.getCheckedOutBranch().Name, prefixReplace)
gui.Views.CommitMessage.ClearTextArea()
@ -421,16 +423,16 @@ func (gui *Gui) handleCommitPress() error {
}
func (gui *Gui) promptToStageAllAndRetry(retry func() error) error {
return gui.ask(askOpts{
title: gui.Tr.NoFilesStagedTitle,
prompt: gui.Tr.NoFilesStagedPrompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.NoFilesStagedTitle,
Prompt: gui.Tr.NoFilesStagedPrompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.StageAllFiles)
if err := gui.Git.WorkingTree.StageAll(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if err := gui.refreshFilesAndSubmodules(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return retry()
@ -440,7 +442,7 @@ func (gui *Gui) promptToStageAllAndRetry(retry func() error) error {
func (gui *Gui) handleAmendCommitPress() error {
if gui.State.FileTreeViewModel.GetItemsLength() == 0 {
return gui.createErrorPanel(gui.Tr.NoFilesStagedTitle)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoFilesStagedTitle)
}
if len(gui.stagedFiles()) == 0 {
@ -448,13 +450,13 @@ func (gui *Gui) handleAmendCommitPress() error {
}
if len(gui.State.Commits) == 0 {
return gui.createErrorPanel(gui.Tr.NoCommitToAmend)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoCommitToAmend)
}
return gui.ask(askOpts{
title: strings.Title(gui.Tr.AmendLastCommit),
prompt: gui.Tr.SureToAmend,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: strings.Title(gui.Tr.AmendLastCommit),
Prompt: gui.Tr.SureToAmend,
HandleConfirm: func() error {
cmdObj := gui.Git.Commit.AmendHeadCmdObj()
gui.logAction(gui.Tr.Actions.AmendCommit)
return gui.withGpgHandling(cmdObj, gui.Tr.AmendingStatus, nil)
@ -466,7 +468,7 @@ func (gui *Gui) handleAmendCommitPress() error {
// their editor rather than via the popup panel
func (gui *Gui) handleCommitEditorPress() error {
if gui.State.FileTreeViewModel.GetItemsLength() == 0 {
return gui.createErrorPanel(gui.Tr.NoFilesStagedTitle)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoFilesStagedTitle)
}
if len(gui.stagedFiles()) == 0 {
@ -480,28 +482,29 @@ func (gui *Gui) handleCommitEditorPress() error {
}
func (gui *Gui) handleStatusFilterPressed() error {
menuItems := []*menuItem{
{
displayString: gui.Tr.FilterStagedFiles,
onPress: func() error {
return gui.setStatusFiltering(filetree.DisplayStaged)
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.FilteringMenuTitle,
Items: []*popup.MenuItem{
{
DisplayString: gui.Tr.FilterStagedFiles,
OnPress: func() error {
return gui.setStatusFiltering(filetree.DisplayStaged)
},
},
{
DisplayString: gui.Tr.FilterUnstagedFiles,
OnPress: func() error {
return gui.setStatusFiltering(filetree.DisplayUnstaged)
},
},
{
DisplayString: gui.Tr.ResetCommitFilterState,
OnPress: func() error {
return gui.setStatusFiltering(filetree.DisplayAll)
},
},
},
{
displayString: gui.Tr.FilterUnstagedFiles,
onPress: func() error {
return gui.setStatusFiltering(filetree.DisplayUnstaged)
},
},
{
displayString: gui.Tr.ResetCommitFilterState,
onPress: func() error {
return gui.setStatusFiltering(filetree.DisplayAll)
},
},
}
return gui.createMenu(gui.Tr.FilteringMenuTitle, menuItems, createMenuOptions{showCancel: true})
})
}
func (gui *Gui) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
@ -517,7 +520,7 @@ func (gui *Gui) editFile(filename string) error {
func (gui *Gui) editFileAtLine(filename string, lineNumber int) error {
cmdStr, err := gui.Git.File.GetEditCmdStr(filename, lineNumber)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.logAction(gui.Tr.Actions.EditFile)
@ -533,7 +536,7 @@ func (gui *Gui) handleFileEdit() error {
}
if node.File == nil {
return gui.createErrorPanel(gui.Tr.ErrCannotEditDirectory)
return gui.PopupHandler.ErrorMsg(gui.Tr.ErrCannotEditDirectory)
}
return gui.editFile(node.GetPath())
@ -549,7 +552,7 @@ func (gui *Gui) handleFileOpen() error {
}
func (gui *Gui) handleRefreshFiles() error {
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
}
func (gui *Gui) refreshStateFiles() error {
@ -666,10 +669,10 @@ func (gui *Gui) refreshStateFiles() error {
func (gui *Gui) promptToContinueRebase() error {
gui.takeOverMergeConflictScrolling()
return gui.ask(askOpts{
title: "continue",
prompt: gui.Tr.ConflictsResolved,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: "continue",
Prompt: gui.Tr.ConflictsResolved,
HandleConfirm: func() error {
return gui.genericMergeCommand(REBASE_OPTION_CONTINUE)
},
})
@ -730,15 +733,15 @@ func (gui *Gui) handlePullFiles() error {
if !currentBranch.IsTrackingRemote() {
suggestedRemote := getSuggestedRemote(gui.State.Remotes)
return gui.prompt(promptOpts{
title: gui.Tr.EnterUpstream,
initialContent: suggestedRemote + " " + currentBranch.Name,
findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
handleConfirm: func(upstream string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.EnterUpstream,
InitialContent: suggestedRemote + " " + currentBranch.Name,
FindSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
HandleConfirm: func(upstream string) error {
var upstreamBranch, upstreamRemote string
split := strings.Split(upstream, " ")
if len(split) != 2 {
return gui.createErrorPanel(gui.Tr.InvalidUpstream)
return gui.PopupHandler.ErrorMsg(gui.Tr.InvalidUpstream)
}
upstreamRemote = split[0]
@ -749,7 +752,7 @@ func (gui *Gui) handlePullFiles() error {
if strings.Contains(errorMessage, "does not exist") {
errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream)
}
return gui.createErrorPanel(errorMessage)
return gui.PopupHandler.ErrorMsg(errorMessage)
}
return gui.pullFiles(PullFilesOptions{UpstreamRemote: upstreamRemote, UpstreamBranch: upstreamBranch, action: action})
},
@ -767,14 +770,9 @@ type PullFilesOptions struct {
}
func (gui *Gui) pullFiles(opts PullFilesOptions) error {
if err := gui.createLoaderPanel(gui.Tr.PullWait); err != nil {
return err
}
// TODO: this doesn't look like a good idea. Why the goroutine?
go utils.Safe(func() { _ = gui.pullWithLock(opts) })
return nil
return gui.PopupHandler.WithLoaderPanel(gui.Tr.PullWait, func() error {
return gui.pullWithLock(opts)
})
}
func (gui *Gui) pullWithLock(opts PullFilesOptions) error {
@ -804,10 +802,7 @@ type pushOpts struct {
}
func (gui *Gui) push(opts pushOpts) error {
if err := gui.createLoaderPanel(gui.Tr.PushWait); err != nil {
return err
}
go utils.Safe(func() {
return gui.PopupHandler.WithLoaderPanel(gui.Tr.PushWait, func() error {
gui.logAction(gui.Tr.Actions.Push)
err := gui.Git.Sync.Push(git_commands.PushOpts{
Force: opts.force,
@ -816,28 +811,29 @@ func (gui *Gui) push(opts pushOpts) error {
SetUpstream: opts.setUpstream,
})
if err != nil && !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
_ = gui.createErrorPanel(gui.Tr.UpdatesRejectedAndForcePushDisabled)
return
}
_ = gui.ask(askOpts{
title: gui.Tr.ForcePush,
prompt: gui.Tr.ForcePushPrompt,
handleConfirm: func() error {
newOpts := opts
newOpts.force = true
if err != nil {
if !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
_ = gui.PopupHandler.ErrorMsg(gui.Tr.UpdatesRejectedAndForcePushDisabled)
return nil
}
_ = gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.ForcePush,
Prompt: gui.Tr.ForcePushPrompt,
HandleConfirm: func() error {
newOpts := opts
newOpts.force = true
return gui.push(newOpts)
},
})
return
return gui.push(newOpts)
},
})
return nil
}
_ = gui.PopupHandler.Error(err)
}
gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
})
return nil
}
func (gui *Gui) pushFiles() error {
@ -870,11 +866,11 @@ func (gui *Gui) pushFiles() error {
if gui.Git.Config.GetPushToCurrent() {
return gui.push(pushOpts{setUpstream: true})
} else {
return gui.prompt(promptOpts{
title: gui.Tr.EnterUpstream,
initialContent: suggestedRemote + " " + currentBranch.Name,
findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
handleConfirm: func(upstream string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.EnterUpstream,
InitialContent: suggestedRemote + " " + currentBranch.Name,
FindSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
HandleConfirm: func(upstream string) error {
var upstreamBranch, upstreamRemote string
split := strings.Split(upstream, " ")
if len(split) == 2 {
@ -914,13 +910,13 @@ func getSuggestedRemote(remotes []*models.Remote) string {
func (gui *Gui) requestToForcePush(opts pushOpts) error {
forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
return gui.createErrorPanel(gui.Tr.ForcePushDisabled)
return gui.PopupHandler.ErrorMsg(gui.Tr.ForcePushDisabled)
}
return gui.ask(askOpts{
title: gui.Tr.ForcePush,
prompt: gui.Tr.ForcePushPrompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.ForcePush,
Prompt: gui.Tr.ForcePushPrompt,
HandleConfirm: func() error {
return gui.push(opts)
},
})
@ -950,16 +946,16 @@ func (gui *Gui) switchToMerge() error {
func (gui *Gui) openFile(filename string) error {
gui.logAction(gui.Tr.Actions.OpenFile)
if err := gui.OSCommand.OpenFile(filename); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
}
func (gui *Gui) handleCustomCommand() error {
return gui.prompt(promptOpts{
title: gui.Tr.CustomCommand,
findSuggestionsFunc: gui.getCustomCommandsHistorySuggestionsFunc(),
handleConfirm: func(command string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.CustomCommand,
FindSuggestionsFunc: gui.getCustomCommandsHistorySuggestionsFunc(),
HandleConfirm: func(command string) error {
gui.Config.GetAppState().CustomCommandsHistory = utils.Limit(
utils.Uniq(
append(gui.Config.GetAppState().CustomCommandsHistory, command),
@ -981,24 +977,25 @@ func (gui *Gui) handleCustomCommand() error {
}
func (gui *Gui) handleCreateStashMenu() error {
menuItems := []*menuItem{
{
displayString: gui.Tr.LcStashAllChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.StashAllChanges)
return gui.handleStashSave(gui.Git.Stash.Save)
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.LcStashOptions,
Items: []*popup.MenuItem{
{
DisplayString: gui.Tr.LcStashAllChanges,
OnPress: func() error {
gui.logAction(gui.Tr.Actions.StashAllChanges)
return gui.handleStashSave(gui.Git.Stash.Save)
},
},
{
DisplayString: gui.Tr.LcStashStagedChanges,
OnPress: func() error {
gui.logAction(gui.Tr.Actions.StashStagedChanges)
return gui.handleStashSave(gui.Git.Stash.SaveStagedChanges)
},
},
},
{
displayString: gui.Tr.LcStashStagedChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.StashStagedChanges)
return gui.handleStashSave(gui.Git.Stash.SaveStagedChanges)
},
},
}
return gui.createMenu(gui.Tr.LcStashOptions, menuItems, createMenuOptions{showCancel: true})
})
}
func (gui *Gui) handleStashChanges() error {
@ -1052,10 +1049,10 @@ func (gui *Gui) handleToggleFileTreeView() error {
}
func (gui *Gui) handleOpenMergeTool() error {
return gui.ask(askOpts{
title: gui.Tr.MergeToolTitle,
prompt: gui.Tr.MergeToolPrompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.MergeToolTitle,
Prompt: gui.Tr.MergeToolPrompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.OpenMergeTool)
return gui.runSubprocessWithSuspenseAndRefresh(
gui.Git.WorkingTree.OpenMergeToolCmdObj(),
@ -1063,3 +1060,35 @@ func (gui *Gui) handleOpenMergeTool() error {
},
})
}
func (gui *Gui) resetSubmodule(submodule *models.SubmoduleConfig) error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.LcResettingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.ResetSubmodule)
file := gui.fileForSubmodule(submodule)
if file != nil {
if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return gui.PopupHandler.Error(err)
}
}
if err := gui.Git.Submodule.Stash(submodule); err != nil {
return gui.PopupHandler.Error(err)
}
if err := gui.Git.Submodule.Reset(submodule); err != nil {
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
})
}
func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
for _, file := range gui.State.FileManager.GetAllFiles() {
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
return file
}
}
return nil
}

View File

@ -1,11 +1,16 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) validateNotInFilterMode() (bool, error) {
if gui.State.Modes.Filtering.Active() {
err := gui.ask(askOpts{
title: gui.Tr.MustExitFilterModeTitle,
prompt: gui.Tr.MustExitFilterModePrompt,
handleConfirm: gui.exitFilterMode,
err := gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.MustExitFilterModeTitle,
Prompt: gui.Tr.MustExitFilterModePrompt,
HandleConfirm: gui.exitFilterMode,
})
return false, err
@ -23,7 +28,7 @@ func (gui *Gui) clearFiltering() error {
gui.State.ScreenMode = SCREEN_NORMAL
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{COMMITS}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}})
}
func (gui *Gui) setFiltering(path string) error {
@ -36,7 +41,7 @@ func (gui *Gui) setFiltering(path string) error {
return err
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{COMMITS}, then: func() {
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(0)
}})
}

View File

@ -3,6 +3,8 @@ package gui
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
func (gui *Gui) handleCreateFilteringMenuPanel() error {
@ -20,24 +22,24 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
}
}
menuItems := []*menuItem{}
menuItems := []*popup.MenuItem{}
if fileName != "" {
menuItems = append(menuItems, &menuItem{
displayString: fmt.Sprintf("%s '%s'", gui.Tr.LcFilterBy, fileName),
onPress: func() error {
menuItems = append(menuItems, &popup.MenuItem{
DisplayString: fmt.Sprintf("%s '%s'", gui.Tr.LcFilterBy, fileName),
OnPress: func() error {
return gui.setFiltering(fileName)
},
})
}
menuItems = append(menuItems, &menuItem{
displayString: gui.Tr.LcFilterPathOption,
onPress: func() error {
return gui.prompt(promptOpts{
findSuggestionsFunc: gui.getFilePathSuggestionsFunc(),
title: gui.Tr.EnterFileName,
handleConfirm: func(response string) error {
menuItems = append(menuItems, &popup.MenuItem{
DisplayString: gui.Tr.LcFilterPathOption,
OnPress: func() error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
FindSuggestionsFunc: gui.getFilePathSuggestionsFunc(),
Title: gui.Tr.EnterFileName,
HandleConfirm: func(response string) error {
return gui.setFiltering(strings.TrimSpace(response))
},
})
@ -45,11 +47,11 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
})
if gui.State.Modes.Filtering.Active() {
menuItems = append(menuItems, &menuItem{
displayString: gui.Tr.LcExitFilterMode,
onPress: gui.clearFiltering,
menuItems = append(menuItems, &popup.MenuItem{
DisplayString: gui.Tr.LcExitFilterMode,
OnPress: gui.clearFiltering,
})
}
return gui.createMenu(gui.Tr.FilteringMenuTitle, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.FilteringMenuTitle, Items: menuItems})
}

View File

@ -84,7 +84,7 @@ func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion
// Notably, unlike other suggestion functions we're not showing all the options
// if nothing has been typed because there'll be too much to display efficiently
func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
_ = gui.WithWaitingStatus(gui.Tr.LcLoadingFileSuggestions, func() error {
_ = gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingFileSuggestions, func() error {
trie := patricia.NewTrie()
// load every non-gitignored file in the repo
ignore, err := gitignore.FromGit()

View File

@ -3,6 +3,7 @@ package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -13,16 +14,16 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
}
if !gui.Git.Flow.GitFlowEnabled() {
return gui.createErrorPanel("You need to install git-flow and enable it in this repo to use git-flow features")
return gui.PopupHandler.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
}
startHandler := func(branchType string) func() error {
return func() error {
title := utils.ResolvePlaceholderString(gui.Tr.NewGitFlowBranchPrompt, map[string]string{"branchType": branchType})
return gui.prompt(promptOpts{
title: title,
handleConfirm: func(name string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: title,
HandleConfirm: func(name string) error {
gui.logAction(gui.Tr.Actions.GitFlowStart)
return gui.runSubprocessWithSuspenseAndRefresh(
gui.Git.Flow.StartCmdObj(branchType, name),
@ -32,39 +33,40 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
}
}
menuItems := []*menuItem{
{
// not localising here because it's one to one with the actual git flow commands
displayString: fmt.Sprintf("finish branch '%s'", branch.Name),
onPress: func() error {
return gui.gitFlowFinishBranch(branch.Name)
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: "git flow",
Items: []*popup.MenuItem{
{
// not localising here because it's one to one with the actual git flow commands
DisplayString: fmt.Sprintf("finish branch '%s'", branch.Name),
OnPress: func() error {
return gui.gitFlowFinishBranch(branch.Name)
},
},
{
DisplayString: "start feature",
OnPress: startHandler("feature"),
},
{
DisplayString: "start hotfix",
OnPress: startHandler("hotfix"),
},
{
DisplayString: "start bugfix",
OnPress: startHandler("bugfix"),
},
{
DisplayString: "start release",
OnPress: startHandler("release"),
},
},
{
displayString: "start feature",
onPress: startHandler("feature"),
},
{
displayString: "start hotfix",
onPress: startHandler("hotfix"),
},
{
displayString: "start bugfix",
onPress: startHandler("bugfix"),
},
{
displayString: "start release",
onPress: startHandler("release"),
},
}
return gui.createMenu("git flow", menuItems, createMenuOptions{})
})
}
func (gui *Gui) gitFlowFinishBranch(branchName string) error {
cmdObj, err := gui.Git.Flow.FinishCmdObj(branchName)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.logAction(gui.Tr.Actions.GitFlowFinish)

View File

@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -176,7 +177,7 @@ func (gui *Gui) scrollDownConfirmationPanel() error {
}
func (gui *Gui) handleRefresh() error {
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleMouseDownMain() error {
@ -218,10 +219,10 @@ func (gui *Gui) fetch() (err error) {
err = gui.Git.Sync.Fetch(git_commands.FetchOptions{})
if err != nil && strings.Contains(err.Error(), "exit status 128") {
_ = gui.createErrorPanel(gui.Tr.PassUnameWrong)
_ = gui.PopupHandler.ErrorMsg(gui.Tr.PassUnameWrong)
}
_ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, COMMITS, REMOTES, TAGS}, mode: ASYNC})
_ = gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
return err
}
@ -232,7 +233,7 @@ func (gui *Gui) backgroundFetch() (err error) {
err = gui.Git.Sync.Fetch(git_commands.FetchOptions{Background: true})
_ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, COMMITS, REMOTES, TAGS}, mode: ASYNC})
_ = gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
return err
}
@ -247,7 +248,7 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
gui.logAction(gui.Tr.Actions.CopyToClipboard)
if err := gui.OSCommand.CopyToClipboard(itemId); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
truncatedItemId := utils.TruncateWithEllipsis(strings.Replace(itemId, "\n", " ", -1), 50)

View File

@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// Currently there is a bug where if we switch to a subprocess from within
@ -23,7 +24,7 @@ func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string,
return err
}
}
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
@ -34,7 +35,7 @@ func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string,
}
func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
return gui.WithWaitingStatus(waitingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(waitingStatus, func() error {
cmdObj := gui.OSCommand.Cmd.NewShell(cmdObj.ToString())
cmdObj.AddEnvVars("TERM=dumb")
cmdWriter := gui.getCmdWriter()
@ -46,8 +47,8 @@ func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, on
if _, err := cmd.Stdout.Write([]byte(fmt.Sprintf("%s\n", style.FgRed.Sprint(err.Error())))); err != nil {
gui.Log.Error(err)
}
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.surfaceError(
_ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
return gui.PopupHandler.Error(
fmt.Errorf(
gui.Tr.GitCommandFailed, gui.UserConfig.Keybinding.Universal.ExtrasMenu,
),
@ -60,6 +61,6 @@ func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, on
}
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
})
}

View File

@ -18,6 +18,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/lbl"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
@ -75,11 +76,11 @@ type Gui struct {
OSCommand *oscommands.OSCommand
// this is the state of the GUI for the current repo
State *guiState
State *GuiRepoState
// this is a mapping of repos to gui states, so that we can restore the original
// gui state when returning from a subrepo
RepoStateMap map[Repo]*guiState
RepoStateMap map[Repo]*GuiRepoState
Config config.AppConfigurer
Updater *updates.Updater
statusManager *statusManager
@ -101,7 +102,7 @@ type Gui struct {
// when you enter into a submodule we'll append the superproject's path to this array
// so that you can return to the superproject
RepoPathStack []string
RepoPathStack *utils.StringStack
// this tells us whether our views have been initially set up
ViewsSetup bool
@ -121,10 +122,21 @@ type Gui struct {
suggestionsAsyncHandler *tasks.AsyncHandler
PopupHandler PopupHandler
PopupHandler popup.IPopupHandler
IsNewRepo bool
Controllers Controllers
// flag as to whether or not the diff view should ignore whitespace
IgnoreWhitespaceInDiffView bool
// if this is true, we'll load our commits using `git log --all`
ShowWholeGitGraph bool
RetainOriginalDir bool
PrevLayout PrevLayout
// this is the initial dir we are in upon opening lazygit. We hold onto this
// in case we want to restore it before quitting for users who have set up
// the feature for changing directory upon quit.
@ -134,6 +146,80 @@ type Gui struct {
InitialDir string
}
// we keep track of some stuff from one render to the next to see if certain
// things have changed
type PrevLayout struct {
Information string
MainWidth int
MainHeight int
}
type GuiRepoState struct {
// the file panels (files and commit files) can render as a tree, so we have
// managers for them which handle rendering a flat list of files in tree form
FileTreeViewModel *filetree.FileTreeViewModel
CommitFileTreeViewModel *filetree.CommitFileTreeViewModel
Submodules []*models.SubmoduleConfig
Branches []*models.Branch
Commits []*models.Commit
StashEntries []*models.StashEntry
SubCommits []*models.Commit
Remotes []*models.Remote
RemoteBranches []*models.RemoteBranch
Tags []*models.Tag
// FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path
FilteredReflogCommits []*models.Commit
// ReflogCommits are the ones used by the branches panel to obtain recency values
// if we're not in filtering mode, CommitFiles and FilteredReflogCommits will be
// one and the same
ReflogCommits []*models.Commit
// Suggestions will sometimes appear when typing into a prompt
Suggestions []*types.Suggestion
MenuItems []*popup.MenuItem
BisectInfo *git_commands.BisectInfo
Updating bool
Panels *panelStates
SplitMainPanel bool
MainContext ContextKey // used to keep the main and secondary views' contexts in sync
IsRefreshingFiles bool
Searching searchingState
Ptmx *os.File
StartupStage StartupStage // Allows us to not load everything at once
Modes Modes
ContextManager ContextManager
Contexts ContextTree
ViewContextMap map[string]Context
ViewTabContextMap map[string][]tabContext
// WindowViewNameMap is a mapping of windows to the current view of that window.
// Some views move between windows for example the commitFiles view and when cycling through
// side windows we need to know which view to give focus to for a given window
WindowViewNameMap map[string]string
// tells us whether we've set up our views for the current repo. We'll need to
// do this whenever we switch back and forth between repos to get the views
// back in sync with the repo state
ViewsSetup bool
// for displaying suggestions while typing in a file name
FilesTrie *patricia.Trie
// this is the message of the last failed commit attempt
failedCommitMessage string
// TODO: move these into the gui struct
ScreenMode WindowMaximisation
}
type Controllers struct {
Submodules *controllers.SubmodulesController
}
type listPanelState struct {
SelectedLineIdx int
}
@ -296,75 +382,6 @@ type guiMutexes struct {
SubprocessMutex sync.Mutex
}
type guiState struct {
// the file panels (files and commit files) can render as a tree, so we have
// managers for them which handle rendering a flat list of files in tree form
FileTreeViewModel *filetree.FileTreeViewModel
CommitFileTreeViewModel *filetree.CommitFileTreeViewModel
Submodules []*models.SubmoduleConfig
Branches []*models.Branch
Commits []*models.Commit
StashEntries []*models.StashEntry
// Suggestions will sometimes appear when typing into a prompt
Suggestions []*types.Suggestion
// FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path
FilteredReflogCommits []*models.Commit
// ReflogCommits are the ones used by the branches panel to obtain recency values
// if we're not in filtering mode, CommitFiles and FilteredReflogCommits will be
// one and the same
ReflogCommits []*models.Commit
SubCommits []*models.Commit
Remotes []*models.Remote
RemoteBranches []*models.RemoteBranch
Tags []*models.Tag
MenuItems []*menuItem
BisectInfo *git_commands.BisectInfo
Updating bool
Panels *panelStates
SplitMainPanel bool
MainContext ContextKey // used to keep the main and secondary views' contexts in sync
RetainOriginalDir bool
IsRefreshingFiles bool
Searching searchingState
// if this is true, we'll load our commits using `git log --all`
ShowWholeGitGraph bool
ScreenMode WindowMaximisation
Ptmx *os.File
PrevMainWidth int
PrevMainHeight int
OldInformation string
StartupStage StartupStage // Allows us to not load everything at once
Modes Modes
ContextManager ContextManager
Contexts ContextTree
ViewContextMap map[string]Context
ViewTabContextMap map[string][]tabContext
// WindowViewNameMap is a mapping of windows to the current view of that window.
// Some views move between windows for example the commitFiles view and when cycling through
// side windows we need to know which view to give focus to for a given window
WindowViewNameMap map[string]string
// tells us whether we've set up our views for the current repo. We'll need to
// do this whenever we switch back and forth between repos to get the views
// back in sync with the repo state
ViewsSetup bool
// flag as to whether or not the diff view should ignore whitespace
IgnoreWhitespaceInDiffView bool
// for displaying suggestions while typing in a file name
FilesTrie *patricia.Trie
// this is the message of the last failed commit attempt
failedCommitMessage string
}
// reuseState determines if we pull the repo state from our repo state map or
// just re-initialize it. For now we're only re-using state when we're going
// in and out of submodules, for the sake of having the cursor back on the submodule
@ -400,14 +417,13 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
initialContext = contexts.BranchCommits
}
gui.State = &guiState{
gui.State = &GuiRepoState{
FileTreeViewModel: filetree.NewFileTreeViewModel(make([]*models.File, 0), gui.Log, showTree),
CommitFileTreeViewModel: filetree.NewCommitFileTreeViewModel(make([]*models.CommitFile, 0), gui.Log, showTree),
Commits: make([]*models.Commit, 0),
FilteredReflogCommits: make([]*models.Commit, 0),
ReflogCommits: make([]*models.Commit, 0),
StashEntries: make([]*models.StashEntry, 0),
BisectInfo: gui.Git.Bisect.GetInfo(),
Panels: &panelStates{
// TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now
Files: &filePanelState{listPanelState{SelectedLineIdx: -1}},
@ -446,6 +462,21 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
gui.RepoStateMap[Repo(currentDir)] = gui.State
}
type guiCommon struct {
gui *Gui
popup.IPopupHandler
}
var _ controllers.IGuiCommon = &guiCommon{}
func (self *guiCommon) LogAction(msg string) {
self.gui.logAction(msg)
}
func (self *guiCommon) Refresh(opts types.RefreshOptions) error {
return self.gui.refreshSidePanels(opts)
}
// for now the split view will always be on
// NewGui builds a new gui handler
func NewGui(
@ -464,8 +495,8 @@ func NewGui(
statusManager: &statusManager{},
viewBufferManagerMap: map[string]*tasks.ViewBufferManager{},
showRecentRepos: showRecentRepos,
RepoPathStack: []string{},
RepoStateMap: map[Repo]*guiState{},
RepoPathStack: &utils.StringStack{},
RepoStateMap: map[Repo]*GuiRepoState{},
CmdLog: []string{},
suggestionsAsyncHandler: tasks.NewAsyncHandler(),
@ -501,11 +532,31 @@ func NewGui(
gui.watchFilesForChanges()
gui.PopupHandler = &RealPopupHandler{gui: gui}
gui.PopupHandler = popup.NewPopupHandler(
cmn,
gui.createPopupPanel,
func() error { return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}) },
func() error { return gui.closeConfirmationPrompt(false) },
gui.createMenu,
gui.withWaitingStatus,
)
authors.SetCustomAuthors(gui.UserConfig.Gui.AuthorColors)
presentation.SetCustomBranches(gui.UserConfig.Gui.BranchColors)
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
controllerCommon := &controllers.ControllerCommon{IGuiCommon: guiCommon, Common: cmn}
gui.Controllers = Controllers{
Submodules: controllers.NewSubmodulesController(
controllerCommon,
gui.enterSubmodule,
gui.Git,
gui.State.Submodules,
gui.getSelectedSubmodule,
),
}
return gui, nil
}
@ -601,7 +652,7 @@ func (gui *Gui) RunAndHandleError() error {
switch err {
case gocui.ErrQuit:
if gui.State.RetainOriginalDir {
if gui.RetainOriginalDir {
if err := gui.recordDirectory(gui.InitialDir); err != nil {
return err
}
@ -633,7 +684,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess oscommands.ICmdOb
return err
}
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
@ -654,7 +705,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
}
if err := gui.g.Suspend(); err != nil {
return false, gui.surfaceError(err)
return false, gui.PopupHandler.Error(err)
}
gui.PauseBackgroundThreads = true
@ -668,7 +719,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
gui.PauseBackgroundThreads = false
if cmdErr != nil {
return false, gui.surfaceError(cmdErr)
return false, gui.PopupHandler.Error(cmdErr)
}
return true, nil
@ -703,7 +754,7 @@ func (gui *Gui) loadNewRepo() error {
return err
}
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
@ -723,7 +774,7 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
task := task
go utils.Safe(func() {
if err := task(done); err != nil {
_ = gui.surfaceError(err)
_ = gui.PopupHandler.Error(err)
}
})
@ -740,11 +791,11 @@ func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
return gui.Config.SaveAppState()
}
return gui.ask(askOpts{
title: "",
prompt: gui.Tr.IntroPopupMessage,
handleConfirm: onConfirm,
handleClose: onConfirm,
return gui.PopupHandler.Ask(popup.AskOpts{
Title: "",
Prompt: gui.Tr.IntroPopupMessage,
HandleConfirm: onConfirm,
HandleClose: onConfirm,
})
}
@ -775,9 +826,9 @@ func (gui *Gui) startBackgroundFetch() {
}
err := gui.backgroundFetch()
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
_ = gui.ask(askOpts{
title: gui.Tr.NoAutomaticGitFetchTitle,
prompt: gui.Tr.NoAutomaticGitFetchBody,
_ = gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.NoAutomaticGitFetchTitle,
Prompt: gui.Tr.NoAutomaticGitFetchBody,
})
} else {
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {

View File

@ -9,28 +9,9 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
// is only handled if the given view has focus, or handled globally if the view
// is ""
type Binding struct {
ViewName string
Contexts []string
Handler func() error
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
Modifier gocui.Modifier
Description string
Alternative string
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
OpensMenu bool
}
// GetDisplayStrings returns the display string of a file
func (b *Binding) GetDisplayStrings(isFocused bool) []string {
return []string{GetKeyDisplay(b.Key), b.Description}
}
var keyMapReversed = map[gocui.Key]string{
gocui.KeyF1: "f1",
gocui.KeyF2: "f2",
@ -203,10 +184,10 @@ func (gui *Gui) getKey(key string) interface{} {
}
// GetInitialKeybindings is a function.
func (gui *Gui) GetInitialKeybindings() []*Binding {
func (gui *Gui) GetInitialKeybindings() []*types.Binding {
config := gui.UserConfig.Keybinding
bindings := []*Binding{
bindings := []*types.Binding{
{
ViewName: "",
Key: gui.getKey(config.Universal.Quit),
@ -1713,57 +1694,6 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleCopySelectedSideContextItemToClipboard,
Description: gui.Tr.LcCopySubmoduleNameToClipboard,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.forSubmodule(gui.handleSubmoduleEnter),
Description: gui.Tr.LcEnterSubmodule,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Remove),
Handler: gui.forSubmodule(gui.removeSubmodule),
Description: gui.Tr.LcRemoveSubmodule,
OpensMenu: true,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Submodules.Update),
Handler: gui.forSubmodule(gui.handleUpdateSubmodule),
Description: gui.Tr.LcSubmoduleUpdate,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleAddSubmodule,
Description: gui.Tr.LcAddSubmodule,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Edit),
Handler: gui.forSubmodule(gui.handleEditSubmoduleUrl),
Description: gui.Tr.LcEditSubmoduleUrl,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Submodules.Init),
Handler: gui.forSubmodule(gui.handleSubmoduleInit),
Description: gui.Tr.LcInitSubmodule,
},
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Submodules.BulkMenu),
Handler: gui.handleBulkSubmoduleActionsMenu,
Description: gui.Tr.LcViewBulkSubmoduleOptions,
OpensMenu: true,
},
{
ViewName: "files",
Contexts: []string{string(FILES_CONTEXT_KEY)},
@ -1841,8 +1771,28 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
},
}
type ContextKeybindings struct {
contextKey ContextKey
viewName string
bindings []*types.Binding
}
for _, contextKeybindings := range []ContextKeybindings{
{
contextKey: SUBMODULES_CONTEXT_KEY,
viewName: "files",
bindings: gui.Controllers.Submodules.Keybindings(gui.getKey, config),
},
} {
for _, binding := range contextKeybindings.bindings {
binding.Contexts = []string{string(contextKeybindings.contextKey)}
binding.ViewName = contextKeybindings.viewName
bindings = append(bindings, binding)
}
}
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
bindings = append(bindings, []*Binding{
bindings = append(bindings, []*types.Binding{
{ViewName: viewName, Key: gui.getKey(config.Universal.PrevBlock), Modifier: gocui.ModNone, Handler: gui.previousSideWindow},
{ViewName: viewName, Key: gui.getKey(config.Universal.NextBlock), Modifier: gocui.ModNone, Handler: gui.nextSideWindow},
{ViewName: viewName, Key: gui.getKey(config.Universal.PrevBlockAlt), Modifier: gocui.ModNone, Handler: gui.previousSideWindow},
@ -1859,7 +1809,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
log.Fatal("Jump to block keybindings cannot be set. Exactly 5 keybindings must be supplied.")
} else {
for i, window := range windows {
bindings = append(bindings, &Binding{
bindings = append(bindings, &types.Binding{
ViewName: "",
Key: gui.getKey(config.Universal.JumpToBlock[i]),
Modifier: gocui.ModNone,
@ -1868,7 +1818,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}
for viewName := range gui.State.Contexts.initialViewTabContextMap() {
bindings = append(bindings, []*Binding{
bindings = append(bindings, []*types.Binding{
{
ViewName: viewName,
Key: gui.getKey(config.Universal.NextTab),

View File

@ -234,9 +234,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
// if the commit files view is the view to be displayed for its window, we'll display it
gui.Views.CommitFiles.Visible = gui.getViewNameForWindow(gui.State.Contexts.CommitFiles.GetWindowName()) == "commitFiles"
if gui.State.OldInformation != informationStr {
if gui.PrevLayout.Information != informationStr {
gui.setViewContent(gui.Views.Information, informationStr)
gui.State.OldInformation = informationStr
gui.PrevLayout.Information = informationStr
}
if !gui.ViewsSetup {
@ -277,9 +277,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
gui.Views.Main.SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo))
mainViewWidth, mainViewHeight := gui.Views.Main.Size()
if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight {
gui.State.PrevMainWidth = mainViewWidth
gui.State.PrevMainHeight = mainViewHeight
if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight {
gui.PrevLayout.MainWidth = mainViewWidth
gui.PrevLayout.MainHeight = mainViewHeight
if err := gui.onResize(); err != nil {
return err
}

View File

@ -89,7 +89,7 @@ func (gui *Gui) copySelectedToClipboard() error {
gui.logAction(gui.Tr.Actions.CopySelectedTextToClipboard)
if err := gui.OSCommand.CopyToClipboard(selected); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil

View File

@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) menuListContext() IListContext {
@ -391,15 +392,15 @@ func (gui *Gui) getListContexts() []IListContext {
}
}
func (gui *Gui) getListContextKeyBindings() []*Binding {
bindings := make([]*Binding, 0)
func (gui *Gui) getListContextKeyBindings() []*types.Binding {
bindings := make([]*types.Binding, 0)
keybindingConfig := gui.UserConfig.Keybinding
for _, listContext := range gui.getListContexts() {
listContext := listContext
bindings = append(bindings, []*Binding{
bindings = append(bindings, []*types.Binding{
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.PrevItem), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
@ -423,7 +424,7 @@ func (gui *Gui) getListContextKeyBindings() []*Binding {
gotoBottomHandler = gui.handleGotoBottomForCommitsPanel
}
bindings = append(bindings, []*Binding{
bindings = append(bindings, []*types.Binding{
{
ViewName: listContext.GetViewName(),
Contexts: []string{string(listContext.GetKey())},

View File

@ -3,31 +3,12 @@ package gui
import (
"errors"
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type menuItem struct {
displayString string
displayStrings []string
onPress func() error
// only applies when displayString is used
opensMenu bool
}
// every item in a list context needs an ID
func (i *menuItem) ID() string {
if i.displayString != "" {
return i.displayString
}
return strings.Join(i.displayStrings, "-")
}
// specific functions
func (gui *Gui) getMenuOptions() map[string]string {
keybindingConfig := gui.UserConfig.Keybinding
@ -42,37 +23,34 @@ func (gui *Gui) handleMenuClose() error {
return gui.returnFromContext()
}
type createMenuOptions struct {
showCancel bool
}
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
if createMenuOptions.showCancel {
// note: items option is mutated by this function
func (gui *Gui) createMenu(opts popup.CreateMenuOptions) error {
if !opts.HideCancel {
// this is mutative but I'm okay with that for now
items = append(items, &menuItem{
displayStrings: []string{gui.Tr.LcCancel},
onPress: func() error {
opts.Items = append(opts.Items, &popup.MenuItem{
DisplayStrings: []string{gui.Tr.LcCancel},
OnPress: func() error {
return nil
},
})
}
gui.State.MenuItems = items
gui.State.MenuItems = opts.Items
stringArrays := make([][]string, len(items))
for i, item := range items {
if item.opensMenu && item.displayStrings != nil {
stringArrays := make([][]string, len(opts.Items))
for i, item := range opts.Items {
if item.OpensMenu && item.DisplayStrings != nil {
return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user")
}
if item.displayStrings == nil {
styledStr := item.displayString
if item.opensMenu {
if item.DisplayStrings == nil {
styledStr := item.DisplayString
if item.OpensMenu {
styledStr = opensMenuStyle(styledStr)
}
stringArrays[i] = []string{styledStr}
} else {
stringArrays[i] = item.displayStrings
stringArrays[i] = item.DisplayStrings
}
}
@ -80,7 +58,7 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(false, list)
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
menuView.Title = title
menuView.Title = opts.Title
menuView.FgColor = theme.GocuiDefaultTextColor
menuView.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error {
return nil
@ -97,7 +75,7 @@ func (gui *Gui) onMenuPress() error {
return err
}
if err := gui.State.MenuItems[selectedLine].onPress(); err != nil {
if err := gui.State.MenuItems[selectedLine].OnPress(); err != nil {
return err
}

View File

@ -9,6 +9,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) handleSelectPrevConflictHunk() error {
@ -189,7 +190,7 @@ func (gui *Gui) getMergingOptions() map[string]string {
}
func (gui *Gui) handleEscapeMerge() error {
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
@ -199,7 +200,7 @@ func (gui *Gui) handleEscapeMerge() error {
func (gui *Gui) onLastConflictResolved() error {
// as part of refreshing files, we handle the situation where a file has had
// its merge conflicts resolved.
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{mode: types.ASYNC, scope: []types.RefreshableView{types.FILES}})
}
func (gui *Gui) resetMergeState() {

View File

@ -4,13 +4,15 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
func (gui *Gui) getBindings(v *gocui.View) []*types.Binding {
var (
bindingsGlobal, bindingsPanel []*Binding
bindingsGlobal, bindingsPanel []*types.Binding
)
bindings := append(gui.GetCustomCommandKeybindings(), gui.GetInitialKeybindings()...)
@ -30,11 +32,11 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
// append dummy element to have a separator between
// panel and global keybindings
bindingsPanel = append(bindingsPanel, &Binding{})
bindingsPanel = append(bindingsPanel, &types.Binding{})
return append(bindingsPanel, bindingsGlobal...)
}
func (gui *Gui) displayDescription(binding *Binding) string {
func (gui *Gui) displayDescription(binding *types.Binding) string {
if binding.OpensMenu {
return opensMenuStyle(binding.Description)
}
@ -54,13 +56,13 @@ func (gui *Gui) handleCreateOptionsMenu() error {
bindings := gui.getBindings(view)
menuItems := make([]*menuItem, len(bindings))
menuItems := make([]*popup.MenuItem, len(bindings))
for i, binding := range bindings {
binding := binding // note to self, never close over loop variables
menuItems[i] = &menuItem{
displayStrings: []string{GetKeyDisplay(binding.Key), gui.displayDescription(binding)},
onPress: func() error {
menuItems[i] = &popup.MenuItem{
DisplayStrings: []string{GetKeyDisplay(binding.Key), gui.displayDescription(binding)},
OnPress: func() error {
if binding.Key == nil {
return nil
}
@ -72,5 +74,9 @@ func (gui *Gui) handleCreateOptionsMenu() error {
}
}
return gui.createMenu(strings.Title(gui.Tr.LcMenu), menuItems, createMenuOptions{})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: strings.Title(gui.Tr.LcMenu),
Items: menuItems,
HideCancel: true,
})
}

View File

@ -4,41 +4,43 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) handleCreatePatchOptionsMenu() error {
if !gui.Git.Patch.PatchManager.Active() {
return gui.createErrorPanel(gui.Tr.NoPatchError)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoPatchError)
}
menuItems := []*menuItem{
menuItems := []*popup.MenuItem{
{
displayString: "reset patch",
onPress: gui.handleResetPatch,
DisplayString: "reset patch",
OnPress: gui.handleResetPatch,
},
{
displayString: "apply patch",
onPress: func() error { return gui.handleApplyPatch(false) },
DisplayString: "apply patch",
OnPress: func() error { return gui.handleApplyPatch(false) },
},
{
displayString: "apply patch in reverse",
onPress: func() error { return gui.handleApplyPatch(true) },
DisplayString: "apply patch in reverse",
OnPress: func() error { return gui.handleApplyPatch(true) },
},
}
if gui.Git.Patch.PatchManager.CanRebase && gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
menuItems = append(menuItems, []*menuItem{
menuItems = append(menuItems, []*popup.MenuItem{
{
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.Git.Patch.PatchManager.To),
onPress: gui.handleDeletePatchFromCommit,
DisplayString: fmt.Sprintf("remove patch from original commit (%s)", gui.Git.Patch.PatchManager.To),
OnPress: gui.handleDeletePatchFromCommit,
},
{
displayString: "move patch out into index",
onPress: gui.handleMovePatchIntoWorkingTree,
DisplayString: "move patch out into index",
OnPress: gui.handleMovePatchIntoWorkingTree,
},
{
displayString: "move patch into new commit",
onPress: gui.handlePullPatchIntoNewCommit,
DisplayString: "move patch into new commit",
OnPress: gui.handlePullPatchIntoNewCommit,
},
}...)
@ -49,10 +51,10 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
menuItems = append(
menuItems[:1],
append(
[]*menuItem{
[]*popup.MenuItem{
{
displayString: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
onPress: gui.handleMovePatchToSelectedCommit,
DisplayString: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
OnPress: gui.handleMovePatchToSelectedCommit,
},
}, menuItems[1:]...,
)...,
@ -61,7 +63,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
}
}
return gui.createMenu(gui.Tr.PatchOptionsTitle, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.PatchOptionsTitle, Items: menuItems})
}
func (gui *Gui) getPatchCommitIndex() int {
@ -75,7 +77,7 @@ func (gui *Gui) getPatchCommitIndex() int {
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
return false, gui.createErrorPanel(gui.Tr.CantPatchWhileRebasingError)
return false, gui.PopupHandler.ErrorMsg(gui.Tr.CantPatchWhileRebasingError)
}
return true, nil
}
@ -96,7 +98,7 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
return err
}
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.RemovePatchFromCommit)
err := gui.Git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
@ -113,7 +115,7 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
return err
}
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.MovePatchToSelectedCommit)
err := gui.Git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
@ -131,7 +133,7 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
}
pull := func(stash bool) error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.MovePatchIntoIndex)
err := gui.Git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
@ -140,10 +142,10 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
}
if len(gui.trackedFiles()) > 0 {
return gui.ask(askOpts{
title: gui.Tr.MustStashTitle,
prompt: gui.Tr.MustStashWarning,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.MustStashTitle,
Prompt: gui.Tr.MustStashWarning,
HandleConfirm: func() error {
return pull(true)
},
})
@ -161,7 +163,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
return err
}
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.MovePatchIntoNewCommit)
err := gui.Git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
@ -180,9 +182,9 @@ func (gui *Gui) handleApplyPatch(reverse bool) error {
}
gui.logAction(action)
if err := gui.Git.Patch.PatchManager.ApplyPatches(reverse); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleResetPatch() error {

View File

@ -0,0 +1,223 @@
package popup
import (
"strings"
"sync"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type IPopupHandler interface {
ErrorMsg(message string) error
Error(err error) error
Ask(opts AskOpts) error
Prompt(opts PromptOpts) error
WithLoaderPanel(message string, f func() error) error
WithWaitingStatus(message string, f func() error) error
Menu(opts CreateMenuOptions) error
}
type CreateMenuOptions struct {
Title string
Items []*MenuItem
HideCancel bool
}
type CreatePopupPanelOpts struct {
HasLoader bool
Editable bool
Title string
Prompt string
HandleConfirm func() error
HandleConfirmPrompt func(string) error
HandleClose func() error
// when HandlersManageFocus is true, do not return from the confirmation context automatically. It's expected that the handlers will manage focus, whether that means switching to another context, or manually returning the context.
HandlersManageFocus bool
FindSuggestionsFunc func(string) []*types.Suggestion
}
type AskOpts struct {
Title string
Prompt string
HandleConfirm func() error
HandleClose func() error
HandlersManageFocus bool
}
type PromptOpts struct {
Title string
InitialContent string
FindSuggestionsFunc func(string) []*types.Suggestion
HandleConfirm func(string) error
}
type MenuItem struct {
DisplayString string
DisplayStrings []string
OnPress func() error
// only applies when displayString is used
OpensMenu bool
}
type RealPopupHandler struct {
*common.Common
index int
sync.Mutex
createPopupPanelFn func(CreatePopupPanelOpts) error
onErrorFn func() error
closePopupFn func() error
createMenuFn func(CreateMenuOptions) error
withWaitingStatusFn func(message string, f func() error) error
}
var _ IPopupHandler = &RealPopupHandler{}
func NewPopupHandler(
common *common.Common,
createPopupPanelFn func(CreatePopupPanelOpts) error,
onErrorFn func() error,
closePopupFn func() error,
createMenuFn func(CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func() error) error,
) *RealPopupHandler {
return &RealPopupHandler{
Common: common,
index: 0,
createPopupPanelFn: createPopupPanelFn,
onErrorFn: onErrorFn,
closePopupFn: closePopupFn,
createMenuFn: createMenuFn,
withWaitingStatusFn: withWaitingStatusFn,
}
}
func (self *RealPopupHandler) Menu(opts CreateMenuOptions) error {
return self.createMenuFn(opts)
}
func (self *RealPopupHandler) WithWaitingStatus(message string, f func() error) error {
return self.withWaitingStatusFn(message, f)
}
func (self *RealPopupHandler) Error(err error) error {
if err == gocui.ErrQuit {
return err
}
return self.ErrorMsg(err.Error())
}
func (self *RealPopupHandler) ErrorMsg(message string) error {
self.Lock()
self.index++
self.Unlock()
coloredMessage := style.FgRed.Sprint(strings.TrimSpace(message))
if err := self.onErrorFn(); err != nil {
return err
}
return self.Ask(AskOpts{
Title: self.Tr.Error,
Prompt: coloredMessage,
})
}
func (self *RealPopupHandler) Ask(opts AskOpts) error {
self.Lock()
self.index++
self.Unlock()
return self.createPopupPanelFn(CreatePopupPanelOpts{
Title: opts.Title,
Prompt: opts.Prompt,
HandleConfirm: opts.HandleConfirm,
HandleClose: opts.HandleClose,
HandlersManageFocus: opts.HandlersManageFocus,
})
}
func (self *RealPopupHandler) Prompt(opts PromptOpts) error {
self.Lock()
self.index++
self.Unlock()
return self.createPopupPanelFn(CreatePopupPanelOpts{
Title: opts.Title,
Prompt: opts.InitialContent,
Editable: true,
HandleConfirmPrompt: opts.HandleConfirm,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
})
}
func (self *RealPopupHandler) WithLoaderPanel(message string, f func() error) error {
index := 0
self.Lock()
self.index++
index = self.index
self.Unlock()
err := self.createPopupPanelFn(CreatePopupPanelOpts{
Prompt: message,
HasLoader: true,
})
if err != nil {
self.Log.Error(err)
return nil
}
go utils.Safe(func() {
if err := f(); err != nil {
self.Log.Error(err)
}
self.Lock()
if index == self.index {
_ = self.closePopupFn()
}
self.Unlock()
})
return nil
}
type TestPopupHandler struct {
OnErrorMsg func(message string) error
OnAsk func(opts AskOpts) error
OnPrompt func(opts PromptOpts) error
}
func (self *TestPopupHandler) Error(err error) error {
return self.ErrorMsg(err.Error())
}
func (self *TestPopupHandler) ErrorMsg(message string) error {
return self.OnErrorMsg(message)
}
func (self *TestPopupHandler) Ask(opts AskOpts) error {
return self.OnAsk(opts)
}
func (self *TestPopupHandler) Prompt(opts PromptOpts) error {
return self.OnPrompt(opts)
}
func (self *TestPopupHandler) WithLoaderPanel(message string, f func() error) error {
return f()
}
func (self *TestPopupHandler) WithWaitingStatus(message string, f func() error) error {
return f()
}
func (self *TestPopupHandler) Menu(opts CreateMenuOptions) error {
panic("not yet implemented")
}

View File

@ -1,87 +0,0 @@
package gui
import (
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
)
type PopupHandler interface {
Error(message string) error
Ask(opts askOpts) error
Prompt(opts promptOpts) error
Loader(message string) error
}
type RealPopupHandler struct {
gui *Gui
}
func (self *RealPopupHandler) Error(message string) error {
gui := self.gui
coloredMessage := style.FgRed.Sprint(strings.TrimSpace(message))
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
return err
}
return self.Ask(askOpts{
title: gui.Tr.Error,
prompt: coloredMessage,
})
}
func (self *RealPopupHandler) Ask(opts askOpts) error {
gui := self.gui
return gui.createPopupPanel(createPopupPanelOpts{
title: opts.title,
prompt: opts.prompt,
handleConfirm: opts.handleConfirm,
handleClose: opts.handleClose,
handlersManageFocus: opts.handlersManageFocus,
})
}
func (self *RealPopupHandler) Prompt(opts promptOpts) error {
gui := self.gui
return gui.createPopupPanel(createPopupPanelOpts{
title: opts.title,
prompt: opts.initialContent,
editable: true,
handleConfirmPrompt: opts.handleConfirm,
findSuggestionsFunc: opts.findSuggestionsFunc,
})
}
func (self *RealPopupHandler) Loader(message string) error {
gui := self.gui
return gui.createPopupPanel(createPopupPanelOpts{
prompt: message,
hasLoader: true,
})
}
type TestPopupHandler struct {
onError func(message string) error
onAsk func(opts askOpts) error
onPrompt func(opts promptOpts) error
}
func (self *TestPopupHandler) Error(message string) error {
return self.onError(message)
}
func (self *TestPopupHandler) Ask(opts askOpts) error {
return self.onAsk(opts)
}
func (self *TestPopupHandler) Prompt(opts promptOpts) error {
return self.onPrompt(opts)
}
func (self *TestPopupHandler) Loader(message string) error {
return nil
}

View File

@ -5,30 +5,31 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
menuItems := make([]*menuItem, 0, 4)
menuItems := make([]*popup.MenuItem, 0, 4)
fromToDisplayStrings := func(from string, to string) []string {
return []string{fmt.Sprintf("%s → %s", from, to)}
}
menuItemsForBranch := func(branch *models.Branch) []*menuItem {
return []*menuItem{
menuItemsForBranch := func(branch *models.Branch) []*popup.MenuItem {
return []*popup.MenuItem{
{
displayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcDefaultBranch),
onPress: func() error {
DisplayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcDefaultBranch),
OnPress: func() error {
return gui.createPullRequest(branch.Name, "")
},
},
{
displayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcSelectBranch),
onPress: func() error {
return gui.prompt(promptOpts{
title: branch.Name + " →",
findSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
handleConfirm: func(targetBranchName string) error {
DisplayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcSelectBranch),
OnPress: func() error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: branch.Name + " →",
FindSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
HandleConfirm: func(targetBranchName string) error {
return gui.createPullRequest(branch.Name, targetBranchName)
}},
)
@ -39,9 +40,9 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
if selectedBranch != checkedOutBranch {
menuItems = append(menuItems,
&menuItem{
displayStrings: fromToDisplayStrings(checkedOutBranch.Name, selectedBranch.Name),
onPress: func() error {
&popup.MenuItem{
DisplayStrings: fromToDisplayStrings(checkedOutBranch.Name, selectedBranch.Name),
OnPress: func() error {
return gui.createPullRequest(checkedOutBranch.Name, selectedBranch.Name)
},
},
@ -51,20 +52,20 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
menuItems = append(menuItems, menuItemsForBranch(selectedBranch)...)
return gui.createMenu(fmt.Sprintf(gui.Tr.CreatePullRequestOptions), menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: fmt.Sprintf(gui.Tr.CreatePullRequestOptions), Items: menuItems})
}
func (gui *Gui) createPullRequest(from string, to string) error {
hostingServiceMgr := gui.getHostingServiceMgr()
url, err := hostingServiceMgr.GetPullRequestURL(from, to)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.logAction(gui.Tr.Actions.OpenPullRequest)
if err := gui.OSCommand.OpenLink(url); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil

View File

@ -4,6 +4,7 @@ import (
"os"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined
@ -28,12 +29,12 @@ func (gui *Gui) recordDirectory(dirName string) error {
}
func (gui *Gui) handleQuitWithoutChangingDirectory() error {
gui.State.RetainOriginalDir = true
gui.RetainOriginalDir = true
return gui.quit()
}
func (gui *Gui) handleQuit() error {
gui.State.RetainOriginalDir = false
gui.RetainOriginalDir = false
return gui.quit()
}
@ -53,12 +54,8 @@ func (gui *Gui) handleTopLevelReturn() error {
}
repoPathStack := gui.RepoPathStack
if len(repoPathStack) > 0 {
n := len(repoPathStack) - 1
path := repoPathStack[n]
gui.RepoPathStack = repoPathStack[:n]
if !repoPathStack.IsEmpty() {
path := repoPathStack.Pop()
return gui.dispatchSwitchToRepo(path, true)
}
@ -76,10 +73,10 @@ func (gui *Gui) quit() error {
}
if gui.UserConfig.ConfirmOnQuit {
return gui.ask(askOpts{
title: "",
prompt: gui.Tr.ConfirmQuit,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: "",
Prompt: gui.Tr.ConfirmQuit,
HandleConfirm: func() error {
return gocui.ErrQuit
},
})

View File

@ -5,6 +5,8 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type RebaseOption string
@ -22,13 +24,13 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error {
options = append(options, REBASE_OPTION_SKIP)
}
menuItems := make([]*menuItem, len(options))
menuItems := make([]*popup.MenuItem, len(options))
for i, option := range options {
// note to self. Never, EVER, close over loop variables in a function
option := option
menuItems[i] = &menuItem{
displayString: option,
onPress: func() error {
menuItems[i] = &popup.MenuItem{
DisplayString: option,
OnPress: func() error {
return gui.genericMergeCommand(option)
},
}
@ -41,14 +43,14 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error {
title = gui.Tr.RebaseOptionsTitle
}
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) genericMergeCommand(command string) error {
status := gui.Git.Status.WorkingTreeState()
if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
return gui.createErrorPanel(gui.Tr.NotMergingOrRebasing)
return gui.PopupHandler.ErrorMsg(gui.Tr.NotMergingOrRebasing)
}
gui.logAction(fmt.Sprintf("Merge/Rebase: %s", command))
@ -97,7 +99,7 @@ func isMergeConflictErr(errStr string) bool {
}
func (gui *Gui) handleGenericMergeCommandResult(result error) error {
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
if result == nil {
@ -110,14 +112,14 @@ func (gui *Gui) handleGenericMergeCommandResult(result error) error {
// assume in this case that we're already done
return nil
} else if isMergeConflictErr(result.Error()) {
return gui.ask(askOpts{
title: gui.Tr.FoundConflictsTitle,
prompt: gui.Tr.FoundConflicts,
handlersManageFocus: true,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.FoundConflictsTitle,
Prompt: gui.Tr.FoundConflicts,
HandlersManageFocus: true,
HandleConfirm: func() error {
return gui.pushContext(gui.State.Contexts.Files)
},
handleClose: func() error {
HandleClose: func() error {
if err := gui.returnFromContext(); err != nil {
return err
}
@ -126,17 +128,17 @@ func (gui *Gui) handleGenericMergeCommandResult(result error) error {
},
})
} else {
return gui.createErrorPanel(result.Error())
return gui.PopupHandler.ErrorMsg(result.Error())
}
}
func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
// prompt user to confirm that they want to abort, then do it
mode := gui.workingTreeStateNoun()
return gui.ask(askOpts{
title: fmt.Sprintf(gui.Tr.AbortTitle, mode),
prompt: fmt.Sprintf(gui.Tr.AbortPrompt, mode),
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: fmt.Sprintf(gui.Tr.AbortTitle, mode),
Prompt: fmt.Sprintf(gui.Tr.AbortPrompt, mode),
HandleConfirm: func() error {
return gui.genericMergeCommand(REBASE_OPTION_ABORT)
},
})

View File

@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -16,24 +17,24 @@ func (gui *Gui) handleCreateRecentReposMenu() error {
reposCount := utils.Min(len(recentRepoPaths), 20)
// we won't show the current repo hence the -1
menuItems := make([]*menuItem, reposCount-1)
menuItems := make([]*popup.MenuItem, reposCount-1)
for i, path := range recentRepoPaths[1:reposCount] {
path := path // cos we're closing over the loop variable
menuItems[i] = &menuItem{
displayStrings: []string{
menuItems[i] = &popup.MenuItem{
DisplayStrings: []string{
filepath.Base(path),
style.FgMagenta.Sprint(path),
},
onPress: func() error {
OnPress: func() error {
// if we were in a submodule, we want to forget about that stack of repos
// so that hitting escape in the new repo does nothing
gui.RepoPathStack = []string{}
gui.RepoPathStack.Clear()
return gui.dispatchSwitchToRepo(path, false)
},
}
}
return gui.createMenu(gui.Tr.RecentRepos, menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.RecentRepos, Items: menuItems})
}
func (gui *Gui) handleShowAllBranchLogs() error {
@ -57,7 +58,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
if err := os.Chdir(path); err != nil {
if os.IsNotExist(err) {
return gui.createErrorPanel(gui.Tr.ErrRepositoryMovedOrDeleted)
return gui.PopupHandler.ErrorMsg(gui.Tr.ErrRepositoryMovedOrDeleted)
}
return err
}

View File

@ -2,6 +2,7 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
// list panel functions
@ -55,7 +56,7 @@ func (gui *Gui) refreshReflogCommits() error {
commits, onlyObtainedNewReflogCommits, err := gui.Git.Loaders.ReflogCommits.
GetReflogCommits(lastReflogCommit, filterPath)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if onlyObtainedNewReflogCommits {
@ -87,10 +88,10 @@ func (gui *Gui) handleCheckoutReflogCommit() error {
return nil
}
err := gui.ask(askOpts{
title: gui.Tr.LcCheckoutCommit,
prompt: gui.Tr.SureCheckoutThisCommit,
handleConfirm: func() error {
err := gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.LcCheckoutCommit,
Prompt: gui.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.CheckoutReflogCommit)
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
},

View File

@ -4,6 +4,8 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -52,16 +54,18 @@ func (gui *Gui) handleDeleteRemoteBranch() error {
}
message := fmt.Sprintf("%s '%s'?", gui.Tr.DeleteRemoteBranchMessage, remoteBranch.FullName())
return gui.ask(askOpts{
title: gui.Tr.DeleteRemoteBranch,
prompt: message,
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.DeleteRemoteBranch,
Prompt: message,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
gui.logAction(gui.Tr.Actions.DeleteRemoteBranch)
err := gui.Git.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name)
gui.handleCredentialsPopup(err)
if err != nil {
_ = gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
})
},
})
@ -84,16 +88,16 @@ func (gui *Gui) handleSetBranchUpstream() error {
},
)
return gui.ask(askOpts{
title: gui.Tr.SetUpstreamTitle,
prompt: message,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.SetUpstreamTitle,
Prompt: message,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.SetBranchUpstream)
if err := gui.Git.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
},
})
}

View File

@ -5,7 +5,9 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -42,7 +44,7 @@ func (gui *Gui) refreshRemotes() error {
remotes, err := gui.Git.Loaders.Remotes.GetRemotes()
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.Remotes = remotes
@ -79,17 +81,17 @@ func (gui *Gui) handleRemoteEnter() error {
}
func (gui *Gui) handleAddRemote() error {
return gui.prompt(promptOpts{
title: gui.Tr.LcNewRemoteName,
handleConfirm: func(remoteName string) error {
return gui.prompt(promptOpts{
title: gui.Tr.LcNewRemoteUrl,
handleConfirm: func(remoteUrl string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.LcNewRemoteName,
HandleConfirm: func(remoteName string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: gui.Tr.LcNewRemoteUrl,
HandleConfirm: func(remoteUrl string) error {
gui.logAction(gui.Tr.Actions.AddRemote)
if err := gui.Git.Remote.AddRemote(remoteName, remoteUrl); err != nil {
return err
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{REMOTES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.REMOTES}})
},
})
},
@ -103,16 +105,16 @@ func (gui *Gui) handleRemoveRemote() error {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.LcRemoveRemote,
prompt: gui.Tr.LcRemoveRemotePrompt + " '" + remote.Name + "'?",
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.LcRemoveRemote,
Prompt: gui.Tr.LcRemoveRemotePrompt + " '" + remote.Name + "'?",
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RemoveRemote)
if err := gui.Git.Remote.RemoveRemote(remote.Name); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
},
})
}
@ -130,14 +132,14 @@ func (gui *Gui) handleEditRemote() error {
},
)
return gui.prompt(promptOpts{
title: editNameMessage,
initialContent: remote.Name,
handleConfirm: func(updatedRemoteName string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: editNameMessage,
InitialContent: remote.Name,
HandleConfirm: func(updatedRemoteName string) error {
if updatedRemoteName != remote.Name {
gui.logAction(gui.Tr.Actions.UpdateRemote)
if err := gui.Git.Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
}
@ -154,15 +156,15 @@ func (gui *Gui) handleEditRemote() error {
url = urls[0]
}
return gui.prompt(promptOpts{
title: editUrlMessage,
initialContent: url,
handleConfirm: func(updatedRemoteUrl string) error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: editUrlMessage,
InitialContent: url,
HandleConfirm: func(updatedRemoteUrl string) error {
gui.logAction(gui.Tr.Actions.UpdateRemote)
if err := gui.Git.Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
},
})
},
@ -175,13 +177,15 @@ func (gui *Gui) handleFetchRemote() error {
return nil
}
return gui.WithWaitingStatus(gui.Tr.FetchingRemoteStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.FetchingRemoteStatus, func() error {
gui.Mutexes.FetchMutex.Lock()
defer gui.Mutexes.FetchMutex.Unlock()
err := gui.Git.Sync.FetchRemote(remote.Name)
gui.handleCredentialsPopup(err)
if err != nil {
_ = gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
})
}

View File

@ -3,12 +3,14 @@ package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) resetToRef(ref string, strength string, envVars []string) error {
if err := gui.Git.Commit.ResetToCommit(ref, strength, envVars); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.Panels.Commits.SelectedLineIdx = 0
@ -20,7 +22,7 @@ func (gui *Gui) resetToRef(ref string, strength string, envVars []string) error
return err
}
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES, BRANCHES, REFLOG, COMMITS}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
return err
}
@ -29,20 +31,23 @@ func (gui *Gui) resetToRef(ref string, strength string, envVars []string) error
func (gui *Gui) createResetMenu(ref string) error {
strengths := []string{"soft", "mixed", "hard"}
menuItems := make([]*menuItem, len(strengths))
menuItems := make([]*popup.MenuItem, len(strengths))
for i, strength := range strengths {
strength := strength
menuItems[i] = &menuItem{
displayStrings: []string{
menuItems[i] = &popup.MenuItem{
DisplayStrings: []string{
fmt.Sprintf("%s reset", strength),
style.FgRed.Sprintf("reset --%s %s", strength, ref),
},
onPress: func() error {
OnPress: func() error {
gui.logAction("Reset")
return gui.resetToRef(ref, strength, []string{})
},
}
}
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.LcResetTo, ref), menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: fmt.Sprintf("%s %s", gui.Tr.LcResetTo, ref),
Items: menuItems,
})
}

View File

@ -4,6 +4,8 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error {
@ -112,10 +114,10 @@ func (gui *Gui) handleResetSelection() error {
}
if !gui.UserConfig.Gui.SkipUnstageLineWarning {
return gui.ask(askOpts{
title: gui.Tr.UnstageLinesTitle,
prompt: gui.Tr.UnstageLinesPrompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.UnstageLinesTitle,
Prompt: gui.Tr.UnstageLinesPrompt,
HandleConfirm: func() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
return gui.applySelection(true, state)
})
@ -149,14 +151,14 @@ func (gui *Gui) applySelection(reverse bool, state *LblPanelState) error {
gui.logAction(gui.Tr.Actions.ApplyPatch)
err := gui.Git.WorkingTree.ApplyPatch(patch, applyFlags...)
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if state.SelectingRange() {
state.SetLineSelectMode()
}
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
if err := gui.refreshStagingPanel(false, -1); err != nil {

View File

@ -2,6 +2,8 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// list panel functions
@ -54,7 +56,7 @@ func (gui *Gui) handleStashApply() error {
err := gui.Git.Stash.Apply(stashEntry.Index)
_ = gui.postStashRefresh()
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
}
@ -63,10 +65,10 @@ func (gui *Gui) handleStashApply() error {
return apply()
}
return gui.ask(askOpts{
title: gui.Tr.StashApply,
prompt: gui.Tr.SureApplyStashEntry,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.StashApply,
Prompt: gui.Tr.SureApplyStashEntry,
HandleConfirm: func() error {
return apply()
},
})
@ -85,7 +87,7 @@ func (gui *Gui) handleStashPop() error {
err := gui.Git.Stash.Pop(stashEntry.Index)
_ = gui.postStashRefresh()
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
}
@ -94,10 +96,10 @@ func (gui *Gui) handleStashPop() error {
return pop()
}
return gui.ask(askOpts{
title: gui.Tr.StashPop,
prompt: gui.Tr.SurePopStashEntry,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.StashPop,
Prompt: gui.Tr.SurePopStashEntry,
HandleConfirm: func() error {
return pop()
},
})
@ -109,15 +111,15 @@ func (gui *Gui) handleStashDrop() error {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.StashDrop,
prompt: gui.Tr.SureDropStashEntry,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.StashDrop,
Prompt: gui.Tr.SureDropStashEntry,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.Stash)
err := gui.Git.Stash.Drop(stashEntry.Index)
_ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH}})
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
},
@ -125,12 +127,12 @@ func (gui *Gui) handleStashDrop() error {
}
func (gui *Gui) postStashRefresh() error {
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH, FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
}
func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 {
return gui.createErrorPanel(gui.Tr.NoTrackedStagedFilesStash)
return gui.PopupHandler.ErrorMsg(gui.Tr.NoTrackedStagedFilesStash)
}
return gui.prompt(promptOpts{
@ -139,7 +141,7 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
err := stashFunc(stashComment)
_ = gui.postStashRefresh()
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
},

View File

@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/utils"
@ -49,8 +50,10 @@ func cursorInSubstring(cx int, prefix string, substring string) bool {
}
func (gui *Gui) handleCheckForUpdate() error {
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
return gui.createLoaderPanel(gui.Tr.CheckingForUpdates)
return gui.PopupHandler.WithWaitingStatus(gui.Tr.CheckingForUpdates, func() error {
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
return nil
})
}
func (gui *Gui) handleStatusClick() error {
@ -136,17 +139,21 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
case 1:
return action(confPaths[0])
default:
menuItems := make([]*menuItem, len(confPaths))
menuItems := make([]*popup.MenuItem, len(confPaths))
for i, file := range confPaths {
i := i
menuItems[i] = &menuItem{
displayString: file,
onPress: func() error {
menuItems[i] = &popup.MenuItem{
DisplayString: file,
OnPress: func() error {
return action(confPaths[i])
},
}
}
return gui.createMenu(gui.Tr.SelectConfigFile, menuItems, createMenuOptions{})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
Title: gui.Tr.SelectConfigFile,
Items: menuItems,
HideCancel: true,
})
}
}

View File

@ -3,6 +3,7 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
// list panel functions
@ -42,10 +43,10 @@ func (gui *Gui) handleCheckoutSubCommit() error {
return nil
}
err := gui.ask(askOpts{
title: gui.Tr.LcCheckoutCommit,
prompt: gui.Tr.SureCheckoutThisCommit,
handleConfirm: func() error {
err := gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.LcCheckoutCommit,
Prompt: gui.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.CheckoutCommit)
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
},

View File

@ -3,8 +3,6 @@ package gui
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/style"
@ -36,7 +34,7 @@ func (gui *Gui) submodulesRenderToMain() error {
if file == nil {
task = NewRenderStringTask(prefix)
} else {
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView)
cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.IgnoreWhitespaceInDiffView)
task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
}
}
@ -60,205 +58,12 @@ func (gui *Gui) refreshStateSubmoduleConfigs() error {
return nil
}
func (gui *Gui) handleSubmoduleEnter(submodule *models.SubmoduleConfig) error {
return gui.enterSubmodule(submodule)
}
func (gui *Gui) enterSubmodule(submodule *models.SubmoduleConfig) error {
wd, err := os.Getwd()
if err != nil {
return err
}
gui.RepoPathStack = append(gui.RepoPathStack, wd)
gui.RepoPathStack.Push(wd)
return gui.dispatchSwitchToRepo(submodule.Path, true)
}
func (gui *Gui) removeSubmodule(submodule *models.SubmoduleConfig) error {
return gui.ask(askOpts{
title: gui.Tr.RemoveSubmodule,
prompt: fmt.Sprintf(gui.Tr.RemoveSubmodulePrompt, submodule.Name),
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RemoveSubmodule)
if err := gui.Git.Submodule.Delete(submodule); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES, FILES}})
},
})
}
func (gui *Gui) handleResetSubmodule(submodule *models.SubmoduleConfig) error {
return gui.WithWaitingStatus(gui.Tr.LcResettingSubmoduleStatus, func() error {
return gui.resetSubmodule(submodule)
})
}
func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
for _, file := range gui.State.FileTreeViewModel.GetAllFiles() {
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
return file
}
}
return nil
}
func (gui *Gui) resetSubmodule(submodule *models.SubmoduleConfig) error {
gui.logAction(gui.Tr.Actions.ResetSubmodule)
file := gui.fileForSubmodule(submodule)
if file != nil {
if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return gui.surfaceError(err)
}
}
if err := gui.Git.Submodule.Stash(submodule); err != nil {
return gui.surfaceError(err)
}
if err := gui.Git.Submodule.Reset(submodule); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES, SUBMODULES}})
}
func (gui *Gui) handleAddSubmodule() error {
return gui.prompt(promptOpts{
title: gui.Tr.LcNewSubmoduleUrl,
handleConfirm: func(submoduleUrl string) error {
nameSuggestion := filepath.Base(strings.TrimSuffix(submoduleUrl, filepath.Ext(submoduleUrl)))
return gui.prompt(promptOpts{
title: gui.Tr.LcNewSubmoduleName,
initialContent: nameSuggestion,
handleConfirm: func(submoduleName string) error {
return gui.prompt(promptOpts{
title: gui.Tr.LcNewSubmodulePath,
initialContent: submoduleName,
handleConfirm: func(submodulePath string) error {
return gui.WithWaitingStatus(gui.Tr.LcAddingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.AddSubmodule)
err := gui.Git.Submodule.Add(submoduleName, submodulePath, submoduleUrl)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
},
})
},
})
},
})
}
func (gui *Gui) handleEditSubmoduleUrl(submodule *models.SubmoduleConfig) error {
return gui.prompt(promptOpts{
title: fmt.Sprintf(gui.Tr.LcUpdateSubmoduleUrl, submodule.Name),
initialContent: submodule.Url,
handleConfirm: func(newUrl string) error {
return gui.WithWaitingStatus(gui.Tr.LcUpdatingSubmoduleUrlStatus, func() error {
gui.logAction(gui.Tr.Actions.UpdateSubmoduleUrl)
err := gui.Git.Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
},
})
}
func (gui *Gui) handleSubmoduleInit(submodule *models.SubmoduleConfig) error {
return gui.WithWaitingStatus(gui.Tr.LcInitializingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.InitialiseSubmodule)
err := gui.Git.Submodule.Init(submodule.Path)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
}
func (gui *Gui) forSubmodule(callback func(*models.SubmoduleConfig) error) func() error {
return func() error {
submodule := gui.getSelectedSubmodule()
if submodule == nil {
return nil
}
return callback(submodule)
}
}
func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
menuItems := []*menuItem{
{
displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.Git.Submodule.BulkInitCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkInitialiseSubmodules)
err := gui.Git.Submodule.BulkInitCmdObj().Run()
if err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
},
},
{
displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.Git.Submodule.BulkUpdateCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkUpdateSubmodules)
if err := gui.Git.Submodule.BulkUpdateCmdObj().Run(); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
},
},
{
displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.Git.Submodule.ForceBulkUpdateCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkStashAndResetSubmodules)
if err := gui.Git.Submodule.ResetSubmodules(gui.State.Submodules); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
},
},
{
displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.Git.Submodule.BulkDeinitCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkDeinitialiseSubmodules)
if err := gui.Git.Submodule.BulkDeinitCmdObj().Run(); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
},
},
}
return gui.createMenu(gui.Tr.LcBulkSubmoduleOptions, menuItems, createMenuOptions{showCancel: true})
}
func (gui *Gui) handleUpdateSubmodule(submodule *models.SubmoduleConfig) error {
return gui.WithWaitingStatus(gui.Tr.LcUpdatingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.UpdateSubmodule)
err := gui.Git.Submodule.Update(submodule.Path)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
})
}

View File

@ -2,6 +2,8 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -41,7 +43,7 @@ func (gui *Gui) tagsRenderToMain() error {
func (gui *Gui) refreshTags() error {
tags, err := gui.Git.Loaders.Tags.GetTags()
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
gui.State.Tags = tags
@ -78,15 +80,15 @@ func (gui *Gui) handleDeleteTag(tag *models.Tag) error {
},
)
return gui.ask(askOpts{
title: gui.Tr.DeleteTagTitle,
prompt: prompt,
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.DeleteTagTitle,
Prompt: prompt,
HandleConfirm: func() error {
gui.logAction(gui.Tr.Actions.DeleteTag)
if err := gui.Git.Tag.Delete(tag.Name); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
},
})
}
@ -99,15 +101,17 @@ func (gui *Gui) handlePushTag(tag *models.Tag) error {
},
)
return gui.prompt(promptOpts{
title: title,
initialContent: "origin",
findSuggestionsFunc: gui.getRemoteSuggestionsFunc(),
handleConfirm: func(response string) error {
return gui.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error {
return gui.PopupHandler.Prompt(popup.PromptOpts{
Title: title,
InitialContent: "origin",
FindSuggestionsFunc: gui.getRemoteSuggestionsFunc(),
HandleConfirm: func(response string) error {
return gui.PopupHandler.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error {
gui.logAction(gui.Tr.Actions.PushTag)
err := gui.Git.Tag.Push(response, tag.Name)
gui.handleCredentialsPopup(err)
if err != nil {
_ = gui.PopupHandler.Error(err)
}
return nil
})

View File

@ -0,0 +1,18 @@
package types
import "github.com/jesseduffield/gocui"
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
// is only handled if the given view has focus, or handled globally if the view
// is ""
type Binding struct {
ViewName string
Contexts []string
Handler func() error
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
Modifier gocui.Modifier
Description string
Alternative string
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
OpensMenu bool
}

32
pkg/gui/types/refresh.go Normal file
View File

@ -0,0 +1,32 @@
package types
// models/views that we can refresh
type RefreshableView int
const (
COMMITS RefreshableView = iota
BRANCHES
FILES
STASH
REFLOG
TAGS
REMOTES
STATUS
SUBMODULES
// not actually a view. Will refactor this later
BISECT_INFO
)
type RefreshMode int
const (
SYNC RefreshMode = iota // wait until everything is done before returning
ASYNC // return immediately, allowing each independent thing to update itself
BLOCK_UI // wrap code in an update call to ensure UI updates all at once and keybindings aren't executed till complete
)
type RefreshOptions struct {
Then func()
Scope []RefreshableView // e.g. []int{COMMITS, BRANCHES}. Leave empty to refresh everything
Mode RefreshMode // one of SYNC (default), ASYNC, and BLOCK_UI
}

View File

@ -2,6 +2,8 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -89,7 +91,7 @@ func (gui *Gui) reflogUndo() error {
undoingStatus := gui.Tr.UndoingStatus
if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
return gui.createErrorPanel(gui.Tr.LcCantUndoWhileRebasing)
return gui.PopupHandler.ErrorMsg(gui.Tr.LcCantUndoWhileRebasing)
}
return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
@ -124,7 +126,7 @@ func (gui *Gui) reflogRedo() error {
redoingStatus := gui.Tr.RedoingStatus
if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
return gui.createErrorPanel(gui.Tr.LcCantRedoWhileRebasing)
return gui.PopupHandler.ErrorMsg(gui.Tr.LcCantRedoWhileRebasing)
}
return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
@ -166,7 +168,7 @@ type handleHardResetWithAutoStashOptions struct {
func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
reset := func() error {
if err := gui.resetToRef(commitSha, "hard", options.EnvVars); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
}
@ -175,24 +177,24 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar
dirtyWorkingTree := len(gui.trackedFiles()) > 0 || len(gui.stagedFiles()) > 0
if dirtyWorkingTree {
// offer to autostash changes
return gui.ask(askOpts{
title: gui.Tr.AutoStashTitle,
prompt: gui.Tr.AutoStashPrompt,
handleConfirm: func() error {
return gui.WithWaitingStatus(options.WaitingStatus, func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: gui.Tr.AutoStashTitle,
Prompt: gui.Tr.AutoStashPrompt,
HandleConfirm: func() error {
return gui.PopupHandler.WithWaitingStatus(options.WaitingStatus, func() error {
if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + commitSha); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if err := reset(); err != nil {
return err
}
err := gui.Git.Stash.Pop(0)
if err := gui.refreshSidePanels(refreshOptions{}); err != nil {
if err := gui.refreshSidePanels(types.RefreshOptions{}); err != nil {
return err
}
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return nil
})
@ -200,7 +202,7 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar
})
}
return gui.WithWaitingStatus(options.WaitingStatus, func() error {
return gui.PopupHandler.WithWaitingStatus(options.WaitingStatus, func() error {
return reset()
})
}

View File

@ -4,13 +4,14 @@ import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
)
func (gui *Gui) showUpdatePrompt(newVersion string) error {
return gui.ask(askOpts{
title: "New version available!",
prompt: fmt.Sprintf("Download version %s? (enter/esc)", newVersion),
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: "New version available!",
Prompt: fmt.Sprintf("Download version %s? (enter/esc)", newVersion),
HandleConfirm: func() error {
gui.startUpdating(newVersion)
return nil
},
@ -19,10 +20,10 @@ func (gui *Gui) showUpdatePrompt(newVersion string) error {
func (gui *Gui) onUserUpdateCheckFinish(newVersion string, err error) error {
if err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
if newVersion == "" {
return gui.createErrorPanel("New version not found")
return gui.PopupHandler.ErrorMsg("New version not found")
}
return gui.showUpdatePrompt(newVersion)
}
@ -55,7 +56,7 @@ func (gui *Gui) onUpdateFinish(statusId int, err error) error {
gui.OnUIThread(func() error {
_ = gui.renderString(gui.Views.AppStatus, "")
if err != nil {
return gui.createErrorPanel("Update failed: " + err.Error())
return gui.PopupHandler.ErrorMsg("Update failed: " + err.Error())
}
return nil
})
@ -64,10 +65,10 @@ func (gui *Gui) onUpdateFinish(statusId int, err error) error {
}
func (gui *Gui) createUpdateQuitConfirmation() error {
return gui.ask(askOpts{
title: "Currently Updating",
prompt: "An update is in progress. Are you sure you want to quit?",
handleConfirm: func() error {
return gui.PopupHandler.Ask(popup.AskOpts{
Title: "Currently Updating",
Prompt: "An update is in progress. Are you sure you want to quit?",
HandleConfirm: func() error {
return gocui.ErrQuit
},
})

View File

@ -7,6 +7,7 @@ import (
"sync"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom"
)
@ -15,34 +16,18 @@ func (gui *Gui) getCyclableWindows() []string {
return []string{"status", "files", "branches", "commits", "stash"}
}
// models/views that we can refresh
type RefreshableView int
const (
COMMITS RefreshableView = iota
BRANCHES
FILES
STASH
REFLOG
TAGS
REMOTES
STATUS
SUBMODULES
// not actually a view. Will refactor this later
BISECT_INFO
)
func getScopeNames(scopes []RefreshableView) []string {
scopeNameMap := map[RefreshableView]string{
COMMITS: "commits",
BRANCHES: "branches",
FILES: "files",
SUBMODULES: "submodules",
STASH: "stash",
REFLOG: "reflog",
TAGS: "tags",
REMOTES: "remotes",
STATUS: "status",
func getScopeNames(scopes []types.RefreshableView) []string {
scopeNameMap := map[types.RefreshableView]string{
types.COMMITS: "commits",
types.BRANCHES: "branches",
types.FILES: "files",
types.SUBMODULES: "submodules",
types.STASH: "stash",
types.REFLOG: "reflog",
types.TAGS: "tags",
types.REMOTES: "remotes",
types.STATUS: "status",
types.BISECT_INFO: "bisect",
}
scopeNames := make([]string, len(scopes))
@ -53,69 +38,55 @@ func getScopeNames(scopes []RefreshableView) []string {
return scopeNames
}
func getModeName(mode RefreshMode) string {
func getModeName(mode types.RefreshMode) string {
switch mode {
case SYNC:
case types.SYNC:
return "sync"
case ASYNC:
case types.ASYNC:
return "async"
case BLOCK_UI:
case types.BLOCK_UI:
return "block-ui"
default:
return "unknown mode"
}
}
type RefreshMode int
const (
SYNC RefreshMode = iota // wait until everything is done before returning
ASYNC // return immediately, allowing each independent thing to update itself
BLOCK_UI // wrap code in an update call to ensure UI updates all at once and keybindings aren't executed till complete
)
type refreshOptions struct {
then func()
scope []RefreshableView // e.g. []int{COMMITS, BRANCHES}. Leave empty to refresh everything
mode RefreshMode // one of SYNC (default), ASYNC, and BLOCK_UI
}
func arrToMap(arr []RefreshableView) map[RefreshableView]bool {
output := map[RefreshableView]bool{}
func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool {
output := map[types.RefreshableView]bool{}
for _, el := range arr {
output[el] = true
}
return output
}
func (gui *Gui) refreshSidePanels(options refreshOptions) error {
if options.scope == nil {
func (gui *Gui) refreshSidePanels(options types.RefreshOptions) error {
if options.Scope == nil {
gui.Log.Infof(
"refreshing all scopes in %s mode",
getModeName(options.mode),
getModeName(options.Mode),
)
} else {
gui.Log.Infof(
"refreshing the following scopes in %s mode: %s",
getModeName(options.mode),
strings.Join(getScopeNames(options.scope), ","),
getModeName(options.Mode),
strings.Join(getScopeNames(options.Scope), ","),
)
}
wg := sync.WaitGroup{}
f := func() {
var scopeMap map[RefreshableView]bool
if len(options.scope) == 0 {
scopeMap = arrToMap([]RefreshableView{COMMITS, BRANCHES, FILES, STASH, REFLOG, TAGS, REMOTES, STATUS, BISECT_INFO})
var scopeMap map[types.RefreshableView]bool
if len(options.Scope) == 0 {
scopeMap = arrToMap([]types.RefreshableView{types.COMMITS, types.BRANCHES, types.FILES, types.STASH, types.REFLOG, types.TAGS, types.REMOTES, types.STATUS, types.BISECT_INFO})
} else {
scopeMap = arrToMap(options.scope)
scopeMap = arrToMap(options.Scope)
}
if scopeMap[COMMITS] || scopeMap[BRANCHES] || scopeMap[REFLOG] || scopeMap[BISECT_INFO] {
if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] {
wg.Add(1)
func() {
if options.mode == ASYNC {
if options.Mode == types.ASYNC {
go utils.Safe(func() { gui.refreshCommits() })
} else {
gui.refreshCommits()
@ -124,10 +95,10 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
}()
}
if scopeMap[FILES] || scopeMap[SUBMODULES] {
if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] {
wg.Add(1)
func() {
if options.mode == ASYNC {
if options.Mode == types.ASYNC {
go utils.Safe(func() { _ = gui.refreshFilesAndSubmodules() })
} else {
_ = gui.refreshFilesAndSubmodules()
@ -136,10 +107,10 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
}()
}
if scopeMap[STASH] {
if scopeMap[types.STASH] {
wg.Add(1)
func() {
if options.mode == ASYNC {
if options.Mode == types.ASYNC {
go utils.Safe(func() { _ = gui.refreshStashEntries() })
} else {
_ = gui.refreshStashEntries()
@ -148,10 +119,10 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
}()
}
if scopeMap[TAGS] {
if scopeMap[types.TAGS] {
wg.Add(1)
func() {
if options.mode == ASYNC {
if options.Mode == types.ASYNC {
go utils.Safe(func() { _ = gui.refreshTags() })
} else {
_ = gui.refreshTags()
@ -160,10 +131,10 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
}()
}
if scopeMap[REMOTES] {
if scopeMap[types.REMOTES] {
wg.Add(1)
func() {
if options.mode == ASYNC {
if options.Mode == types.ASYNC {
go utils.Safe(func() { _ = gui.refreshRemotes() })
} else {
_ = gui.refreshRemotes()
@ -176,12 +147,12 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
gui.refreshStatus()
if options.then != nil {
options.then()
if options.Then != nil {
options.Then()
}
}
if options.mode == BLOCK_UI {
if options.Mode == types.BLOCK_UI {
gui.OnUIThread(func() error {
f()
return nil

View File

@ -1,10 +1,10 @@
package gui
func (gui *Gui) toggleWhitespaceInDiffView() error {
gui.State.IgnoreWhitespaceInDiffView = !gui.State.IgnoreWhitespaceInDiffView
gui.IgnoreWhitespaceInDiffView = !gui.IgnoreWhitespaceInDiffView
toastMessage := gui.Tr.ShowingWhitespaceInDiffView
if gui.State.IgnoreWhitespaceInDiffView {
if gui.IgnoreWhitespaceInDiffView {
toastMessage = gui.Tr.IgnoringWhitespaceInDiffView
}
gui.raiseToast(toastMessage)

View File

@ -3,7 +3,9 @@ package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) handleCreateResetMenu() error {
@ -14,92 +16,92 @@ func (gui *Gui) handleCreateResetMenu() error {
nukeStr = fmt.Sprintf("%s (%s)", nukeStr, gui.Tr.LcAndResetSubmodules)
}
menuItems := []*menuItem{
menuItems := []*popup.MenuItem{
{
displayStrings: []string{
DisplayStrings: []string{
gui.Tr.LcDiscardAllChangesToAllFiles,
red.Sprint(nukeStr),
},
onPress: func() error {
OnPress: func() error {
gui.logAction(gui.Tr.Actions.NukeWorkingTree)
if err := gui.Git.WorkingTree.ResetAndClean(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
displayStrings: []string{
DisplayStrings: []string{
gui.Tr.LcDiscardAnyUnstagedChanges,
red.Sprint("git checkout -- ."),
},
onPress: func() error {
OnPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardUnstagedFileChanges)
if err := gui.Git.WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
displayStrings: []string{
DisplayStrings: []string{
gui.Tr.LcDiscardUntrackedFiles,
red.Sprint("git clean -fd"),
},
onPress: func() error {
OnPress: func() error {
gui.logAction(gui.Tr.Actions.RemoveUntrackedFiles)
if err := gui.Git.WorkingTree.RemoveUntrackedFiles(); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
displayStrings: []string{
DisplayStrings: []string{
gui.Tr.LcSoftReset,
red.Sprint("git reset --soft HEAD"),
},
onPress: func() error {
OnPress: func() error {
gui.logAction(gui.Tr.Actions.SoftReset)
if err := gui.Git.WorkingTree.ResetSoft("HEAD"); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
displayStrings: []string{
DisplayStrings: []string{
"mixed reset",
red.Sprint("git reset --mixed HEAD"),
},
onPress: func() error {
OnPress: func() error {
gui.logAction(gui.Tr.Actions.MixedReset)
if err := gui.Git.WorkingTree.ResetMixed("HEAD"); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
displayStrings: []string{
DisplayStrings: []string{
gui.Tr.LcHardReset,
red.Sprint("git reset --hard HEAD"),
},
onPress: func() error {
OnPress: func() error {
gui.logAction(gui.Tr.Actions.HardReset)
if err := gui.Git.WorkingTree.ResetHard("HEAD"); err != nil {
return gui.surfaceError(err)
return gui.PopupHandler.Error(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
}
return gui.createMenu("", menuItems, createMenuOptions{showCancel: true})
return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: "", Items: menuItems})
}

View File

@ -504,7 +504,6 @@ func chineseTranslationSet() TranslationSet {
InitialiseSubmodule: "初始化子模块",
BulkInitialiseSubmodules: "批量初始化子模块",
BulkUpdateSubmodules: "批量更新子模块",
BulkStashAndResetSubmodules: "批量存储和重置子模块",
BulkDeinitialiseSubmodules: "批量取消初始化子模块",
UpdateSubmodule: "更新子模块",
DeleteTag: "删除标签",

View File

@ -540,7 +540,6 @@ type Actions struct {
InitialiseSubmodule string
BulkInitialiseSubmodules string
BulkUpdateSubmodules string
BulkStashAndResetSubmodules string
BulkDeinitialiseSubmodules string
UpdateSubmodule string
CreateLightweightTag string
@ -651,7 +650,7 @@ func EnglishTranslationSet() TranslationSet {
NoBranchesThisRepo: "No branches for this repo",
CommitMessageConfirm: "{{.keyBindClose}}: close, {{.keyBindNewLine}}: new line, {{.keyBindConfirm}}: confirm",
CommitWithoutMessageErr: "You cannot commit without a commit message",
CloseConfirm: "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm",
CloseConfirm: "{{.keyBindClose}}: close/cancel, {{.keyBindConfirm}}: confirm",
LcClose: "close",
LcQuit: "quit",
LcSquashDown: "squash down",
@ -1097,7 +1096,6 @@ func EnglishTranslationSet() TranslationSet {
InitialiseSubmodule: "Initialise submodule",
BulkInitialiseSubmodules: "Bulk initialise submodules",
BulkUpdateSubmodules: "Bulk update submodules",
BulkStashAndResetSubmodules: "Bulk stash and reset submodules",
BulkDeinitialiseSubmodules: "Bulk deinitialise submodules",
UpdateSubmodule: "Update submodule",
DeleteTag: "Delete tag",

View File

@ -144,12 +144,10 @@ func (u *Updater) CheckForNewUpdate(onFinish func(string, error) error, userRequ
return
}
go utils.Safe(func() {
newVersion, err := u.checkForNewUpdate()
if err = onFinish(newVersion, err); err != nil {
u.Log.Error(err)
}
})
newVersion, err := u.checkForNewUpdate()
if err = onFinish(newVersion, err); err != nil {
u.Log.Error(err)
}
}
func (u *Updater) skipUpdateCheck() bool {

27
pkg/utils/string_stack.go Normal file
View File

@ -0,0 +1,27 @@
package utils
type StringStack struct {
stack []string
}
func (self *StringStack) Push(s string) {
self.stack = append(self.stack, s)
}
func (self *StringStack) Pop() string {
if len(self.stack) == 0 {
return ""
}
n := len(self.stack) - 1
last := self.stack[n]
self.stack = self.stack[:n]
return last
}
func (self *StringStack) IsEmpty() bool {
return len(self.stack) == 0
}
func (self *StringStack) Clear() {
self.stack = []string{}
}