1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-09 13:47:11 +02:00

lots more refactoring

This commit is contained in:
Jesse Duffield 2023-03-21 20:57:52 +11:00
parent 8edad826ca
commit 509e3efa70
45 changed files with 779 additions and 729 deletions

View File

@ -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) + " "
}

View File

@ -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
}

View File

@ -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) + " "
}

View File

@ -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{}),
}
}

View File

@ -96,8 +96,8 @@ type ContextTree struct {
CustomPatchBuilder *PatchExplorerContext CustomPatchBuilder *PatchExplorerContext
CustomPatchBuilderSecondary types.Context CustomPatchBuilderSecondary types.Context
MergeConflicts *MergeConflictsContext MergeConflicts *MergeConflictsContext
Confirmation types.Context Confirmation *ConfirmationContext
CommitMessage types.Context CommitMessage *CommitMessageContext
CommandLog types.Context CommandLog types.Context
// display contexts // display contexts

View File

@ -2,7 +2,6 @@ package context
import ( import (
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -16,11 +15,7 @@ type MenuContext struct {
var _ types.IListContext = (*MenuContext)(nil) var _ types.IListContext = (*MenuContext)(nil)
func NewMenuContext( func NewMenuContext(
view *gocui.View,
c *types.HelperCommon, c *types.HelperCommon,
getOptionsMap func() map[string]string,
renderToDescriptionView func(string),
) *MenuContext { ) *MenuContext {
viewModel := NewMenuViewModel() viewModel := NewMenuViewModel()
@ -28,11 +23,10 @@ func NewMenuContext(
MenuViewModel: viewModel, MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{ ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view, View: c.Views().Menu,
WindowName: "menu", WindowName: "menu",
Key: "menu", Key: "menu",
Kind: types.TEMPORARY_POPUP, Kind: types.TEMPORARY_POPUP,
OnGetOptionsMap: getOptionsMap,
Focusable: true, Focusable: true,
HasUncontrolledBounds: true, HasUncontrolledBounds: true,
}), ContextCallbackOpts{}), }), ContextCallbackOpts{}),

View File

@ -30,7 +30,6 @@ func NewMergeConflictsContext(
opts ContextCallbackOpts, opts ContextCallbackOpts,
c *types.HelperCommon, c *types.HelperCommon,
getOptionsMap func() map[string]string,
) *MergeConflictsContext { ) *MergeConflictsContext {
viewModel := &ConflictsViewModel{ viewModel := &ConflictsViewModel{
state: mergeconflicts.NewState(), state: mergeconflicts.NewState(),
@ -46,7 +45,6 @@ func NewMergeConflictsContext(
View: view, View: view,
WindowName: "main", WindowName: "main",
Key: MERGE_CONFLICTS_CONTEXT_KEY, Key: MERGE_CONFLICTS_CONTEXT_KEY,
OnGetOptionsMap: getOptionsMap,
Focusable: true, Focusable: true,
HighlightOnFocus: true, HighlightOnFocus: true,
}), }),

View File

@ -24,8 +24,6 @@ func NewPatchExplorerContext(
windowName string, windowName string,
key types.ContextKey, key types.ContextKey,
onFocus func(types.OnFocusOpts) error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getIncludedLineIndices func() []int, getIncludedLineIndices func() []int,
c *types.HelperCommon, c *types.HelperCommon,
@ -43,10 +41,7 @@ func NewPatchExplorerContext(
Kind: types.MAIN_CONTEXT, Kind: types.MAIN_CONTEXT,
Focusable: true, Focusable: true,
HighlightOnFocus: true, HighlightOnFocus: true,
}), ContextCallbackOpts{ }), ContextCallbackOpts{}),
OnFocus: onFocus,
OnFocusLost: onFocusLost,
}),
} }
} }

View File

@ -1,31 +1,53 @@
package context package context
import ( import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/tasks"
) )
type SuggestionsContext struct { type SuggestionsContext struct {
*BasicViewModel[*types.Suggestion] *BasicViewModel[*types.Suggestion]
*ListContextTrait *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) var _ types.IListContext = (*SuggestionsContext)(nil)
func NewSuggestionsContext( func NewSuggestionsContext(
getModel func() []*types.Suggestion,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
c *types.HelperCommon, c *types.HelperCommon,
) *SuggestionsContext { ) *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) viewModel := NewBasicViewModel(getModel)
return &SuggestionsContext{ return &SuggestionsContext{
State: state,
BasicViewModel: viewModel, BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{ ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view, View: c.Views().Suggestions,
WindowName: "suggestions", WindowName: "suggestions",
Key: SUGGESTIONS_CONTEXT_KEY, Key: SUGGESTIONS_CONTEXT_KEY,
Kind: types.PERSISTENT_POPUP, Kind: types.PERSISTENT_POPUP,
@ -47,3 +69,22 @@ func (self *SuggestionsContext) GetSelectedItemId() string {
return item.Value 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() {}
}
})
}

View File

@ -76,23 +76,6 @@ func (gui *Gui) contextTree() *context.ContextTree {
gui.Views.Staging, gui.Views.Staging,
"main", "main",
context.STAGING_MAIN_CONTEXT_KEY, 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 }, func() []int { return nil },
gui.c, gui.c,
), ),
@ -100,23 +83,6 @@ func (gui *Gui) contextTree() *context.ContextTree {
gui.Views.StagingSecondary, gui.Views.StagingSecondary,
"secondary", "secondary",
context.STAGING_SECONDARY_CONTEXT_KEY, 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.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil }, func() []int { return nil },
gui.c, gui.c,
), ),
@ -124,21 +90,6 @@ func (gui *Gui) contextTree() *context.ContextTree {
gui.Views.PatchBuilding, gui.Views.PatchBuilding,
"main", "main",
context.PATCH_BUILDING_MAIN_CONTEXT_KEY, context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
// no need to change wrap on the secondary view because it can't be interacted with
gui.Views.PatchBuilding.Wrap = false
return gui.helpers.PatchBuilding.RefreshPatchBuildingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.Views.PatchBuilding.Wrap = true
if gui.git.Patch.PatchBuilder.IsEmpty() {
gui.git.Patch.PatchBuilder.Reset()
}
return nil
},
func() []int { func() []int {
filename := gui.State.Contexts.CommitFiles.GetSelectedPath() filename := gui.State.Contexts.CommitFiles.GetSelectedPath()
includedLineIndices, err := gui.git.Patch.PatchBuilder.GetFileIncLineIndices(filename) includedLineIndices, err := gui.git.Patch.PatchBuilder.GetFileIncLineIndices(filename)
@ -165,37 +116,9 @@ func (gui *Gui) contextTree() *context.ContextTree {
gui.Views.MergeConflicts, gui.Views.MergeConflicts,
context.ContextCallbackOpts{}, context.ContextCallbackOpts{},
gui.c, gui.c,
func() map[string]string {
// wrapping in a function because contexts are initialized before helpers
return gui.helpers.MergeConflicts.GetMergingOptions()
},
),
Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.TEMPORARY_POPUP,
View: gui.Views.Confirmation,
WindowName: "confirmation",
Key: context.CONFIRMATION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
},
),
CommitMessage: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: gui.Views.CommitMessage,
WindowName: "commitMessage",
Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
},
), ),
Confirmation: context.NewConfirmationContext(gui.c),
CommitMessage: context.NewCommitMessageContext(gui.c),
Search: context.NewSimpleContext( Search: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP, Kind: types.PERSISTENT_POPUP,
@ -214,12 +137,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
Key: context.COMMAND_LOG_CONTEXT_KEY, Key: context.COMMAND_LOG_CONTEXT_KEY,
Focusable: true, Focusable: true,
}), }),
context.ContextCallbackOpts{ context.ContextCallbackOpts{},
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.Views.Extras.Autoscroll = true
return nil
},
},
), ),
Options: context.NewDisplayContext(context.OPTIONS_CONTEXT_KEY, gui.Views.Options, "options"), Options: context.NewDisplayContext(context.OPTIONS_CONTEXT_KEY, gui.Views.Options, "options"),
AppStatus: context.NewDisplayContext(context.APP_STATUS_CONTEXT_KEY, gui.Views.AppStatus, "appStatus"), AppStatus: context.NewDisplayContext(context.APP_STATUS_CONTEXT_KEY, gui.Views.AppStatus, "appStatus"),

View File

@ -24,7 +24,7 @@ func (gui *Gui) resetControllers() {
) )
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper) rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions) suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.State.Contexts)
setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage }) setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
getSavedCommitMessage := func() string { getSavedCommitMessage := func() string {
return gui.State.savedCommitMessage return gui.State.savedCommitMessage
@ -66,6 +66,7 @@ func (gui *Gui) resetControllers() {
Window: helpers.NewWindowHelper(helperCommon, viewHelper, gui.State.Contexts), Window: helpers.NewWindowHelper(helperCommon, viewHelper, gui.State.Contexts),
View: viewHelper, View: viewHelper,
Refresh: refreshHelper, Refresh: refreshHelper,
Confirmation: helpers.NewConfirmationHelper(helperCommon, gui.State.Contexts),
} }
gui.CustomCommandsClient = custom_commands.NewClient( gui.CustomCommandsClient = custom_commands.NewClient(
@ -151,6 +152,9 @@ func (gui *Gui) resetControllers() {
reflogCommitsController := controllers.NewReflogCommitsController(common, gui.State.Contexts.ReflogCommits) reflogCommitsController := controllers.NewReflogCommitsController(common, gui.State.Contexts.ReflogCommits)
subCommitsController := controllers.NewSubCommitsController(common, gui.State.Contexts.SubCommits) subCommitsController := controllers.NewSubCommitsController(common, gui.State.Contexts.SubCommits)
statusController := controllers.NewStatusController(common) statusController := controllers.NewStatusController(common)
commandLogController := controllers.NewCommandLogController(common)
confirmationController := controllers.NewConfirmationController(common)
suggestionsController := controllers.NewSuggestionsController(common)
setSubCommits := func(commits []*models.Commit) { setSubCommits := func(commits []*models.Commit) {
gui.Mutexes.SubCommitsMutex.Lock() gui.Mutexes.SubCommitsMutex.Lock()
@ -279,6 +283,18 @@ func (gui *Gui) resetControllers() {
statusController, statusController,
) )
controllers.AttachControllers(gui.State.Contexts.CommandLog,
commandLogController,
)
controllers.AttachControllers(gui.State.Contexts.Confirmation,
confirmationController,
)
controllers.AttachControllers(gui.State.Contexts.Suggestions,
suggestionsController,
)
controllers.AttachControllers(gui.State.Contexts.Global, controllers.AttachControllers(gui.State.Contexts.Global,
syncController, syncController,
undoController, undoController,
@ -303,7 +319,7 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
view := getView() view := getView()
view.ClearTextArea() view.ClearTextArea()
view.TextArea.TypeString(text) view.TextArea.TypeString(text)
_ = gui.resizePopupPanel(view, view.TextArea.GetContent()) _ = gui.helpers.Confirmation.ResizePopupPanel(view, view.TextArea.GetContent())
view.RenderTextArea() view.RenderTextArea()
} }
} }

View File

@ -0,0 +1,42 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CommandLogController struct {
baseController
*controllerCommon
}
var _ types.IController = &CommandLogController{}
func NewCommandLogController(
common *controllerCommon,
) *CommandLogController {
return &CommandLogController{
baseController: baseController{},
controllerCommon: common,
}
}
func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{}
return bindings
}
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.c.Views().Extras.Autoscroll = true
return nil
}
}
func (self *CommandLogController) Context() types.Context {
return self.context()
}
func (self *CommandLogController) context() types.Context {
return self.contexts.CommandLog
}

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -31,28 +32,39 @@ func NewCommitMessageController(
} }
} }
// TODO: merge that commit panel PR because we're not currently showing how to add a newline as it's
// handled by the editor func rather than by the controller here.
func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{ bindings := []*types.Binding{
{ {
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText), Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
Handler: self.confirm, Handler: self.confirm,
Description: self.c.Tr.LcConfirm,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.Return), Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close, Handler: self.close,
Description: self.c.Tr.LcClose,
}, },
} }
return bindings return bindings
} }
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.context().RenderCommitLength()
return nil
}
}
func (self *CommitMessageController) Context() types.Context { func (self *CommitMessageController) Context() types.Context {
return self.context() return self.context()
} }
// this method is pointless in this context but I'm keeping it consistent // this method is pointless in this context but I'm keeping it consistent
// with other contexts so that when generics arrive it's easier to refactor // with other contexts so that when generics arrive it's easier to refactor
func (self *CommitMessageController) context() types.Context { func (self *CommitMessageController) context() *context.CommitMessageContext {
return self.contexts.CommitMessage return self.contexts.CommitMessage
} }

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -21,14 +22,36 @@ func NewConfirmationController(
} }
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{} bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
Description: self.c.Tr.LcConfirm,
Display: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: func() error { return self.context().State.OnClose() },
Description: self.c.Tr.LcCloseCancel,
Display: true,
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: func() error {
if len(self.contexts.Suggestions.State.Suggestions) > 0 {
return self.c.ReplaceContext(self.contexts.Suggestions)
}
return nil
},
},
}
return bindings return bindings
} }
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) error { func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error { return func(types.OnFocusLostOpts) error {
deactivateConfirmationPrompt(self.controllerCommon) self.helpers.Confirmation.DeactivateConfirmationPrompt()
return nil return nil
} }
} }
@ -37,17 +60,6 @@ func (self *ConfirmationController) Context() types.Context {
return self.context() return self.context()
} }
func (self *ConfirmationController) context() types.Context { func (self *ConfirmationController) context() *context.ConfirmationContext {
return self.contexts.Confirmation return self.contexts.Confirmation
} }
func deactivateConfirmationPrompt(c *controllerCommon) {
c.mutexes.PopupMutex.Lock()
c.c.State().GetRepoState().SetCurrentPopupOpts(nil)
c.mutexes.PopupMutex.Unlock()
c.c.Views().Confirmation.Visible = false
c.c.Views().Suggestions.Visible = false
gui.clearConfirmationViewKeyBindings()
}

View File

@ -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())
}

View File

@ -25,6 +25,7 @@ type Helpers struct {
Window *WindowHelper Window *WindowHelper
View *ViewHelper View *ViewHelper
Refresh *RefreshHelper Refresh *RefreshHelper
Confirmation *ConfirmationHelper
} }
func NewStubHelpers() *Helpers { func NewStubHelpers() *Helpers {
@ -52,5 +53,6 @@ func NewStubHelpers() *Helpers {
Window: &WindowHelper{}, Window: &WindowHelper{},
View: &ViewHelper{}, View: &ViewHelper{},
Refresh: &RefreshHelper{}, Refresh: &RefreshHelper{},
Confirmation: &ConfirmationHelper{},
} }
} }

View File

@ -1,11 +1,8 @@
package helpers package helpers
import ( import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types" "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) { func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) {
self.context().GetMutex().Lock() self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock() defer self.context().GetMutex().Unlock()

View File

@ -6,6 +6,7 @@ import (
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models" "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/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
@ -35,8 +36,8 @@ type ISuggestionsHelper interface {
type SuggestionsHelper struct { type SuggestionsHelper struct {
c *types.HelperCommon c *types.HelperCommon
model *types.Model model *types.Model
refreshSuggestionsFn func() contexts *context.ContextTree
} }
var _ ISuggestionsHelper = &SuggestionsHelper{} var _ ISuggestionsHelper = &SuggestionsHelper{}
@ -44,12 +45,12 @@ var _ ISuggestionsHelper = &SuggestionsHelper{}
func NewSuggestionsHelper( func NewSuggestionsHelper(
c *types.HelperCommon, c *types.HelperCommon,
model *types.Model, model *types.Model,
refreshSuggestionsFn func(), contexts *context.ContextTree,
) *SuggestionsHelper { ) *SuggestionsHelper {
return &SuggestionsHelper{ return &SuggestionsHelper{
c: c, c: c,
model: model, model: model,
refreshSuggestionsFn: refreshSuggestionsFn, contexts: contexts,
} }
} }
@ -127,7 +128,7 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
// cache the trie for future use // cache the trie for future use
self.model.FilesTrie = trie self.model.FilesTrie = trie
self.refreshSuggestionsFn() self.contexts.Suggestions.RefreshSuggestions()
return err return err
}) })

View File

@ -28,12 +28,16 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.
Handler: self.press, Handler: self.press,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.Confirm), Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: self.press, Handler: self.press,
Description: self.c.Tr.LcExecute,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.Return), Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close, Handler: self.close,
Description: self.c.Tr.LcClose,
Display: true,
}, },
} }

View File

@ -41,21 +41,25 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
Key: opts.GetKey(opts.Config.Universal.PrevBlock), Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.withRenderAndFocus(self.PrevConflict), Handler: self.withRenderAndFocus(self.PrevConflict),
Description: self.c.Tr.PrevConflict, Description: self.c.Tr.PrevConflict,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.NextBlock), Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.withRenderAndFocus(self.NextConflict), Handler: self.withRenderAndFocus(self.NextConflict),
Description: self.c.Tr.NextConflict, Description: self.c.Tr.NextConflict,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.PrevItem), Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.withRenderAndFocus(self.PrevConflictHunk), Handler: self.withRenderAndFocus(self.PrevConflictHunk),
Description: self.c.Tr.SelectPrevHunk, Description: self.c.Tr.SelectPrevHunk,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.NextItem), Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.withRenderAndFocus(self.NextConflictHunk), Handler: self.withRenderAndFocus(self.NextConflictHunk),
Description: self.c.Tr.SelectNextHunk, Description: self.c.Tr.SelectNextHunk,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt), Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
@ -89,6 +93,7 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
Key: opts.GetKey(opts.Config.Universal.Undo), Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.withRenderAndFocus(self.HandleUndo), Handler: self.withRenderAndFocus(self.HandleUndo),
Description: self.c.Tr.LcUndo, Description: self.c.Tr.LcUndo,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Files.OpenMergeTool), Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
@ -99,11 +104,13 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
Key: opts.GetKey(opts.Config.Universal.Select), Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.withRenderAndFocus(self.HandlePickHunk), Handler: self.withRenderAndFocus(self.HandlePickHunk),
Description: self.c.Tr.PickHunk, Description: self.c.Tr.PickHunk,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Main.PickBothHunks), Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.withRenderAndFocus(self.HandlePickAllHunks), Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
Description: self.c.Tr.PickAllHunks, Description: self.c.Tr.PickAllHunks,
Display: true,
}, },
{ {
Key: opts.GetKey(opts.Config.Universal.Return), Key: opts.GetKey(opts.Config.Universal.Return),

View File

@ -59,6 +59,27 @@ func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsO
return []*gocui.ViewMouseBinding{} return []*gocui.ViewMouseBinding{}
} }
func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) error {
return func(opts types.OnFocusOpts) error {
// no need to change wrap on the secondary view because it can't be interacted with
self.c.Views().PatchBuilding.Wrap = false
return self.helpers.PatchBuilding.RefreshPatchBuildingPanel(opts)
}
}
func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(opts types.OnFocusLostOpts) error {
self.c.Views().PatchBuilding.Wrap = true
if self.git.Patch.PatchBuilder.IsEmpty() {
self.git.Patch.PatchBuilder.Reset()
}
return nil
}
}
func (self *PatchBuildingController) OpenFile() error { func (self *PatchBuildingController) OpenFile() error {
self.context().GetMutex().Lock() self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock() defer self.context().GetMutex().Unlock()

View File

@ -99,6 +99,29 @@ func (self *StagingController) GetMouseKeybindings(opts types.KeybindingsOpts) [
return []*gocui.ViewMouseBinding{} return []*gocui.ViewMouseBinding{}
} }
func (self *StagingController) GetOnFocus() func(types.OnFocusOpts) error {
return func(opts types.OnFocusOpts) error {
self.c.Views().Staging.Wrap = false
self.c.Views().StagingSecondary.Wrap = false
return self.helpers.Staging.RefreshStagingPanel(opts)
}
}
func (self *StagingController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(opts types.OnFocusLostOpts) error {
self.context.SetState(nil)
if opts.NewContextKey != self.otherContext.GetKey() {
self.c.Views().Staging.Wrap = true
self.c.Views().StagingSecondary.Wrap = true
_ = self.contexts.Staging.Render(false)
_ = self.contexts.StagingSecondary.Render(false)
}
return nil
}
}
func (self *StagingController) OpenFile() error { func (self *StagingController) OpenFile() error {
self.context.GetMutex().Lock() self.context.GetMutex().Lock()
defer self.context.GetMutex().Unlock() defer self.context.GetMutex().Unlock()

View File

@ -53,8 +53,8 @@ func (self *SubCommitsController) GetOnRenderToMain() func() error {
} }
} }
func (self *SubCommitsController) GetOnFocus() func() error { func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
return func() error { return func(types.OnFocusOpts) error {
context := self.context context := self.context
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() { if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
context.SetLimitCommits(false) context.SetLimitCommits(false)

View File

@ -22,14 +22,27 @@ func NewSuggestionsController(
} }
func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding { func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{} bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: func() error { return self.context().State.OnClose() },
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: func() error { return self.c.ReplaceContext(self.contexts.Confirmation) },
},
}
return bindings return bindings
} }
func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts) error { func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error { return func(types.OnFocusLostOpts) error {
deactivateConfirmationPrompt self.helpers.Confirmation.DeactivateConfirmationPrompt()
return nil return nil
} }
} }

View File

@ -71,12 +71,12 @@ func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, ch rune, mod g
// This function is called again on refresh as part of the general resize popup call, // This function is called again on refresh as part of the general resize popup call,
// but we need to call it here so that when we go to render the text area it's not // but we need to call it here so that when we go to render the text area it's not
// considered out of bounds to add a newline, meaning we can avoid unnecessary scrolling. // considered out of bounds to add a newline, meaning we can avoid unnecessary scrolling.
err := gui.resizePopupPanel(v, v.TextArea.GetContent()) err := gui.helpers.Confirmation.ResizePopupPanel(v, v.TextArea.GetContent())
if err != nil { if err != nil {
gui.c.Log.Error(err) gui.c.Log.Error(err)
} }
v.RenderTextArea() v.RenderTextArea()
gui.RenderCommitLength() gui.State.Contexts.CommitMessage.RenderCommitLength()
return matched return matched
} }
@ -86,11 +86,12 @@ func (gui *Gui) defaultEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.M
v.RenderTextArea() v.RenderTextArea()
if gui.findSuggestions != nil { suggestionsContext := gui.State.Contexts.Suggestions
if suggestionsContext.State.FindSuggestions != nil {
input := v.TextArea.GetContent() input := v.TextArea.GetContent()
gui.suggestionsAsyncHandler.Do(func() func() { suggestionsContext.State.AsyncHandler.Do(func() func() {
suggestions := gui.findSuggestions(input) suggestions := suggestionsContext.State.FindSuggestions(input)
return func() { gui.setSuggestions(suggestions) } return func() { suggestionsContext.SetSuggestions(suggestions) }
}) })
} }

View File

@ -17,16 +17,6 @@ func (gui *Gui) validateNotInFilterMode() bool {
return true return true
} }
func (gui *Gui) outsideFilterMode(f func() error) func() error {
return func() error {
if !gui.validateNotInFilterMode() {
return nil
}
return f()
}
}
func (gui *Gui) exitFilterMode() error { func (gui *Gui) exitFilterMode() error {
return gui.clearFiltering() return gui.clearFiltering()
} }

View File

@ -1,6 +1,7 @@
package gui package gui
import ( import (
goContext "context"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -94,10 +95,6 @@ type Gui struct {
Mutexes types.Mutexes Mutexes types.Mutexes
// 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
// when you enter into a submodule we'll append the superproject's path to this array // when you enter into a submodule we'll append the superproject's path to this array
// so that you can return to the superproject // so that you can return to the superproject
RepoPathStack *utils.StringStack RepoPathStack *utils.StringStack
@ -113,8 +110,6 @@ type Gui struct {
// the extras window contains things like the command log // the extras window contains things like the command log
ShowExtrasWindow bool ShowExtrasWindow bool
suggestionsAsyncHandler *tasks.AsyncHandler
PopupHandler types.IPopupHandler PopupHandler types.IPopupHandler
IsNewRepo bool IsNewRepo bool
@ -198,9 +193,6 @@ type GuiRepoState struct {
Model *types.Model Model *types.Model
Modes *types.Modes Modes *types.Modes
// Suggestions will sometimes appear when typing into a prompt
Suggestions []*types.Suggestion
SplitMainPanel bool SplitMainPanel bool
LimitCommits bool LimitCommits bool
@ -412,18 +404,17 @@ func NewGui(
initialDir string, initialDir string,
) (*Gui, error) { ) (*Gui, error) {
gui := &Gui{ gui := &Gui{
Common: cmn, Common: cmn,
gitVersion: gitVersion, gitVersion: gitVersion,
Config: config, Config: config,
Updater: updater, Updater: updater,
statusManager: &statusManager{}, statusManager: &statusManager{},
viewBufferManagerMap: map[string]*tasks.ViewBufferManager{}, viewBufferManagerMap: map[string]*tasks.ViewBufferManager{},
viewPtmxMap: map[string]*os.File{}, viewPtmxMap: map[string]*os.File{},
showRecentRepos: showRecentRepos, showRecentRepos: showRecentRepos,
RepoPathStack: &utils.StringStack{}, RepoPathStack: &utils.StringStack{},
RepoStateMap: map[Repo]*GuiRepoState{}, RepoStateMap: map[Repo]*GuiRepoState{},
CmdLog: []string{}, CmdLog: []string{},
suggestionsAsyncHandler: tasks.NewAsyncHandler(),
// originally we could only hide the command log permanently via the config // originally we could only hide the command log permanently via the config
// but now we do it via state. So we need to still support the config for the // but now we do it via state. So we need to still support the config for the
@ -446,7 +437,9 @@ func NewGui(
gui.PopupHandler = popup.NewPopupHandler( gui.PopupHandler = popup.NewPopupHandler(
cmn, cmn,
gui.createPopupPanel, func(ctx goContext.Context, opts types.CreatePopupPanelOpts) error {
return gui.helpers.Confirmation.CreatePopupPanel(ctx, opts)
},
func() error { return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) }, func() error { return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) },
gui.popContext, gui.popContext,
gui.currentContext, gui.currentContext,

View File

@ -144,3 +144,7 @@ func (self *guiCommon) MainViewPairs() types.MainViewPairs {
func (self *guiCommon) State() types.IStateAccessor { func (self *guiCommon) State() types.IStateAccessor {
return self.gui.stateAccessor return self.gui.stateAccessor
} }
func (self *guiCommon) KeybindingsOpts() types.KeybindingsOpts {
return self.gui.keybindingOpts()
}

View File

@ -50,7 +50,7 @@ func (self *GuiDriver) CurrentContext() types.Context {
} }
func (self *GuiDriver) ContextForView(viewName string) types.Context { func (self *GuiDriver) ContextForView(viewName string) types.Context {
context, ok := self.gui.contextForView(viewName) context, ok := self.gui.helpers.View.ContextForView(viewName)
if !ok { if !ok {
return nil return nil
} }

View File

@ -11,7 +11,17 @@ import (
func (gui *Gui) noPopupPanel(f func() error) func() error { func (gui *Gui) noPopupPanel(f func() error) func() error {
return func() error { return func() error {
if gui.popupPanelFocused() { if gui.helpers.Confirmation.IsPopupPanelFocused() {
return nil
}
return f()
}
}
func (gui *Gui) outsideFilterMode(f func() error) func() error {
return func() error {
if !gui.validateNotInFilterMode() {
return nil return nil
} }
@ -34,8 +44,7 @@ func (self *Gui) GetCheatsheetKeybindings() []*types.Binding {
return bindings return bindings
} }
// renaming receiver to 'self' to aid refactoring. Will probably end up moving all Gui handlers to this pattern eventually. func (self *Gui) keybindingOpts() types.KeybindingsOpts {
func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBinding) {
config := self.c.UserConfig.Keybinding config := self.c.UserConfig.Keybinding
guards := types.KeybindingGuards{ guards := types.KeybindingGuards{
@ -43,11 +52,18 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
NoPopupPanel: self.noPopupPanel, NoPopupPanel: self.noPopupPanel,
} }
opts := types.KeybindingsOpts{ return types.KeybindingsOpts{
GetKey: keybindings.GetKey, GetKey: keybindings.GetKey,
Config: config, Config: config,
Guards: guards, Guards: guards,
} }
}
// renaming receiver to 'self' to aid refactoring. Will probably end up moving all Gui handlers to this pattern eventually.
func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBinding) {
config := self.c.UserConfig.Keybinding
opts := self.c.KeybindingsOpts()
bindings := []*types.Binding{ bindings := []*types.Binding{
{ {
@ -486,7 +502,7 @@ func (gui *Gui) SetKeybinding(binding *types.Binding) error {
if gocui.IsMouseKey(binding.Key) { if gocui.IsMouseKey(binding.Key) {
handler = func() error { handler = func() error {
// we ignore click events on views that aren't popup panels, when a popup panel is focused // we ignore click events on views that aren't popup panels, when a popup panel is focused
if gui.popupPanelFocused() && gui.currentViewName() != binding.ViewName { if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName {
return nil return nil
} }
@ -502,7 +518,7 @@ func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error {
baseHandler := binding.Handler baseHandler := binding.Handler
newHandler := func(opts gocui.ViewMouseBindingOpts) error { newHandler := func(opts gocui.ViewMouseBindingOpts) error {
// we ignore click events on views that aren't popup panels, when a popup panel is focused // we ignore click events on views that aren't popup panels, when a popup panel is focused
if gui.popupPanelFocused() && gui.currentViewName() != binding.ViewName { if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName {
return nil return nil
} }

View File

@ -163,7 +163,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
// if you run `lazygit --logs` // if you run `lazygit --logs`
// this will let you see these branches as prettified json // this will let you see these branches as prettified json
// gui.c.Log.Info(utils.AsJson(gui.State.Model.Branches[0:4])) // gui.c.Log.Info(utils.AsJson(gui.State.Model.Branches[0:4]))
return gui.resizeCurrentPopupPanel() return gui.helpers.Confirmation.ResizeCurrentPopupPanel()
} }
func (gui *Gui) prepareView(viewName string) (*gocui.View, error) { func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {
@ -245,7 +245,7 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
return func(g *gocui.Gui) error { return func(g *gocui.Gui) error {
newView := gui.g.CurrentView() newView := gui.g.CurrentView()
// for now we don't consider losing focus to a popup panel as actually losing focus // for now we don't consider losing focus to a popup panel as actually losing focus
if newView != previousView && !gui.isPopupPanel(newView.Name()) { if newView != previousView && !gui.helpers.Confirmation.IsPopupPanel(newView.Name()) {
if err := gui.onViewFocusLost(previousView); err != nil { if err := gui.onViewFocusLost(previousView); err != nil {
return err return err
} }

View File

@ -14,14 +14,7 @@ import (
) )
func (gui *Gui) menuListContext() *context.MenuContext { func (gui *Gui) menuListContext() *context.MenuContext {
return context.NewMenuContext( return context.NewMenuContext(gui.c)
gui.Views.Menu,
gui.c,
gui.getMenuOptions,
func(content string) {
gui.Views.Tooltip.SetContent(content)
},
)
} }
func (gui *Gui) filesListContext() *context.WorkingTreeContext { func (gui *Gui) filesListContext() *context.WorkingTreeContext {
@ -237,14 +230,7 @@ func (gui *Gui) submodulesListContext() *context.SubmodulesContext {
} }
func (gui *Gui) suggestionsListContext() *context.SuggestionsContext { func (gui *Gui) suggestionsListContext() *context.SuggestionsContext {
return context.NewSuggestionsContext( return context.NewSuggestionsContext(gui.c)
func() []*types.Suggestion { return gui.State.Suggestions },
gui.Views.Suggestions,
func(startIdx int, length int) [][]string {
return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions)
},
gui.c,
)
} }
func (gui *Gui) getListContexts() []types.IListContext { func (gui *Gui) getListContexts() []types.IListContext {

View File

@ -1,25 +1,12 @@
package gui package gui
import ( import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
func (gui *Gui) getMenuOptions() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
keybindings.Label(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
}
}
// note: items option is mutated by this function // note: items option is mutated by this function
func (gui *Gui) createMenu(opts types.CreateMenuOptions) error { func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
if !opts.HideCancel { if !opts.HideCancel {

View File

@ -2,12 +2,11 @@ package gui
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
) )
type OptionsMapMgr struct { type OptionsMapMgr struct {
@ -21,36 +20,73 @@ func (gui *Gui) renderContextOptionsMap(c types.Context) {
// render the options available for the current context at the bottom of the screen // render the options available for the current context at the bottom of the screen
func (self *OptionsMapMgr) renderContextOptionsMap(c types.Context) { func (self *OptionsMapMgr) renderContextOptionsMap(c types.Context) {
optionsMap := c.GetOptionsMap() bindingsToDisplay := lo.Filter(c.GetKeybindings(self.c.KeybindingsOpts()), func(binding *types.Binding, _ int) bool {
if optionsMap == nil { return binding.Display
optionsMap = self.globalOptionsMap() })
var optionsMap []bindingInfo
if len(bindingsToDisplay) == 0 {
optionsMap = self.globalOptions()
} else {
optionsMap = lo.Map(bindingsToDisplay, func(binding *types.Binding, _ int) bindingInfo {
return bindingInfo{
key: keybindings.LabelFromKey(binding.Key),
description: binding.Description,
}
})
} }
self.renderOptions(self.optionsMapToString(optionsMap)) self.renderOptions(self.formatBindingInfos(optionsMap))
} }
func (self *OptionsMapMgr) optionsMapToString(optionsMap map[string]string) string { func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string {
options := maps.MapToSlice(optionsMap, func(key string, description string) string { return strings.Join(
return key + ": " + description lo.Map(bindingInfos, func(bindingInfo bindingInfo, _ int) string {
}) return fmt.Sprintf("%s: %s", bindingInfo.key, bindingInfo.description)
sort.Strings(options) }),
return strings.Join(options, ", ") ", ")
} }
func (self *OptionsMapMgr) renderOptions(options string) { func (self *OptionsMapMgr) renderOptions(options string) {
self.c.SetViewContent(self.c.Views().Options, options) self.c.SetViewContent(self.c.Views().Options, options)
} }
func (self *OptionsMapMgr) globalOptionsMap() map[string]string { func (self *OptionsMapMgr) globalOptions() []bindingInfo {
keybindingConfig := self.c.UserConfig.Keybinding keybindingConfig := self.c.UserConfig.Keybinding
return map[string]string{ return []bindingInfo{
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)): self.c.Tr.LcScroll, {
fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcNavigate, key: fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)),
keybindings.Label(keybindingConfig.Universal.Return): self.c.Tr.LcCancel, description: self.c.Tr.LcScroll,
keybindings.Label(keybindingConfig.Universal.Quit): self.c.Tr.LcQuit, },
keybindings.Label(keybindingConfig.Universal.OptionMenuAlt1): self.c.Tr.LcMenu, {
fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): self.c.Tr.LcJump, key: fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)),
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)): self.c.Tr.LcScrollLeftRight, description: self.c.Tr.LcNavigate,
},
{
key: keybindings.Label(keybindingConfig.Universal.Return),
description: self.c.Tr.LcCancel,
},
{
key: keybindings.Label(keybindingConfig.Universal.Quit),
description: self.c.Tr.LcQuit,
},
{
key: keybindings.Label(keybindingConfig.Universal.OptionMenuAlt1),
description: self.c.Tr.LcMenu,
},
{
key: fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])),
description: self.c.Tr.LcJump,
},
{
key: fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)),
description: self.c.Tr.LcScrollLeftRight,
},
} }
} }
type bindingInfo struct {
key string
description string
}

View File

@ -1,26 +0,0 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) getSelectedSuggestionValue() string {
selectedSuggestion := gui.getSelectedSuggestion()
if selectedSuggestion != nil {
return selectedSuggestion.Value
}
return ""
}
func (gui *Gui) getSelectedSuggestion() *types.Suggestion {
return gui.State.Contexts.Suggestions.GetSelected()
}
func (gui *Gui) setSuggestions(suggestions []*types.Suggestion) {
gui.State.Suggestions = suggestions
gui.State.Contexts.Suggestions.SetSelectedLineIdx(0)
gui.c.ResetViewOrigin(gui.Views.Suggestions)
_ = gui.State.Contexts.Suggestions.HandleRender()
}

View File

@ -82,6 +82,8 @@ type IGuiCommon interface {
Mutexes() Mutexes Mutexes() Mutexes
State() IStateAccessor State() IStateAccessor
KeybindingsOpts() KeybindingsOpts
} }
type IPopupHandler interface { type IPopupHandler interface {

View File

@ -17,6 +17,12 @@ type Binding struct {
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
OpensMenu bool OpensMenu bool
// If true, the keybinding will appear at the bottom of the screen. If
// the given view has no bindings with Display: true, the default keybindings
// will be displayed instead.
// TODO: implement this
Display bool
// to be displayed if the keybinding is highlighted from within a menu // to be displayed if the keybinding is highlighted from within a menu
Tooltip string Tooltip string
} }

View File

@ -62,71 +62,6 @@ func (gui *Gui) currentViewName() string {
return currentView.Name() return currentView.Name()
} }
func (gui *Gui) resizeCurrentPopupPanel() error {
v := gui.g.CurrentView()
if v == nil {
return nil
}
if v == gui.Views.Menu {
gui.resizeMenu()
} else if v == gui.Views.Confirmation || v == gui.Views.Suggestions {
gui.resizeConfirmationPanel()
} else if gui.isPopupPanel(v.Name()) {
return gui.resizePopupPanel(v, v.Buffer())
}
return nil
}
func (gui *Gui) resizePopupPanel(v *gocui.View, content string) error {
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(v.Wrap, content)
_, err := gui.g.SetView(v.Name(), x0, y0, x1, y1, 0)
return err
}
func (gui *Gui) resizeMenu() {
itemCount := gui.State.Contexts.Menu.GetList().Len()
offset := 3
panelWidth := gui.getConfirmationPanelWidth()
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
menuBottom := y1 - offset
_, _ = gui.g.SetView(gui.Views.Menu.Name(), x0, y0, x1, menuBottom, 0)
tooltipTop := menuBottom + 1
tooltipHeight := gui.getMessageHeight(true, gui.State.Contexts.Menu.GetSelected().Tooltip, panelWidth) + 2 // plus 2 for the frame
_, _ = gui.g.SetView(gui.Views.Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
}
func (gui *Gui) resizeConfirmationPanel() {
suggestionsViewHeight := 0
if gui.Views.Suggestions.Visible {
suggestionsViewHeight = 11
}
panelWidth := gui.getConfirmationPanelWidth()
prompt := gui.Views.Confirmation.Buffer()
wrap := true
if gui.Views.Confirmation.Editable {
prompt = gui.Views.Confirmation.TextArea.GetContent()
wrap = false
}
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
confirmationViewBottom := y1 - suggestionsViewHeight
_, _ = gui.g.SetView(gui.Views.Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
suggestionsViewTop := confirmationViewBottom + 1
_, _ = gui.g.SetView(gui.Views.Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
}
func (gui *Gui) isPopupPanel(viewName string) bool {
return viewName == "commitMessage" || viewName == "confirmation" || viewName == "menu"
}
func (gui *Gui) popupPanelFocused() bool {
return gui.isPopupPanel(gui.currentViewName())
}
func (gui *Gui) onViewTabClick(windowName string, tabIndex int) error { func (gui *Gui) onViewTabClick(windowName string, tabIndex int) error {
tabs := gui.viewTabMap()[windowName] tabs := gui.viewTabMap()[windowName]
if len(tabs) == 0 { if len(tabs) == 0 {

View File

@ -156,6 +156,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor) gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
gui.Views.Confirmation.Visible = false gui.Views.Confirmation.Visible = false
gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.defaultEditor)
gui.Views.Suggestions.Visible = false gui.Views.Suggestions.Visible = false

View File

@ -94,9 +94,9 @@ func chineseTranslationSet() TranslationSet {
LcNewBranch: "新分支", LcNewBranch: "新分支",
LcDeleteBranch: "删除分支", LcDeleteBranch: "删除分支",
NoBranchesThisRepo: "此仓库中没有分支", NoBranchesThisRepo: "此仓库中没有分支",
CommitMessageConfirm: "{{.keyBindClose}}:关闭,{{.keyBindNewLine}}:新行,{{.keyBindConfirm}}:确认",
CommitWithoutMessageErr: "您必须编写提交消息才能进行提交", CommitWithoutMessageErr: "您必须编写提交消息才能进行提交",
CloseConfirm: "{{.keyBindClose}}:关闭,{{.keyBindConfirm}}:确认", LcCloseCancel: "关闭",
LcConfirm: "确认",
LcClose: "关闭", LcClose: "关闭",
LcQuit: "退出", LcQuit: "退出",
LcSquashDown: "向下压缩", LcSquashDown: "向下压缩",
@ -117,8 +117,6 @@ func chineseTranslationSet() TranslationSet {
LcAmendToCommit: "用已暂存的更改来修补提交", LcAmendToCommit: "用已暂存的更改来修补提交",
LcRenameCommitEditor: "使用编辑器重命名提交", LcRenameCommitEditor: "使用编辑器重命名提交",
Error: "错误", Error: "错误",
LcSelectHunk: "切换区块",
LcNavigateConflicts: "浏览冲突",
LcPickHunk: "选中区块", LcPickHunk: "选中区块",
LcPickAllHunks: "选中所有区块", LcPickAllHunks: "选中所有区块",
LcUndo: "撤销", LcUndo: "撤销",

View File

@ -60,9 +60,9 @@ func dutchTranslationSet() TranslationSet {
LcNewBranch: "nieuwe branch", LcNewBranch: "nieuwe branch",
LcDeleteBranch: "verwijder branch", LcDeleteBranch: "verwijder branch",
NoBranchesThisRepo: "Geen branches voor deze repo", NoBranchesThisRepo: "Geen branches voor deze repo",
CommitMessageConfirm: "{{.keyBindClose}}: Sluiten, {{.keyBindNewLine}}: Nieuwe lijn, {{.keyBindConfirm}}: Bevestig",
CommitWithoutMessageErr: "Je kan geen commit maken zonder commit bericht", CommitWithoutMessageErr: "Je kan geen commit maken zonder commit bericht",
CloseConfirm: "{{.keyBindClose}}: Sluiten, {{.keyBindConfirm}}: Bevestig", LcCloseCancel: "sluiten",
LcConfirm: "bevestig",
LcClose: "sluiten", LcClose: "sluiten",
LcQuit: "quit", LcQuit: "quit",
LcSquashDown: "squash beneden", LcSquashDown: "squash beneden",
@ -83,8 +83,6 @@ func dutchTranslationSet() TranslationSet {
LcRenameCommitEditor: "hernoem commit met editor", LcRenameCommitEditor: "hernoem commit met editor",
NoCommitsThisBranch: "Geen commits in deze branch", NoCommitsThisBranch: "Geen commits in deze branch",
Error: "Foutmelding", Error: "Foutmelding",
LcSelectHunk: "selecteer stuk",
LcNavigateConflicts: "navigeer conflicts",
LcPickHunk: "kies stuk", LcPickHunk: "kies stuk",
LcPickAllHunks: "kies beide stukken", LcPickAllHunks: "kies beide stukken",
LcUndo: "ongedaan maken", LcUndo: "ongedaan maken",

View File

@ -80,10 +80,10 @@ type TranslationSet struct {
LcNewBranch string LcNewBranch string
LcDeleteBranch string LcDeleteBranch string
NoBranchesThisRepo string NoBranchesThisRepo string
CommitMessageConfirm string
CommitWithoutMessageErr string CommitWithoutMessageErr string
CloseConfirm string
LcClose string LcClose string
LcCloseCancel string
LcConfirm string
LcQuit string LcQuit string
LcSquashDown string LcSquashDown string
LcFixupCommit string LcFixupCommit string
@ -107,8 +107,6 @@ type TranslationSet struct {
NoCommitsThisBranch string NoCommitsThisBranch string
UpdateRefHere string UpdateRefHere string
Error string Error string
LcSelectHunk string
LcNavigateConflicts string
LcPickHunk string LcPickHunk string
LcPickAllHunks string LcPickAllHunks string
LcUndo string LcUndo string
@ -750,10 +748,10 @@ func EnglishTranslationSet() TranslationSet {
LcNewBranch: "new branch", LcNewBranch: "new branch",
LcDeleteBranch: "delete branch", LcDeleteBranch: "delete branch",
NoBranchesThisRepo: "No branches for this repo", NoBranchesThisRepo: "No branches for this repo",
CommitMessageConfirm: "{{.keyBindClose}}: close, {{.keyBindNewLine}}: new line, {{.keyBindConfirm}}: confirm",
CommitWithoutMessageErr: "You cannot commit without a commit message", CommitWithoutMessageErr: "You cannot commit without a commit message",
CloseConfirm: "{{.keyBindClose}}: close/cancel, {{.keyBindConfirm}}: confirm",
LcClose: "close", LcClose: "close",
LcCloseCancel: "close/cancel",
LcConfirm: "confirm",
LcQuit: "quit", LcQuit: "quit",
LcSquashDown: "squash down", LcSquashDown: "squash down",
LcFixupCommit: "fixup commit", LcFixupCommit: "fixup commit",
@ -777,8 +775,6 @@ func EnglishTranslationSet() TranslationSet {
SureResetCommitAuthor: "The author field of this commit will be updated to match the configured user. This also renews the author timestamp. Continue?", SureResetCommitAuthor: "The author field of this commit will be updated to match the configured user. This also renews the author timestamp. Continue?",
LcRenameCommitEditor: "reword commit with editor", LcRenameCommitEditor: "reword commit with editor",
Error: "Error", Error: "Error",
LcSelectHunk: "select hunk",
LcNavigateConflicts: "navigate conflicts",
LcPickHunk: "pick hunk", LcPickHunk: "pick hunk",
LcPickAllHunks: "pick all hunks", LcPickAllHunks: "pick all hunks",
LcUndo: "undo", LcUndo: "undo",

View File

@ -85,9 +85,9 @@ func japaneseTranslationSet() TranslationSet {
LcNewBranch: "新しいブランチを作成", LcNewBranch: "新しいブランチを作成",
LcDeleteBranch: "ブランチを削除", LcDeleteBranch: "ブランチを削除",
NoBranchesThisRepo: "リポジトリにブランチが存在しません", NoBranchesThisRepo: "リポジトリにブランチが存在しません",
CommitMessageConfirm: "{{.keyBindClose}}: 閉じる, {{.keyBindNewLine}}: 改行, {{.keyBindConfirm}}: 確定",
CommitWithoutMessageErr: "コミットメッセージを入力してください", CommitWithoutMessageErr: "コミットメッセージを入力してください",
CloseConfirm: "{{.keyBindClose}}: 閉じる/キャンセル, {{.keyBindConfirm}}: 確認", LcCloseCancel: "閉じる/キャンセル",
LcConfirm: "確認",
LcClose: "閉じる", LcClose: "閉じる",
LcQuit: "終了", LcQuit: "終了",
// LcSquashDown: "squash down", // LcSquashDown: "squash down",
@ -108,8 +108,6 @@ func japaneseTranslationSet() TranslationSet {
LcAmendToCommit: "ステージされた変更でamendコミット", LcAmendToCommit: "ステージされた変更でamendコミット",
LcRenameCommitEditor: "エディタでコミットメッセージを編集", LcRenameCommitEditor: "エディタでコミットメッセージを編集",
Error: "エラー", Error: "エラー",
LcSelectHunk: "hunkを選択",
LcNavigateConflicts: "コンフリクトを移動",
// LcPickHunk: "pick hunk", // LcPickHunk: "pick hunk",
// LcPickAllHunks: "pick all hunks", // LcPickAllHunks: "pick all hunks",
LcUndo: "アンドゥ", LcUndo: "アンドゥ",

View File

@ -84,9 +84,9 @@ func koreanTranslationSet() TranslationSet {
LcNewBranch: "새 브랜치 생성", LcNewBranch: "새 브랜치 생성",
LcDeleteBranch: "브랜치 삭제", LcDeleteBranch: "브랜치 삭제",
NoBranchesThisRepo: "저장소에 브랜치가 존재하지 않습니다.", NoBranchesThisRepo: "저장소에 브랜치가 존재하지 않습니다.",
CommitMessageConfirm: "{{.keyBindClose}}: 닫기, {{.keyBindNewLine}}: 개행, {{.keyBindConfirm}}: 확인",
CommitWithoutMessageErr: "커밋 메시지를 입력하세요.", CommitWithoutMessageErr: "커밋 메시지를 입력하세요.",
CloseConfirm: "{{.keyBindClose}}: 닫기/취소, {{.keyBindConfirm}}: 확인", LcCloseCancel: "닫기/취소",
LcConfirm: "확인",
LcClose: "닫기", LcClose: "닫기",
LcQuit: "종료", LcQuit: "종료",
LcSquashDown: "squash down", LcSquashDown: "squash down",
@ -109,8 +109,6 @@ func koreanTranslationSet() TranslationSet {
SureResetCommitAuthor: "The author field of this commit will be updated to match the configured user. This also renews the author timestamp. Continue?", SureResetCommitAuthor: "The author field of this commit will be updated to match the configured user. This also renews the author timestamp. Continue?",
LcRenameCommitEditor: "에디터에서 커밋메시지 수정", LcRenameCommitEditor: "에디터에서 커밋메시지 수정",
Error: "오류", Error: "오류",
LcSelectHunk: "hunk를 선택",
LcNavigateConflicts: "navigate conflicts",
LcPickHunk: "pick hunk", LcPickHunk: "pick hunk",
LcPickAllHunks: "pick all hunks", LcPickAllHunks: "pick all hunks",
LcUndo: "되돌리기", LcUndo: "되돌리기",

View File

@ -55,9 +55,9 @@ func polishTranslationSet() TranslationSet {
LcNewBranch: "nowa gałąź", LcNewBranch: "nowa gałąź",
LcDeleteBranch: "usuń gałąź", LcDeleteBranch: "usuń gałąź",
NoBranchesThisRepo: "Brak gałęzi dla tego repozytorium", NoBranchesThisRepo: "Brak gałęzi dla tego repozytorium",
CommitMessageConfirm: "{{.keyBindClose}}: zamknij, {{.keyBindNewLine}}: nowa linia, {{.keyBindConfirm}}: potwierdź",
CommitWithoutMessageErr: "Nie możesz commitować bez komunikatu", CommitWithoutMessageErr: "Nie możesz commitować bez komunikatu",
CloseConfirm: "{{.keyBindClose}}: zamknij, {{.keyBindConfirm}}: potwierdź", LcCloseCancel: "zamknij",
LcConfirm: "potwierdź",
LcClose: "zamknij", LcClose: "zamknij",
LcSquashDown: "ściśnij", LcSquashDown: "ściśnij",
LcFixupCommit: "napraw commit", LcFixupCommit: "napraw commit",
@ -68,8 +68,6 @@ func polishTranslationSet() TranslationSet {
LcRewordCommit: "zmień nazwę commita", LcRewordCommit: "zmień nazwę commita",
LcRenameCommitEditor: "zmień nazwę commita w edytorze", LcRenameCommitEditor: "zmień nazwę commita w edytorze",
Error: "Błąd", Error: "Błąd",
LcSelectHunk: "wybierz kawałek",
LcNavigateConflicts: "nawiguj konflikty",
LcPickHunk: "wybierz kawałek", LcPickHunk: "wybierz kawałek",
LcPickAllHunks: "wybierz oba kawałki", LcPickAllHunks: "wybierz oba kawałki",
LcUndo: "cofnij", LcUndo: "cofnij",