From 3b018e040fa023bf64f84e5f57ed7f5440b94830 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 11 Aug 2018 13:17:20 +1000 Subject: [PATCH] make commit messages multilined and add ability save commit message between edits --- commit_message_panel.go | 42 +++++++++++++++++++++++++++++++++++++++++ confirmation_panel.go | 21 +++++++++++++-------- files_panel.go | 23 +++++++--------------- gitcommands.go | 1 - gui.go | 17 ++++++++++++++--- keybindings.go | 3 +++ view_helpers.go | 18 ++++++++++++++++++ 7 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 commit_message_panel.go diff --git a/commit_message_panel.go b/commit_message_panel.go new file mode 100644 index 000000000..7ff609976 --- /dev/null +++ b/commit_message_panel.go @@ -0,0 +1,42 @@ +package main + +import "github.com/jesseduffield/gocui" + +func handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { + message := trimmedContent(v) + if message == "" { + return createErrorPanel(g, "You cannot commit without a commit message") + } + if output, err := gitCommit(g, message); err != nil { + if err == errNoUsername { + return createErrorPanel(g, err.Error()) + } + return createErrorPanel(g, output) + } + refreshFiles(g) + g.SetViewOnBottom("commitMessage") + return refreshCommits(g) +} + +func handleCommitClose(g *gocui.Gui, v *gocui.View) error { + g.SetViewOnBottom("commitMessage") + return switchFocus(g, v, getFilesView(g)) +} + +func handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { + // resising ahead of time so that the top line doesn't get hidden to make + // room for the cursor on the second line + x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer()) + if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil { + if err != gocui.ErrUnknownView { + return err + } + } + + v.EditNewLine() + return nil +} + +func handleCommitFocused(g *gocui.Gui, v *gocui.View) error { + return renderString(g, "options", "esc: close, enter: confirm") +} diff --git a/confirmation_panel.go b/confirmation_panel.go index c82b6b65b..03e078155 100644 --- a/confirmation_panel.go +++ b/confirmation_panel.go @@ -56,6 +56,7 @@ func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, } func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, initialValue *[]byte, handleConfirm func(*gocui.Gui, *gocui.View) error) error { + g.SetViewOnBottom("commitMessage") if initialValue == nil { initialValue = &[]byte{} } @@ -66,9 +67,7 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, init return err } - g.Cursor = true - - handleConfirmAndClear := func(gui *gocui.Gui, view *gocui.View) error { + handleConfirm := func(gui *gocui.Gui, view *gocui.View) error { *initialValue = nil return handleConfirm(g, view) } @@ -84,7 +83,7 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, init confirmationView.Title = title restorePreviousBuffer(confirmationView, initialValue) switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleConfirmAndClear, handleClose) + return setKeyBindings(g, handleConfirm, handleClose) } return nil } @@ -106,6 +105,7 @@ func getCursorPositionFromBuffer(initialValue *[]byte) (int, int) { } func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { + g.SetViewOnBottom("commitMessage") g.Update(func(g *gocui.Gui) error { // delete the existing confirmation panel if it exists if view, _ := g.View("confirmation"); view != nil { @@ -172,15 +172,20 @@ func trimTrailingNewline(str string) string { return str } -func resizeConfirmationPanel(g *gocui.Gui) error { +func resizeConfirmationPanel(g *gocui.Gui, viewName string) error { // If the confirmation panel is already displayed, just resize the width, // otherwise continue - if v, err := g.View("confirmation"); err == nil { + g.Update(func(g *gocui.Gui) error { + v, err := g.View(viewName) + if err != nil { + return nil + } content := trimTrailingNewline(v.Buffer()) x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content) - if _, err = g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { + if _, err := g.SetView(viewName, x0, y0, x1, y1, 0); err != nil { return err } - } + return nil + }) return nil } diff --git a/files_panel.go b/files_panel.go index b2de2c226..75e20885f 100644 --- a/files_panel.go +++ b/files_panel.go @@ -15,9 +15,8 @@ import ( ) var ( - savedCommitMessage = &[]byte{} - errNoFiles = errors.New("No changed files") - errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`) + errNoFiles = errors.New("No changed files") + errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`) ) func stagedFiles(files []GitFile) []GitFile { @@ -178,19 +177,11 @@ func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts { return createErrorPanel(g, "There are no staged files to commit") } - createPromptPanel(g, filesView, "Commit message", savedCommitMessage, func(g *gocui.Gui, v *gocui.View) error { - message := trimmedContent(v) - if message == "" { - return createErrorPanel(g, "You cannot commit without a commit message") - } - if output, err := gitCommit(g, message); err != nil { - if err == errNoUsername { - return createErrorPanel(g, err.Error()) - } - return createErrorPanel(g, output) - } - refreshFiles(g) - return refreshCommits(g) + commitMessageView := getCommitMessageView(g) + g.Update(func(g *gocui.Gui) error { + g.SetViewOnTop("commitMessage") + switchFocus(g, filesView, commitMessageView) + return nil }) return nil } diff --git a/gitcommands.go b/gitcommands.go index 7c1ec84bd..798627c24 100644 --- a/gitcommands.go +++ b/gitcommands.go @@ -218,7 +218,6 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) { func runCommand(command string) (string, error) { commandLog(command) splitCmd := strings.Split(command, " ") - devLog(splitCmd) cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() return sanitisedCommandOutput(cmdOut, err) } diff --git a/gui.go b/gui.go index 4cf7d7df5..b6258ec95 100644 --- a/gui.go +++ b/gui.go @@ -199,15 +199,26 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.BgColor = gocui.ColorDefault v.FgColor = gocui.ColorBlue v.Frame = false } - if err = resizeConfirmationPanel(g); err != nil { - return err + if getCommitMessageView(g) == nil { + // doesn't matter where this view starts because it will be hidden + if commitMessageView, err := g.SetView("commitMessage", 0, 0, width, height, 0); err != nil { + if err != gocui.ErrUnknownView { + return err + } + g.SetViewOnBottom("commitMessage") + commitMessageView.Title = "Commit message" + commitMessageView.FgColor = gocui.ColorWhite + commitMessageView.Editable = true + } } + resizeConfirmationPanel(g, "commitMessage") + resizeConfirmationPanel(g, "confirmation") + if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil { if err != gocui.ErrUnknownView { return err diff --git a/keybindings.go b/keybindings.go index 07309790f..bc4a67015 100644 --- a/keybindings.go +++ b/keybindings.go @@ -58,6 +58,9 @@ func keybindings(g *gocui.Gui) error { {ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply}, {ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop}, {ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop}, + {ViewName: "commitMessage", Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: handleCommitConfirm}, + {ViewName: "commitMessage", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleCommitClose}, + {ViewName: "commitMessage", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: handleNewlineCommitMessage}, } // Would make these keybindings global but that interferes with editing diff --git a/view_helpers.go b/view_helpers.go index 2f5f2caf0..fdf3a802f 100644 --- a/view_helpers.go +++ b/view_helpers.go @@ -75,6 +75,8 @@ func newLineFocused(g *gocui.Gui, v *gocui.View) error { return handleBranchSelect(g, v) case "confirmation": return nil + case "commitMessage": + return handleCommitFocused(g, v) case "main": // TODO: pull this out into a 'view focused' function refreshMergePanel(g) @@ -215,3 +217,19 @@ func loader() string { index := nanos / 50000000 % int64(len(characters)) return characters[index : index+1] } + +// TODO: refactor properly +func getFilesView(g *gocui.Gui) *gocui.View { + v, _ := g.View("files") + return v +} + +func getCommitsView(g *gocui.Gui) *gocui.View { + v, _ := g.View("commits") + return v +} + +func getCommitMessageView(g *gocui.Gui) *gocui.View { + v, _ := g.View("commitMessage") + return v +}