mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	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.
This commit is contained in:
		| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								pkg/gui/context/prompt_context.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								pkg/gui/context/prompt_context.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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, | ||||
| 		})), | ||||
| 	} | ||||
| } | ||||
| @@ -84,6 +84,7 @@ func NewContextTree(c *ContextCommon) *ContextTree { | ||||
| 			c, | ||||
| 		), | ||||
| 		Confirmation:  NewConfirmationContext(c), | ||||
| 		Prompt:        NewPromptContext(c), | ||||
| 		CommitMessage: NewCommitMessageContext(c), | ||||
| 		CommitDescription: NewSimpleContext( | ||||
| 			NewBaseContext(NewBaseContextOpts{ | ||||
|   | ||||
| @@ -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, | ||||
| 	) | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										95
									
								
								pkg/gui/controllers/prompt_controller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								pkg/gui/controllers/prompt_controller.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
| @@ -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() | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() }, | ||||
| 	) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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" | ||||
| 	}) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user