1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-23 00:39:13 +02:00

Merge branch 'feature/multiline-commit-restoring'

This commit is contained in:
Jesse Duffield
2018-08-11 13:19:17 +10:00
8 changed files with 119 additions and 34 deletions

1
ZHgalGrWSF Normal file
View File

@ -0,0 +1 @@
GaUMygWjJa

43
commit_message_panel.go Normal file
View File

@ -0,0 +1,43 @@
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")
switchFocus(g, v, getFilesView(g))
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,10 @@ 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 {
initialValue = &[]byte{}
}
// only need to fit one line // only need to fit one line
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "") x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "")
if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
@ -63,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)
} }
@ -79,15 +81,31 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, init
confirmationView.Editable = true confirmationView.Editable = true
confirmationView.Title = title confirmationView.Title = title
confirmationView.Write(*initialValue) restorePreviousBuffer(confirmationView, initialValue)
confirmationView.SetCursor(len(*initialValue), 0)
switchFocus(g, currentView, confirmationView) switchFocus(g, currentView, confirmationView)
return setKeyBindings(g, handleConfirmAndClear, handleClose) return setKeyBindings(g, handleConfirm, handleClose)
} }
return nil 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 { 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 {
@ -154,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

@ -107,13 +107,11 @@ func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile {
} }
func runDirectCommand(command string) (string, error) { func runDirectCommand(command string) (string, error) {
timeStart := time.Now()
commandLog(command) commandLog(command)
cmdOut, err := exec. cmdOut, err := exec.
Command(state.Platform.shell, state.Platform.shellArg, command). Command(state.Platform.shell, state.Platform.shellArg, command).
CombinedOutput() CombinedOutput()
devLog("run direct command time for command: ", command, time.Now().Sub(timeStart))
return sanitisedCommandOutput(cmdOut, err) return sanitisedCommandOutput(cmdOut, err)
} }
@ -218,12 +216,9 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
} }
func runCommand(command string) (string, error) { func runCommand(command string) (string, error) {
commandStartTime := time.Now()
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()
devLog("run command time: ", time.Now().Sub(commandStartTime))
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
}