1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-01 13:17:53 +02:00

more refactoring

This commit is contained in:
Jesse Duffield 2022-02-05 14:42:56 +11:00
parent 8e3484d8e9
commit 482bdc4f1e
30 changed files with 372 additions and 227 deletions

View File

@ -124,7 +124,7 @@ func formatBinding(binding *types.Binding) string {
func getBindingSections(mApp *app.App) []*bindingSection {
bindingSections := []*bindingSection{}
bindings := mApp.Gui.GetInitialKeybindings()
bindings, _ := mApp.Gui.GetInitialKeybindings()
type contextAndViewType struct {
subtitle string

View File

@ -257,7 +257,7 @@ func (gui *Gui) handleToggleCommitFileDirCollapsed() error {
func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
// sometimes the commitFiles view is already shown in another window, so we need to ensure that window
// no longer considers the commitFiles view as its main view.
gui.resetWindowForView(gui.Views.CommitFiles)
gui.resetWindowContext(gui.State.Contexts.CommitFiles)
gui.State.Contexts.CommitFiles.SetSelectedLineIdx(0)
gui.State.Contexts.CommitFiles.SetRefName(opts.RefName)

View File

@ -60,6 +60,10 @@ func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
return errors.New("cannot pass multiple opts to pushContext")
}
if c.GetKey() == context.GLOBAL_CONTEXT_KEY {
return errors.New("Cannot push global context")
}
gui.State.ContextManager.Lock()
// push onto stack
@ -112,6 +116,8 @@ func (gui *Gui) returnFromContext() error {
gui.State.ContextManager.ContextStack = gui.State.ContextManager.ContextStack[:n]
gui.g.SetCurrentContext(string(newContext.GetKey()))
gui.State.ContextManager.Unlock()
if err := gui.deactivateContext(currentContext); err != nil {
@ -146,12 +152,7 @@ func (gui *Gui) deactivateContext(c types.Context) error {
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error {
v, err := gui.g.View(c.GetViewName())
if err != nil {
return nil
}
if types.ContextKey(v.Context) != c.GetKey() {
if gui.State.ViewContextMap[c.GetViewName()].GetKey() != c.GetKey() {
return nil
}
@ -174,19 +175,18 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
if err != nil {
return err
}
originalViewContextKey := types.ContextKey(v.Context)
// ensure that any other window for which this view was active is now set to the default for that window.
gui.setViewAsActiveForWindow(v)
if viewName == "main" {
gui.changeMainViewsContext(c.GetKey())
} else {
gui.changeMainViewsContext(context.MAIN_NORMAL_CONTEXT_KEY)
}
originalViewContextKey := gui.State.ViewContextMap[viewName].GetKey()
gui.setWindowContext(c)
gui.setViewTabForContext(c)
if viewName == "main" {
gui.changeMainViewsContext(c)
} else {
gui.changeMainViewsContext(gui.State.Contexts.Normal)
}
gui.g.SetCurrentContext(string(c.GetKey()))
if _, err := gui.g.SetCurrentView(viewName); err != nil {
return err
}
@ -200,7 +200,7 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
}
}
v.Context = string(c.GetKey())
gui.State.ViewContextMap[viewName] = c
gui.g.Cursor = v.Editable
@ -310,21 +310,6 @@ func (gui *Gui) defaultSideContext() types.Context {
}
}
// remove the need to do this: always use a mapping
func (gui *Gui) setInitialViewContexts() {
// arguably we should only have our ViewContextMap and we should do away with
// contexts on views, or vice versa
for viewName, context := range gui.State.ViewContextMap {
// see if the view exists. If it does, set the context on it
view, err := gui.g.View(viewName)
if err != nil {
continue
}
view.Context = string(context.GetKey())
}
}
// getFocusLayout returns a manager function for when view gain and lose focus
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
var previousView *gocui.View
@ -364,7 +349,7 @@ func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error
_ = oldView.SetOriginX(0)
if oldView == gui.Views.CommitFiles && newView != gui.Views.Main && newView != gui.Views.Secondary && newView != gui.Views.Search {
gui.resetWindowForView(gui.Views.CommitFiles)
gui.resetWindowContext(gui.State.Contexts.CommitFiles)
if err := gui.deactivateContext(gui.State.Contexts.CommitFiles); err != nil {
return err
}
@ -377,20 +362,20 @@ func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error
// which currently just means a context that affects both the main and secondary views
// other views can have their context changed directly but this function helps
// keep the main and secondary views in sync
func (gui *Gui) changeMainViewsContext(contextKey types.ContextKey) {
if gui.State.MainContext == contextKey {
func (gui *Gui) changeMainViewsContext(c types.Context) {
if gui.State.MainContext == c.GetKey() {
return
}
switch contextKey {
switch c.GetKey() {
case context.MAIN_NORMAL_CONTEXT_KEY, context.MAIN_PATCH_BUILDING_CONTEXT_KEY, context.MAIN_STAGING_CONTEXT_KEY, context.MAIN_MERGING_CONTEXT_KEY:
gui.Views.Main.Context = string(contextKey)
gui.Views.Secondary.Context = string(contextKey)
gui.State.ViewContextMap[gui.Views.Main.Name()] = c
gui.State.ViewContextMap[gui.Views.Secondary.Name()] = c
default:
panic(fmt.Sprintf("unknown context for main: %s", contextKey))
panic(fmt.Sprintf("unknown context for main: %s", c.GetKey()))
}
gui.State.MainContext = contextKey
gui.State.MainContext = c.GetKey()
}
func (gui *Gui) viewTabNames(viewName string) []string {
@ -452,8 +437,11 @@ func (gui *Gui) contextForContextKey(contextKey types.ContextKey) (types.Context
}
func (gui *Gui) rerenderView(view *gocui.View) error {
contextKey := types.ContextKey(view.Context)
context := gui.mustContextForContextKey(contextKey)
context, ok := gui.State.ViewContextMap[view.Name()]
if !ok {
panic("no context set against view " + view.Name())
}
return context.HandleRender()
}
@ -467,6 +455,10 @@ func (gui *Gui) getSideContextSelectedItemId() string {
return currentSideContext.GetSelectedItemId()
}
func (gui *Gui) isContextVisible(c types.Context) bool {
return gui.State.WindowViewNameMap[c.GetWindowName()] == c.GetViewName() && gui.State.ViewContextMap[c.GetViewName()].GetKey() == c.GetKey()
}
// currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext()

View File

@ -1,6 +1,7 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -11,8 +12,8 @@ type BaseContext struct {
windowName string
onGetOptionsMap func() map[string]string
keybindingsFns []types.KeybindingsFn
keybindings []*types.Binding
keybindingsFns []types.KeybindingsFn
mouseKeybindingsFns []types.MouseKeybindingsFn
*ParentContextMgr
}
@ -80,3 +81,18 @@ func (self *BaseContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Bin
func (self *BaseContext) AddKeybindingsFn(fn types.KeybindingsFn) {
self.keybindingsFns = append(self.keybindingsFns, fn)
}
func (self *BaseContext) AddMouseKeybindingsFn(fn types.MouseKeybindingsFn) {
self.mouseKeybindingsFns = append(self.mouseKeybindingsFns, fn)
}
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
bindings := []*gocui.ViewMouseBinding{}
for i := range self.mouseKeybindingsFns {
// the first binding in the bindings array takes precedence but we want the
// last keybindingsFn to take precedence to we add them in reverse
bindings = append(bindings, self.mouseKeybindingsFns[len(self.mouseKeybindingsFns)-1-i](opts)...)
}
return bindings
}

View File

@ -3,6 +3,7 @@ package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
const (
GLOBAL_CONTEXT_KEY types.ContextKey = "global"
STATUS_CONTEXT_KEY types.ContextKey = "status"
FILES_CONTEXT_KEY types.ContextKey = "files"
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
@ -29,6 +30,7 @@ const (
)
var AllContextKeys = []types.ContextKey{
GLOBAL_CONTEXT_KEY,
STATUS_CONTEXT_KEY,
FILES_CONTEXT_KEY,
LOCAL_BRANCHES_CONTEXT_KEY,
@ -55,6 +57,7 @@ var AllContextKeys = []types.ContextKey{
}
type ContextTree struct {
Global types.Context
Status types.Context
Files *WorkingTreeContext
Submodules types.IListContext

View File

@ -7,6 +7,7 @@ import (
func (gui *Gui) allContexts() []types.Context {
return []types.Context{
gui.State.Contexts.Global,
gui.State.Contexts.Status,
gui.State.Contexts.Files,
gui.State.Contexts.Submodules,
@ -34,12 +35,23 @@ func (gui *Gui) allContexts() []types.Context {
func (gui *Gui) contextTree() *context.ContextTree {
return &context.ContextTree{
Global: NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT,
ViewName: "",
WindowName: "",
Key: context.GLOBAL_CONTEXT_KEY,
}),
NewSimpleContextOpts{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),
},
),
Status: NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
ViewName: "status",
Key: context.STATUS_CONTEXT_KEY,
WindowName: "status",
Key: context.STATUS_CONTEXT_KEY,
}),
NewSimpleContextOpts{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),

View File

@ -0,0 +1,10 @@
package controllers
import "github.com/jesseduffield/lazygit/pkg/gui/types"
func AttachControllers(context types.Context, controllers ...types.IController) {
for _, controller := range controllers {
context.AddKeybindingsFn(controller.GetKeybindings)
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
}
}

View File

@ -0,0 +1,16 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type baseController struct{}
func (self *baseController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return nil
}
func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return nil
}

View File

@ -11,6 +11,8 @@ import (
)
type BisectController struct {
baseController
c *types.ControllerCommon
context types.IListContext
git *commands.GitCommand
@ -32,10 +34,11 @@ func NewBisectController(
getCommits func() []*models.Commit,
) *BisectController {
return &BisectController{
c: c,
context: context,
git: git,
bisectHelper: bisectHelper,
baseController: baseController{},
c: c,
context: context,
git: git,
bisectHelper: bisectHelper,
getSelectedLocalCommit: getSelectedLocalCommit,
getCommits: getCommits,

View File

@ -190,6 +190,21 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
}
}
func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: "main",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
},
{
ViewName: "secondary",
Key: gocui.MouseLeft,
Handler: self.onClickSecondary,
},
}
}
func (self *FilesController) press(node *filetree.FileNode) error {
if node.IsLeaf() {
file := node.File
@ -672,3 +687,13 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
},
})
}
func (self *FilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
clickedViewLineIdx := opts.Cy + opts.Oy
return self.EnterFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: clickedViewLineIdx})
}
func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) error {
clickedViewLineIdx := opts.Cy + opts.Oy
return self.EnterFile(types.OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: clickedViewLineIdx})
}

View File

@ -7,6 +7,8 @@ import (
)
type GlobalController struct {
baseController
c *types.ControllerCommon
os *oscommands.OSCommand
}
@ -16,8 +18,9 @@ func NewGlobalController(
os *oscommands.OSCommand,
) *GlobalController {
return &GlobalController{
c: c,
os: os,
baseController: baseController{},
c: c,
os: os,
}
}

View File

@ -22,6 +22,7 @@ type (
)
type LocalCommitsController struct {
baseController
c *types.ControllerCommon
context types.IListContext
os *oscommands.OSCommand
@ -68,6 +69,7 @@ func NewLocalCommitsController(
setShowWholeGitGraph func(bool),
) *LocalCommitsController {
return &LocalCommitsController{
baseController: baseController{},
c: c,
context: context,
os: os,

View File

@ -6,6 +6,8 @@ import (
)
type MenuController struct {
baseController
c *types.ControllerCommon
context types.IListContext
@ -20,6 +22,8 @@ func NewMenuController(
getSelectedMenuItem func() *types.MenuItem,
) *MenuController {
return &MenuController{
baseController: baseController{},
c: c,
context: context,
getSelectedMenuItem: getSelectedMenuItem,

View File

@ -10,6 +10,8 @@ import (
)
type RemotesController struct {
baseController
c *types.ControllerCommon
context types.IListContext
git *commands.GitCommand
@ -30,6 +32,7 @@ func NewRemotesController(
setRemoteBranches func([]*models.RemoteBranch),
) *RemotesController {
return &RemotesController{
baseController: baseController{},
c: c,
git: git,
contexts: contexts,

View File

@ -13,6 +13,8 @@ import (
)
type SubmodulesController struct {
baseController
c *types.ControllerCommon
context types.IListContext
git *commands.GitCommand
@ -31,6 +33,7 @@ func NewSubmodulesController(
getSelectedSubmodule func() *models.SubmoduleConfig,
) *SubmodulesController {
return &SubmodulesController{
baseController: baseController{},
c: c,
context: context,
git: git,

View File

@ -11,10 +11,8 @@ import (
)
type SyncController struct {
// I've said publicly that I'm against single-letter variable names but in this
// case I would actually prefer a _zero_ letter variable name in the form of
// struct embedding, but Go does not allow hiding public fields in an embedded struct
// to the client
baseController
c *types.ControllerCommon
git *commands.GitCommand
@ -35,8 +33,9 @@ func NewSyncController(
CheckMergeOrRebase func(error) error,
) *SyncController {
return &SyncController{
c: c,
git: git,
baseController: baseController{},
c: c,
git: git,
getCheckedOutBranch: getCheckedOutBranch,
suggestionsHelper: suggestionsHelper,

View File

@ -9,6 +9,8 @@ import (
)
type TagsController struct {
baseController
c *types.ControllerCommon
context *context.TagsContext
git *commands.GitCommand
@ -35,6 +37,7 @@ func NewTagsController(
switchToSubCommitsContext func(string) error,
) *TagsController {
return &TagsController{
baseController: baseController{},
c: c,
context: context,
git: git,

View File

@ -19,6 +19,8 @@ import (
// two user actions, meaning we end up undoing reflog entry C. Redoing works in a similar way.
type UndoController struct {
baseController
c *types.ControllerCommon
git *commands.GitCommand
@ -39,6 +41,7 @@ func NewUndoController(
getFilteredReflogCommits func() []*models.Commit,
) *UndoController {
return &UndoController{
baseController: baseController{},
c: c,
git: git,
refsHelper: refsHelper,

View File

@ -182,11 +182,6 @@ func (gui *Gui) handleRefresh() error {
func (gui *Gui) handleMouseDownMain() error {
switch gui.currentSideContext() {
case gui.State.Contexts.Files:
// set filename, set primary/secondary selected, set line number, then switch context
// I'll need to know it was changed though.
// Could I pass something along to the context change?
return gui.Controllers.Files.EnterFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()})
case gui.State.Contexts.CommitFiles:
return gui.enterCommitFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()})
}
@ -194,15 +189,6 @@ func (gui *Gui) handleMouseDownMain() error {
return nil
}
func (gui *Gui) handleMouseDownSecondary() error {
switch gui.g.CurrentView() {
case gui.Views.Files:
return gui.Controllers.Files.EnterFile(types.OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: gui.Views.Secondary.SelectedLineIdx()})
}
return nil
}
func (gui *Gui) fetch() (err error) {
gui.c.LogAction("Fetch")
err = gui.git.Sync.Fetch(git_commands.FetchOptions{})

View File

@ -590,6 +590,15 @@ func (gui *Gui) resetControllers() {
gui.getSelectedSubmodule,
)
bisectController := controllers.NewBisectController(
controllerCommon,
gui.State.Contexts.BranchCommits,
gui.git,
gui.helpers.Bisect,
gui.getSelectedLocalCommit,
func() []*models.Commit { return gui.State.Model.Commits },
)
gui.Controllers = Controllers{
Submodules: submodulesController,
Global: controllers.NewGlobalController(
@ -660,14 +669,6 @@ func (gui *Gui) resetControllers() {
gui.State.Contexts.Menu,
gui.getSelectedMenuItem,
),
Bisect: controllers.NewBisectController(
controllerCommon,
gui.State.Contexts.BranchCommits,
gui.git,
gui.helpers.Bisect,
gui.getSelectedLocalCommit,
func() []*models.Commit { return gui.State.Model.Commits },
),
Undo: controllers.NewUndoController(
controllerCommon,
gui.git,
@ -678,17 +679,13 @@ func (gui *Gui) resetControllers() {
Sync: syncController,
}
gui.State.Contexts.Submodules.AddKeybindingsFn(gui.Controllers.Submodules.GetKeybindings)
gui.Controllers.Files.Attach(gui.State.Contexts.Files)
gui.State.Contexts.Files.AddKeybindingsFn(gui.Controllers.Files.GetKeybindings)
gui.State.Contexts.Tags.AddKeybindingsFn(gui.Controllers.Tags.GetKeybindings)
// TODO: commit to one name here: local commits or branch commits
gui.State.Contexts.BranchCommits.AddKeybindingsFn(gui.Controllers.LocalCommits.GetKeybindings)
gui.State.Contexts.BranchCommits.AddKeybindingsFn(gui.Controllers.Bisect.GetKeybindings)
gui.State.Contexts.Remotes.AddKeybindingsFn(gui.Controllers.Remotes.GetKeybindings)
gui.State.Contexts.Menu.AddKeybindingsFn(gui.Controllers.Menu.GetKeybindings)
gui.State.Contexts.Menu.AddKeybindingsFn(gui.Controllers.Menu.GetKeybindings)
// TODO: handle global contexts
controllers.AttachControllers(gui.State.Contexts.Files, gui.Controllers.Files)
controllers.AttachControllers(gui.State.Contexts.Tags, gui.Controllers.Tags)
controllers.AttachControllers(gui.State.Contexts.Submodules, gui.Controllers.Submodules)
controllers.AttachControllers(gui.State.Contexts.BranchCommits, gui.Controllers.LocalCommits, bisectController)
controllers.AttachControllers(gui.State.Contexts.Remotes, gui.Controllers.Remotes)
controllers.AttachControllers(gui.State.Contexts.Menu, gui.Controllers.Menu)
controllers.AttachControllers(gui.State.Contexts.Global, gui.Controllers.Sync, gui.Controllers.Undo, gui.Controllers.Global)
}
var RuneReplacements = map[rune]string{

View File

@ -39,7 +39,6 @@ import (
// original playback speed. Speed may be a decimal.
func Test(t *testing.T) {
return
mode := integration.GetModeFromEnv()
speedEnv := os.Getenv("SPEED")
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""

View File

@ -194,8 +194,7 @@ func (gui *Gui) noPopupPanel(f func() error) func() error {
}
}
// GetInitialKeybindings is a function.
func (gui *Gui) GetInitialKeybindings() []*types.Binding {
func (gui *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBinding) {
config := gui.c.UserConfig.Keybinding
guards := types.KeybindingGuards{
@ -306,12 +305,6 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCreateOptionsMenu,
},
{
ViewName: "",
Key: gocui.MouseMiddle,
Modifier: gocui.ModNone,
Handler: gui.handleCreateOptionsMenu,
},
{
ViewName: "status",
Key: gui.getKey(config.Universal.Edit),
@ -788,13 +781,6 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Modifier: gocui.ModNone,
Handler: gui.scrollDownSecondary,
},
{
ViewName: "secondary",
Contexts: []string{string(context.MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleMouseDownSecondary,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_NORMAL_CONTEXT_KEY)},
@ -1361,35 +1347,24 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Guards: guards,
}
// global bindings
for _, controller := range []types.IController{
gui.Controllers.Sync,
gui.Controllers.Undo,
gui.Controllers.Global,
} {
context := controller.Context()
viewName := ""
var contextKeys []string
// nil context means global keybinding
if context != nil {
viewName = context.GetViewName()
contextKeys = []string{string(context.GetKey())}
}
for _, binding := range controller.GetKeybindings(keybindingsOpts) {
binding.Contexts = contextKeys
mouseKeybindings := []*gocui.ViewMouseBinding{}
for _, c := range gui.allContexts() {
viewName := c.GetViewName()
contextKey := c.GetKey()
for _, binding := range c.GetKeybindings(keybindingsOpts) {
// TODO: move all mouse keybindings into the mouse keybindings approach below
if !gocui.IsMouseKey(binding.Key) && contextKey != context.GLOBAL_CONTEXT_KEY {
binding.Contexts = []string{string(contextKey)}
}
binding.ViewName = viewName
bindings = append(bindings, binding)
}
}
for _, context := range gui.allContexts() {
viewName := context.GetViewName()
contextKey := context.GetKey()
for _, binding := range context.GetKeybindings(keybindingsOpts) {
binding.Contexts = []string{string(contextKey)}
binding.ViewName = viewName
bindings = append(bindings, binding)
for _, binding := range c.GetMouseKeybindings(keybindingsOpts) {
if contextKey != context.GLOBAL_CONTEXT_KEY {
binding.FromContext = string(contextKey)
}
mouseKeybindings = append(mouseKeybindings, binding)
}
}
@ -1438,7 +1413,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
}...)
}
return bindings
return bindings, mouseKeybindings
}
func (gui *Gui) resetKeybindings() error {
@ -1446,7 +1421,10 @@ func (gui *Gui) resetKeybindings() error {
bindings := gui.GetCustomCommandKeybindings()
bindings = append(bindings, gui.GetInitialKeybindings()...)
bindings, mouseBindings := gui.GetInitialKeybindings()
// prepending because we want to give our custom keybindings precedence over default keybindings
bindings = append(gui.GetCustomCommandKeybindings(), bindings...)
for _, binding := range bindings {
if err := gui.SetKeybinding(binding); err != nil {
@ -1454,6 +1432,12 @@ func (gui *Gui) resetKeybindings() error {
}
}
for _, binding := range mouseBindings {
if err := gui.SetMouseKeybinding(binding); err != nil {
return err
}
}
for viewName := range gui.State.Contexts.InitialViewTabContextMap() {
viewName := viewName
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(viewName, tabIndex) }
@ -1474,7 +1458,8 @@ func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View)
func (gui *Gui) SetKeybinding(binding *types.Binding) error {
handler := binding.Handler
if isMouseKey(binding.Key) {
// TODO: move all mouse-ey stuff into new mouse approach
if gocui.IsMouseKey(binding.Key) {
handler = func() error {
// we ignore click events on views that aren't popup panels, when a popup panel is focused
if gui.popupPanelFocused() && gui.currentViewName() != binding.ViewName {
@ -1488,19 +1473,18 @@ func (gui *Gui) SetKeybinding(binding *types.Binding) error {
return gui.g.SetKeybinding(binding.ViewName, binding.Contexts, binding.Key, binding.Modifier, gui.wrappedHandler(handler))
}
func isMouseKey(key interface{}) bool {
switch key {
case
gocui.MouseLeft,
gocui.MouseRight,
gocui.MouseMiddle,
gocui.MouseRelease,
gocui.MouseWheelUp,
gocui.MouseWheelDown,
gocui.MouseWheelLeft,
gocui.MouseWheelRight:
return true
default:
return false
// warning: mutates the binding
func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error {
baseHandler := binding.Handler
newHandler := func(opts gocui.ViewMouseBindingOpts) error {
// we ignore click events on views that aren't popup panels, when a popup panel is focused
if gui.popupPanelFocused() && gui.currentViewName() != binding.ViewName {
return nil
}
return baseHandler(opts)
}
binding.Handler = newHandler
return gui.g.SetViewClickBinding(binding)
}

View File

@ -2,7 +2,6 @@ package gui
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@ -262,8 +261,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
continue
}
// ignore contexts whose view is owned by another context right now
if types.ContextKey(view.Context) != listContext.GetKey() {
if !gui.isContextVisible(listContext) {
continue
}
@ -300,8 +298,6 @@ func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {
}
func (gui *Gui) onInitialViewsCreationForRepo() error {
gui.setInitialViewContexts()
// hide any popup views. This only applies when we've just switched repos
for _, viewName := range gui.popupViewNames() {
view, err := gui.g.View(viewName)

View File

@ -3,28 +3,25 @@ package gui
import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) getBindings(v *gocui.View) []*types.Binding {
func (gui *Gui) getBindings(context types.Context) []*types.Binding {
var (
bindingsGlobal, bindingsPanel []*types.Binding
)
bindings := append(gui.GetCustomCommandKeybindings(), gui.GetInitialKeybindings()...)
bindings, _ := gui.GetInitialKeybindings()
bindings = append(gui.GetCustomCommandKeybindings(), bindings...)
for _, binding := range bindings {
if GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
switch binding.ViewName {
case "":
if len(binding.Contexts) == 0 {
bindingsGlobal = append(bindingsGlobal, binding)
case v.Name():
if len(binding.Contexts) == 0 || utils.IncludesString(binding.Contexts, v.Context) {
bindingsPanel = append(bindingsPanel, binding)
}
} else if utils.IncludesString(binding.Contexts, string(context.GetKey())) {
bindingsPanel = append(bindingsPanel, binding)
}
}
}
@ -48,12 +45,8 @@ func opensMenuStyle(str string) string {
}
func (gui *Gui) handleCreateOptionsMenu() error {
view := gui.g.CurrentView()
if view == nil {
return nil
}
bindings := gui.getBindings(view)
context := gui.currentContext()
bindings := gui.getBindings(context)
menuItems := make([]*types.MenuItem, len(bindings))

View File

@ -318,7 +318,7 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
gui.c.Log.Error(err)
}
if types.ContextKey(gui.Views.Files.Context) == context.FILES_CONTEXT_KEY {
if gui.isContextVisible(gui.State.Contexts.Files) {
// doing this a little custom (as opposed to using gui.c.PostRefreshUpdate) because we handle selecting the file explicitly below
if err := gui.State.Contexts.Files.HandleRender(); err != nil {
return err
@ -503,7 +503,15 @@ func (gui *Gui) refreshRemotes() error {
}
}
return gui.c.PostRefreshUpdate(gui.mustContextForContextKey(types.ContextKey(gui.Views.Branches.Context)))
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Remotes); err != nil {
return err
}
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.RemoteBranches); err != nil {
return err
}
return nil
}
func (gui *Gui) refreshStashEntries() error {

View File

@ -1,6 +1,9 @@
package types
import "github.com/jesseduffield/lazygit/pkg/config"
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
)
type ContextKind int
@ -10,6 +13,8 @@ const (
TEMPORARY_POPUP
PERSISTENT_POPUP
EXTRAS_CONTEXT
// only used by the one global context
GLOBAL_CONTEXT
)
type ParentContexter interface {
@ -31,6 +36,8 @@ type IBaseContext interface {
GetKeybindings(opts KeybindingsOpts) []*Binding
AddKeybindingsFn(KeybindingsFn)
GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding
AddMouseKeybindingsFn(MouseKeybindingsFn)
}
type Context interface {
@ -56,9 +63,11 @@ type KeybindingsOpts struct {
}
type KeybindingsFn func(opts KeybindingsOpts) []*Binding
type MouseKeybindingsFn func(opts KeybindingsOpts) []*gocui.ViewMouseBinding
type HasKeybindings interface {
GetKeybindings(opts KeybindingsOpts) []*Binding
GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding
}
type IController interface {

View File

@ -1,6 +1,8 @@
package gui
import "github.com/jesseduffield/gocui"
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// A window refers to a place on the screen which can hold one or more views.
// A view is a box that renders content, and within a window only one view will
@ -17,28 +19,21 @@ func (gui *Gui) getViewNameForWindow(window string) string {
return viewName
}
func (gui *Gui) getWindowForView(view *gocui.View) string {
if view == gui.Views.CommitFiles {
return gui.State.Contexts.CommitFiles.GetWindowName()
}
return view.Name()
}
func (gui *Gui) setViewAsActiveForWindow(view *gocui.View) {
// for now all we actually care about is the context's view so we're storing that
func (gui *Gui) setWindowContext(c types.Context) {
if gui.State.WindowViewNameMap == nil {
gui.State.WindowViewNameMap = map[string]string{}
}
gui.State.WindowViewNameMap[gui.getWindowForView(view)] = view.Name()
gui.State.WindowViewNameMap[c.GetWindowName()] = c.GetViewName()
}
func (gui *Gui) currentWindow() string {
return gui.getWindowForView(gui.g.CurrentView())
return gui.currentContext().GetWindowName()
}
func (gui *Gui) resetWindowForView(view *gocui.View) {
window := gui.getWindowForView(view)
func (gui *Gui) resetWindowContext(c types.Context) {
// we assume here that the window contains as its default view a view with the same name as the window
gui.State.WindowViewNameMap[window] = window
windowName := c.GetWindowName()
gui.State.WindowViewNameMap[windowName] = windowName
}

View File

@ -69,6 +69,30 @@ type tabClickBinding struct {
handler tabClickHandler
}
type ViewMouseBinding struct {
// the view that is clicked
ViewName string
// the context we are in when the click occurs. Not necessarily the context
// of the view we're clicking. If this is blank then it is a global binding.
FromContext string
Handler func(ViewMouseBindingOpts) error
// must be a mouse key
Key Key
}
type ViewMouseBindingOpts struct {
// cursor x/y
Cx int
Cy int
// origin x/y
Ox int
Oy int
}
type GuiMutexes struct {
// tickingMutex ensures we don't have two loops ticking. The point of 'ticking'
// is to refresh the gui rapidly so that loader characters can be animated.
@ -110,17 +134,18 @@ type Gui struct {
PlayMode PlayMode
StartTime time.Time
tabClickBindings []*tabClickBinding
gEvents chan GocuiEvent
userEvents chan userEvent
views []*View
currentView *View
managers []Manager
keybindings []*keybinding
maxX, maxY int
outputMode OutputMode
stop chan struct{}
blacklist []Key
tabClickBindings []*tabClickBinding
viewMouseBindings []*ViewMouseBinding
gEvents chan GocuiEvent
userEvents chan userEvent
views []*View
currentView *View
managers []Manager
keybindings []*keybinding
maxX, maxY int
outputMode OutputMode
stop chan struct{}
blacklist []Key
// BgColor and FgColor allow to configure the background and foreground
// colors of the GUI.
@ -162,6 +187,8 @@ type Gui struct {
screen tcell.Screen
suspendedMutex sync.Mutex
suspended bool
currentContext string
}
// NewGui returns a new Gui object with a given output mode.
@ -237,6 +264,10 @@ func (g *Gui) Size() (x, y int) {
return g.maxX, g.maxY
}
func (g *Gui) SetCurrentContext(context string) {
g.currentContext = context
}
// SetRune writes a rune at the given point, relative to the top-left
// corner of the terminal. It checks if the position is valid and applies
// the given colors.
@ -472,6 +503,7 @@ func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) e
func (g *Gui) DeleteAllKeybindings() {
g.keybindings = []*keybinding{}
g.tabClickBindings = []*tabClickBinding{}
g.viewMouseBindings = []*ViewMouseBinding{}
}
// DeleteKeybindings deletes all keybindings of view.
@ -495,6 +527,12 @@ func (g *Gui) SetTabClickBinding(viewName string, handler tabClickHandler) error
return nil
}
func (g *Gui) SetViewClickBinding(binding *ViewMouseBinding) error {
g.viewMouseBindings = append(g.viewMouseBindings, binding)
return nil
}
// BlackListKeybinding adds a keybinding to the blacklist
func (g *Gui) BlacklistKeybinding(k Key) error {
for _, j := range g.blacklist {
@ -1098,6 +1136,17 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
return err
}
if ev.Mod == ModNone && IsMouseKey(ev.Key) {
opts := ViewMouseBindingOpts{Cx: newCx, Cy: newCy, Ox: v.ox, Oy: v.oy}
matched, err := g.execMouseKeybindings(v.Name(), ev, opts)
if err != nil {
return err
}
if matched {
return nil
}
}
if _, err := g.execKeybindings(v, ev); err != nil {
return err
}
@ -1106,6 +1155,40 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
return nil
}
func (g *Gui) execMouseKeybindings(viewName string, ev *GocuiEvent, opts ViewMouseBindingOpts) (bool, error) {
// first pass looks for ones that match both the view and the current context
for _, binding := range g.viewMouseBindings {
if binding.ViewName == viewName && binding.FromContext == g.currentContext && ev.Key == binding.Key {
return true, binding.Handler(opts)
}
}
for _, binding := range g.viewMouseBindings {
if binding.ViewName == viewName && ev.Key == binding.Key {
return true, binding.Handler(opts)
}
}
return false, nil
}
func IsMouseKey(key interface{}) bool {
switch key {
case
MouseLeft,
MouseRight,
MouseMiddle,
MouseRelease,
MouseWheelUp,
MouseWheelDown,
MouseWheelLeft,
MouseWheelRight:
return true
default:
return false
}
}
// execKeybindings executes the keybinding handlers that match the passed view
// and event. The value of matched is true if there is a match and no errors.
func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error) {
@ -1136,10 +1219,10 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error)
if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
continue
}
if kb.matchView(v) {
if g.matchView(v, kb) {
return g.execKeybinding(v, kb)
}
if v != nil && kb.matchView(v.ParentView) {
if v != nil && g.matchView(v.ParentView, kb) {
matchingParentViewKb = kb
}
if globalKb == nil && kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) {
@ -1340,3 +1423,27 @@ func (g *Gui) Resume() error {
return g.screen.Resume()
}
// matchView returns if the keybinding matches the current view (and the view's context)
func (g *Gui) matchView(v *View, kb *keybinding) bool {
// if the user is typing in a field, ignore char keys
if v == nil {
return false
}
if v.Editable == true && kb.ch != 0 {
return false
}
if kb.viewName != v.name {
return false
}
// if the keybinding doesn't specify contexts, it applies for all contexts
if len(kb.contexts) == 0 {
return true
}
for _, context := range kb.contexts {
if context == g.currentContext {
return true
}
}
return false
}

View File

@ -124,30 +124,6 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
return kb.key == key && kb.ch == ch && kb.mod == mod
}
// matchView returns if the keybinding matches the current view (and the view's context)
func (kb *keybinding) matchView(v *View) bool {
// if the user is typing in a field, ignore char keys
if v == nil {
return false
}
if v.Editable == true && kb.ch != 0 {
return false
}
if kb.viewName != v.name {
return false
}
// if the keybinding doesn't specify contexts, it applies for all contexts
if len(kb.contexts) == 0 {
return true
}
for _, context := range kb.contexts {
if context == v.Context {
return true
}
}
return false
}
// translations for strings to keys
var translate = map[string]Key{
"F1": KeyF1,

View File

@ -149,8 +149,6 @@ type View struct {
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
ParentView *View
Context string // this is for assigning keybindings to a view only in certain contexts
searcher *searcher
// KeybindOnEdit should be set to true when you want to execute keybindings even when the view is editable