mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-29 23:17:32 +02:00
Switch to editor from commit message panel (#2881)
This commit is contained in:
commit
c31fcb7134
@ -68,6 +68,19 @@ func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj
|
||||
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
|
||||
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
|
||||
messageArgs := self.commitMessageArgs(summary, description)
|
||||
|
@ -132,16 +132,17 @@ type UpdateConfig struct {
|
||||
}
|
||||
|
||||
type KeybindingConfig struct {
|
||||
Universal KeybindingUniversalConfig `yaml:"universal"`
|
||||
Status KeybindingStatusConfig `yaml:"status"`
|
||||
Files KeybindingFilesConfig `yaml:"files"`
|
||||
Branches KeybindingBranchesConfig `yaml:"branches"`
|
||||
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
|
||||
Commits KeybindingCommitsConfig `yaml:"commits"`
|
||||
Stash KeybindingStashConfig `yaml:"stash"`
|
||||
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
|
||||
Main KeybindingMainConfig `yaml:"main"`
|
||||
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
|
||||
Universal KeybindingUniversalConfig `yaml:"universal"`
|
||||
Status KeybindingStatusConfig `yaml:"status"`
|
||||
Files KeybindingFilesConfig `yaml:"files"`
|
||||
Branches KeybindingBranchesConfig `yaml:"branches"`
|
||||
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
|
||||
Commits KeybindingCommitsConfig `yaml:"commits"`
|
||||
Stash KeybindingStashConfig `yaml:"stash"`
|
||||
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
|
||||
Main KeybindingMainConfig `yaml:"main"`
|
||||
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
|
||||
CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"`
|
||||
}
|
||||
|
||||
// damn looks like we have some inconsistencies here with -alt and -alt1
|
||||
@ -305,6 +306,10 @@ type KeybindingSubmodulesConfig struct {
|
||||
BulkMenu string `yaml:"bulkMenu"`
|
||||
}
|
||||
|
||||
type KeybindingCommitMessageConfig struct {
|
||||
SwitchToEditor string `yaml:"switchToEditor"`
|
||||
}
|
||||
|
||||
// OSConfig contains config on the level of the os
|
||||
type OSConfig struct {
|
||||
// Command for editing a file. Should contain "{{filename}}".
|
||||
@ -652,6 +657,9 @@ func GetDefaultConfig() *UserConfig {
|
||||
Update: "u",
|
||||
BulkMenu: "b",
|
||||
},
|
||||
CommitMessage: KeybindingCommitMessageConfig{
|
||||
SwitchToEditor: "<c-o>",
|
||||
},
|
||||
},
|
||||
OS: OSConfig{},
|
||||
DisableStartupPopups: false,
|
||||
|
@ -5,7 +5,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CommitMessageContext struct {
|
||||
@ -32,6 +35,8 @@ type CommitMessageViewModel struct {
|
||||
preservedMessage string
|
||||
// invoked when pressing enter in the commit message panel
|
||||
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
|
||||
// We store this separately to 'preservedMessage' because 'preservedMessage'
|
||||
@ -98,12 +103,21 @@ func (self *CommitMessageContext) SetPanelState(
|
||||
descriptionTitle string,
|
||||
preserveMessage bool,
|
||||
onConfirm func(string, string) error,
|
||||
onSwitchToEditor func(string) error,
|
||||
) {
|
||||
self.viewModel.selectedindex = index
|
||||
self.viewModel.preserveMessage = preserveMessage
|
||||
self.viewModel.onConfirm = onConfirm
|
||||
self.viewModel.onSwitchToEditor = onSwitchToEditor
|
||||
self.GetView().Title = summaryTitle
|
||||
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() {
|
||||
@ -117,3 +131,11 @@ func (self *CommitMessageContext) RenderCommitLength() {
|
||||
func getBufferLength(view *gocui.View) string {
|
||||
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
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"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),
|
||||
Handler: self.confirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
|
||||
Handler: self.switchToEditor,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@ -43,7 +48,7 @@ func (self *CommitDescriptionController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) context() types.Context {
|
||||
func (self *CommitDescriptionController) context() *context.CommitMessageContext {
|
||||
return self.c.Contexts().CommitMessage
|
||||
}
|
||||
|
||||
@ -58,3 +63,7 @@ func (self *CommitDescriptionController) close() error {
|
||||
func (self *CommitDescriptionController) confirm() error {
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToEditor() error {
|
||||
return self.c.Helpers().Commits.SwitchToEditor()
|
||||
}
|
||||
|
@ -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 {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
@ -48,6 +46,10 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitDescription,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
|
||||
Handler: self.switchToEditor,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@ -86,6 +88,10 @@ func (self *CommitMessageController) switchToCommitDescription() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) switchToEditor() error {
|
||||
return self.c.Helpers().Commits.SwitchToEditor()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
currentIndex := self.context().GetSelectedIndex()
|
||||
newIndex := currentIndex + value
|
||||
|
@ -1,9 +1,12 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type ICommitsHelper interface {
|
||||
@ -62,6 +65,28 @@ func (self *CommitsHelper) JoinCommitMessageAndDescription() string {
|
||||
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) {
|
||||
if message != "" {
|
||||
self.SetMessageAndDescriptionInView(message)
|
||||
@ -83,6 +108,7 @@ type OpenCommitMessagePanelOpts struct {
|
||||
DescriptionTitle string
|
||||
PreserveMessage bool
|
||||
OnConfirm func(summary string, description string) error
|
||||
OnSwitchToEditor func(string) error
|
||||
InitialMessage string
|
||||
}
|
||||
|
||||
@ -101,6 +127,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
opts.DescriptionTitle,
|
||||
opts.PreserveMessage,
|
||||
onConfirm,
|
||||
opts.OnSwitchToEditor,
|
||||
)
|
||||
|
||||
self.UpdateCommitPanelView(opts.InitialMessage)
|
||||
|
@ -104,6 +104,7 @@ func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage strin
|
||||
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||
PreserveMessage: true,
|
||||
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
|
||||
// their editor rather than via the popup panel
|
||||
func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
|
||||
|
@ -279,10 +279,37 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||
PreserveMessage: false,
|
||||
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 {
|
||||
err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description)
|
||||
if err != nil {
|
||||
|
@ -2,9 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@ -164,10 +162,6 @@ func (gui *Gui) createAllViews() error {
|
||||
|
||||
gui.Views.CommitDescription.Visible = false
|
||||
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.Editable = true
|
||||
gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor)
|
||||
|
@ -199,6 +199,7 @@ type TranslationSet struct {
|
||||
CommitSummaryTitle string
|
||||
CommitDescriptionTitle string
|
||||
CommitDescriptionSubTitle string
|
||||
CommitDescriptionSubTitleNoSwitch string
|
||||
LocalBranchesTitle string
|
||||
SearchTitle string
|
||||
TagsTitle string
|
||||
@ -969,7 +970,8 @@ func EnglishTranslationSet() TranslationSet {
|
||||
RebaseOptionsTitle: "Rebase options",
|
||||
CommitSummaryTitle: "Commit summary",
|
||||
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",
|
||||
SearchTitle: "Search",
|
||||
TagsTitle: "Tags",
|
||||
|
@ -74,6 +74,10 @@ func (self *CommitMessagePanelDriver) Cancel() {
|
||||
self.getViewDriver().PressEscape()
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelDriver) SwitchToEditor() {
|
||||
self.getViewDriver().Press(self.t.keys.CommitMessage.SwitchToEditor)
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelDriver) SelectPreviousMessage() *CommitMessagePanelDriver {
|
||||
self.getViewDriver().SelectPreviousItem()
|
||||
return self
|
||||
|
54
pkg/integration/tests/commit/commit_switch_to_editor.go
Normal file
54
pkg/integration/tests/commit/commit_switch_to_editor.go
Normal 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(""))
|
||||
},
|
||||
})
|
@ -55,6 +55,7 @@ var tests = []*components.IntegrationTest{
|
||||
commit.Amend,
|
||||
commit.Commit,
|
||||
commit.CommitMultiline,
|
||||
commit.CommitSwitchToEditor,
|
||||
commit.CommitWipWithPrefix,
|
||||
commit.CommitWithPrefix,
|
||||
commit.CreateTag,
|
||||
|
Loading…
x
Reference in New Issue
Block a user