mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 02:24:25 -05:00
I copied all files except dot files (.github and .gitignore), the _examples folder, and go.mod/go.sum. At some point we may want to copy the files back to the gocui repo when other clients (e.g. lazydocker) want to use the newer versions of them.
260 lines
8.1 KiB
Go
260 lines
8.1 KiB
Go
package controllers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/constants"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type StatusController struct {
|
|
baseController
|
|
c *ControllerCommon
|
|
}
|
|
|
|
var _ types.IController = &StatusController{}
|
|
|
|
func NewStatusController(
|
|
c *ControllerCommon,
|
|
) *StatusController {
|
|
return &StatusController{
|
|
baseController: baseController{},
|
|
c: c,
|
|
}
|
|
}
|
|
|
|
func (self *StatusController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
|
bindings := []*types.Binding{
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
|
Handler: self.openConfig,
|
|
Description: self.c.Tr.OpenConfig,
|
|
Tooltip: self.c.Tr.OpenFileTooltip,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Universal.Edit),
|
|
Handler: self.editConfig,
|
|
Description: self.c.Tr.EditConfig,
|
|
Tooltip: self.c.Tr.EditFileTooltip,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
|
|
Handler: self.handleCheckForUpdate,
|
|
Description: self.c.Tr.CheckForUpdate,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Status.RecentRepos),
|
|
Handler: self.c.Helpers().Repos.CreateRecentReposMenu,
|
|
Description: self.c.Tr.SwitchRepo,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph),
|
|
Handler: func() error { self.switchToOrRotateAllBranchesLogs(); return nil },
|
|
Description: self.c.Tr.AllBranchesLogGraph,
|
|
},
|
|
{
|
|
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraphReverse),
|
|
Handler: func() error { self.switchToOrRotateAllBranchesLogsBackward(); return nil },
|
|
Description: self.c.Tr.AllBranchesLogGraphReverse,
|
|
},
|
|
}
|
|
|
|
return bindings
|
|
}
|
|
|
|
func (self *StatusController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
|
return []*gocui.ViewMouseBinding{
|
|
{
|
|
ViewName: self.Context().GetViewName(),
|
|
Key: gocui.MouseLeft,
|
|
Handler: self.onClick,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (self *StatusController) GetOnRenderToMain() func() {
|
|
return func() {
|
|
switch self.c.UserConfig().Gui.StatusPanelView {
|
|
case "dashboard":
|
|
self.showDashboard()
|
|
case "allBranchesLog":
|
|
self.showAllBranchLogs()
|
|
default:
|
|
self.showDashboard()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (self *StatusController) Context() types.Context {
|
|
return self.c.Contexts().Status
|
|
}
|
|
|
|
func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
|
|
// TODO: move into some abstraction (status is currently not a listViewContext where a lot of this code lives)
|
|
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
|
if currentBranch == nil {
|
|
// need to wait for branches to refresh
|
|
return nil
|
|
}
|
|
|
|
self.c.Context().Push(self.Context(), types.OnFocusOpts{})
|
|
|
|
upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig()))
|
|
repoName := self.c.Git().RepoPaths.RepoName()
|
|
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
|
if workingTreeState.Any() {
|
|
workingTreeStatus := fmt.Sprintf("(%s)", workingTreeState.LowerCaseTitle(self.c.Tr))
|
|
if cursorInSubstring(opts.X, upstreamStatus+" ", workingTreeStatus) {
|
|
return self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu()
|
|
}
|
|
if cursorInSubstring(opts.X, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
|
|
return self.c.Helpers().Repos.CreateRecentReposMenu()
|
|
}
|
|
} else if cursorInSubstring(opts.X, upstreamStatus+" ", repoName) {
|
|
return self.c.Helpers().Repos.CreateRecentReposMenu()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runeCount(str string) int {
|
|
return len([]rune(str))
|
|
}
|
|
|
|
func cursorInSubstring(cx int, prefix string, substring string) bool {
|
|
return cx >= runeCount(prefix) && cx < runeCount(prefix+substring)
|
|
}
|
|
|
|
func lazygitTitle() string {
|
|
return `
|
|
_ _ _
|
|
| | (_) |
|
|
| | __ _ _____ _ __ _ _| |_
|
|
| |/ _` + "`" + ` |_ / | | |/ _` + "`" + ` | | __|
|
|
| | (_| |/ /| |_| | (_| | | |_
|
|
|_|\__,_/___|\__, |\__, |_|\__|
|
|
__/ | __/ |
|
|
|___/ |___/ `
|
|
}
|
|
|
|
func (self *StatusController) askForConfigFile(action func(file string) error) error {
|
|
confPaths := self.c.GetConfig().GetUserConfigPaths()
|
|
switch len(confPaths) {
|
|
case 0:
|
|
return errors.New(self.c.Tr.NoConfigFileFoundErr)
|
|
case 1:
|
|
return action(confPaths[0])
|
|
default:
|
|
menuItems := lo.Map(confPaths, func(path string, _ int) *types.MenuItem {
|
|
return &types.MenuItem{
|
|
Label: path,
|
|
OnPress: func() error {
|
|
return action(path)
|
|
},
|
|
}
|
|
})
|
|
|
|
return self.c.Menu(types.CreateMenuOptions{
|
|
Title: self.c.Tr.SelectConfigFile,
|
|
Items: menuItems,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (self *StatusController) openConfig() error {
|
|
return self.askForConfigFile(self.c.Helpers().Files.OpenFile)
|
|
}
|
|
|
|
func (self *StatusController) editConfig() error {
|
|
return self.askForConfigFile(func(file string) error {
|
|
return self.c.Helpers().Files.EditFiles([]string{file})
|
|
})
|
|
}
|
|
|
|
func (self *StatusController) showAllBranchLogs() {
|
|
cmdObj := self.c.Git().Branch.AllBranchesLogCmdObj()
|
|
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
|
|
|
title := self.c.Tr.LogTitle
|
|
if i, n := self.c.Git().Branch.GetAllBranchesLogIdxAndCount(); n > 1 {
|
|
title = fmt.Sprintf(self.c.Tr.LogXOfYTitle, i+1, n)
|
|
}
|
|
self.c.RenderToMainViews(types.RefreshMainOpts{
|
|
Pair: self.c.MainViewPairs().Normal,
|
|
Main: &types.ViewUpdateOpts{
|
|
Title: title,
|
|
Task: task,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Switches to the all branches view, or, if already on that view,
|
|
// rotates to the next command in the list, and then renders it.
|
|
func (self *StatusController) switchToOrRotateAllBranchesLogs() {
|
|
// A bit of a hack to ensure we only rotate to the next branch log command
|
|
// if we currently are looking at a branch log. Otherwise, we should just show
|
|
// the current index (if we are coming from the dashboard).
|
|
if self.c.Views().Main.Title != self.c.Tr.StatusTitle {
|
|
self.c.Git().Branch.RotateAllBranchesLogIdx()
|
|
}
|
|
self.showAllBranchLogs()
|
|
}
|
|
|
|
// Switches to the all branches view, or, if already on that view,
|
|
// rotates to the previous command in the list, and then renders it.
|
|
func (self *StatusController) switchToOrRotateAllBranchesLogsBackward() {
|
|
// A bit of a hack to ensure we only rotate to the previous branch log command
|
|
// if we currently are looking at a branch log. Otherwise, we should just show
|
|
// the current index (if we are coming from the dashboard).
|
|
if self.c.Views().Main.Title != self.c.Tr.StatusTitle {
|
|
self.c.Git().Branch.RotateAllBranchesLogIdxBackward()
|
|
}
|
|
self.showAllBranchLogs()
|
|
}
|
|
|
|
func (self *StatusController) showDashboard() {
|
|
versionStr := "master"
|
|
version, err := types.ParseVersionNumber(self.c.GetConfig().GetVersion())
|
|
if err == nil {
|
|
// Don't just take the version string as is, but format it again. This
|
|
// way it will be correct even if a distribution omits the "v", or the
|
|
// ".0" at the end.
|
|
versionStr = fmt.Sprintf("v%d.%d.%d", version.Major, version.Minor, version.Patch)
|
|
}
|
|
|
|
dashboardString := strings.Join(
|
|
[]string{
|
|
lazygitTitle(),
|
|
fmt.Sprintf("Copyright %d Jesse Duffield", time.Now().Year()),
|
|
fmt.Sprintf("Keybindings: %s", fmt.Sprintf(constants.Links.Docs.Keybindings, versionStr)),
|
|
fmt.Sprintf("Config Options: %s", fmt.Sprintf(constants.Links.Docs.Config, versionStr)),
|
|
fmt.Sprintf("Tutorial: %s", constants.Links.Docs.Tutorial),
|
|
fmt.Sprintf("Raise an Issue: %s", constants.Links.Issues),
|
|
fmt.Sprintf("Release Notes: %s", constants.Links.Releases),
|
|
style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free
|
|
}, "\n\n") + "\n"
|
|
|
|
self.c.RenderToMainViews(types.RefreshMainOpts{
|
|
Pair: self.c.MainViewPairs().Normal,
|
|
Main: &types.ViewUpdateOpts{
|
|
Title: self.c.Tr.StatusTitle,
|
|
Task: types.NewRenderStringTask(dashboardString),
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *StatusController) handleCheckForUpdate() error {
|
|
return self.c.Helpers().Update.CheckForUpdateInForeground()
|
|
}
|