mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-06 05:57:38 -04:00
The rule below the header of a branch's pull request is drawn as wide as the main view. Its width is read when the render is asked for, before the layout has settled it. If the layout then changes the width of the main view, for example because it unsplits the view for the content that comes next, the rule comes out as wide as the view used to be. Make a prefix a function that is called with the width the render is laid out to, once the layout has run. The function it returns produces the text on the render's own goroutine before the command starts. This way a prefix that takes a while to produce, such as one that runs commands of its own, holds up only the render and not the UI. All prefixes are still static here; the next commit lays out the rule of the pull request header to the width. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
328 lines
10 KiB
Go
328 lines
10 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
|
|
"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{
|
|
{
|
|
Keys: opts.GetKeys(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.String()}),
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
|
Handler: self.withItem(self.enter),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(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,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(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,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.New),
|
|
Handler: self.add,
|
|
Description: self.c.Tr.NewSubmodule,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Edit),
|
|
Handler: self.withItem(self.editURL),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.EditSubmoduleUrl,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Submodules.Init),
|
|
Handler: self.withItem(self.init),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Initialize,
|
|
Tooltip: self.c.Tr.InitSubmoduleTooltip,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(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, git_commands.DiffModeRendered, !file.HasUnstagedChanges && file.HasStagedChanges, file.Names())
|
|
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), types.StaticPrefix(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.RefreshFromWorker(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.RefreshFromWorker(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.RefreshFromWorker(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.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Keys: 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.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Keys: 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.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Keys: 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.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
|
return nil
|
|
})
|
|
},
|
|
Keys: 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.RefreshFromWorker(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
|
|
}
|