From d991d74b063c8bc8edf27321bf8a98d1a51e3a54 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 22 Feb 2022 21:16:00 +1100 Subject: [PATCH] add commit message controller --- pkg/commands/oscommands/cmd_obj_runner.go | 3 + pkg/gui/commit_message_panel.go | 33 ++------ .../controllers/commit_message_controller.go | 79 +++++++++++++++++++ pkg/gui/controllers/files_controller.go | 32 ++++---- pkg/gui/controllers/helpers/gpg_helper.go | 74 +++++++++++++++++ pkg/gui/controllers/helpers/helpers.go | 2 + pkg/gui/gpg.go | 66 ---------------- pkg/gui/gui.go | 37 +++++++-- pkg/gui/gui_common.go | 4 + pkg/gui/keybindings.go | 12 --- pkg/gui/types/common.go | 4 + 11 files changed, 217 insertions(+), 129 deletions(-) create mode 100644 pkg/gui/controllers/commit_message_controller.go create mode 100644 pkg/gui/controllers/helpers/gpg_helper.go delete mode 100644 pkg/gui/gpg.go diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go index 9522bc627..92e024758 100644 --- a/pkg/commands/oscommands/cmd_obj_runner.go +++ b/pkg/commands/oscommands/cmd_obj_runner.go @@ -207,6 +207,9 @@ func (self *cmdObjRunner) runAndStreamAux( cmdObj ICmdObj, onRun func(*cmdHandler, io.Writer), ) error { + // if we're streaming this we don't want any fancy terminal stuff + cmdObj.AddEnvVars("TERM=dumb") + cmdWriter := self.guiIO.newCmdWriterFn() if cmdObj.ShouldLog() { diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 071503a6b..35ffc822f 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -8,28 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) handleCommitConfirm() error { - message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent()) - gui.State.failedCommitMessage = message - if message == "" { - return gui.c.ErrorMsg(gui.c.Tr.CommitWithoutMessageErr) - } - - cmdObj := gui.git.Commit.CommitCmdObj(message) - gui.c.LogAction(gui.c.Tr.Actions.Commit) - - _ = gui.c.PopContext() - return gui.withGpgHandling(cmdObj, gui.c.Tr.CommittingStatus, func() error { - gui.Views.CommitMessage.ClearTextArea() - gui.State.failedCommitMessage = "" - return nil - }) -} - -func (gui *Gui) handleCommitClose() error { - return gui.c.PopContext() -} - func (gui *Gui) handleCommitMessageFocused() error { message := utils.ResolvePlaceholderString( gui.c.Tr.CommitMessageConfirm, @@ -45,15 +23,14 @@ func (gui *Gui) handleCommitMessageFocused() error { return gui.renderString(gui.Views.Options, message) } -func (gui *Gui) getBufferLength(view *gocui.View) string { - return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " " -} - -// RenderCommitLength is a function. func (gui *Gui) RenderCommitLength() { if !gui.c.UserConfig.Gui.CommitLength.Show { return } - gui.Views.CommitMessage.Subtitle = gui.getBufferLength(gui.Views.CommitMessage) + gui.Views.CommitMessage.Subtitle = getBufferLength(gui.Views.CommitMessage) +} + +func getBufferLength(view *gocui.View) string { + return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " " } diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go new file mode 100644 index 000000000..6992e1eee --- /dev/null +++ b/pkg/gui/controllers/commit_message_controller.go @@ -0,0 +1,79 @@ +package controllers + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type CommitMessageController struct { + baseController + *controllerCommon + + getCommitMessage func() string + onCommitAttempt func(message string) + onCommitSuccess func() +} + +var _ types.IController = &CommitMessageController{} + +func NewCommitMessageController( + common *controllerCommon, + getCommitMessage func() string, + onCommitAttempt func(message string), + onCommitSuccess func(), +) *CommitMessageController { + return &CommitMessageController{ + baseController: baseController{}, + controllerCommon: common, + + getCommitMessage: getCommitMessage, + onCommitAttempt: onCommitAttempt, + onCommitSuccess: onCommitSuccess, + } +} + +func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { + bindings := []*types.Binding{ + { + Key: opts.GetKey(opts.Config.Universal.SubmitEditorText), + Handler: self.handleCommitConfirm, + }, + { + Key: opts.GetKey(opts.Config.Universal.Return), + Handler: self.handleCommitClose, + }, + } + + return bindings +} + +func (self *CommitMessageController) Context() types.Context { + return self.context() +} + +// this method is pointless in this context but I'm keeping it consistent +// with other contexts so that when generics arrive it's easier to refactor +func (self *CommitMessageController) context() types.Context { + return self.contexts.CommitMessage +} + +func (self *CommitMessageController) handleCommitConfirm() error { + message := self.getCommitMessage() + self.onCommitAttempt(message) + + if message == "" { + return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr) + } + + cmdObj := self.git.Commit.CommitCmdObj(message) + self.c.LogAction(self.c.Tr.Actions.Commit) + + _ = self.c.PopContext() + return self.helpers.GPG.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error { + self.onCommitSuccess() + return nil + }) +} + +func (self *CommitMessageController) handleCommitClose() error { + return self.c.PopContext() +} diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 018322f1a..e12554a3d 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -8,7 +8,6 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/filetree" @@ -20,11 +19,10 @@ type FilesController struct { baseController *controllerCommon - enterSubmodule func(submodule *models.SubmoduleConfig) error - setCommitMessage func(message string) - withGpgHandling func(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error - getFailedCommitMessage func() string - switchToMergeFn func(path string) error + enterSubmodule func(submodule *models.SubmoduleConfig) error + setCommitMessage func(message string) + getSavedCommitMessage func() string + switchToMergeFn func(path string) error } var _ types.IController = &FilesController{} @@ -33,17 +31,15 @@ func NewFilesController( common *controllerCommon, enterSubmodule func(submodule *models.SubmoduleConfig) error, setCommitMessage func(message string), - withGpgHandling func(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error, - getFailedCommitMessage func() string, + getSavedCommitMessage func() string, switchToMergeFn func(path string) error, ) *FilesController { return &FilesController{ - controllerCommon: common, - enterSubmodule: enterSubmodule, - setCommitMessage: setCommitMessage, - withGpgHandling: withGpgHandling, - getFailedCommitMessage: getFailedCommitMessage, - switchToMergeFn: switchToMergeFn, + controllerCommon: common, + enterSubmodule: enterSubmodule, + setCommitMessage: setCommitMessage, + getSavedCommitMessage: getSavedCommitMessage, + switchToMergeFn: switchToMergeFn, } } @@ -409,9 +405,9 @@ func (self *FilesController) HandleCommitPress() error { return self.promptToStageAllAndRetry(self.HandleCommitPress) } - failedCommitMessage := self.getFailedCommitMessage() - if len(failedCommitMessage) > 0 { - self.setCommitMessage(failedCommitMessage) + savedCommitMessage := self.getSavedCommitMessage() + if len(savedCommitMessage) > 0 { + self.setCommitMessage(savedCommitMessage) } else { commitPrefixConfig := self.commitPrefixConfigForRepo() if commitPrefixConfig != nil { @@ -470,7 +466,7 @@ func (self *FilesController) handleAmendCommitPress() error { HandleConfirm: func() error { cmdObj := self.git.Commit.AmendHeadCmdObj() self.c.LogAction(self.c.Tr.Actions.AmendCommit) - return self.withGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil) + return self.helpers.GPG.WithGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil) }, }) } diff --git a/pkg/gui/controllers/helpers/gpg_helper.go b/pkg/gui/controllers/helpers/gpg_helper.go new file mode 100644 index 000000000..2e287c2b4 --- /dev/null +++ b/pkg/gui/controllers/helpers/gpg_helper.go @@ -0,0 +1,74 @@ +package helpers + +import ( + "fmt" + + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type GpgHelper struct { + c *types.HelperCommon + os *oscommands.OSCommand + git *commands.GitCommand +} + +func NewGpgHelper( + c *types.HelperCommon, + os *oscommands.OSCommand, + git *commands.GitCommand, +) *GpgHelper { + return &GpgHelper{ + c: c, + os: os, + git: git, + } +} + +// Currently there is a bug where if we switch to a subprocess from within +// WithWaitingStatus we get stuck there and can't return to lazygit. We could +// fix this bug, or just stop running subprocesses from within there, given that +// we don't need to see a loading status if we're in a subprocess. +// TODO: we shouldn't need to use a shell here, but looks like that NewShell function contains some windows specific quoting stuff. We should centralise that. +func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { + useSubprocess := self.git.Config.UsingGpg() + if useSubprocess { + success, err := self.c.RunSubprocess(self.os.Cmd.NewShell(cmdObj.ToString())) + if success && onSuccess != nil { + if err := onSuccess(); err != nil { + return err + } + } + if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil { + return err + } + + return err + } else { + return self.runAndStream(cmdObj, waitingStatus, onSuccess) + } +} + +func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { + cmdObj = self.os.Cmd.NewShell(cmdObj.ToString()) + + return self.c.WithWaitingStatus(waitingStatus, func() error { + if err := cmdObj.StreamOutput().Run(); err != nil { + _ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) + return self.c.Error( + fmt.Errorf( + self.c.Tr.GitCommandFailed, self.c.UserConfig.Keybinding.Universal.ExtrasMenu, + ), + ) + } + + if onSuccess != nil { + if err := onSuccess(); err != nil { + return err + } + } + + return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) + }) +} diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go index 2a7c43ff0..c45852e29 100644 --- a/pkg/gui/controllers/helpers/helpers.go +++ b/pkg/gui/controllers/helpers/helpers.go @@ -11,6 +11,7 @@ type Helpers struct { CherryPick *CherryPickHelper Host *HostHelper PatchBuilding *PatchBuildingHelper + GPG *GpgHelper } func NewStubHelpers() *Helpers { @@ -25,5 +26,6 @@ func NewStubHelpers() *Helpers { CherryPick: &CherryPickHelper{}, Host: &HostHelper{}, PatchBuilding: &PatchBuildingHelper{}, + GPG: &GpgHelper{}, } } diff --git a/pkg/gui/gpg.go b/pkg/gui/gpg.go deleted file mode 100644 index 60d728c42..000000000 --- a/pkg/gui/gpg.go +++ /dev/null @@ -1,66 +0,0 @@ -package gui - -import ( - "fmt" - - "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 -// WithWaitingStatus we get stuck there and can't return to lazygit. We could -// fix this bug, or just stop running subprocesses from within there, given that -// we don't need to see a loading status if we're in a subprocess. -// TODO: work out if we actually need to use a shell command here -func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { - gui.LogCommand(cmdObj.ToString(), true) - - useSubprocess := gui.git.Config.UsingGpg() - if useSubprocess { - success, err := gui.runSubprocessWithSuspense(gui.os.Cmd.NewShell(cmdObj.ToString())) - if success && onSuccess != nil { - if err := onSuccess(); err != nil { - return err - } - } - if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil { - return err - } - - return err - } else { - return gui.RunAndStream(cmdObj, waitingStatus, onSuccess) - } -} - -func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { - return gui.c.WithWaitingStatus(waitingStatus, func() error { - cmdObj := gui.os.Cmd.NewShell(cmdObj.ToString()) - cmdObj.AddEnvVars("TERM=dumb") - cmdWriter := gui.getCmdWriter() - cmd := cmdObj.GetCmd() - cmd.Stdout = cmdWriter - cmd.Stderr = cmdWriter - - if err := cmd.Run(); err != nil { - if _, err := cmd.Stdout.Write([]byte(fmt.Sprintf("%s\n", style.FgRed.Sprint(err.Error())))); err != nil { - gui.c.Log.Error(err) - } - _ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - return gui.c.Error( - fmt.Errorf( - gui.c.Tr.GitCommandFailed, gui.c.UserConfig.Keybinding.Universal.ExtrasMenu, - ), - ) - } - - if onSuccess != nil { - if err := onSuccess(); err != nil { - return err - } - } - - return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) - }) -} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 93476636c..511406e77 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -193,8 +193,9 @@ type GuiRepoState struct { // back in sync with the repo state ViewsSetup bool - // this is the message of the last failed commit attempt - failedCommitMessage string + // we store a commit message in this field if we've escaped the commit message + // panel without committing or if our commit failed + savedCommitMessage string ScreenMode WindowMaximisation } @@ -503,6 +504,7 @@ func (gui *Gui) resetControllers() { Files: helpers.NewFilesHelper(controllerCommon, gui.git, osCommand), WorkingTree: helpers.NewWorkingTreeHelper(model), Tags: helpers.NewTagsHelper(controllerCommon, gui.git), + GPG: helpers.NewGpgHelper(controllerCommon, gui.os, gui.git), MergeAndRebase: rebaseHelper, CherryPick: helpers.NewCherryPickHelper( controllerCommon, @@ -538,15 +540,39 @@ func (gui *Gui) resetControllers() { reflogController := controllers.NewReflogController(common) subCommitsController := controllers.NewSubCommitsController(common) + getSavedCommitMessage := func() string { + return gui.State.savedCommitMessage + } + + getCommitMessage := func() string { + return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent()) + } + + setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }) + + onCommitAttempt := func(message string) { + gui.Views.CommitMessage.ClearTextArea() + } + + onCommitSuccess := func() { + gui.State.savedCommitMessage = "" + } + + commitMessageController := controllers.NewCommitMessageController( + common, + getCommitMessage, + onCommitAttempt, + onCommitSuccess, + ) + gui.Controllers = Controllers{ Submodules: submodulesController, Global: controllers.NewGlobalController(common), Files: controllers.NewFilesController( common, gui.enterSubmodule, - gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }), - gui.withGpgHandling, - func() string { return gui.State.failedCommitMessage }, + setCommitMessage, + getSavedCommitMessage, gui.switchToMerge, ), Tags: controllers.NewTagsController(common), @@ -604,6 +630,7 @@ func (gui *Gui) resetControllers() { controllers.AttachControllers(gui.State.Contexts.Remotes, gui.Controllers.Remotes) controllers.AttachControllers(gui.State.Contexts.Stash, stashController) controllers.AttachControllers(gui.State.Contexts.Menu, gui.Controllers.Menu) + controllers.AttachControllers(gui.State.Contexts.CommitMessage, commitMessageController) controllers.AttachControllers(gui.State.Contexts.Global, gui.Controllers.Sync, gui.Controllers.Undo, gui.Controllers.Global) listControllerFactory := controllers.NewListControllerFactory(gui.c) diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go index ba9540178..2f44ebbce 100644 --- a/pkg/gui/gui_common.go +++ b/pkg/gui/gui_common.go @@ -34,6 +34,10 @@ func (self *guiCommon) RunSubprocessAndRefresh(cmdObj oscommands.ICmdObj) error return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj) } +func (self *guiCommon) RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error) { + return self.gui.runSubprocessWithSuspense(cmdObj) +} + func (self *guiCommon) PushContext(context types.Context, opts ...types.OnFocusOpts) error { return self.gui.pushContext(context, opts...) } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index fa0e893e0..19eb86baf 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -430,18 +430,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi Handler: self.handleCopySelectedSideContextItemToClipboard, Description: self.c.Tr.LcCopyCommitShaToClipboard, }, - { - ViewName: "commitMessage", - Key: opts.GetKey(opts.Config.Universal.SubmitEditorText), - Modifier: gocui.ModNone, - Handler: self.handleCommitConfirm, - }, - { - ViewName: "commitMessage", - Key: opts.GetKey(opts.Config.Universal.Return), - Modifier: gocui.ModNone, - Handler: self.handleCommitClose, - }, { ViewName: "credentials", Key: opts.GetKey(opts.Config.Universal.Confirm), diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 9c13dcd67..650aa51eb 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -27,7 +27,11 @@ type IGuiCommon interface { PostRefreshUpdate(Context) error // this just re-renders the screen Render() + + // returns true if command completed successfully + RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error) RunSubprocessAndRefresh(oscommands.ICmdObj) error + PushContext(context Context, opts ...OnFocusOpts) error PopContext() error CurrentContext() Context