1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-08-06 22:33:07 +02:00

Improve mouse handling of suggestions panel (#4726)

- **PR Description**

In prompts with a suggestions panel (e.g. the "Execute shell command"
window, or the "Filter by path" panel), you can now
- scroll the list of suggestions with the mouse wheel even when the
  focus is in the edit field
- click in the suggestions list or in the edit field to switch focus
  between them
- double-click a suggestion to trigger it
This commit is contained in:
Stefan Haller
2025-07-11 11:26:39 +02:00
committed by GitHub
7 changed files with 73 additions and 37 deletions

View File

@ -88,3 +88,7 @@ func (self *SuggestionsContext) RefreshSuggestions() {
func (self *SuggestionsContext) RangeSelectEnabled() bool { func (self *SuggestionsContext) RangeSelectEnabled() bool {
return false return false
} }
func (self *SuggestionsContext) GetOnClick() func() error {
return self.State.OnConfirm
}

View File

@ -2,7 +2,6 @@ package controllers
import ( import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"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/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
@ -58,9 +57,10 @@ func (self *CommitDescriptionController) Context() types.Context {
func (self *CommitDescriptionController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { func (self *CommitDescriptionController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{ return []*gocui.ViewMouseBinding{
{ {
ViewName: self.Context().GetViewName(), ViewName: self.Context().GetViewName(),
Key: gocui.MouseLeft, FocusedView: self.c.Contexts().CommitMessage.GetViewName(),
Handler: self.onClick, Key: gocui.MouseLeft,
Handler: self.onClick,
}, },
} }
} }
@ -137,10 +137,6 @@ func (self *CommitDescriptionController) openCommitMenu() error {
} }
func (self *CommitDescriptionController) onClick(opts gocui.ViewMouseBindingOpts) error { func (self *CommitDescriptionController) onClick(opts gocui.ViewMouseBindingOpts) error {
// Activate the description panel when the commit message panel is currently active self.c.Context().Replace(self.c.Contexts().CommitDescription)
if self.c.Context().Current().GetKey() == context.COMMIT_MESSAGE_CONTEXT_KEY {
self.c.Context().Replace(self.c.Contexts().CommitDescription)
}
return nil return nil
} }

View File

@ -62,9 +62,10 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{ return []*gocui.ViewMouseBinding{
{ {
ViewName: self.Context().GetViewName(), ViewName: self.Context().GetViewName(),
Key: gocui.MouseLeft, FocusedView: self.c.Contexts().CommitDescription.GetViewName(),
Handler: self.onClick, Key: gocui.MouseLeft,
Handler: self.onClick,
}, },
} }
} }
@ -194,10 +195,6 @@ func (self *CommitMessageController) openCommitMenu() error {
} }
func (self *CommitMessageController) onClick(opts gocui.ViewMouseBindingOpts) error { func (self *CommitMessageController) onClick(opts gocui.ViewMouseBindingOpts) error {
// Activate the commit message panel when the commit description panel is currently active self.c.Context().Replace(self.c.Contexts().CommitMessage)
if self.c.Context().Current().GetKey() == context.COMMIT_DESCRIPTION_CONTEXT_KEY {
self.c.Context().Replace(self.c.Contexts().CommitMessage)
}
return nil return nil
} }

View File

@ -3,6 +3,7 @@ package controllers
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -41,15 +42,7 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
Key: opts.GetKey(opts.Config.Universal.TogglePanel), Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: func() error { Handler: func() error {
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 { if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
subtitle := "" self.switchToSuggestions()
if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil {
// We assume that whenever things are deletable, they
// are also editable, so we show both keybindings
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit)
}
self.c.Views().Suggestions.Subtitle = subtitle
self.c.Context().Replace(self.c.Contexts().Suggestions)
} }
return nil return nil
}, },
@ -59,6 +52,22 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
return bindings return bindings
} }
func (self *ConfirmationController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.c.Contexts().Suggestions.GetViewName(),
FocusedView: self.c.Contexts().Confirmation.GetViewName(),
Key: gocui.MouseLeft,
Handler: func(gocui.ViewMouseBindingOpts) error {
self.switchToSuggestions()
// Let it fall through to the ListController's click handler so that
// the clicked line gets selected:
return gocui.ErrKeybindingNotHandled
},
},
}
}
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) { func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) {
return func(types.OnFocusLostOpts) { return func(types.OnFocusLostOpts) {
self.c.Helpers().Confirmation.DeactivateConfirmationPrompt() self.c.Helpers().Confirmation.DeactivateConfirmationPrompt()
@ -72,3 +81,15 @@ func (self *ConfirmationController) Context() types.Context {
func (self *ConfirmationController) context() *context.ConfirmationContext { func (self *ConfirmationController) context() *context.ConfirmationContext {
return self.c.Contexts().Confirmation return self.c.Contexts().Confirmation
} }
func (self *ConfirmationController) switchToSuggestions() {
subtitle := ""
if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil {
// We assume that whenever things are deletable, they
// are also editable, so we show both keybindings
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit)
}
self.c.Views().Suggestions.Subtitle = subtitle
self.c.Context().Replace(self.c.Contexts().Suggestions)
}

View File

@ -89,8 +89,6 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
// self.c.Model().FilesTrie. On the main thread we'll be doing a fuzzy search via // self.c.Model().FilesTrie. On the main thread we'll be doing a fuzzy search via
// self.c.Model().FilesTrie. So if we've looked for a file previously, we'll start with // self.c.Model().FilesTrie. So if we've looked for a file previously, we'll start with
// the old trie and eventually it'll be swapped out for the new one. // the old trie and eventually it'll be swapped out for the new one.
// Notably, unlike other suggestion functions we're not showing all the options
// if nothing has been typed because there'll be too much to display efficiently
func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion { func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion {
_ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func(gocui.Task) error { _ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func(gocui.Task) error {
trie := patricia.NewTrie() trie := patricia.NewTrie()
@ -105,7 +103,9 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
if err != nil { if err != nil {
return err return err
} }
trie.Insert(patricia.Prefix(path), path) if path != "." {
trie.Insert(patricia.Prefix(path), path)
}
return nil return nil
}) })

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -69,6 +70,19 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []
return bindings return bindings
} }
func (self *SuggestionsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.c.Contexts().Confirmation.GetViewName(),
FocusedView: self.c.Contexts().Suggestions.GetViewName(),
Key: gocui.MouseLeft,
Handler: func(gocui.ViewMouseBindingOpts) error {
return self.switchToConfirmation()
},
},
}
}
func (self *SuggestionsController) switchToConfirmation() error { func (self *SuggestionsController) switchToConfirmation() error {
self.c.Views().Suggestions.Subtitle = "" self.c.Views().Suggestions.Subtitle = ""
self.c.Views().Suggestions.Highlight = false self.c.Views().Suggestions.Highlight = false

View File

@ -504,15 +504,19 @@ func (gui *Gui) SetKeybinding(binding *types.Binding) error {
func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error { 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.
// Unless both the current view and the clicked-on view are either commit message or commit
// description, because we want to allow switching between those two views by clicking.
isCommitMessageView := func(viewName string) bool {
return viewName == "commitMessage" || viewName == "commitDescription"
}
if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName && if gui.helpers.Confirmation.IsPopupPanelFocused() && gui.currentViewName() != binding.ViewName &&
(!isCommitMessageView(gui.currentViewName()) || !isCommitMessageView(binding.ViewName)) { !gocui.IsMouseScrollKey(opts.Key) {
return nil // we ignore click events on views that aren't popup panels, when a popup panel is focused.
// Unless both the current view and the clicked-on view are either commit message or commit
// description, or a confirmation and the suggestions view, because we want to allow switching
// between those two views by clicking.
isCommitMessageOrSuggestionsView := func(viewName string) bool {
return viewName == "commitMessage" || viewName == "commitDescription" ||
viewName == "confirmation" || viewName == "suggestions"
}
if !isCommitMessageOrSuggestionsView(gui.currentViewName()) || !isCommitMessageOrSuggestionsView(binding.ViewName) {
return nil
}
} }
return baseHandler(opts) return baseHandler(opts)