1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-17 01:42:45 +02:00
commit
messages
multilined
and add ability save commit message between edits
This commit is contained in:
Jesse Duffield
2018-08-11 13:17:20 +10:00
parent b2fbccd392
commit 3b018e040f
7 changed files with 97 additions and 28 deletions

42
commit_message_panel.go Normal file
View File

@ -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")
}

View File

@ -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 { 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 { if initialValue == nil {
initialValue = &[]byte{} initialValue = &[]byte{}
} }
@ -66,9 +67,7 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, init
return err return err
} }
g.Cursor = true handleConfirm := func(gui *gocui.Gui, view *gocui.View) error {
handleConfirmAndClear := func(gui *gocui.Gui, view *gocui.View) error {
*initialValue = nil *initialValue = nil
return handleConfirm(g, view) return handleConfirm(g, view)
} }
@ -84,7 +83,7 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, init
confirmationView.Title = title confirmationView.Title = title
restorePreviousBuffer(confirmationView, initialValue) restorePreviousBuffer(confirmationView, initialValue)
switchFocus(g, currentView, confirmationView) switchFocus(g, currentView, confirmationView)
return setKeyBindings(g, handleConfirmAndClear, handleClose) return setKeyBindings(g, handleConfirm, handleClose)
} }
return nil 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 { 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 { g.Update(func(g *gocui.Gui) error {
// delete the existing confirmation panel if it exists // delete the existing confirmation panel if it exists
if view, _ := g.View("confirmation"); view != nil { if view, _ := g.View("confirmation"); view != nil {
@ -172,15 +172,20 @@ func trimTrailingNewline(str string) string {
return str 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, // If the confirmation panel is already displayed, just resize the width,
// otherwise continue // 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()) content := trimTrailingNewline(v.Buffer())
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content) 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 err
} }
} return nil
})
return nil return nil
} }

View File

@ -15,9 +15,8 @@ import (
) )
var ( var (
savedCommitMessage = &[]byte{} errNoFiles = errors.New("No changed files")
errNoFiles = errors.New("No changed files") errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`)
errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`)
) )
func stagedFiles(files []GitFile) []GitFile { 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 { if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts {
return createErrorPanel(g, "There are no staged files to commit") return createErrorPanel(g, "There are no staged files to commit")
} }
createPromptPanel(g, filesView, "Commit message", savedCommitMessage, func(g *gocui.Gui, v *gocui.View) error { commitMessageView := getCommitMessageView(g)
message := trimmedContent(v) g.Update(func(g *gocui.Gui) error {
if message == "" { g.SetViewOnTop("commitMessage")
return createErrorPanel(g, "You cannot commit without a commit message") switchFocus(g, filesView, commitMessageView)
} return nil
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)
}) })
return nil return nil
} }

View File

@ -218,7 +218,6 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
func runCommand(command string) (string, error) { func runCommand(command string) (string, error) {
commandLog(command) commandLog(command)
splitCmd := strings.Split(command, " ") splitCmd := strings.Split(command, " ")
devLog(splitCmd)
cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput()
return sanitisedCommandOutput(cmdOut, err) return sanitisedCommandOutput(cmdOut, err)
} }

17
gui.go
View File

@ -199,15 +199,26 @@ func layout(g *gocui.Gui) error {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
return err return err
} }
v.BgColor = gocui.ColorDefault
v.FgColor = gocui.ColorBlue v.FgColor = gocui.ColorBlue
v.Frame = false v.Frame = false
} }
if err = resizeConfirmationPanel(g); err != nil { if getCommitMessageView(g) == nil {
return err // 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 v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
return err return err

View File

@ -58,6 +58,9 @@ func keybindings(g *gocui.Gui) error {
{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply}, {ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
{ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop}, {ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop},
{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop}, {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 // Would make these keybindings global but that interferes with editing

View File

@ -75,6 +75,8 @@ func newLineFocused(g *gocui.Gui, v *gocui.View) error {
return handleBranchSelect(g, v) return handleBranchSelect(g, v)
case "confirmation": case "confirmation":
return nil return nil
case "commitMessage":
return handleCommitFocused(g, v)
case "main": case "main":
// TODO: pull this out into a 'view focused' function // TODO: pull this out into a 'view focused' function
refreshMergePanel(g) refreshMergePanel(g)
@ -215,3 +217,19 @@ func loader() string {
index := nanos / 50000000 % int64(len(characters)) index := nanos / 50000000 % int64(len(characters))
return characters[index : index+1] 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
}