1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-11-23 16:22:24 +03:00

Don't allow empty input in most prompts (#5043)

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).
This commit is contained in:
Stefan Haller
2025-11-15 15:41:54 +01:00
committed by GitHub
12 changed files with 61 additions and 28 deletions

View File

@@ -2,7 +2,6 @@ package controllers
import ( import (
"fmt" "fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -38,7 +37,7 @@ func (self *DiffingMenuAction) Call() error {
Title: self.c.Tr.EnterRefName, Title: self.c.Tr.EnterRefName,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(), FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error { 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}) self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil return nil
}, },

View File

@@ -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}}) self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
return nil return nil
}, },
AllowEmptyInput: true,
}) })
return nil return nil

View File

@@ -2,7 +2,6 @@ package controllers
import ( import (
"fmt" "fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -66,7 +65,7 @@ func (self *FilteringMenuAction) Call() error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(), FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(),
Title: self.c.Tr.EnterFileName, Title: self.c.Tr.EnterFileName,
HandleConfirm: func(response string) error { 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(), FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
Title: self.c.Tr.EnterAuthor, Title: self.c.Tr.EnterAuthor,
HandleConfirm: func(response string) error { HandleConfirm: func(response string) error {
return self.setFilteringAuthor(strings.TrimSpace(response)) return self.setFilteringAuthor(response)
}, },
}) })

View File

@@ -24,16 +24,7 @@ func NewConfirmationHelper(c *HelperCommon) *ConfirmationHelper {
// This file is for the rendering of confirmation panels along with setting and handling associated // This file is for the rendering of confirmation panels along with setting and handling associated
// keybindings. // keybindings.
func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error { func (self *ConfirmationHelper) closeAndCallConfirmationFunction(cancel goContext.CancelFunc, function func() error) 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() cancel()
self.c.Context().Pop() self.c.Context().Pop()
@@ -46,13 +37,44 @@ func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.Can
return nil return nil
} }
func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error {
return func() error {
return self.closeAndCallConfirmationFunction(cancel, function)
}
} }
func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error { func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(
return self.wrappedConfirmationFunction(cancel, func() error { cancel goContext.CancelFunc,
return function(getResponse()) function func(string) error,
getResponse func() string,
allowEmptyInput bool,
preserveWhitespace 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
// 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
}
response := getResponse()
if !preserveWhitespace {
response = strings.TrimSpace(response)
}
if response == "" && !allowEmptyInput {
self.c.ErrorToast(self.c.Tr.PromptInputCannotBeEmptyToast)
return nil
}
return self.closeAndCallConfirmationFunction(cancel, func() error {
return function(response)
}) })
} }
}
func (self *ConfirmationHelper) DeactivateConfirmation() { func (self *ConfirmationHelper) DeactivateConfirmation() {
self.c.Mutexes().PopupMutex.Lock() self.c.Mutexes().PopupMutex.Lock()
@@ -229,12 +251,15 @@ func (self *ConfirmationHelper) setConfirmationKeyBindings(cancel goContext.Canc
func (self *ConfirmationHelper) setPromptKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { func (self *ConfirmationHelper) setPromptKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
onConfirm := self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, 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, opts.PreserveWhitespace)
onSuggestionConfirm := self.wrappedPromptConfirmationFunction( onSuggestionConfirm := self.wrappedPromptConfirmationFunction(
cancel, cancel,
opts.HandleConfirmPrompt, opts.HandleConfirmPrompt,
self.getSelectedSuggestionValue, self.getSelectedSuggestionValue,
opts.AllowEmptyInput,
opts.PreserveWhitespace,
) )
onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose)

View File

@@ -41,6 +41,7 @@ func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.Cr
return nil return nil
}, },
AllowEmptyInput: true,
}) })
return nil return nil

View File

@@ -130,23 +130,21 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo
return f() return f()
}, },
AllowEmptyInput: true,
}) })
return nil 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{ self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.NewBranchName, Title: self.c.Tr.NewBranchName,
HandleConfirm: func(branchName string) error { HandleConfirm: func(branchName string) error {
if branchName == "" {
return errors.New(self.c.Tr.BranchNameCannotBeBlank)
}
opts.Branch = branchName opts.Branch = branchName
return f() return f()
}, },
AllowEmptyInput: false,
}) })
return nil return nil

View File

@@ -19,6 +19,7 @@ func (self *ShellCommandAction) Call() error {
Title: self.c.Tr.ShellCommand, Title: self.c.Tr.ShellCommand,
FindSuggestionsFunc: self.GetShellCommandsHistorySuggestionsFunc(), FindSuggestionsFunc: self.GetShellCommandsHistorySuggestionsFunc(),
AllowEditSuggestion: true, AllowEditSuggestion: true,
PreserveWhitespace: true,
HandleConfirm: func(command string) error { HandleConfirm: func(command string) error {
if self.shouldSaveCommand(command) { if self.shouldSaveCommand(command) {
self.c.GetAppState().ShellCommandsHistory = utils.Limit( self.c.GetAppState().ShellCommandsHistory = utils.Limit(

View File

@@ -209,6 +209,7 @@ func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntr
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
return nil return nil
}, },
AllowEmptyInput: true,
}) })
return nil return nil

View File

@@ -139,6 +139,8 @@ func (self *PopupHandler) Prompt(opts types.PromptOpts) {
HandleDeleteSuggestion: opts.HandleDeleteSuggestion, HandleDeleteSuggestion: opts.HandleDeleteSuggestion,
FindSuggestionsFunc: opts.FindSuggestionsFunc, FindSuggestionsFunc: opts.FindSuggestionsFunc,
AllowEditSuggestion: opts.AllowEditSuggestion, AllowEditSuggestion: opts.AllowEditSuggestion,
AllowEmptyInput: opts.AllowEmptyInput,
PreserveWhitespace: opts.PreserveWhitespace,
Mask: opts.Mask, Mask: opts.Mask,
}) })
} }

View File

@@ -125,6 +125,8 @@ func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrap
HandleConfirm: func(str string) error { HandleConfirm: func(str string) error {
return wrappedF(str) return wrappedF(str)
}, },
AllowEmptyInput: true,
PreserveWhitespace: true,
}) })
return nil return nil

View File

@@ -172,6 +172,8 @@ type CreatePopupPanelOpts struct {
FindSuggestionsFunc func(string) []*Suggestion FindSuggestionsFunc func(string) []*Suggestion
Mask bool Mask bool
AllowEditSuggestion bool AllowEditSuggestion bool
AllowEmptyInput bool
PreserveWhitespace bool
} }
type ConfirmOpts struct { type ConfirmOpts struct {
@@ -190,6 +192,8 @@ type PromptOpts struct {
FindSuggestionsFunc func(string) []*Suggestion FindSuggestionsFunc func(string) []*Suggestion
HandleConfirm func(string) error HandleConfirm func(string) error
AllowEditSuggestion bool AllowEditSuggestion bool
AllowEmptyInput bool
PreserveWhitespace bool
// CAPTURE THIS // CAPTURE THIS
HandleClose func() error HandleClose func() error
HandleDeleteSuggestion func(int) error HandleDeleteSuggestion func(int) error

View File

@@ -618,6 +618,7 @@ type TranslationSet struct {
MustStashTitle string MustStashTitle string
ConfirmationTitle string ConfirmationTitle string
PromptTitle string PromptTitle string
PromptInputCannotBeEmptyToast string
PrevPage string PrevPage string
NextPage string NextPage string
GotoTop string GotoTop string
@@ -861,7 +862,6 @@ type TranslationSet struct {
NewWorktreePath string NewWorktreePath string
NewWorktreeBase string NewWorktreeBase string
RemoveWorktreeTooltip string RemoveWorktreeTooltip string
BranchNameCannotBeBlank string
NewBranchName string NewBranchName string
NewBranchNameLeaveBlank string NewBranchNameLeaveBlank string
ViewWorktreeOptions string ViewWorktreeOptions string
@@ -1713,6 +1713,7 @@ func EnglishTranslationSet() *TranslationSet {
MustStashTitle: "Must stash", MustStashTitle: "Must stash",
ConfirmationTitle: "Confirmation panel", ConfirmationTitle: "Confirmation panel",
PromptTitle: "Input prompt", PromptTitle: "Input prompt",
PromptInputCannotBeEmptyToast: "Empty input is not allowed",
PrevPage: "Previous page", PrevPage: "Previous page",
NextPage: "Next page", NextPage: "Next page",
GotoTop: "Scroll to top", GotoTop: "Scroll to top",
@@ -1954,7 +1955,6 @@ func EnglishTranslationSet() *TranslationSet {
NewWorktreePath: "New worktree path", NewWorktreePath: "New worktree path",
NewWorktreeBase: "New worktree base ref", 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.", 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", NewBranchName: "New branch name",
NewBranchNameLeaveBlank: "New branch name (leave blank to checkout {{.default}})", NewBranchNameLeaveBlank: "New branch name (leave blank to checkout {{.default}})",
ViewWorktreeOptions: "View worktree options", ViewWorktreeOptions: "View worktree options",