From 726363096778dc95d7cf7c5d1e88824efc23929f Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 17 May 2023 21:28:27 +0200 Subject: [PATCH 1/4] Remove obsolete comment --- pkg/gui/controllers/commit_message_controller.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index 141f24618..9e180db5a 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -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{ { From 61bd3e8dd2d00db3259ddc4f930770127b6de3d0 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 18 May 2023 19:15:23 +0200 Subject: [PATCH 2/4] Add key binding for switching from the commit message panel to an editor This is useful for when you begin to type the message in lazygit's commit panel, and then realize that you'd rather use your editor's more powerful editing capabilities. Pressing will take you right there. --- pkg/commands/git_commands/commit.go | 13 +++++++++ pkg/config/user_config.go | 28 ++++++++++++------- pkg/gui/context/commit_message_context.go | 12 ++++++++ .../commit_description_controller.go | 11 +++++++- .../controllers/commit_message_controller.go | 8 ++++++ pkg/gui/controllers/helpers/commits_helper.go | 27 ++++++++++++++++++ .../helpers/working_tree_helper.go | 16 +++++++++++ .../controllers/local_commits_controller.go | 27 ++++++++++++++++++ 8 files changed, 131 insertions(+), 11 deletions(-) diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go index 16702f9be..ec27d66cb 100644 --- a/pkg/commands/git_commands/commit.go +++ b/pkg/commands/git_commands/commit.go @@ -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) diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 495e76df8..09631374f 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -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: "", + }, }, OS: OSConfig{}, DisableStartupPopups: false, diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go index 4ad99e12c..0da0211a8 100644 --- a/pkg/gui/context/commit_message_context.go +++ b/pkg/gui/context/commit_message_context.go @@ -32,6 +32,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,10 +100,12 @@ 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 } @@ -117,3 +121,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 +} diff --git a/pkg/gui/controllers/commit_description_controller.go b/pkg/gui/controllers/commit_description_controller.go index 78d275184..13bb5949f 100644 --- a/pkg/gui/controllers/commit_description_controller.go +++ b/pkg/gui/controllers/commit_description_controller.go @@ -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() +} diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index 9e180db5a..fc5aca970 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -46,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 @@ -84,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 diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 10a28ad5f..5d388c319 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -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) diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go index dba9d2a6a..4f7a6bd83 100644 --- a/pkg/gui/controllers/helpers/working_tree_helper.go +++ b/pkg/gui/controllers/helpers/working_tree_helper.go @@ -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 { diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 4a2627cdc..38ddaa515 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -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 { From 8f628296adb177ac06dace8f4da92ac14384da79 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 18 May 2023 19:20:42 +0200 Subject: [PATCH 3/4] Mention ctrl+o binding in commit message sub title Only do this when an onSwitchToEditor function is actually provided. For the "Move patch into new commit" command we don't, because it isn't totally straightforward in that case. --- pkg/gui/context/commit_message_context.go | 10 ++++++++++ pkg/gui/views.go | 6 ------ pkg/i18n/english.go | 4 +++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go index 0da0211a8..1ac158839 100644 --- a/pkg/gui/context/commit_message_context.go +++ b/pkg/gui/context/commit_message_context.go @@ -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 { @@ -108,6 +111,13 @@ func (self *CommitMessageContext) SetPanelState( 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() { diff --git a/pkg/gui/views.go b/pkg/gui/views.go index d9cf15ffe..327feb1bc 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -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) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 109f13e42..50b62549f 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -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", From 91ec42f3f8ddb14af44c785ba4606ef134d5f68e Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 21 Aug 2023 10:02:14 +0200 Subject: [PATCH 4/4] Add integration test --- .../components/commit_message_panel_driver.go | 4 ++ .../tests/commit/commit_switch_to_editor.go | 54 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 3 files changed, 59 insertions(+) create mode 100644 pkg/integration/tests/commit/commit_switch_to_editor.go diff --git a/pkg/integration/components/commit_message_panel_driver.go b/pkg/integration/components/commit_message_panel_driver.go index f9089ee2e..52ad60815 100644 --- a/pkg/integration/components/commit_message_panel_driver.go +++ b/pkg/integration/components/commit_message_panel_driver.go @@ -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 diff --git a/pkg/integration/tests/commit/commit_switch_to_editor.go b/pkg/integration/tests/commit/commit_switch_to_editor.go new file mode 100644 index 000000000..069384ab1 --- /dev/null +++ b/pkg/integration/tests/commit/commit_switch_to_editor.go @@ -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("")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index a8b22413c..779286fbc 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -55,6 +55,7 @@ var tests = []*components.IntegrationTest{ commit.Amend, commit.Commit, commit.CommitMultiline, + commit.CommitSwitchToEditor, commit.CommitWipWithPrefix, commit.CommitWithPrefix, commit.CreateTag,