From 8d8cf42786f2f57f6972a95c34553130cba4f5d2 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 14:04:45 +0100 Subject: [PATCH 1/7] Refactor: extract body of wrappedConfirmationFunction into a helper function And call this new helper function from both wrappedConfirmationFunction and wrappedPromptConfirmationFunction; this gives us more flexibility to do different things in each of those. --- .../helpers/confirmation_helper.go | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index 1599d5e86..cc06e03af 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -24,34 +24,40 @@ func NewConfirmationHelper(c *HelperCommon) *ConfirmationHelper { // This file is for the rendering of confirmation panels along with setting and handling associated // keybindings. +func (self *ConfirmationHelper) closeAndCallConfirmationFunction(cancel goContext.CancelFunc, function func() error) error { + if self.c.GocuiGui().IsPasting { + // The user is pasting multi-line text into a prompt; we don't want to handle the + // line feeds as "confirm" keybindings. Simply ignoring them is the best we can do; this + // will cause the entire pasted text to appear as a single line in the prompt. Hopefully + // the user knows that ctrl-u allows them to delete it again... + return nil + } + + cancel() + + self.c.Context().Pop() + + if function != nil { + if err := function(); err != nil { + return err + } + } + + return nil +} + func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error { return func() error { - if self.c.GocuiGui().IsPasting { - // The user is pasting multi-line text into a prompt; we don't want to handle the - // line feeds as "confirm" keybindings. Simply ignoring them is the best we can do; this - // will cause the entire pasted text to appear as a single line in the prompt. Hopefully - // the user knows that ctrl-u allows them to delete it again... - return nil - } - - cancel() - - self.c.Context().Pop() - - if function != nil { - if err := function(); err != nil { - return err - } - } - - return nil + return self.closeAndCallConfirmationFunction(cancel, function) } } func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error { - return self.wrappedConfirmationFunction(cancel, func() error { - return function(getResponse()) - }) + return func() error { + return self.closeAndCallConfirmationFunction(cancel, func() error { + return function(getResponse()) + }) + } } func (self *ConfirmationHelper) DeactivateConfirmation() { From 48330141bd72c4408c42503a02fb368d62a024f0 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 14:13:24 +0100 Subject: [PATCH 2/7] Cleanup: put the IsPasting logic in the right place Previously it was used both for the Confirm handler and the Cancel handler, as well as for the Confirm handler of confirmation popups (not prompts). There was no other way to do it given how wrappedConfirmationFunction was shared between all these; but now there is. The logic is really only needed for the Confirm handler of prompts. This doesn't fix anything, it just makes things clearer. --- .../controllers/helpers/confirmation_helper.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index cc06e03af..d96159aea 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -25,14 +25,6 @@ func NewConfirmationHelper(c *HelperCommon) *ConfirmationHelper { // keybindings. func (self *ConfirmationHelper) closeAndCallConfirmationFunction(cancel goContext.CancelFunc, function func() error) error { - if self.c.GocuiGui().IsPasting { - // The user is pasting multi-line text into a prompt; we don't want to handle the - // line feeds as "confirm" keybindings. Simply ignoring them is the best we can do; this - // will cause the entire pasted text to appear as a single line in the prompt. Hopefully - // the user knows that ctrl-u allows them to delete it again... - return nil - } - cancel() self.c.Context().Pop() @@ -54,6 +46,14 @@ func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.Can func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error { return func() error { + if self.c.GocuiGui().IsPasting { + // The user is pasting multi-line text into a prompt; we don't want to handle the + // line feeds as "confirm" keybindings. Simply ignoring them is the best we can do; this + // will cause the entire pasted text to appear as a single line in the prompt. Hopefully + // the user knows that ctrl-u allows them to delete it again... + return nil + } + return self.closeAndCallConfirmationFunction(cancel, func() error { return function(getResponse()) }) From a7bc1012b9ec97699fdee89586cd3f7445c3339b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 14:46:19 +0100 Subject: [PATCH 3/7] Cleanup: fix incorrect comment The comment was apparently copy/pasted from above; the branch name cannot be blank in this case. --- pkg/gui/controllers/helpers/worktree_helper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go index ea889781e..419dcbe64 100644 --- a/pkg/gui/controllers/helpers/worktree_helper.go +++ b/pkg/gui/controllers/helpers/worktree_helper.go @@ -135,7 +135,7 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo return nil } - // prompt for the new branch name where a blank means we just check out the branch + // prompt for the new branch name self.c.Prompt(types.PromptOpts{ Title: self.c.Tr.NewBranchName, HandleConfirm: func(branchName string) error { From b3435bd59cc96a81f4ac46b750e3551546e49b70 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 13:05:39 +0100 Subject: [PATCH 4/7] Add AllowEmptyInput flag to PromptOpts Most of our prompts don't (shouldn't) allow empty input, but most callers didn't check, and would run into cryptic errors when the user pressed enter at an empty prompt (e.g. when creating a new branch). Now we simply don't allow hitting enter in this case, and show an error toast instead. This behavior is opt-out, because there are a few cases where empty input is supported (e.g. creating a stash). --- pkg/gui/controllers/files_controller.go | 1 + .../helpers/confirmation_helper.go | 20 ++++++++++++++++--- .../controllers/helpers/credentials_helper.go | 1 + .../controllers/helpers/worktree_helper.go | 2 ++ pkg/gui/controllers/stash_controller.go | 1 + pkg/gui/popup/popup_handler.go | 1 + .../custom_commands/handler_creator.go | 1 + pkg/gui/types/common.go | 2 ++ pkg/i18n/english.go | 2 ++ 9 files changed, 28 insertions(+), 3 deletions(-) diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 0289936de..d9477db4a 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -1211,6 +1211,7 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}}) return nil }, + AllowEmptyInput: true, }) return nil diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index d96159aea..2650de203 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -44,7 +44,12 @@ func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.Can } } -func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error { +func (self *ConfirmationHelper) wrappedPromptConfirmationFunction( + cancel goContext.CancelFunc, + function func(string) error, + getResponse func() string, + allowEmptyInput bool, +) func() error { return func() error { if self.c.GocuiGui().IsPasting { // The user is pasting multi-line text into a prompt; we don't want to handle the @@ -54,8 +59,15 @@ func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goConte return nil } + response := getResponse() + + if response == "" && !allowEmptyInput { + self.c.ErrorToast(self.c.Tr.PromptInputCannotBeEmptyToast) + return nil + } + return self.closeAndCallConfirmationFunction(cancel, func() error { - return function(getResponse()) + return function(response) }) } } @@ -235,12 +247,14 @@ func (self *ConfirmationHelper) setConfirmationKeyBindings(cancel goContext.Canc func (self *ConfirmationHelper) setPromptKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { onConfirm := self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, - func() string { return self.c.Views().Prompt.TextArea.GetContent() }) + func() string { return self.c.Views().Prompt.TextArea.GetContent() }, + opts.AllowEmptyInput) onSuggestionConfirm := self.wrappedPromptConfirmationFunction( cancel, opts.HandleConfirmPrompt, self.getSelectedSuggestionValue, + opts.AllowEmptyInput, ) onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) diff --git a/pkg/gui/controllers/helpers/credentials_helper.go b/pkg/gui/controllers/helpers/credentials_helper.go index 0783e65f2..2d3654887 100644 --- a/pkg/gui/controllers/helpers/credentials_helper.go +++ b/pkg/gui/controllers/helpers/credentials_helper.go @@ -41,6 +41,7 @@ func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.Cr return nil }, + AllowEmptyInput: true, }) return nil diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go index 419dcbe64..39ffadbdc 100644 --- a/pkg/gui/controllers/helpers/worktree_helper.go +++ b/pkg/gui/controllers/helpers/worktree_helper.go @@ -130,6 +130,7 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo return f() }, + AllowEmptyInput: true, }) return nil @@ -147,6 +148,7 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo return f() }, + AllowEmptyInput: false, }) return nil diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go index 889bc0872..87f84d7ea 100644 --- a/pkg/gui/controllers/stash_controller.go +++ b/pkg/gui/controllers/stash_controller.go @@ -209,6 +209,7 @@ func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntr self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) return nil }, + AllowEmptyInput: true, }) return nil diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go index d8ce654b4..2cfbca946 100644 --- a/pkg/gui/popup/popup_handler.go +++ b/pkg/gui/popup/popup_handler.go @@ -139,6 +139,7 @@ func (self *PopupHandler) Prompt(opts types.PromptOpts) { HandleDeleteSuggestion: opts.HandleDeleteSuggestion, FindSuggestionsFunc: opts.FindSuggestionsFunc, AllowEditSuggestion: opts.AllowEditSuggestion, + AllowEmptyInput: opts.AllowEmptyInput, Mask: opts.Mask, }) } diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go index 73e68c3b3..1bfaaaa0f 100644 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ b/pkg/gui/services/custom_commands/handler_creator.go @@ -125,6 +125,7 @@ func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrap HandleConfirm: func(str string) error { return wrappedF(str) }, + AllowEmptyInput: true, }) return nil diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index 905a9b65d..f754e0da5 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -172,6 +172,7 @@ type CreatePopupPanelOpts struct { FindSuggestionsFunc func(string) []*Suggestion Mask bool AllowEditSuggestion bool + AllowEmptyInput bool } type ConfirmOpts struct { @@ -190,6 +191,7 @@ type PromptOpts struct { FindSuggestionsFunc func(string) []*Suggestion HandleConfirm func(string) error AllowEditSuggestion bool + AllowEmptyInput bool // CAPTURE THIS HandleClose func() error HandleDeleteSuggestion func(int) error diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index cb070b892..794f58570 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -618,6 +618,7 @@ type TranslationSet struct { MustStashTitle string ConfirmationTitle string PromptTitle string + PromptInputCannotBeEmptyToast string PrevPage string NextPage string GotoTop string @@ -1713,6 +1714,7 @@ func EnglishTranslationSet() *TranslationSet { MustStashTitle: "Must stash", ConfirmationTitle: "Confirmation panel", PromptTitle: "Input prompt", + PromptInputCannotBeEmptyToast: "Empty input is not allowed", PrevPage: "Previous page", NextPage: "Next page", GotoTop: "Scroll to top", From d7e733cd5603b2e1d93680c72b7960a133235edf Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 14:48:04 +0100 Subject: [PATCH 5/7] Remove now unnecessary check for empty string As of the previous commit, branchName can no longer be empty, so no need to handle this. --- pkg/gui/controllers/helpers/worktree_helper.go | 4 ---- pkg/i18n/english.go | 2 -- 2 files changed, 6 deletions(-) diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go index 39ffadbdc..671743fe6 100644 --- a/pkg/gui/controllers/helpers/worktree_helper.go +++ b/pkg/gui/controllers/helpers/worktree_helper.go @@ -140,10 +140,6 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo self.c.Prompt(types.PromptOpts{ Title: self.c.Tr.NewBranchName, HandleConfirm: func(branchName string) error { - if branchName == "" { - return errors.New(self.c.Tr.BranchNameCannotBeBlank) - } - opts.Branch = branchName return f() diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 794f58570..c9ca03275 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -862,7 +862,6 @@ type TranslationSet struct { NewWorktreePath string NewWorktreeBase string RemoveWorktreeTooltip string - BranchNameCannotBeBlank string NewBranchName string NewBranchNameLeaveBlank string ViewWorktreeOptions string @@ -1956,7 +1955,6 @@ func EnglishTranslationSet() *TranslationSet { NewWorktreePath: "New worktree path", NewWorktreeBase: "New worktree base ref", RemoveWorktreeTooltip: "Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory.", - BranchNameCannotBeBlank: "Branch name cannot be blank", NewBranchName: "New branch name", NewBranchNameLeaveBlank: "New branch name (leave blank to checkout {{.default}})", ViewWorktreeOptions: "View worktree options", From 7c126cd2fc6d3723004948d3a33178c0e58ee539 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 14:24:54 +0100 Subject: [PATCH 6/7] Strip leading/trailing whitespace from prompt input This doesn't really solve a pressing problem, because I guess it's unlikely that users add spaces at the beginning or end of what they type into a prompt; but it could happen, and in this case we almost always want to strip it. Just adding this here for completeness while I was working on this code. The only exception is the input prompt of custom commands, because who knows what users want to use that input for in their custom command. --- pkg/gui/controllers/helpers/confirmation_helper.go | 7 ++++++- pkg/gui/controllers/shell_command_action.go | 1 + pkg/gui/popup/popup_handler.go | 1 + pkg/gui/services/custom_commands/handler_creator.go | 3 ++- pkg/gui/types/common.go | 2 ++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index 2650de203..b1c334914 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -49,6 +49,7 @@ func (self *ConfirmationHelper) wrappedPromptConfirmationFunction( function func(string) error, getResponse func() string, allowEmptyInput bool, + preserveWhitespace bool, ) func() error { return func() error { if self.c.GocuiGui().IsPasting { @@ -60,6 +61,9 @@ func (self *ConfirmationHelper) wrappedPromptConfirmationFunction( } response := getResponse() + if !preserveWhitespace { + response = strings.TrimSpace(response) + } if response == "" && !allowEmptyInput { self.c.ErrorToast(self.c.Tr.PromptInputCannotBeEmptyToast) @@ -248,13 +252,14 @@ func (self *ConfirmationHelper) setConfirmationKeyBindings(cancel goContext.Canc func (self *ConfirmationHelper) setPromptKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { onConfirm := self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return self.c.Views().Prompt.TextArea.GetContent() }, - opts.AllowEmptyInput) + opts.AllowEmptyInput, opts.PreserveWhitespace) onSuggestionConfirm := self.wrappedPromptConfirmationFunction( cancel, opts.HandleConfirmPrompt, self.getSelectedSuggestionValue, opts.AllowEmptyInput, + opts.PreserveWhitespace, ) onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) diff --git a/pkg/gui/controllers/shell_command_action.go b/pkg/gui/controllers/shell_command_action.go index 185f74893..114c7fcb1 100644 --- a/pkg/gui/controllers/shell_command_action.go +++ b/pkg/gui/controllers/shell_command_action.go @@ -19,6 +19,7 @@ func (self *ShellCommandAction) Call() error { Title: self.c.Tr.ShellCommand, FindSuggestionsFunc: self.GetShellCommandsHistorySuggestionsFunc(), AllowEditSuggestion: true, + PreserveWhitespace: true, HandleConfirm: func(command string) error { if self.shouldSaveCommand(command) { self.c.GetAppState().ShellCommandsHistory = utils.Limit( diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go index 2cfbca946..d8824016f 100644 --- a/pkg/gui/popup/popup_handler.go +++ b/pkg/gui/popup/popup_handler.go @@ -140,6 +140,7 @@ func (self *PopupHandler) Prompt(opts types.PromptOpts) { FindSuggestionsFunc: opts.FindSuggestionsFunc, AllowEditSuggestion: opts.AllowEditSuggestion, AllowEmptyInput: opts.AllowEmptyInput, + PreserveWhitespace: opts.PreserveWhitespace, Mask: opts.Mask, }) } diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go index 1bfaaaa0f..04899b004 100644 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ b/pkg/gui/services/custom_commands/handler_creator.go @@ -125,7 +125,8 @@ func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrap HandleConfirm: func(str string) error { return wrappedF(str) }, - AllowEmptyInput: true, + AllowEmptyInput: true, + PreserveWhitespace: true, }) return nil diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go index f754e0da5..2afe4f7d3 100644 --- a/pkg/gui/types/common.go +++ b/pkg/gui/types/common.go @@ -173,6 +173,7 @@ type CreatePopupPanelOpts struct { Mask bool AllowEditSuggestion bool AllowEmptyInput bool + PreserveWhitespace bool } type ConfirmOpts struct { @@ -192,6 +193,7 @@ type PromptOpts struct { HandleConfirm func(string) error AllowEditSuggestion bool AllowEmptyInput bool + PreserveWhitespace bool // CAPTURE THIS HandleClose func() error HandleDeleteSuggestion func(int) error From 95e5ed6f47c9d34d45b1292e190abe0163bcafab Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 15 Nov 2025 14:48:59 +0100 Subject: [PATCH 7/7] Remove TrimSpace calls that are no longer needed The prompt code takes care of this now. --- pkg/gui/controllers/diffing_menu_action.go | 3 +-- pkg/gui/controllers/filtering_menu_action.go | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/gui/controllers/diffing_menu_action.go b/pkg/gui/controllers/diffing_menu_action.go index a53ad65e5..3ae5903d9 100644 --- a/pkg/gui/controllers/diffing_menu_action.go +++ b/pkg/gui/controllers/diffing_menu_action.go @@ -2,7 +2,6 @@ package controllers import ( "fmt" - "strings" "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" "github.com/jesseduffield/lazygit/pkg/gui/types" @@ -38,7 +37,7 @@ func (self *DiffingMenuAction) Call() error { Title: self.c.Tr.EnterRefName, FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(), HandleConfirm: func(response string) error { - self.c.Modes().Diffing.Ref = strings.TrimSpace(response) + self.c.Modes().Diffing.Ref = response self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) return nil }, diff --git a/pkg/gui/controllers/filtering_menu_action.go b/pkg/gui/controllers/filtering_menu_action.go index 2ed072676..01a236f7a 100644 --- a/pkg/gui/controllers/filtering_menu_action.go +++ b/pkg/gui/controllers/filtering_menu_action.go @@ -2,7 +2,6 @@ package controllers import ( "fmt" - "strings" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/types" @@ -66,7 +65,7 @@ func (self *FilteringMenuAction) Call() error { FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(), Title: self.c.Tr.EnterFileName, HandleConfirm: func(response string) error { - return self.setFilteringPath(strings.TrimSpace(response)) + return self.setFilteringPath(response) }, }) @@ -82,7 +81,7 @@ func (self *FilteringMenuAction) Call() error { FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), Title: self.c.Tr.EnterAuthor, HandleConfirm: func(response string) error { - return self.setFilteringAuthor(strings.TrimSpace(response)) + return self.setFilteringAuthor(response) }, })