mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Constructing a menu item key from a literal character requires
gocui.NewKeyRune('r'), which is a bit noisy. Add a private menuKey helper in
both the controllers and helpers packages so the common case in either reads as
menuKey('r'). Duplicating the one-liner is cheaper than a cross-package import
dependency and avoids forcing every controller file to qualify the call.
The reason for doing this now is that we are going to change MenuItem.Key to a
slice of keys later in the branch, which means we'd have to add `[]gocui.Key{`
at each call site, making them even more noisy. With the menuKey helper we can
just change its signature and leave all clients unchanged.
326 lines
10 KiB
Go
326 lines
10 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/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/utils"
|
|
)
|
|
|
|
type SubmodulesController struct {
|
|
baseController
|
|
*ListControllerTrait[*models.SubmoduleConfig]
|
|
c *ControllerCommon
|
|
}
|
|
|
|
var _ types.IController = &SubmodulesController{}
|
|
|
|
func NewSubmodulesController(
|
|
c *ControllerCommon,
|
|
) *SubmodulesController {
|
|
return &SubmodulesController{
|
|
baseController: baseController{},
|
|
ListControllerTrait: NewListControllerTrait(
|
|
c,
|
|
c.Contexts().Submodules,
|
|
c.Contexts().Submodules.GetSelected,
|
|
c.Contexts().Submodules.GetSelectedItems,
|
|
),
|
|
c: c,
|
|
}
|
|
}
|
|
|
|
func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
|
return []*types.Binding{
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.GoInto),
|
|
Handler: self.withItem(self.enter),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Enter,
|
|
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.EnterSubmoduleTooltip,
|
|
map[string]string{"escape": opts.Config.Universal.Return}),
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.Select),
|
|
Handler: self.withItem(self.enter),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.Remove),
|
|
Handler: self.withItem(self.remove),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Remove,
|
|
Tooltip: self.c.Tr.RemoveSubmoduleTooltip,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Submodules.Update),
|
|
Handler: self.withItem(self.update),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Update,
|
|
Tooltip: self.c.Tr.SubmoduleUpdateTooltip,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.New),
|
|
Handler: self.add,
|
|
Description: self.c.Tr.NewSubmodule,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.Edit),
|
|
Handler: self.withItem(self.editURL),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.EditSubmoduleUrl,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Submodules.Init),
|
|
Handler: self.withItem(self.init),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Initialize,
|
|
Tooltip: self.c.Tr.InitSubmoduleTooltip,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Submodules.BulkMenu),
|
|
Handler: self.openBulkActionsMenu,
|
|
Description: self.c.Tr.ViewBulkSubmoduleOptions,
|
|
OpensMenu: true,
|
|
},
|
|
{
|
|
Handler: self.easterEgg,
|
|
Description: self.c.Tr.EasterEgg,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (self *SubmodulesController) GetOnDoubleClick() func() error {
|
|
return self.withItemGraceful(self.enter)
|
|
}
|
|
|
|
func (self *SubmodulesController) GetOnRenderToMain() func() {
|
|
return func() {
|
|
self.c.Helpers().Diff.WithDiffModeCheck(func() {
|
|
var task types.UpdateTask
|
|
submodule := self.context().GetSelected()
|
|
if submodule == nil {
|
|
task = types.NewRenderStringTask("No submodules")
|
|
} else {
|
|
prefix := fmt.Sprintf(
|
|
"Name: %s\nPath: %s\nUrl: %s\n\n",
|
|
style.FgGreen.Sprint(submodule.FullName()),
|
|
style.FgYellow.Sprint(submodule.FullPath()),
|
|
style.FgCyan.Sprint(submodule.Url),
|
|
)
|
|
|
|
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
|
if file == nil {
|
|
task = types.NewRenderStringTask(prefix)
|
|
} else {
|
|
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, nil)
|
|
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
|
}
|
|
}
|
|
|
|
self.c.RenderToMainViews(types.RefreshMainOpts{
|
|
Pair: self.c.MainViewPairs().Normal,
|
|
Main: &types.ViewUpdateOpts{
|
|
Title: "Submodule",
|
|
Task: task,
|
|
},
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func (self *SubmodulesController) enter(submodule *models.SubmoduleConfig) error {
|
|
return self.c.Helpers().Repos.EnterSubmodule(submodule)
|
|
}
|
|
|
|
func (self *SubmodulesController) add() error {
|
|
self.c.Prompt(types.PromptOpts{
|
|
Title: self.c.Tr.NewSubmoduleUrl,
|
|
HandleConfirm: func(submoduleUrl string) error {
|
|
nameSuggestion := filepath.Base(strings.TrimSuffix(submoduleUrl, filepath.Ext(submoduleUrl)))
|
|
|
|
self.c.Prompt(types.PromptOpts{
|
|
Title: self.c.Tr.NewSubmoduleName,
|
|
InitialContent: nameSuggestion,
|
|
HandleConfirm: func(submoduleName string) error {
|
|
self.c.Prompt(types.PromptOpts{
|
|
Title: self.c.Tr.NewSubmodulePath,
|
|
InitialContent: submoduleName,
|
|
HandleConfirm: func(submodulePath string) error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.AddingSubmoduleStatus, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.AddSubmodule)
|
|
err := self.c.Git().Submodule.Add(submoduleName, submodulePath, submoduleUrl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
})
|
|
|
|
return nil
|
|
},
|
|
})
|
|
|
|
return nil
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) error {
|
|
self.c.Prompt(types.PromptOpts{
|
|
Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.FullName()),
|
|
InitialContent: submodule.Url,
|
|
HandleConfirm: func(newUrl string) error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl)
|
|
err := self.c.Git().Submodule.UpdateUrl(submodule, newUrl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.InitializingSubmoduleStatus, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule)
|
|
err := self.c.Git().Submodule.Init(submodule.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *SubmodulesController) openBulkActionsMenu() error {
|
|
return self.c.Menu(types.CreateMenuOptions{
|
|
Title: self.c.Tr.BulkSubmoduleOptions,
|
|
Items: []*types.MenuItem{
|
|
{
|
|
LabelColumns: []string{self.c.Tr.BulkInitSubmodules, style.FgGreen.Sprint(self.c.Git().Submodule.BulkInitCmdObj().ToString())},
|
|
OnPress: func() error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules)
|
|
err := self.c.Git().Submodule.BulkInitCmdObj().Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Key: menuKey('i'),
|
|
},
|
|
{
|
|
LabelColumns: []string{self.c.Tr.BulkUpdateSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateCmdObj().ToString())},
|
|
OnPress: func() error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules)
|
|
if err := self.c.Git().Submodule.BulkUpdateCmdObj().Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Key: menuKey('u'),
|
|
},
|
|
{
|
|
LabelColumns: []string{self.c.Tr.BulkUpdateRecursiveSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateRecursivelyCmdObj().ToString())},
|
|
OnPress: func() error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.BulkUpdateRecursiveSubmodules)
|
|
if err := self.c.Git().Submodule.BulkUpdateRecursivelyCmdObj().Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Key: menuKey('r'),
|
|
},
|
|
{
|
|
LabelColumns: []string{self.c.Tr.BulkDeinitSubmodules, style.FgRed.Sprint(self.c.Git().Submodule.BulkDeinitCmdObj().ToString())},
|
|
OnPress: func() error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules)
|
|
if err := self.c.Git().Submodule.BulkDeinitCmdObj().Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Key: menuKey('d'),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleStatus, func(gocui.Task) error {
|
|
self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule)
|
|
err := self.c.Git().Submodule.Update(submodule.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) error {
|
|
self.c.Confirm(types.ConfirmOpts{
|
|
Title: self.c.Tr.RemoveSubmodule,
|
|
Prompt: fmt.Sprintf(self.c.Tr.RemoveSubmodulePrompt, submodule.FullName()),
|
|
HandleConfirm: func() error {
|
|
self.c.LogAction(self.c.Tr.Actions.RemoveSubmodule)
|
|
if err := self.c.Git().Submodule.Delete(submodule); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES, types.FILES}})
|
|
return nil
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *SubmodulesController) easterEgg() error {
|
|
self.c.Context().Push(self.c.Contexts().Snake, types.OnFocusOpts{})
|
|
return nil
|
|
}
|
|
|
|
func (self *SubmodulesController) context() *context.SubmodulesContext {
|
|
return self.c.Contexts().Submodules
|
|
}
|