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:
1
ZHgalGrWSF
Normal file
1
ZHgalGrWSF
Normal file
@ -0,0 +1 @@
|
|||||||
|
GaUMygWjJa
|
43
commit_message_panel.go
Normal file
43
commit_message_panel.go
Normal 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")
|
||||||
|
}
|
@ -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 {
|
||||||
content := trimTrailingNewline(v.Buffer())
|
v, err := g.View(viewName)
|
||||||
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content)
|
if err != nil {
|
||||||
if _, err = g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
|
return nil
|
||||||
return err
|
}
|
||||||
}
|
content := trimTrailingNewline(v.Buffer())
|
||||||
}
|
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content)
|
||||||
|
if _, err := g.SetView(viewName, x0, y0, x1, y1, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ 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"`)
|
||||||
)
|
)
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
gui.go
15
gui.go
@ -199,14 +199,25 @@ 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 {
|
||||||
|
// 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
|
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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user