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

Switch to editor from commit message panel (#2881)

This commit is contained in:
Stefan Haller 2023-08-21 10:12:26 +02:00 committed by GitHub
commit c31fcb7134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 203 additions and 20 deletions

View File

@ -68,6 +68,19 @@ func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj
return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv()) return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv())
} }
func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only", "--edit", "--file="+tmpMessageFile).ToArgv())
}
func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").
Arg("--edit").
Arg("--file="+tmpMessageFile).
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
ToArgv())
}
// RewordLastCommit rewords the topmost commit with the given message // RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(summary string, description string) error { func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
messageArgs := self.commitMessageArgs(summary, description) messageArgs := self.commitMessageArgs(summary, description)

View File

@ -142,6 +142,7 @@ type KeybindingConfig struct {
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"` CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
Main KeybindingMainConfig `yaml:"main"` Main KeybindingMainConfig `yaml:"main"`
Submodules KeybindingSubmodulesConfig `yaml:"submodules"` Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"`
} }
// damn looks like we have some inconsistencies here with -alt and -alt1 // damn looks like we have some inconsistencies here with -alt and -alt1
@ -305,6 +306,10 @@ type KeybindingSubmodulesConfig struct {
BulkMenu string `yaml:"bulkMenu"` BulkMenu string `yaml:"bulkMenu"`
} }
type KeybindingCommitMessageConfig struct {
SwitchToEditor string `yaml:"switchToEditor"`
}
// OSConfig contains config on the level of the os // OSConfig contains config on the level of the os
type OSConfig struct { type OSConfig struct {
// Command for editing a file. Should contain "{{filename}}". // Command for editing a file. Should contain "{{filename}}".
@ -652,6 +657,9 @@ func GetDefaultConfig() *UserConfig {
Update: "u", Update: "u",
BulkMenu: "b", BulkMenu: "b",
}, },
CommitMessage: KeybindingCommitMessageConfig{
SwitchToEditor: "<c-o>",
},
}, },
OS: OSConfig{}, OS: OSConfig{},
DisableStartupPopups: false, DisableStartupPopups: false,

View File

@ -5,7 +5,10 @@ import (
"strings" "strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
) )
type CommitMessageContext struct { type CommitMessageContext struct {
@ -32,6 +35,8 @@ type CommitMessageViewModel struct {
preservedMessage string preservedMessage string
// invoked when pressing enter in the commit message panel // invoked when pressing enter in the commit message panel
onConfirm func(string, string) error onConfirm func(string, string) error
// invoked when pressing the switch-to-editor key binding
onSwitchToEditor func(string) error
// The message typed in before cycling through history // The message typed in before cycling through history
// We store this separately to 'preservedMessage' because 'preservedMessage' // We store this separately to 'preservedMessage' because 'preservedMessage'
@ -98,12 +103,21 @@ func (self *CommitMessageContext) SetPanelState(
descriptionTitle string, descriptionTitle string,
preserveMessage bool, preserveMessage bool,
onConfirm func(string, string) error, onConfirm func(string, string) error,
onSwitchToEditor func(string) error,
) { ) {
self.viewModel.selectedindex = index self.viewModel.selectedindex = index
self.viewModel.preserveMessage = preserveMessage self.viewModel.preserveMessage = preserveMessage
self.viewModel.onConfirm = onConfirm self.viewModel.onConfirm = onConfirm
self.viewModel.onSwitchToEditor = onSwitchToEditor
self.GetView().Title = summaryTitle self.GetView().Title = summaryTitle
self.c.Views().CommitDescription.Title = descriptionTitle self.c.Views().CommitDescription.Title = descriptionTitle
subtitleTemplate := lo.Ternary(onSwitchToEditor != nil, self.c.Tr.CommitDescriptionSubTitle, self.c.Tr.CommitDescriptionSubTitleNoSwitch)
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(subtitleTemplate,
map[string]string{
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
"switchToEditorKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.SwitchToEditor),
})
} }
func (self *CommitMessageContext) RenderCommitLength() { func (self *CommitMessageContext) RenderCommitLength() {
@ -117,3 +131,11 @@ func (self *CommitMessageContext) RenderCommitLength() {
func getBufferLength(view *gocui.View) string { func getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " " return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
} }
func (self *CommitMessageContext) SwitchToEditor(message string) error {
return self.viewModel.onSwitchToEditor(message)
}
func (self *CommitMessageContext) CanSwitchToEditor() bool {
return self.viewModel.onSwitchToEditor != nil
}

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -34,6 +35,10 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor), Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor),
Handler: self.confirm, Handler: self.confirm,
}, },
{
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
Handler: self.switchToEditor,
},
} }
return bindings return bindings
@ -43,7 +48,7 @@ func (self *CommitDescriptionController) Context() types.Context {
return self.context() return self.context()
} }
func (self *CommitDescriptionController) context() types.Context { func (self *CommitDescriptionController) context() *context.CommitMessageContext {
return self.c.Contexts().CommitMessage return self.c.Contexts().CommitMessage
} }
@ -58,3 +63,7 @@ func (self *CommitDescriptionController) close() error {
func (self *CommitDescriptionController) confirm() error { func (self *CommitDescriptionController) confirm() error {
return self.c.Helpers().Commits.HandleCommitConfirm() return self.c.Helpers().Commits.HandleCommitConfirm()
} }
func (self *CommitDescriptionController) switchToEditor() error {
return self.c.Helpers().Commits.SwitchToEditor()
}

View File

@ -22,8 +22,6 @@ func NewCommitMessageController(
} }
} }
// TODO: merge that commit panel PR because we're not currently showing how to add a newline as it's
// handled by the editor func rather than by the controller here.
func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{ bindings := []*types.Binding{
{ {
@ -48,6 +46,10 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
Key: opts.GetKey(opts.Config.Universal.TogglePanel), Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: self.switchToCommitDescription, Handler: self.switchToCommitDescription,
}, },
{
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
Handler: self.switchToEditor,
},
} }
return bindings return bindings
@ -86,6 +88,10 @@ func (self *CommitMessageController) switchToCommitDescription() error {
return nil return nil
} }
func (self *CommitMessageController) switchToEditor() error {
return self.c.Helpers().Commits.SwitchToEditor()
}
func (self *CommitMessageController) handleCommitIndexChange(value int) error { func (self *CommitMessageController) handleCommitIndexChange(value int) error {
currentIndex := self.context().GetSelectedIndex() currentIndex := self.context().GetSelectedIndex()
newIndex := currentIndex + value newIndex := currentIndex + value

View File

@ -1,9 +1,12 @@
package helpers package helpers
import ( import (
"path/filepath"
"strings" "strings"
"time"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
) )
type ICommitsHelper interface { type ICommitsHelper interface {
@ -62,6 +65,28 @@ func (self *CommitsHelper) JoinCommitMessageAndDescription() string {
return self.getCommitSummary() + "\n" + self.getCommitDescription() return self.getCommitSummary() + "\n" + self.getCommitDescription()
} }
func (self *CommitsHelper) SwitchToEditor() error {
if !self.c.Contexts().CommitMessage.CanSwitchToEditor() {
return nil
}
message := lo.Ternary(len(self.getCommitDescription()) == 0,
self.getCommitSummary(),
self.getCommitSummary()+"\n\n"+self.getCommitDescription())
filepath := filepath.Join(self.c.OS().GetTempDir(), self.c.Git().RepoPaths.RepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".msg")
err := self.c.OS().CreateFileWithContent(filepath, message)
if err != nil {
return err
}
err = self.CloseCommitMessagePanel()
if err != nil {
return err
}
return self.c.Contexts().CommitMessage.SwitchToEditor(filepath)
}
func (self *CommitsHelper) UpdateCommitPanelView(message string) { func (self *CommitsHelper) UpdateCommitPanelView(message string) {
if message != "" { if message != "" {
self.SetMessageAndDescriptionInView(message) self.SetMessageAndDescriptionInView(message)
@ -83,6 +108,7 @@ type OpenCommitMessagePanelOpts struct {
DescriptionTitle string DescriptionTitle string
PreserveMessage bool PreserveMessage bool
OnConfirm func(summary string, description string) error OnConfirm func(summary string, description string) error
OnSwitchToEditor func(string) error
InitialMessage string InitialMessage string
} }
@ -101,6 +127,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
opts.DescriptionTitle, opts.DescriptionTitle,
opts.PreserveMessage, opts.PreserveMessage,
onConfirm, onConfirm,
opts.OnSwitchToEditor,
) )
self.UpdateCommitPanelView(opts.InitialMessage) self.UpdateCommitPanelView(opts.InitialMessage)

View File

@ -104,6 +104,7 @@ func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage strin
DescriptionTitle: self.c.Tr.CommitDescriptionTitle, DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
PreserveMessage: true, PreserveMessage: true,
OnConfirm: self.handleCommit, OnConfirm: self.handleCommit,
OnSwitchToEditor: self.switchFromCommitMessagePanelToEditor,
}, },
) )
} }
@ -117,6 +118,21 @@ func (self *WorkingTreeHelper) handleCommit(summary string, description string)
}) })
} }
func (self *WorkingTreeHelper) switchFromCommitMessagePanelToEditor(filepath string) error {
// We won't be able to tell whether the commit was successful, because
// RunSubprocessAndRefresh doesn't return the error (it opens an error alert
// itself and returns nil on error). But even if we could, we wouldn't have
// access to the last message that the user typed, and it might be very
// different from what was last in the commit panel. So the best we can do
// here is to always clear the remembered commit message.
self.commitsHelper.OnCommitSuccess()
self.c.LogAction(self.c.Tr.Actions.Commit)
return self.c.RunSubprocessAndRefresh(
self.c.Git().Commit.CommitInEditorWithMessageFileCmdObj(filepath),
)
}
// HandleCommitEditorPress - handle when the user wants to commit changes via // HandleCommitEditorPress - handle when the user wants to commit changes via
// their editor rather than via the popup panel // their editor rather than via the popup panel
func (self *WorkingTreeHelper) HandleCommitEditorPress() error { func (self *WorkingTreeHelper) HandleCommitEditorPress() error {

View File

@ -279,10 +279,37 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
DescriptionTitle: self.c.Tr.CommitDescriptionTitle, DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
PreserveMessage: false, PreserveMessage: false,
OnConfirm: self.handleReword, OnConfirm: self.handleReword,
OnSwitchToEditor: self.switchFromCommitMessagePanelToEditor,
}, },
) )
} }
func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepath string) error {
if self.isHeadCommit() {
return self.c.RunSubprocessAndRefresh(
self.c.Git().Commit.RewordLastCommitInEditorWithMessageFileCmdObj(filepath))
}
err := self.c.Git().Rebase.BeginInteractiveRebaseForCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx(), false)
if err != nil {
return err
}
// now the selected commit should be our head so we'll amend it with the new message
err = self.c.RunSubprocessAndRefresh(
self.c.Git().Commit.RewordLastCommitInEditorWithMessageFileCmdObj(filepath))
if err != nil {
return err
}
err = self.c.Git().Rebase.ContinueRebase()
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (self *LocalCommitsController) handleReword(summary string, description string) error { func (self *LocalCommitsController) handleReword(summary string, description string) error {
err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description) err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description)
if err != nil { if err != nil {

View File

@ -2,9 +2,7 @@ package gui
import ( import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -164,10 +162,6 @@ func (gui *Gui) createAllViews() error {
gui.Views.CommitDescription.Visible = false gui.Views.CommitDescription.Visible = false
gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle
gui.Views.CommitDescription.Subtitle = utils.ResolvePlaceholderString(gui.Tr.CommitDescriptionSubTitle,
map[string]string{
"togglePanelKeyBinding": keybindings.Label(gui.UserConfig.Keybinding.Universal.TogglePanel),
})
gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitDescription.Editable = true gui.Views.CommitDescription.Editable = true
gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor) gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor)

View File

@ -199,6 +199,7 @@ type TranslationSet struct {
CommitSummaryTitle string CommitSummaryTitle string
CommitDescriptionTitle string CommitDescriptionTitle string
CommitDescriptionSubTitle string CommitDescriptionSubTitle string
CommitDescriptionSubTitleNoSwitch string
LocalBranchesTitle string LocalBranchesTitle string
SearchTitle string SearchTitle string
TagsTitle string TagsTitle string
@ -969,7 +970,8 @@ func EnglishTranslationSet() TranslationSet {
RebaseOptionsTitle: "Rebase options", RebaseOptionsTitle: "Rebase options",
CommitSummaryTitle: "Commit summary", CommitSummaryTitle: "Commit summary",
CommitDescriptionTitle: "Commit description", CommitDescriptionTitle: "Commit description",
CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus", CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus, {{.switchToEditorKeyBinding}} to switch to editor",
CommitDescriptionSubTitleNoSwitch: "Press {{.togglePanelKeyBinding}} to toggle focus",
LocalBranchesTitle: "Local branches", LocalBranchesTitle: "Local branches",
SearchTitle: "Search", SearchTitle: "Search",
TagsTitle: "Tags", TagsTitle: "Tags",

View File

@ -74,6 +74,10 @@ func (self *CommitMessagePanelDriver) Cancel() {
self.getViewDriver().PressEscape() self.getViewDriver().PressEscape()
} }
func (self *CommitMessagePanelDriver) SwitchToEditor() {
self.getViewDriver().Press(self.t.keys.CommitMessage.SwitchToEditor)
}
func (self *CommitMessagePanelDriver) SelectPreviousMessage() *CommitMessagePanelDriver { func (self *CommitMessagePanelDriver) SelectPreviousMessage() *CommitMessagePanelDriver {
self.getViewDriver().SelectPreviousItem() self.getViewDriver().SelectPreviousItem()
return self return self

View File

@ -0,0 +1,54 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CommitSwitchToEditor = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit, then switch from built-in commit message panel to editor",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFile("file1", "file1 content")
shell.CreateFile("file2", "file2 content")
// Set an editor that appends a line to the existing message. Since
// git adds all this "# Please enter the commit message for your changes"
// stuff, this will result in an extra blank line before the added line.
shell.SetConfig("core.editor", "sh -c 'echo third line >>.git/COMMIT_EDITMSG'")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
IsEmpty()
t.Views().Files().
IsFocused().
PressPrimaryAction(). // stage one of the files
Press(keys.Files.CommitChanges)
t.ExpectPopup().CommitMessagePanel().
Type("first line").
SwitchToDescription().
Type("second line").
SwitchToSummary().
SwitchToEditor()
t.Views().Commits().
Lines(
Contains("first line"),
)
t.Views().Commits().Focus()
t.Views().Main().Content(MatchesRegexp(`first line\n\s*\n\s*second line\n\s*\n\s*third line`))
// Now check that the preserved commit message was cleared:
t.Views().Files().
Focus().
PressPrimaryAction(). // stage the other file
Press(keys.Files.CommitChanges)
t.ExpectPopup().CommitMessagePanel().
InitialText(Equals(""))
},
})

View File

@ -55,6 +55,7 @@ var tests = []*components.IntegrationTest{
commit.Amend, commit.Amend,
commit.Commit, commit.Commit,
commit.CommitMultiline, commit.CommitMultiline,
commit.CommitSwitchToEditor,
commit.CommitWipWithPrefix, commit.CommitWipWithPrefix,
commit.CommitWithPrefix, commit.CommitWithPrefix,
commit.CreateTag, commit.CreateTag,