diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2023-03-21 20:57:52 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2023-04-30 13:19:53 +1000 |
commit | 509e3efa70512ed34b90177eb17d6481664bb958 (patch) | |
tree | 663333126d6764706462271712583e4c8e93d786 /pkg/gui | |
parent | 8edad826caf2fa48bfad33f9f8c4f3ba49a052da (diff) |
lots more refactoring
Diffstat (limited to 'pkg/gui')
39 files changed, 765 insertions, 701 deletions
diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go deleted file mode 100644 index 8455e7d02..000000000 --- a/pkg/gui/commit_message_panel.go +++ /dev/null @@ -1,38 +0,0 @@ -package gui - -import ( - "strconv" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -func (gui *Gui) handleCommitMessageFocused() error { - message := utils.ResolvePlaceholderString( - gui.c.Tr.CommitMessageConfirm, - map[string]string{ - "keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return), - "keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm), - "keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline), - }, - ) - - gui.RenderCommitLength() - - gui.c.SetViewContent(gui.Views.Options, message) - return nil -} - -func (gui *Gui) RenderCommitLength() { - if !gui.c.UserConfig.Gui.CommitLength.Show { - return - } - - gui.Views.CommitMessage.Subtitle = getBufferLength(gui.Views.CommitMessage) -} - -func getBufferLength(view *gocui.View) string { - return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " " -} diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go deleted file mode 100644 index ff13c6fbe..000000000 --- a/pkg/gui/confirmation_panel.go +++ /dev/null @@ -1,316 +0,0 @@ -package gui - -import ( - "context" - "fmt" - "strings" - - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/theme" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/mattn/go-runewidth" -) - -// This file is for the rendering of confirmation panels along with setting and handling associated -// keybindings. - -func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error { - return func() error { - cancel() - - if err := gui.c.PopContext(); err != nil { - return err - } - - if function != nil { - if err := function(); err != nil { - return gui.c.Error(err) - } - } - - return nil - } -} - -func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error { - return func() error { - cancel() - - if err := gui.c.PopContext(); err != nil { - return err - } - - if function != nil { - if err := function(getResponse()); err != nil { - return gui.c.Error(err) - } - } - - return nil - } -} - -func (gui *Gui) deactivateConfirmationPrompt() { - gui.Mutexes.PopupMutex.Lock() - gui.State.CurrentPopupOpts = nil - gui.Mutexes.PopupMutex.Unlock() - - gui.Views.Confirmation.Visible = false - gui.Views.Suggestions.Visible = false - - gui.clearConfirmationViewKeyBindings() -} - -func (gui *Gui) 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 (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) { - panelWidth := gui.getConfirmationPanelWidth() - panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) - return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight) -} - -func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) { - return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight) -} - -func (gui *Gui) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) { - width, height := gui.g.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 (gui *Gui) getConfirmationPanelWidth() int { - width, _ := gui.g.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 (gui *Gui) prepareConfirmationPanel( - ctx context.Context, - opts types.ConfirmOpts, -) error { - gui.Views.Confirmation.HasLoader = opts.HasLoader - if opts.HasLoader { - gui.g.StartTicking(ctx) - } - gui.Views.Confirmation.Title = opts.Title - // for now we do not support wrapping in our editor - gui.Views.Confirmation.Wrap = !opts.Editable - gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor - gui.Views.Confirmation.Mask = runeForMask(opts.Mask) - _ = gui.Views.Confirmation.SetOrigin(0, 0) - - gui.findSuggestions = opts.FindSuggestionsFunc - if opts.FindSuggestionsFunc != nil { - suggestionsView := gui.Views.Suggestions - suggestionsView.Wrap = false - suggestionsView.FgColor = theme.GocuiDefaultTextColor - gui.setSuggestions(opts.FindSuggestionsFunc("")) - suggestionsView.Visible = true - suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel) - } - - gui.resizeConfirmationPanel() - return nil -} - -func runeForMask(mask bool) rune { - if mask { - return '*' - } - return 0 -} - -func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error { - gui.Mutexes.PopupMutex.Lock() - defer gui.Mutexes.PopupMutex.Unlock() - - ctx, cancel := context.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 - if gui.State.CurrentPopupOpts != nil && !gui.State.CurrentPopupOpts.HasLoader { - gui.Log.Error("ignoring create popup panel because a popup panel is already open") - cancel() - return nil - } - - // remove any previous keybindings - gui.clearConfirmationViewKeyBindings() - - err := gui.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 := gui.Views.Confirmation - confirmationView.Editable = opts.Editable - confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor) - - if opts.Editable { - textArea := confirmationView.TextArea - textArea.Clear() - textArea.TypeString(opts.Prompt) - gui.resizeConfirmationPanel() - confirmationView.RenderTextArea() - } else { - gui.c.ResetViewOrigin(confirmationView) - gui.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt)) - } - - if err := gui.setKeyBindings(cancel, opts); err != nil { - cancel() - return err - } - - gui.State.CurrentPopupOpts = &opts - - return gui.c.PushContext(gui.State.Contexts.Confirmation) -} - -func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error { - actions := utils.ResolvePlaceholderString( - gui.c.Tr.CloseConfirm, - map[string]string{ - "keyBindClose": "esc", - "keyBindConfirm": "enter", - }, - ) - - gui.c.SetViewContent(gui.Views.Options, actions) - var onConfirm func() error - if opts.HandleConfirmPrompt != nil { - onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() }) - } else { - onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm) - } - - keybindingConfig := gui.c.UserConfig.Keybinding - onSuggestionConfirm := gui.wrappedPromptConfirmationFunction( - cancel, - opts.HandleConfirmPrompt, - gui.getSelectedSuggestionValue, - ) - - bindings := []*types.Binding{ - { - ViewName: "confirmation", - Key: keybindings.GetKey(keybindingConfig.Universal.Confirm), - Handler: onConfirm, - }, - { - ViewName: "confirmation", - Key: keybindings.GetKey(keybindingConfig.Universal.Return), - Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose), - }, - { - ViewName: "confirmation", - Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel), - Handler: func() error { - if len(gui.State.Suggestions) > 0 { - return gui.c.ReplaceContext(gui.State.Contexts.Suggestions) - } - return nil - }, - }, - { - ViewName: "suggestions", - Key: keybindings.GetKey(keybindingConfig.Universal.Confirm), - Handler: onSuggestionConfirm, - }, - { - ViewName: "suggestions", - Key: keybindings.GetKey(keybindingConfig.Universal.Return), - Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose), - }, - { - ViewName: "suggestions", - Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel), - Handler: func() error { return gui.c.ReplaceContext(gui.State.Contexts.Confirmation) }, - }, - } - - for _, binding := range bindings { - if err := gui.SetKeybinding(binding); err != nil { - return err - } - } - - return nil -} - -func (gui *Gui) clearConfirmationViewKeyBindings() { - keybindingConfig := gui.c.UserConfig.Keybinding - _ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone) - _ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone) - _ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone) - _ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone) -} - -func (gui *Gui) refreshSuggestions() { - gui.suggestionsAsyncHandler.Do(func() func() { - findSuggestionsFn := gui.findSuggestions - if findSuggestionsFn != nil { - suggestions := gui.findSuggestions(gui.c.GetPromptInput()) - return func() { gui.setSuggestions(suggestions) } - } else { - return func() {} - } - }) -} - -func (gui *Gui) handleAskFocused() error { - keybindingConfig := gui.c.UserConfig.Keybinding - - message := utils.ResolvePlaceholderString( - gui.c.Tr.CloseConfirm, - map[string]string{ - "keyBindClose": keybindings.Label(keybindingConfig.Universal.Return), - "keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm), - }, - ) - - gui.c.SetViewContent(gui.Views.Options, message) - return nil -} diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go new file mode 100644 index 000000000..42e0de5f1 --- /dev/null +++ b/pkg/gui/context/commit_message_context.go @@ -0,0 +1,47 @@ +package context + +import ( + "strconv" + "strings" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type CommitMessageContext struct { + *SimpleContext + c *types.HelperCommon +} + +var _ types.Context = (*CommitMessageContext)(nil) + +func NewCommitMessageContext( + c *types.HelperCommon, +) *CommitMessageContext { + return &CommitMessageContext{ + c: c, + SimpleContext: NewSimpleContext( + NewBaseContext(NewBaseContextOpts{ + Kind: types.PERSISTENT_POPUP, + View: c.Views().CommitMessage, + WindowName: "commitMessage", + Key: COMMIT_MESSAGE_CONTEXT_KEY, + Focusable: true, + HasUncontrolledBounds: true, + }), + ContextCallbackOpts{}, + ), + } +} + +func (self *CommitMessageContext) RenderCommitLength() { + if !self.c.UserConfig.Gui.CommitLength.Show { + return + } + + self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage) +} + +func getBufferLength(view *gocui.View) string { + return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " " +} diff --git a/pkg/gui/context/confirmation_context.go b/pkg/gui/context/confirmation_context.go new file mode 100644 index 000000000..0224fb0f0 --- /dev/null +++ b/pkg/gui/context/confirmation_context.go @@ -0,0 +1,35 @@ +package context + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type ConfirmationContext struct { + *SimpleContext + c *types.HelperCommon + + State ConfirmationContextState +} + +type ConfirmationContextState struct { + OnConfirm func() error + OnClose func() error +} + +var _ types.Context = (*ConfirmationContext)(nil) + +func NewConfirmationContext( + c *types.HelperCommon, +) *ConfirmationContext { + return &ConfirmationContext{ + c: c, + SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ + View: c.Views().Confirmation, + WindowName: "confirmation", + Key: CONFIRMATION_CONTEXT_KEY, + Kind: types.TEMPORARY_POPUP, + Focusable: true, + HasUncontrolledBounds: true, + }), ContextCallbackOpts{}), + } +} diff --git a/pkg/gui/context/context.go b/pkg/gui/context/context.go index 5a88b4a26..7f1439466 100644 --- a/pkg/gui/context/context.go +++ b/pkg/gui/context/context.go @@ -96,8 +96,8 @@ type ContextTree struct { CustomPatchBuilder *PatchExplorerContext CustomPatchBuilderSecondary types.Context MergeConflicts *MergeConflictsContext - Confirmation types.Context - CommitMessage types.Context + Confirmation *ConfirmationContext + CommitMessage *CommitMessageContext CommandLog types.Context // display contexts diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go index 8afd7df47..070ec3392 100644 --- a/pkg/gui/context/menu_context.go +++ b/pkg/gui/context/menu_context.go @@ -2,7 +2,6 @@ package context import ( "github.com/jesseduffield/generics/slices" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" @@ -16,11 +15,7 @@ type MenuContext struct { var _ types.IListContext = (*MenuContext)(nil) func NewMenuContext( - view *gocui.View, - c *types.HelperCommon, - getOptionsMap func() map[string]string, - renderToDescriptionView func(string), ) *MenuContext { viewModel := NewMenuViewModel() @@ -28,11 +23,10 @@ func NewMenuContext( MenuViewModel: viewModel, ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: view, + View: c.Views().Menu, WindowName: "menu", Key: "menu", Kind: types.TEMPORARY_POPUP, - OnGetOptionsMap: getOptionsMap, Focusable: true, HasUncontrolledBounds: true, }), ContextCallbackOpts{}), diff --git a/pkg/gui/context/merge_conflicts_context.go b/pkg/gui/context/merge_conflicts_context.go index 1e765ffe7..4066805bb 100644 --- a/pkg/gui/context/merge_conflicts_context.go +++ b/pkg/gui/context/merge_conflicts_context.go @@ -30,7 +30,6 @@ func NewMergeConflictsContext( opts ContextCallbackOpts, c *types.HelperCommon, - getOptionsMap func() map[string]string, ) *MergeConflictsContext { viewModel := &ConflictsViewModel{ state: mergeconflicts.NewState(), @@ -46,7 +45,6 @@ func NewMergeConflictsContext( View: view, WindowName: "main", Key: MERGE_CONFLICTS_CONTEXT_KEY, - OnGetOptionsMap: getOptionsMap, Focusable: true, HighlightOnFocus: true, }), diff --git a/pkg/gui/context/patch_explorer_context.go b/pkg/gui/context/patch_explorer_context.go index 5a3375f33..272784a9c 100644 --- a/pkg/gui/context/patch_explorer_context.go +++ b/pkg/gui/context/patch_explorer_context.go @@ -24,8 +24,6 @@ func NewPatchExplorerContext( windowName string, key types.ContextKey, - onFocus func(types.OnFocusOpts) error, - onFocusLost func(opts types.OnFocusLostOpts) error, getIncludedLineIndices func() []int, c *types.HelperCommon, @@ -43,10 +41,7 @@ func NewPatchExplorerContext( Kind: types.MAIN_CONTEXT, Focusable: true, HighlightOnFocus: true, - }), ContextCallbackOpts{ - OnFocus: onFocus, - OnFocusLost: onFocusLost, - }), + }), ContextCallbackOpts{}), } } diff --git a/pkg/gui/context/suggestions_context.go b/pkg/gui/context/suggestions_context.go index 346492e0c..8a28ce514 100644 --- a/pkg/gui/context/suggestions_context.go +++ b/pkg/gui/context/suggestions_context.go @@ -1,31 +1,53 @@ package context import ( - "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/tasks" ) type SuggestionsContext struct { *BasicViewModel[*types.Suggestion] *ListContextTrait + + State *SuggestionsContextState +} + +type SuggestionsContextState struct { + Suggestions []*types.Suggestion + OnConfirm func() error + OnClose func() error + AsyncHandler *tasks.AsyncHandler + + // FindSuggestions will take a string that the user has typed into a prompt + // and return a slice of suggestions which match that string. + FindSuggestions func(string) []*types.Suggestion } var _ types.IListContext = (*SuggestionsContext)(nil) func NewSuggestionsContext( - getModel func() []*types.Suggestion, - view *gocui.View, - getDisplayStrings func(startIdx int, length int) [][]string, - c *types.HelperCommon, ) *SuggestionsContext { + state := &SuggestionsContextState{ + AsyncHandler: tasks.NewAsyncHandler(), + } + getModel := func() []*types.Suggestion { + return state.Suggestions + } + + getDisplayStrings := func(startIdx int, length int) [][]string { + return presentation.GetSuggestionListDisplayStrings(state.Suggestions) + } + viewModel := NewBasicViewModel(getModel) return &SuggestionsContext{ + State: state, BasicViewModel: viewModel, ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: view, + View: c.Views().Suggestions, WindowName: "suggestions", Key: SUGGESTIONS_CONTEXT_KEY, Kind: types.PERSISTENT_POPUP, @@ -47,3 +69,22 @@ func (self *SuggestionsContext) GetSelectedItemId() string { return item.Value } + +func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) { + self.State.Suggestions = suggestions + self.SetSelectedLineIdx(0) + self.c.ResetViewOrigin(self.GetView()) + _ = self.HandleRender() +} + +func (self *SuggestionsContext) RefreshSuggestions() { + self.State.AsyncHandler.Do(func() func() { + findSuggestionsFn := self.State.FindSuggestions + if findSuggestionsFn != nil { + suggestions := findSuggestionsFn(self.c.GetPromptInput()) + return func() { self.SetSuggestions(suggestions) } + } else { + return func() {} + } + }) +} diff --git a/pkg/gui/context_config.go b/pkg/gui/context_config.go index 87887a9e8..994fc8a32 100644 --- a/pkg/gui/context_config.go +++ b/pkg/gui/context_config.go @@ -76,23 +76,6 @@ func (gui *Gui) contextTree() *context.ContextTree { gui.Views.Staging, "main", context.STAGING_MAIN_CONTEXT_KEY, - func(opts types.OnFocusOpts) error { - gui.Views.Staging.Wrap = false - gui.Views.StagingSecondary.Wrap = false - - return gui.helpers.Staging.RefreshStagingPanel(opts) - }, - func(opts types.OnFocusLostOpts) error { - gui.State.Contexts.Staging.SetState(nil) - - if opts.NewContextKey != context.STAGING_SECONDARY_CONTEXT_KEY { - gui.Views.Staging.Wrap = true - gui.Views.StagingSecondary.Wrap = true - _ = gui.State.Contexts.Staging.Render(false) - _ = gui.State.Contexts.StagingSecondary.Render(false) - } - return nil - }, func() []int { return nil }, gui.c, ), @@ -100,23 +83,6 @@ func (gui *Gui) contextTree() *context.ContextTree { gui.Views.StagingSecondary, "secondary", context.STAGING_SECONDARY_CONTEXT_KEY, - func(opts types.OnFocusOpts) error { - gui.Views.Staging.Wrap = false - gui.Views.StagingSecondary.Wrap = false - - return gui.helpers.Staging.RefreshStagingPanel(opts) - }, - func(opts types.OnFocusLostOpts) error { - gui.State.Contexts.StagingSecondary.SetState(nil) - - if opts.NewContextKey != context.STAGING_MAIN_CONTEXT_KEY { - gui.Views.Staging.Wrap = true - gui.Views.StagingSeconda |