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/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 | 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/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/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/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/prompt_controller.go b/pkg/gui/controllers/prompt_controller.go new file mode 100644 index 000000000..1f6953951 --- /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: gocui.KeyEnter, + 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/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, }, diff --git a/pkg/gui/controllers/suggestions_controller.go b/pkg/gui/controllers/suggestions_controller.go index d97e56289..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()), }, @@ -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/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()), }, 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/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 { 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/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 } 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/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..2c29dd7c4 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 @@ -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/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") } 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"