From 5a630aeda1dd6eeac21f65a9d3d775e7693f5253 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 31 Aug 2025 15:36:52 +0200 Subject: [PATCH 1/8] Refactor: add a separate Prompt view So far, confirmations and prompts were handled by the same view, context, and controller, with a bunch of conditional code based on whether the view is editable. This was more or less ok so far, since it does save a little bit of code duplication; however, now we need separate views, because we don't have dynamic keybindings, but we want to map "confirm" to different keys in confirmations (the "universal.confirm" user config) and prompts (hard-coded to enter, because it doesn't make sense to customize it there). It also allows us to get rid of the conditional code, which is a nice benefit; and the code duplication is actually not *that* bad. --- pkg/cheatsheet/generate.go | 1 + pkg/gui/context/context.go | 4 + pkg/gui/context/prompt_context.go | 30 ++++ pkg/gui/context/setup.go | 1 + pkg/gui/controllers.go | 5 + .../controllers/confirmation_controller.go | 60 +------ .../helpers/confirmation_helper.go | 152 ++++++++++++------ pkg/gui/controllers/prompt_controller.go | 95 +++++++++++ pkg/gui/controllers/suggestions_controller.go | 20 +-- pkg/gui/global_handlers.go | 24 --- pkg/gui/gui.go | 2 +- pkg/gui/keybindings.go | 4 +- pkg/gui/types/views.go | 1 + pkg/gui/views.go | 8 +- pkg/i18n/english.go | 2 + pkg/integration/components/popup.go | 6 +- pkg/integration/components/prompt_driver.go | 2 +- pkg/integration/components/views.go | 4 + 18 files changed, 273 insertions(+), 148 deletions(-) create mode 100644 pkg/gui/context/prompt_context.go create mode 100644 pkg/gui/controllers/prompt_controller.go diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go index 004b13db1..3a5299062 100644 --- a/pkg/cheatsheet/generate.go +++ b/pkg/cheatsheet/generate.go @@ -116,6 +116,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string { "commitDescription": tr.CommitDescriptionTitle, "commits": tr.CommitsTitle, "confirmation": tr.ConfirmationTitle, + "prompt": tr.PromptTitle, "information": tr.InformationTitle, "main": tr.NormalTitle, "patchBuilding": tr.PatchBuildingTitle, diff --git a/pkg/gui/context/context.go b/pkg/gui/context/context.go index a993cf253..8af05e36f 100644 --- a/pkg/gui/context/context.go +++ b/pkg/gui/context/context.go @@ -41,6 +41,7 @@ const ( MENU_CONTEXT_KEY types.ContextKey = "menu" CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation" + PROMPT_CONTEXT_KEY types.ContextKey = "prompt" SEARCH_CONTEXT_KEY types.ContextKey = "search" COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage" COMMIT_DESCRIPTION_CONTEXT_KEY types.ContextKey = "commitDescription" @@ -73,6 +74,7 @@ var AllContextKeys = []types.ContextKey{ MENU_CONTEXT_KEY, CONFIRMATION_CONTEXT_KEY, + PROMPT_CONTEXT_KEY, SEARCH_CONTEXT_KEY, COMMIT_MESSAGE_CONTEXT_KEY, SUBMODULES_CONTEXT_KEY, @@ -106,6 +108,7 @@ type ContextTree struct { CustomPatchBuilderSecondary types.Context MergeConflicts *MergeConflictsContext Confirmation *ConfirmationContext + Prompt *PromptContext CommitMessage *CommitMessageContext CommitDescription types.Context CommandLog types.Context @@ -141,6 +144,7 @@ func (self *ContextTree) Flatten() []types.Context { self.Stash, self.Menu, self.Confirmation, + self.Prompt, self.CommitMessage, self.CommitDescription, diff --git a/pkg/gui/context/prompt_context.go b/pkg/gui/context/prompt_context.go new file mode 100644 index 000000000..c1def571c --- /dev/null +++ b/pkg/gui/context/prompt_context.go @@ -0,0 +1,30 @@ +package context + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type PromptContext struct { + *SimpleContext + c *ContextCommon + + State ConfirmationContextState +} + +var _ types.Context = (*PromptContext)(nil) + +func NewPromptContext( + c *ContextCommon, +) *PromptContext { + return &PromptContext{ + c: c, + SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + View: c.Views().Prompt, + WindowName: "prompt", + Key: PROMPT_CONTEXT_KEY, + Kind: types.TEMPORARY_POPUP, + Focusable: true, + HasUncontrolledBounds: true, + })), + } +} diff --git a/pkg/gui/context/setup.go b/pkg/gui/context/setup.go index 3a87c100a..8f498e6a9 100644 --- a/pkg/gui/context/setup.go +++ b/pkg/gui/context/setup.go @@ -84,6 +84,7 @@ func NewContextTree(c *ContextCommon) *ContextTree { c, ), Confirmation: NewConfirmationContext(c), + Prompt: NewPromptContext(c), CommitMessage: NewCommitMessageContext(c), CommitDescription: NewSimpleContext( NewBaseContext(NewBaseContextOpts{ diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 20b52b2cb..f3ea245cf 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -193,6 +193,7 @@ func (gui *Gui) resetHelpersAndControllers() { statusController := controllers.NewStatusController(common) commandLogController := controllers.NewCommandLogController(common) confirmationController := controllers.NewConfirmationController(common) + promptController := controllers.NewPromptController(common) suggestionsController := controllers.NewSuggestionsController(common) jumpToSideWindowController := controllers.NewJumpToSideWindowController(common, gui.handleNextTab) @@ -399,6 +400,10 @@ func (gui *Gui) resetHelpersAndControllers() { confirmationController, ) + controllers.AttachControllers(gui.State.Contexts.Prompt, + promptController, + ) + controllers.AttachControllers(gui.State.Contexts.Suggestions, suggestionsController, ) diff --git a/pkg/gui/controllers/confirmation_controller.go b/pkg/gui/controllers/confirmation_controller.go index 1e4e5cd46..206818f40 100644 --- a/pkg/gui/controllers/confirmation_controller.go +++ b/pkg/gui/controllers/confirmation_controller.go @@ -1,9 +1,6 @@ package controllers import ( - "fmt" - - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -39,45 +36,19 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [ DisplayOnScreen: true, }, { - Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: func() error { - if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 { - self.switchToSuggestions() - } - return nil - }, - }, - { - Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), - Handler: self.handleCopyToClipboard, - Description: self.c.Tr.CopyToClipboardMenu, - DisplayOnScreen: true, - GetDisabledReason: self.copyToClipboardEnabled, + Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), + Handler: self.handleCopyToClipboard, + Description: self.c.Tr.CopyToClipboardMenu, + DisplayOnScreen: true, }, } return bindings } -func (self *ConfirmationController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { - return []*gocui.ViewMouseBinding{ - { - ViewName: self.c.Contexts().Suggestions.GetViewName(), - FocusedView: self.c.Contexts().Confirmation.GetViewName(), - Key: gocui.MouseLeft, - Handler: func(gocui.ViewMouseBindingOpts) error { - self.switchToSuggestions() - // Let it fall through to the ListController's click handler so that - // the clicked line gets selected: - return gocui.ErrKeybindingNotHandled - }, - }, - } -} - func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) { return func(types.OnFocusLostOpts) { - self.c.Helpers().Confirmation.DeactivateConfirmationPrompt() + self.c.Helpers().Confirmation.DeactivateConfirmation() } } @@ -89,18 +60,6 @@ func (self *ConfirmationController) context() *context.ConfirmationContext { return self.c.Contexts().Confirmation } -func (self *ConfirmationController) switchToSuggestions() { - subtitle := "" - if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil { - // We assume that whenever things are deletable, they - // are also editable, so we show both keybindings - subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle, - self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit) - } - self.c.Views().Suggestions.Subtitle = subtitle - self.c.Context().Replace(self.c.Contexts().Suggestions) -} - func (self *ConfirmationController) handleCopyToClipboard() error { confirmationView := self.c.Views().Confirmation text := confirmationView.Buffer() @@ -111,12 +70,3 @@ func (self *ConfirmationController) handleCopyToClipboard() error { self.c.Toast(self.c.Tr.MessageCopiedToClipboard) return nil } - -func (self *ConfirmationController) copyToClipboardEnabled() *types.DisabledReason { - if self.c.Views().Confirmation.Editable { - // The empty text is intentional. We don't want to get a toast when invoking this, we only - // want to prevent it from showing up in the options bar. - return &types.DisabledReason{Text: ""} - } - return nil -} diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index c59275275..f8e25c47c 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -46,17 +46,27 @@ func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goConte }) } -func (self *ConfirmationHelper) DeactivateConfirmationPrompt() { +func (self *ConfirmationHelper) DeactivateConfirmation() { self.c.Mutexes().PopupMutex.Lock() self.c.State().GetRepoState().SetCurrentPopupOpts(nil) self.c.Mutexes().PopupMutex.Unlock() self.c.Views().Confirmation.Visible = false - self.c.Views().Suggestions.Visible = false self.clearConfirmationViewKeyBindings() } +func (self *ConfirmationHelper) DeactivatePrompt() { + self.c.Mutexes().PopupMutex.Lock() + self.c.State().GetRepoState().SetCurrentPopupOpts(nil) + self.c.Mutexes().PopupMutex.Unlock() + + self.c.Views().Prompt.Visible = false + self.c.Views().Suggestions.Visible = false + + self.clearPromptViewKeyBindings() +} + func getMessageHeight(wrap bool, editable bool, message string, width int, tabWidth int) int { wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, editable, message, width, tabWidth) return len(wrappedLines) @@ -105,15 +115,28 @@ func (self *ConfirmationHelper) prepareConfirmationPanel( opts types.ConfirmOpts, ) { self.c.Views().Confirmation.Title = opts.Title - // for now we do not support wrapping in our editor - self.c.Views().Confirmation.Wrap = !opts.Editable self.c.Views().Confirmation.FgColor = theme.GocuiDefaultTextColor - self.c.Views().Confirmation.Mask = runeForMask(opts.Mask) - self.c.Views().Confirmation.SetOrigin(0, 0) - suggestionsContext := self.c.Contexts().Suggestions - suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc + self.c.ResetViewOrigin(self.c.Views().Confirmation) + self.c.SetViewContent(self.c.Views().Confirmation, style.AttrBold.Sprint(strings.TrimSpace(opts.Prompt))) +} + +func (self *ConfirmationHelper) preparePromptPanel( + opts types.ConfirmOpts, +) { + self.c.Views().Prompt.Title = opts.Title + self.c.Views().Prompt.FgColor = theme.GocuiDefaultTextColor + self.c.Views().Prompt.Mask = runeForMask(opts.Mask) + self.c.Views().Prompt.SetOrigin(0, 0) + + textArea := self.c.Views().Prompt.TextArea + textArea.Clear() + textArea.TypeString(opts.Prompt) + self.c.Views().Prompt.RenderTextArea() + if opts.FindSuggestionsFunc != nil { + suggestionsContext := self.c.Contexts().Suggestions + suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc suggestionsView := self.c.Views().Suggestions suggestionsView.Wrap = false suggestionsView.FgColor = theme.GocuiDefaultTextColor @@ -150,44 +173,59 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ // remove any previous keybindings self.clearConfirmationViewKeyBindings() + self.clearPromptViewKeyBindings() - self.prepareConfirmationPanel( - types.ConfirmOpts{ - Title: opts.Title, - Prompt: opts.Prompt, - FindSuggestionsFunc: opts.FindSuggestionsFunc, - Editable: opts.Editable, - Mask: opts.Mask, - }) - confirmationView := self.c.Views().Confirmation - confirmationView.Editable = opts.Editable - + var context types.Context if opts.Editable { - textArea := confirmationView.TextArea - textArea.Clear() - textArea.TypeString(opts.Prompt) - confirmationView.RenderTextArea() - } else { - self.c.ResetViewOrigin(confirmationView) - self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(strings.TrimSpace(opts.Prompt))) - } + self.c.Contexts().Suggestions.State.FindSuggestions = opts.FindSuggestionsFunc - self.setKeyBindings(cancel, opts) + self.preparePromptPanel( + types.ConfirmOpts{ + Title: opts.Title, + Prompt: opts.Prompt, + FindSuggestionsFunc: opts.FindSuggestionsFunc, + Mask: opts.Mask, + }) + + context = self.c.Contexts().Prompt + + self.setPromptKeyBindings(cancel, opts) + } else { + if opts.FindSuggestionsFunc != nil { + panic("non-editable confirmation views do not support suggestions") + } + + self.c.Contexts().Suggestions.State.FindSuggestions = nil + + self.prepareConfirmationPanel( + types.ConfirmOpts{ + Title: opts.Title, + Prompt: opts.Prompt, + }) + + context = self.c.Contexts().Confirmation + + self.setConfirmationKeyBindings(cancel, opts) + } self.c.Contexts().Suggestions.State.AllowEditSuggestion = opts.AllowEditSuggestion self.c.State().GetRepoState().SetCurrentPopupOpts(&opts) - self.c.Context().Push(self.c.Contexts().Confirmation, types.OnFocusOpts{}) + self.c.Context().Push(context, types.OnFocusOpts{}) } -func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { - var onConfirm func() error - if opts.HandleConfirmPrompt != nil { - onConfirm = self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return self.c.Views().Confirmation.TextArea.GetContent() }) - } else { - onConfirm = self.wrappedConfirmationFunction(cancel, opts.HandleConfirm) - } +func (self *ConfirmationHelper) setConfirmationKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) { + onConfirm := self.wrappedConfirmationFunction(cancel, opts.HandleConfirm) + onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose) + + self.c.Contexts().Confirmation.State.OnConfirm = onConfirm + self.c.Contexts().Confirmation.State.OnClose = onClose +} + +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() }) onSuggestionConfirm := self.wrappedPromptConfirmationFunction( cancel, @@ -206,8 +244,8 @@ func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts return opts.HandleDeleteSuggestion(idx) } - self.c.Contexts().Confirmation.State.OnConfirm = onConfirm - self.c.Contexts().Confirmation.State.OnClose = onClose + self.c.Contexts().Prompt.State.OnConfirm = onConfirm + self.c.Contexts().Prompt.State.OnClose = onClose self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm self.c.Contexts().Suggestions.State.OnClose = onClose self.c.Contexts().Suggestions.State.OnDeleteSuggestion = onDeleteSuggestion @@ -217,6 +255,12 @@ func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() { noop := func() error { return nil } self.c.Contexts().Confirmation.State.OnConfirm = noop self.c.Contexts().Confirmation.State.OnClose = noop +} + +func (self *ConfirmationHelper) clearPromptViewKeyBindings() { + noop := func() error { return nil } + self.c.Contexts().Prompt.State.OnConfirm = noop + self.c.Contexts().Prompt.State.OnClose = noop self.c.Contexts().Suggestions.State.OnConfirm = noop self.c.Contexts().Suggestions.State.OnClose = noop self.c.Contexts().Suggestions.State.OnDeleteSuggestion = noop @@ -238,8 +282,10 @@ func (self *ConfirmationHelper) ResizeCurrentPopupPanels() { switch c { case self.c.Contexts().Menu: self.resizeMenu(parentPopupContext) - case self.c.Contexts().Confirmation, self.c.Contexts().Suggestions: + case self.c.Contexts().Confirmation: self.resizeConfirmationPanel(parentPopupContext) + case self.c.Contexts().Prompt, self.c.Contexts().Suggestions: + self.resizePromptPanel(parentPopupContext) case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription: self.ResizeCommitMessagePanels(parentPopupContext) } @@ -300,26 +346,30 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int { } func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types.Context) { + panelWidth := self.getPopupPanelWidth() + contentWidth := panelWidth - 2 // minus 2 for the frame + confirmationView := self.c.Views().Confirmation + prompt := confirmationView.Buffer() + panelHeight := getMessageHeight(true, false, prompt, contentWidth, confirmationView.TabWidth) + x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext) + _, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, y1, 0) +} + +func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Context) { suggestionsViewHeight := 0 if self.c.Views().Suggestions.Visible { suggestionsViewHeight = 11 } panelWidth := self.getPopupPanelWidth() contentWidth := panelWidth - 2 // minus 2 for the frame - confirmationView := self.c.Views().Confirmation - prompt := confirmationView.Buffer() - wrap := true - editable := confirmationView.Editable - if editable { - prompt = confirmationView.TextArea.GetContent() - wrap = false - } - panelHeight := getMessageHeight(wrap, editable, prompt, contentWidth, confirmationView.TabWidth) + suggestionsViewHeight + promptView := self.c.Views().Prompt + prompt := promptView.TextArea.GetContent() + panelHeight := getMessageHeight(false, true, prompt, contentWidth, promptView.TabWidth) + suggestionsViewHeight x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext) - confirmationViewBottom := y1 - suggestionsViewHeight - _, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, confirmationViewBottom, 0) + promptViewBottom := y1 - suggestionsViewHeight + _, _ = self.c.GocuiGui().SetView(promptView.Name(), x0, y0, x1, promptViewBottom, 0) - suggestionsViewTop := confirmationViewBottom + 1 + suggestionsViewTop := promptViewBottom + 1 _, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0) } diff --git a/pkg/gui/controllers/prompt_controller.go b/pkg/gui/controllers/prompt_controller.go new file mode 100644 index 000000000..4d98e294f --- /dev/null +++ b/pkg/gui/controllers/prompt_controller.go @@ -0,0 +1,95 @@ +package controllers + +import ( + "fmt" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type PromptController struct { + baseController + c *ControllerCommon +} + +var _ types.IController = &PromptController{} + +func NewPromptController( + c *ControllerCommon, +) *PromptController { + return &PromptController{ + baseController: baseController{}, + c: c, + } +} + +func (self *PromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { + bindings := []*types.Binding{ + { + Key: opts.GetKey(opts.Config.Universal.Confirm), + Handler: func() error { return self.context().State.OnConfirm() }, + Description: self.c.Tr.Confirm, + DisplayOnScreen: true, + }, + { + Key: opts.GetKey(opts.Config.Universal.Return), + Handler: func() error { return self.context().State.OnClose() }, + Description: self.c.Tr.CloseCancel, + DisplayOnScreen: true, + }, + { + Key: opts.GetKey(opts.Config.Universal.TogglePanel), + Handler: func() error { + if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 { + self.switchToSuggestions() + } + return nil + }, + }, + } + + return bindings +} + +func (self *PromptController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { + return []*gocui.ViewMouseBinding{ + { + ViewName: self.c.Contexts().Suggestions.GetViewName(), + FocusedView: self.c.Contexts().Prompt.GetViewName(), + Key: gocui.MouseLeft, + Handler: func(gocui.ViewMouseBindingOpts) error { + self.switchToSuggestions() + // Let it fall through to the ListController's click handler so that + // the clicked line gets selected: + return gocui.ErrKeybindingNotHandled + }, + }, + } +} + +func (self *PromptController) GetOnFocusLost() func(types.OnFocusLostOpts) { + return func(types.OnFocusLostOpts) { + self.c.Helpers().Confirmation.DeactivatePrompt() + } +} + +func (self *PromptController) Context() types.Context { + return self.context() +} + +func (self *PromptController) context() *context.PromptContext { + return self.c.Contexts().Prompt +} + +func (self *PromptController) switchToSuggestions() { + subtitle := "" + if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil { + // We assume that whenever things are deletable, they + // are also editable, so we show both keybindings + subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle, + self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit) + } + self.c.Views().Suggestions.Subtitle = subtitle + self.c.Context().Replace(self.c.Contexts().Suggestions) +} diff --git a/pkg/gui/controllers/suggestions_controller.go b/pkg/gui/controllers/suggestions_controller.go index d97e56289..01ec3a145 100644 --- a/pkg/gui/controllers/suggestions_controller.go +++ b/pkg/gui/controllers/suggestions_controller.go @@ -42,7 +42,7 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) [] }, { Key: opts.GetKey(opts.Config.Universal.TogglePanel), - Handler: self.switchToConfirmation, + Handler: self.switchToPrompt, }, { Key: opts.GetKey(opts.Config.Universal.Remove), @@ -55,11 +55,11 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) [] Handler: func() error { if self.context().State.AllowEditSuggestion { if selectedItem := self.c.Contexts().Suggestions.GetSelected(); selectedItem != nil { - self.c.Contexts().Confirmation.GetView().TextArea.Clear() - self.c.Contexts().Confirmation.GetView().TextArea.TypeString(selectedItem.Value) - self.c.Contexts().Confirmation.GetView().RenderTextArea() + self.c.Contexts().Prompt.GetView().TextArea.Clear() + self.c.Contexts().Prompt.GetView().TextArea.TypeString(selectedItem.Value) + self.c.Contexts().Prompt.GetView().RenderTextArea() self.c.Contexts().Suggestions.RefreshSuggestions() - return self.switchToConfirmation() + return self.switchToPrompt() } } return nil @@ -73,26 +73,26 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) [] func (self *SuggestionsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { return []*gocui.ViewMouseBinding{ { - ViewName: self.c.Contexts().Confirmation.GetViewName(), + ViewName: self.c.Contexts().Prompt.GetViewName(), FocusedView: self.c.Contexts().Suggestions.GetViewName(), Key: gocui.MouseLeft, Handler: func(gocui.ViewMouseBindingOpts) error { - return self.switchToConfirmation() + return self.switchToPrompt() }, }, } } -func (self *SuggestionsController) switchToConfirmation() error { +func (self *SuggestionsController) switchToPrompt() error { self.c.Views().Suggestions.Subtitle = "" self.c.Views().Suggestions.Highlight = false - self.c.Context().Replace(self.c.Contexts().Confirmation) + self.c.Context().Replace(self.c.Contexts().Prompt) return nil } func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts) { return func(types.OnFocusLostOpts) { - self.c.Helpers().Confirmation.DeactivateConfirmationPrompt() + self.c.Helpers().Confirmation.DeactivatePrompt() } } diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go index 9abadf4e0..9b6551d33 100644 --- a/pkg/gui/global_handlers.go +++ b/pkg/gui/global_handlers.go @@ -90,60 +90,36 @@ func (gui *Gui) scrollDownSecondary() error { } func (gui *Gui) scrollUpConfirmationPanel() error { - if gui.Views.Confirmation.Editable { - return nil - } - gui.scrollUpView(gui.Views.Confirmation) return nil } func (gui *Gui) scrollDownConfirmationPanel() error { - if gui.Views.Confirmation.Editable { - return nil - } - gui.scrollDownView(gui.Views.Confirmation) return nil } func (gui *Gui) pageUpConfirmationPanel() error { - if gui.Views.Confirmation.Editable { - return nil - } - gui.Views.Confirmation.ScrollUp(gui.Contexts().Confirmation.GetViewTrait().PageDelta()) return nil } func (gui *Gui) pageDownConfirmationPanel() error { - if gui.Views.Confirmation.Editable { - return nil - } - gui.Views.Confirmation.ScrollDown(gui.Contexts().Confirmation.GetViewTrait().PageDelta()) return nil } func (gui *Gui) goToConfirmationPanelTop() error { - if gui.Views.Confirmation.Editable { - return gocui.ErrKeybindingNotHandled - } - gui.Views.Confirmation.ScrollUp(gui.Views.Confirmation.ViewLinesHeight()) return nil } func (gui *Gui) goToConfirmationPanelBottom() error { - if gui.Views.Confirmation.Editable { - return gocui.ErrKeybindingNotHandled - } - gui.Views.Confirmation.ScrollDown(gui.Views.Confirmation.ViewLinesHeight()) return nil diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 1bd5788f2..c9252e03d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -697,7 +697,7 @@ func NewGui( return gui.helpers.AppStatus.WithWaitingStatusSync(message, f) }, func(message string, kind types.ToastKind) { gui.helpers.AppStatus.Toast(message, kind) }, - func() string { return gui.Views.Confirmation.TextArea.GetContent() }, + func() string { return gui.Views.Prompt.TextArea.GetContent() }, func() bool { return gui.c.InDemo() }, ) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index c966b148a..084a25159 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -507,11 +507,11 @@ func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error { !gocui.IsMouseScrollKey(opts.Key) { // we ignore click events on views that aren't popup panels, when a popup panel is focused. // Unless both the current view and the clicked-on view are either commit message or commit - // description, or a confirmation and the suggestions view, because we want to allow switching + // description, or a prompt and the suggestions view, because we want to allow switching // between those two views by clicking. isCommitMessageOrSuggestionsView := func(viewName string) bool { return viewName == "commitMessage" || viewName == "commitDescription" || - viewName == "confirmation" || viewName == "suggestions" + viewName == "prompt" || viewName == "suggestions" } if !isCommitMessageOrSuggestionsView(gui.currentViewName()) || !isCommitMessageOrSuggestionsView(binding.ViewName) { return nil diff --git a/pkg/gui/types/views.go b/pkg/gui/types/views.go index 867dff92e..46a67d23a 100644 --- a/pkg/gui/types/views.go +++ b/pkg/gui/types/views.go @@ -25,6 +25,7 @@ type Views struct { Options *gocui.View Confirmation *gocui.View + Prompt *gocui.View Menu *gocui.View CommitMessage *gocui.View CommitDescription *gocui.View diff --git a/pkg/gui/views.go b/pkg/gui/views.go index 8eb62c623..629f5e396 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -68,6 +68,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping { {viewPtr: &gui.Views.Menu, name: "menu"}, {viewPtr: &gui.Views.Suggestions, name: "suggestions"}, {viewPtr: &gui.Views.Confirmation, name: "confirmation"}, + {viewPtr: &gui.Views.Prompt, name: "prompt"}, {viewPtr: &gui.Views.Tooltip, name: "tooltip"}, // this guy will cover everything else when it appears @@ -127,9 +128,14 @@ func (gui *Gui) createAllViews() error { gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor) gui.Views.Confirmation.Visible = false - gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor) + gui.Views.Confirmation.Wrap = true gui.Views.Confirmation.AutoRenderHyperLinks = true + gui.Views.Prompt.Visible = false + gui.Views.Prompt.Wrap = false // We don't want wrapping in one-line prompts + gui.Views.Prompt.Editable = true + gui.Views.Prompt.Editor = gocui.EditorFunc(gui.promptEditor) + gui.Views.Suggestions.Visible = false gui.Views.Menu.Visible = false diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index ea9f805a2..9db91b7a8 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -611,6 +611,7 @@ type TranslationSet struct { MustStashWarning string MustStashTitle string ConfirmationTitle string + PromptTitle string PrevPage string NextPage string GotoTop string @@ -1692,6 +1693,7 @@ func EnglishTranslationSet() *TranslationSet { MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?", MustStashTitle: "Must stash", ConfirmationTitle: "Confirmation panel", + PromptTitle: "Input prompt", PrevPage: "Previous page", NextPage: "Next page", GotoTop: "Scroll to top", diff --git a/pkg/integration/components/popup.go b/pkg/integration/components/popup.go index aa80770b2..3cdc4f1f2 100644 --- a/pkg/integration/components/popup.go +++ b/pkg/integration/components/popup.go @@ -13,7 +13,7 @@ func (self *Popup) Confirmation() *ConfirmationDriver { func (self *Popup) inConfirm() { self.t.assertWithRetries(func() (bool, string) { currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "confirmation" && !currentView.Editable, "Expected confirmation popup to be focused" + return currentView.Name() == "confirmation", "Expected confirmation popup to be focused" }) } @@ -26,7 +26,7 @@ func (self *Popup) Prompt() *PromptDriver { func (self *Popup) inPrompt() { self.t.assertWithRetries(func() (bool, string) { currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "confirmation" && currentView.Editable, "Expected prompt popup to be focused" + return currentView.Name() == "prompt", "Expected prompt popup to be focused" }) } @@ -45,7 +45,7 @@ func (self *Popup) inAlert() { // basically the same thing as a confirmation popup with the current implementation self.t.assertWithRetries(func() (bool, string) { currentView := self.t.gui.CurrentContext().GetView() - return currentView.Name() == "confirmation" && !currentView.Editable, "Expected alert popup to be focused" + return currentView.Name() == "confirmation", "Expected alert popup to be focused" }) } diff --git a/pkg/integration/components/prompt_driver.go b/pkg/integration/components/prompt_driver.go index a19c29aa4..34c07614b 100644 --- a/pkg/integration/components/prompt_driver.go +++ b/pkg/integration/components/prompt_driver.go @@ -6,7 +6,7 @@ type PromptDriver struct { } func (self *PromptDriver) getViewDriver() *ViewDriver { - return self.t.Views().Confirmation() + return self.t.Views().Prompt() } // asserts that the popup has the expected title diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go index 873aca650..1d32f4828 100644 --- a/pkg/integration/components/views.go +++ b/pkg/integration/components/views.go @@ -128,6 +128,10 @@ func (self *Views) Confirmation() *ViewDriver { return self.regularView("confirmation") } +func (self *Views) Prompt() *ViewDriver { + return self.regularView("prompt") +} + func (self *Views) CommitMessage() *ViewDriver { return self.regularView("commitMessage") } From 0a64e1abb3631e1a426085759aee67f4bb24879b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 31 Aug 2025 18:58:56 +0200 Subject: [PATCH 2/8] Update cheatsheets for the previous commit Done in a separate commit because the diff is already so long. --- docs/keybindings/Keybindings_en.md | 7 +++++++ docs/keybindings/Keybindings_ja.md | 7 +++++++ docs/keybindings/Keybindings_ko.md | 7 +++++++ docs/keybindings/Keybindings_nl.md | 7 +++++++ docs/keybindings/Keybindings_pl.md | 7 +++++++ docs/keybindings/Keybindings_pt.md | 7 +++++++ docs/keybindings/Keybindings_ru.md | 7 +++++++ docs/keybindings/Keybindings_zh-CN.md | 7 +++++++ docs/keybindings/Keybindings_zh-TW.md | 7 +++++++ 9 files changed, 63 insertions(+) diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index f759f093d..01a935102 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -160,6 +160,13 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` 0 `` | Focus main view | | | `` / `` | Search the current view by text | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | Confirm | | +| `` `` | Close/Cancel | | + ## Local branches | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md index 76d269ea5..6a307b8c1 100644 --- a/docs/keybindings/Keybindings_ja.md +++ b/docs/keybindings/Keybindings_ja.md @@ -52,6 +52,13 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味 | `` ] `` | 次のタブ | | | `` [ `` | 前のタブ | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | 確認 | | +| `` `` | 閉じる/キャンセル | | + ## コミット | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md index a97fda889..96ef98ad0 100644 --- a/docs/keybindings/Keybindings_ko.md +++ b/docs/keybindings/Keybindings_ko.md @@ -52,6 +52,13 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` ] `` | 이전 탭 | | | `` [ `` | 다음 탭 | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | 확인 | | +| `` `` | 닫기/취소 | | + ## Reflog | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 37482fb74..71c35fcdf 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -190,6 +190,13 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` w `` | View worktree options | | | `` / `` | Start met zoeken | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | Bevestig | | +| `` `` | Sluiten | | + ## Menu | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 6595f2f3c..5f7765709 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -125,6 +125,13 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ | `` `` | Wyjdź z budowniczego niestandardowej łatki | | | `` / `` | Szukaj w bieżącym widoku po tekście | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | Potwierdź | | +| `` `` | Zamknij/Anuluj | | + ## Lokalne gałęzie | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md index 9eac7beb0..23ff457cb 100644 --- a/docs/keybindings/Keybindings_pt.md +++ b/docs/keybindings/Keybindings_pt.md @@ -218,6 +218,13 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` w `` | View worktree options | | | `` / `` | Filter the current view by text | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | Confirmar | | +| `` `` | Fechar/Cancelar | | + ## Menu | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md index 753d74c36..2586a78e4 100644 --- a/docs/keybindings/Keybindings_ru.md +++ b/docs/keybindings/Keybindings_ru.md @@ -52,6 +52,13 @@ _Связки клавиш_ | `` ] `` | Следующая вкладка | | | `` [ `` | Предыдущая вкладка | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | Подтвердить | | +| `` `` | Закрыть/отменить | | + ## Worktrees | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md index 185a6abb4..decb0e603 100644 --- a/docs/keybindings/Keybindings_zh-CN.md +++ b/docs/keybindings/Keybindings_zh-CN.md @@ -52,6 +52,13 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | `` ] `` | 下一个标签 | | | `` [ `` | 上一个标签 | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | 确认 | | +| `` `` | 关闭 | | + ## Reflog | Key | Action | Info | diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md index b91243c74..66fae12b7 100644 --- a/docs/keybindings/Keybindings_zh-TW.md +++ b/docs/keybindings/Keybindings_zh-TW.md @@ -52,6 +52,13 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B | `` ] `` | 下一個索引標籤 | | | `` [ `` | 上一個索引標籤 | | +## Input prompt + +| Key | Action | Info | +|-----|--------|-------------| +| `` `` | 確認 | | +| `` `` | 關閉/取消 | | + ## 主面板 (補丁生成) | Key | Action | Info | From b006c831819f62e9e8c76ccb89ddd660614a3e7e Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 15 Aug 2025 17:36:36 +0200 Subject: [PATCH 3/8] Hard-code "enter" for editable prompts Rebinding the universal.confirm keybinding currently doesn't make sense, because the rebound key would also be used for editable prompts, which means you would only be able to bind it to a ctrl key (not "y", which is desirable for some people), and also it would allow you to enter a line feed in a branch name. Fix this by always using enter for editable prompts. --- pkg/gui/controllers/prompt_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gui/controllers/prompt_controller.go b/pkg/gui/controllers/prompt_controller.go index 4d98e294f..1f6953951 100644 --- a/pkg/gui/controllers/prompt_controller.go +++ b/pkg/gui/controllers/prompt_controller.go @@ -27,7 +27,7 @@ func NewPromptController( func (self *PromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { bindings := []*types.Binding{ { - Key: opts.GetKey(opts.Config.Universal.Confirm), + Key: gocui.KeyEnter, Handler: func() error { return self.context().State.OnConfirm() }, Description: self.c.Tr.Confirm, DisplayOnScreen: true, From 6303c6423215f1a353d400d03e9b2b59c274c805 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 15 Aug 2025 17:38:27 +0200 Subject: [PATCH 4/8] Hard-code "enter" for search prompt Like with the previous commit, it doesn't make sense to use any other key than enter for the search prompt. --- pkg/gui/controllers/search_prompt_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gui/controllers/search_prompt_controller.go b/pkg/gui/controllers/search_prompt_controller.go index 65dd23383..9eca74c90 100644 --- a/pkg/gui/controllers/search_prompt_controller.go +++ b/pkg/gui/controllers/search_prompt_controller.go @@ -24,7 +24,7 @@ func NewSearchPromptController( func (self *SearchPromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { return []*types.Binding{ { - Key: opts.GetKey(opts.Config.Universal.Confirm), + Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: self.confirm, }, From ea7050437d7668012fe8733ecad24acd30f2eafc Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 15 Aug 2025 17:46:33 +0200 Subject: [PATCH 5/8] Fix keybinding for switching to a worktree The universal.confirm keybinding is the wrong one to use for this, we want universal.goInto instead. They are both bound to "enter" by default, but when remapping confirm to "y" we don't want to use that for entering worktrees. --- pkg/gui/controllers/worktrees_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gui/controllers/worktrees_controller.go b/pkg/gui/controllers/worktrees_controller.go index 61066d533..d3f44e4ad 100644 --- a/pkg/gui/controllers/worktrees_controller.go +++ b/pkg/gui/controllers/worktrees_controller.go @@ -52,7 +52,7 @@ func (self *WorktreesController) GetKeybindings(opts types.KeybindingsOpts) []*t DisplayOnScreen: true, }, { - Key: opts.GetKey(opts.Config.Universal.Confirm), + Key: opts.GetKey(opts.Config.Universal.GoInto), Handler: self.withItem(self.enter), GetDisabledReason: self.require(self.singleItemSelected()), }, From 81868de2645bcee5322759a8663f76a50f72e47e Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 15 Aug 2025 17:49:09 +0200 Subject: [PATCH 6/8] Hard-code "enter" for adding lines in integration tests This one doesn't make a difference in practice because we don't remap the key in tests, but if we would, then this would no longer work correctly. It's just more correct this way. --- pkg/integration/components/commit_description_panel_driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/integration/components/commit_description_panel_driver.go b/pkg/integration/components/commit_description_panel_driver.go index 6bde2cd29..905080380 100644 --- a/pkg/integration/components/commit_description_panel_driver.go +++ b/pkg/integration/components/commit_description_panel_driver.go @@ -27,7 +27,7 @@ func (self *CommitDescriptionPanelDriver) SwitchToSummary() *CommitMessagePanelD } func (self *CommitDescriptionPanelDriver) AddNewline() *CommitDescriptionPanelDriver { - self.t.pressFast(self.t.keys.Universal.Confirm) + self.t.pressFast("") return self } From b413710d8c1ea475afdd03a1100846de89acbe58 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 15 Aug 2025 18:00:53 +0200 Subject: [PATCH 7/8] Add separate keybindings for confirmMenu and confirmSuggestion It seems useful to have the flexibility to remap "enter" in confirmations to "y", but keep "enter" for menus and suggestions (even though we sometimes use menus as confirmations, but it's still good to give users the choice). --- docs/Config.md | 2 ++ pkg/config/user_config.go | 4 ++++ pkg/gui/controllers/menu_controller.go | 2 +- pkg/gui/controllers/suggestions_controller.go | 2 +- pkg/integration/components/menu_driver.go | 2 +- pkg/integration/components/prompt_driver.go | 4 ++-- schema/config.json | 8 ++++++++ 7 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/Config.md b/docs/Config.md index 8c4d157c1..4bd589fe0 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -557,6 +557,8 @@ keybinding: select: goInto: confirm: + confirmMenu: + confirmSuggestion: confirmInEditor: confirmInEditor-alt: remove: d diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 66a5cddd3..c30d030ae 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -425,6 +425,8 @@ type KeybindingUniversalConfig struct { Select string `yaml:"select"` GoInto string `yaml:"goInto"` Confirm string `yaml:"confirm"` + ConfirmMenu string `yaml:"confirmMenu"` + ConfirmSuggestion string `yaml:"confirmSuggestion"` ConfirmInEditor string `yaml:"confirmInEditor"` ConfirmInEditorAlt string `yaml:"confirmInEditor-alt"` Remove string `yaml:"remove"` @@ -889,6 +891,8 @@ func GetDefaultConfig() *UserConfig { Select: "", GoInto: "", Confirm: "", + ConfirmMenu: "", + ConfirmSuggestion: "", ConfirmInEditor: "", ConfirmInEditorAlt: "", Remove: "d", diff --git a/pkg/gui/controllers/menu_controller.go b/pkg/gui/controllers/menu_controller.go index 25d919f54..0465308df 100644 --- a/pkg/gui/controllers/menu_controller.go +++ b/pkg/gui/controllers/menu_controller.go @@ -38,7 +38,7 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types. GetDisabledReason: self.require(self.singleItemSelected()), }, { - Key: opts.GetKey(opts.Config.Universal.Confirm), + Key: opts.GetKey(opts.Config.Universal.ConfirmMenu), Handler: self.withItem(self.press), GetDisabledReason: self.require(self.singleItemSelected()), Description: self.c.Tr.Execute, diff --git a/pkg/gui/controllers/suggestions_controller.go b/pkg/gui/controllers/suggestions_controller.go index 01ec3a145..715ee12e9 100644 --- a/pkg/gui/controllers/suggestions_controller.go +++ b/pkg/gui/controllers/suggestions_controller.go @@ -32,7 +32,7 @@ func NewSuggestionsController( func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { bindings := []*types.Binding{ { - Key: opts.GetKey(opts.Config.Universal.Confirm), + Key: opts.GetKey(opts.Config.Universal.ConfirmSuggestion), Handler: func() error { return self.context().State.OnConfirm() }, GetDisabledReason: self.require(self.singleItemSelected()), }, diff --git a/pkg/integration/components/menu_driver.go b/pkg/integration/components/menu_driver.go index eb7392d5a..95f29dcd3 100644 --- a/pkg/integration/components/menu_driver.go +++ b/pkg/integration/components/menu_driver.go @@ -21,7 +21,7 @@ func (self *MenuDriver) Title(expected *TextMatcher) *MenuDriver { func (self *MenuDriver) Confirm() *MenuDriver { self.checkNecessaryChecksCompleted() - self.getViewDriver().PressEnter() + self.getViewDriver().Press(self.t.keys.Universal.ConfirmMenu) return self } diff --git a/pkg/integration/components/prompt_driver.go b/pkg/integration/components/prompt_driver.go index 34c07614b..2c29dd7c4 100644 --- a/pkg/integration/components/prompt_driver.go +++ b/pkg/integration/components/prompt_driver.go @@ -72,7 +72,7 @@ func (self *PromptDriver) ConfirmFirstSuggestion() { self.t.Views().Suggestions(). IsFocused(). SelectedLineIdx(0). - PressEnter() + Press(self.t.keys.Universal.ConfirmSuggestion) } func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) { @@ -80,7 +80,7 @@ func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) { self.t.Views().Suggestions(). IsFocused(). NavigateToLine(matcher). - PressEnter() + Press(self.t.keys.Universal.ConfirmSuggestion) } func (self *PromptDriver) DeleteSuggestion(matcher *TextMatcher) *PromptDriver { diff --git a/schema/config.json b/schema/config.json index 47d125e87..aa4e41012 100644 --- a/schema/config.json +++ b/schema/config.json @@ -1366,6 +1366,14 @@ "type": "string", "default": "\u003center\u003e" }, + "confirmMenu": { + "type": "string", + "default": "\u003center\u003e" + }, + "confirmSuggestion": { + "type": "string", + "default": "\u003center\u003e" + }, "confirmInEditor": { "type": "string", "default": "\u003ca-enter\u003e" From b3a3410a1a98aa334bdf58ac7a695d4403281371 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 15 Aug 2025 18:12:44 +0200 Subject: [PATCH 8/8] Remove keybindings for menu items that are the same as the menu confirm key This is needed when remapping the confirmMenu key to, say, "y", and there's a menu that has an item with a "y" binding. This already worked correctly (confirm takes precedence, as desired), but it's still confusing to see the item binding. --- pkg/gui/menu_panel.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index b0e69cfe0..ddd45f97e 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -3,6 +3,7 @@ package gui import ( "fmt" + "github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/theme" ) @@ -20,6 +21,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { } maxColumnSize := 1 + confirmKey := keybindings.GetKey(gui.c.UserConfig().Keybinding.Universal.ConfirmMenu) for _, item := range opts.Items { if item.LabelColumns == nil { @@ -31,6 +33,11 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { } maxColumnSize = max(maxColumnSize, len(item.LabelColumns)) + + // Remove all item keybindings that are the same as the confirm binding + if item.Key == confirmKey { + item.Key = nil + } } for _, item := range opts.Items {