From 7aa884ed8f19eacb03a5ee28baba989783746706 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 10 Aug 2018 23:36:54 +1000 Subject: [PATCH 1/4] step one on restoring multiline commits --- confirmation_panel.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/confirmation_panel.go b/confirmation_panel.go index 7ae55eaf9..c82b6b65b 100644 --- a/confirmation_panel.go +++ b/confirmation_panel.go @@ -56,6 +56,9 @@ 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 { + if initialValue == nil { + initialValue = &[]byte{} + } // only need to fit one line x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "") if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { @@ -79,14 +82,29 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, init confirmationView.Editable = true confirmationView.Title = title - confirmationView.Write(*initialValue) - confirmationView.SetCursor(len(*initialValue), 0) + restorePreviousBuffer(confirmationView, initialValue) switchFocus(g, currentView, confirmationView) return setKeyBindings(g, handleConfirmAndClear, handleClose) } return nil } +func restorePreviousBuffer(confirmationView *gocui.View, initialValue *[]byte) { + confirmationView.Write(*initialValue) + x, y := getCursorPositionFromBuffer(initialValue) + devLog("New cursor position:", x, y) + confirmationView.SetCursor(0, 0) + confirmationView.MoveCursor(x, y, false) +} + +func getCursorPositionFromBuffer(initialValue *[]byte) (int, int) { + split := strings.Split(string(*initialValue), "\n") + lastLine := split[len(split)-1] + x := len(lastLine) + y := len(split) + return x, y +} + func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { g.Update(func(g *gocui.Gui) error { // delete the existing confirmation panel if it exists From b2fbccd39233feddf5543dd57c6f04533895cdb2 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 11 Aug 2018 12:35:17 +1000 Subject: [PATCH 2/4] remove time logging --- ZHgalGrWSF | 1 + gitcommands.go | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) create mode 100644 ZHgalGrWSF diff --git a/ZHgalGrWSF b/ZHgalGrWSF new file mode 100644 index 000000000..f1f0d2371 --- /dev/null +++ b/ZHgalGrWSF @@ -0,0 +1 @@ +GaUMygWjJa \ No newline at end of file diff --git a/gitcommands.go b/gitcommands.go index 2d073aec9..7c1ec84bd 100644 --- a/gitcommands.go +++ b/gitcommands.go @@ -107,13 +107,11 @@ func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile { } func runDirectCommand(command string) (string, error) { - timeStart := time.Now() commandLog(command) cmdOut, err := exec. Command(state.Platform.shell, state.Platform.shellArg, command). CombinedOutput() - devLog("run direct command time for command: ", command, time.Now().Sub(timeStart)) return sanitisedCommandOutput(cmdOut, err) } @@ -218,12 +216,10 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) { } func runCommand(command string) (string, error) { - commandStartTime := time.Now() commandLog(command) splitCmd := strings.Split(command, " ") devLog(splitCmd) cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() - devLog("run command time: ", time.Now().Sub(commandStartTime)) return sanitisedCommandOutput(cmdOut, err) } From 3b018e040fa023bf64f84e5f57ed7f5440b94830 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 11 Aug 2018 13:17:20 +1000 Subject: [PATCH 3/4] 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 +} From 47bf649a69f11df1d0713b075f9a5c4b53f26f87 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 11 Aug 2018 13:18:33 +1000 Subject: [PATCH 4/4] switch focus back to files view after confirming commit message --- commit_message_panel.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commit_message_panel.go b/commit_message_panel.go index 7ff609976..49551c1eb 100644 --- a/commit_message_panel.go +++ b/commit_message_panel.go @@ -15,6 +15,7 @@ func handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { } refreshFiles(g) g.SetViewOnBottom("commitMessage") + switchFocus(g, v, getFilesView(g)) return refreshCommits(g) }