summaryrefslogtreecommitdiffstats
path: root/pkg/gui/controllers/helpers
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-03-21 20:57:52 +1100
committerJesse Duffield <jessedduffield@gmail.com>2023-04-30 13:19:53 +1000
commit509e3efa70512ed34b90177eb17d6481664bb958 (patch)
tree663333126d6764706462271712583e4c8e93d786 /pkg/gui/controllers/helpers
parent8edad826caf2fa48bfad33f9f8c4f3ba49a052da (diff)
lots more refactoring
Diffstat (limited to 'pkg/gui/controllers/helpers')
-rw-r--r--pkg/gui/controllers/helpers/confirmation_helper.go321
-rw-r--r--pkg/gui/controllers/helpers/helpers.go2
-rw-r--r--pkg/gui/controllers/helpers/merge_conflicts_helper.go15
-rw-r--r--pkg/gui/controllers/helpers/suggestions_helper.go15
4 files changed, 331 insertions, 22 deletions
diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go
new file mode 100644
index 000000000..04211b7ac
--- /dev/null
+++ b/pkg/gui/controllers/helpers/confirmation_helper.go
@@ -0,0 +1,321 @@
+package helpers
+
+import (
+ goContext "context"
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+
+ "github.com/jesseduffield/lazygit/pkg/gui/style"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/theme"
+ "github.com/mattn/go-runewidth"
+)
+
+type ConfirmationHelper struct {
+ c *types.HelperCommon
+ contexts *context.ContextTree
+}
+
+func NewConfirmationHelper(
+ c *types.HelperCommon,
+ contexts *context.ContextTree,
+) *ConfirmationHelper {
+ return &ConfirmationHelper{
+ c: c,
+ contexts: contexts,
+ }
+}
+
+// This file is for the rendering of confirmation panels along with setting and handling associated
+// keybindings.
+
+func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error {
+ return func() error {
+ cancel()
+
+ if err := self.c.PopContext(); err != nil {
+ return err
+ }
+
+ if function != nil {
+ if err := function(); err != nil {
+ return self.c.Error(err)
+ }
+ }
+
+ return nil
+ }
+}
+
+func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error {
+ return self.wrappedConfirmationFunction(cancel, func() error {
+ return function(getResponse())
+ })
+}
+
+func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
+ 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 getMessageHeight(wrap bool, message string, width int) int {
+ lines := strings.Split(message, "\n")
+ lineCount := 0
+ // if we need to wrap, calculate height to fit content within view's width
+ if wrap {
+ for _, line := range lines {
+ lineCount += runewidth.StringWidth(line)/width + 1
+ }
+ } else {
+ lineCount = len(lines)
+ }
+ return lineCount
+}
+
+func (self *ConfirmationHelper) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
+ panelWidth := self.getConfirmationPanelWidth()
+ panelHeight := getMessageHeight(wrap, prompt, panelWidth)
+ return self.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
+}
+
+func (self *ConfirmationHelper) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
+ return self.getConfirmationPanelDimensionsAux(panelWidth, contentHeight)
+}
+
+func (self *ConfirmationHelper) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
+ width, height := self.c.GocuiGui().Size()
+ if panelHeight > height*3/4 {
+ panelHeight = height * 3 / 4
+ }
+ return width/2 - panelWidth/2,
+ height/2 - panelHeight/2 - panelHeight%2 - 1,
+ width/2 + panelWidth/2,
+ height/2 + panelHeight/2
+}
+
+func (self *ConfirmationHelper) getConfirmationPanelWidth() int {
+ width, _ := self.c.GocuiGui().Size()
+ // we want a minimum width up to a point, then we do it based on ratio.
+ panelWidth := 4 * width / 7
+ minWidth := 80
+ if panelWidth < minWidth {
+ if width-2 < minWidth {
+ panelWidth = width - 2
+ } else {
+ panelWidth = minWidth
+ }
+ }
+
+ return panelWidth
+}
+
+func (self *ConfirmationHelper) prepareConfirmationPanel(
+ ctx goContext.Context,
+ opts types.ConfirmOpts,
+) error {
+ self.c.Views().Confirmation.HasLoader = opts.HasLoader
+ if opts.HasLoader {
+ self.c.GocuiGui().StartTicking(ctx)
+ }
+ 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.contexts.Suggestions
+ suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc
+ if opts.FindSuggestionsFunc != nil {
+ suggestionsView := self.c.Views().Suggestions
+ suggestionsView.Wrap = false
+ suggestionsView.FgColor = theme.GocuiDefaultTextColor
+ suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
+ suggestionsView.Visible = true
+ suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig.Keybinding.Universal.TogglePanel)
+ }
+
+ self.ResizeConfirmationPanel()
+ return nil
+}
+
+func runeForMask(mask bool) rune {
+ if mask {
+ return '*'
+ }
+ return 0
+}
+
+func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts types.CreatePopupPanelOpts) error {
+ self.c.Mutexes().PopupMutex.Lock()
+ defer self.c.Mutexes().PopupMutex.Unlock()
+
+ ctx, cancel := goContext.WithCancel(ctx)
+
+ // we don't allow interruptions of non-loader popups in case we get stuck somehow
+ // e.g. a credentials popup never gets its required user input so a process hangs
+ // forever.
+ // The proper solution is to have a queue of popup options
+ currentPopupOpts := self.c.State().GetRepoState().GetCurrentPopupOpts()
+ if currentPopupOpts != nil && !currentPopupOpts.HasLoader {
+ self.c.Log.Error("ignoring create popup panel because a popup panel is already open")
+ cancel()
+ return nil
+ }
+
+ // remove any previous keybindings
+ self.clearConfirmationViewKeyBindings()
+
+ err := self.prepareConfirmationPanel(
+ ctx,
+ types.ConfirmOpts{
+ Title: opts.Title,
+ Prompt: opts.Prompt,
+ HasLoader: opts.HasLoader,
+ FindSuggestionsFunc: opts.FindSuggestionsFunc,
+ Editable: opts.Editable,
+ Mask: opts.Mask,
+ })
+ if err != nil {
+ cancel()
+ return err
+ }
+ confirmationView := self.c.Views().Confirmation
+ confirmationView.Editable = opts.Editable
+
+ if opts.Editable {
+ textArea := confirmationView.TextArea
+ textArea.Clear()
+ textArea.TypeString(opts.Prompt)
+ self.ResizeConfirmationPanel()
+ confirmationView.RenderTextArea()
+ } else {
+ self.c.ResetViewOrigin(confirmationView)
+ self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt))
+ }
+
+ if err := self.setKeyBindings(cancel, opts); err != nil {
+ cancel()
+ return err
+ }
+
+ self.c.State().GetRepoState().SetCurrentPopupOpts(&opts)
+
+ return self.c.PushContext(self.contexts.Confirmation)
+}
+
+func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) error {
+ 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)
+ }
+
+ onSuggestionConfirm := self.wrappedPromptConfirmationFunction(
+ cancel,
+ opts.HandleConfirmPrompt,
+ self.getSelectedSuggestionValue,
+ )
+
+ onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose)
+
+ self.contexts.Confirmation.State.OnConfirm = onConfirm
+ self.contexts.Confirmation.State.OnClose = onClose
+ self.contexts.Suggestions.State.OnConfirm = onSuggestionConfirm
+ self.contexts.Suggestions.State.OnClose = onClose
+
+ return nil
+}
+
+func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() {
+ noop := func() error { return nil }
+ self.contexts.Confirmation.State.OnConfirm = noop
+ self.contexts.Confirmation.State.OnClose = noop
+ self.contexts.Suggestions.State.OnConfirm = noop
+ self.contexts.Suggestions.State.OnClose = noop
+}
+
+func (self *ConfirmationHelper) getSelectedSuggestionValue() string {
+ selectedSuggestion := self.contexts.Suggestions.GetSelected()
+
+ if selectedSuggestion != nil {
+ return selectedSuggestion.Value
+ }
+
+ return ""
+}
+
+func (self *ConfirmationHelper) ResizeConfirmationPanel() {
+ suggestionsViewHeight := 0
+ if self.c.Views().Suggestions.Visible {
+ suggestionsViewHeight = 11
+ }
+ panelWidth := self.getConfirmationPanelWidth()
+ prompt := self.c.Views().Confirmation.Buffer()
+ wrap := true
+ if self.c.Views().Confirmation.Editable {
+ prompt = self.c.Views().Confirmation.TextArea.GetContent()
+ wrap = false
+ }
+ panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
+ x0, y0, x1, y1 := self.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
+ confirmationViewBottom := y1 - suggestionsViewHeight
+ _, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
+
+ suggestionsViewTop := confirmationViewBottom + 1
+ _, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
+}
+
+func (self *ConfirmationHelper) ResizeCurrentPopupPanel() error {
+ v := self.c.GocuiGui().CurrentView()
+ if v == nil {
+ return nil
+ }
+
+ if v == self.c.Views().Menu {
+ self.resizeMenu()
+ } else if v == self.c.Views().Confirmation || v == self.c.Views().Suggestions {
+ self.ResizeConfirmationPanel()
+ } else if self.IsPopupPanel(v.Name()) {
+ return self.ResizePopupPanel(v, v.Buffer())
+ }
+
+ return nil
+}
+
+func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string) error {
+ x0, y0, x1, y1 := self.getConfirmationPanelDimensions(v.Wrap, content)
+ _, err := self.c.GocuiGui().SetView(v.Name(), x0, y0, x1, y1, 0)
+ return err
+}
+
+func (self *ConfirmationHelper) resizeMenu() {
+ itemCount := self.contexts.Menu.GetList().Len()
+ offset := 3
+ panelWidth := self.getConfirmationPanelWidth()
+ x0, y0, x1, y1 := self.getConfirmationPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
+ menuBottom := y1 - offset
+ _, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
+
+ tooltipTop := menuBottom + 1
+ tooltipHeight := getMessageHeight(true, self.contexts.Menu.GetSelected().Tooltip, panelWidth) + 2 // plus 2 for the frame
+ _, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
+}
+
+func (self *ConfirmationHelper) IsPopupPanel(viewName string) bool {
+ return viewName == "commitMessage" || viewName == "confirmation" || viewName == "menu"
+}
+
+func (self *ConfirmationHelper) IsPopupPanelFocused() bool {
+ return self.IsPopupPanel(self.c.CurrentContext().GetViewName())
+}
diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go
index 7d9ce6a30..8c676f4de 100644
--- a/pkg/gui/controllers/helpers/helpers.go
+++ b/pkg/gui/controllers/helpers/helpers.go
@@ -25,6 +25,7 @@ type Helpers struct {
Window *WindowHelper
View *ViewHelper
Refresh *RefreshHelper
+ Confirmation *ConfirmationHelper
}
func NewStubHelpers() *Helpers {
@@ -52,5 +53,6 @@ func NewStubHelpers() *Helpers {
Window: &WindowHelper{},
View: &ViewHelper{},
Refresh: &RefreshHelper{},
+ Confirmation: &ConfirmationHelper{},
}
}
diff --git a/pkg/gui/controllers/helpers/merge_conflicts_helper.go b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
index d83626591..b099a69cd 100644
--- a/pkg/gui/controllers/helpers/merge_conflicts_helper.go
+++ b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
@@ -1,11 +1,8 @@
package helpers
import (
- "fmt"
-
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/gui/context"
- "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -27,18 +24,6 @@ func NewMergeConflictsHelper(
}
}
-func (self *MergeConflictsHelper) GetMergingOptions() map[string]string {
- keybindingConfig := self.c.UserConfig.Keybinding
-
- return map[string]string{
- fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcSelectHunk,
- fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock)): self.c.Tr.LcNavigateConflicts,
- keybindings.Label(keybindingConfig.Universal.Select): self.c.Tr.LcPickHunk,
- keybindings.Label(keybindingConfig.Main.PickBothHunks): self.c.Tr.LcPickAllHunks,
- keybindings.Label(keybindingConfig.Universal.Undo): self.c.Tr.LcUndo,
- }
-}
-
func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
diff --git a/pkg/gui/controllers/helpers/suggestions_helper.go b/pkg/gui/controllers/helpers/suggestions_helper.go
index 0cc4a642b..53a076f66 100644
--- a/pkg/gui/controllers/helpers/suggestions_helper.go
+++ b/pkg/gui/controllers/helpers/suggestions_helper.go
@@ -6,6 +6,7 @@ import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -35,8 +36,8 @@ type ISuggestionsHelper interface {
type SuggestionsHelper struct {
c *types.HelperCommon
- model *types.Model
- refreshSuggestionsFn func()
+ model *types.Model
+ contexts *context.ContextTree
}
var _ ISuggestionsHelper = &SuggestionsHelper{}
@@ -44,12 +45,12 @@ var _ ISuggestionsHelper = &SuggestionsHelper{}
func NewSuggestionsHelper(
c *types.HelperCommon,
model *types.Model,
- refreshSuggestionsFn func(),
+ contexts *context.ContextTree,
) *SuggestionsHelper {
return &SuggestionsHelper{
- c: c,
- model: model,
- refreshSuggestionsFn: refreshSuggestionsFn,
+ c: c,
+ model: model,
+ contexts: contexts,
}
}
@@ -127,7 +128,7 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
// cache the trie for future use
self.model.FilesTrie = trie
- self.refreshSuggestionsFn()
+ self.contexts.Suggestions.RefreshSuggestions()
return err
})