Merge branch 'master' into gh-integration

This commit is contained in:
Yuki Osaki 2021-11-06 12:44:44 +09:00
commit 37aa558d34
64 changed files with 933 additions and 366 deletions

View file

@ -1,5 +1,5 @@
# Go's proxy servers are not very up-to-date so that's why we use `GOPROXY=direct`
# We specify the `awesome` branch to avoid the default behaviour of looking for a semver tag.
GOPROXY=direct go get -u github.com/jesseduffield/gocui@awesome && go mod vendor
GOPROXY=direct go get -u github.com/jesseduffield/gocui@awesome && go mod vendor && go mod tidy
# Note to self if you ever want to fork a repo be sure to use this same approach: it's important to use the branch name (e.g. master)

View file

@ -61,6 +61,14 @@ git:
manualCommit: false
# extra args passed to `git merge`, e.g. --no-ff
args: ''
log:
# one of date-order, author-date-order, topo-order.
# topo-order makes it easier to read the git log graph, but commits may not
# appear chronologically. See https://git-scm.com/docs/git-log#_commit_ordering
order: 'topo-order'
# one of always, never, when-maximised
# this determines whether the git graph is rendered in the commits panel
showGraph: 'when-maximised'
skipHookPrefix: WIP
autoFetch: true
branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
@ -100,11 +108,13 @@ keybinding:
nextPage: '.' # go to previous page in list
gotoTop: '<' # go to top of list
gotoBottom: '>' # go to bottom of list
scrollLeft: 'H' # scroll left within list view
scrollRight: 'L' # scroll right within list view
prevBlock: '<left>' # goto the previous block / panel
nextBlock: '<right>' # goto the next block / panel
prevBlock-alt: 'h' # goto the previous block / panel
nextBlock-alt: 'l' # goto the next block / panel
jumpToBlock: ["1", "2", "3", "4", "5"] # goto the Nth block / panel
jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
nextMatch: 'n'
prevMatch: 'N'
optionMenu: 'x' # show help menu
@ -192,6 +202,7 @@ keybinding:
checkoutCommit: '<space>'
resetCherryPick: '<c-R>'
copyCommitMessageToClipboard: '<c-y>'
openLogMenu: '<c-l>'
stash:
popStash: 'g'
commitFiles:

4
go.mod
View file

@ -20,7 +20,7 @@ require (
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75
github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
github.com/jesseduffield/yaml v2.1.0+incompatible
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
@ -40,7 +40,7 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 // indirect
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0

8
go.sum
View file

@ -71,8 +71,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75 h1:zu+WBGwscCwu7GEuxANGl8E51HbW6ueqTF1XdAoqnZs=
github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474 h1:4H/oJcUmwJpqyXzqfn+lsjQ/bjpm/HszzLrVbCjgqj4=
github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I=
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
@ -178,8 +178,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View file

@ -37,7 +37,12 @@ type CommitListBuilder struct {
}
// NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *oscommands.OSCommand, tr *i18n.TranslationSet) *CommitListBuilder {
func NewCommitListBuilder(
log *logrus.Entry,
gitCommand *GitCommand,
osCommand *oscommands.OSCommand,
tr *i18n.TranslationSet,
) *CommitListBuilder {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
@ -88,6 +93,8 @@ type GetCommitsOptions struct {
FilterPath string
IncludeRebaseCommits bool
RefName string // e.g. "HEAD" or "my_branch"
// determines if we show the whole git graph i.e. pass the '--all' flag
All bool
}
func (c *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*models.Commit, error) {
@ -404,10 +411,20 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd {
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath))
}
config := c.GitCommand.Config.GetUserConfig().Git.Log
orderFlag := "--" + config.Order
allFlag := ""
if opts.All {
allFlag = " --all"
}
return c.OSCommand.ExecutableFromString(
fmt.Sprintf(
"git log %s --oneline %s %s --abbrev=%d %s",
"git log %s %s %s --oneline %s %s --abbrev=%d %s",
c.OSCommand.Quote(opts.RefName),
orderFlag,
allFlag,
prettyFormat,
limitFlag,
20,

View file

@ -69,8 +69,10 @@ type GitConfig struct {
OverrideGpg bool `yaml:"overrideGpg"`
DisableForcePushing bool `yaml:"disableForcePushing"`
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
ParseEmoji bool `yaml:"parseEmoji"`
EnableGhCommand bool `yaml:"enableGhCommand"`
// this shoudl really be under 'gui', not 'git'
ParseEmoji bool `yaml:"parseEmoji"`
Log LogConfig `yaml:"log"`
EnableGhCommand bool `yaml:"enableGhCommand"`
}
type PagingConfig struct {
@ -84,6 +86,11 @@ type MergingConfig struct {
Args string `yaml:"args"`
}
type LogConfig struct {
Order string `yaml:"order"` // one of date-order, author-date-order, topo-order
ShowGraph string `yaml:"showGraph"` // one of always, never, when-maximised
}
type CommitPrefixConfig struct {
Pattern string `yaml:"pattern"`
Replace string `yaml:"replace"`
@ -119,6 +126,8 @@ type KeybindingUniversalConfig struct {
NextItemAlt string `yaml:"nextItem-alt"`
PrevPage string `yaml:"prevPage"`
NextPage string `yaml:"nextPage"`
ScrollLeft string `yaml:"scrollLeft"`
ScrollRight string `yaml:"scrollRight"`
GotoTop string `yaml:"gotoTop"`
GotoBottom string `yaml:"gotoBottom"`
PrevBlock string `yaml:"prevBlock"`
@ -229,6 +238,7 @@ type KeybindingCommitsConfig struct {
CheckoutCommit string `yaml:"checkoutCommit"`
ResetCherryPick string `yaml:"resetCherryPick"`
CopyCommitMessageToClipboard string `yaml:"copyCommitMessageToClipboard"`
OpenLogMenu string `yaml:"openLogMenu"`
}
type KeybindingStashConfig struct {
@ -339,6 +349,10 @@ func GetDefaultConfig() *UserConfig {
ManualCommit: false,
Args: "",
},
Log: LogConfig{
Order: "topo-order",
ShowGraph: "when-maximised",
},
SkipHookPrefix: "WIP",
AutoFetch: true,
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
@ -373,6 +387,8 @@ func GetDefaultConfig() *UserConfig {
NextItemAlt: "j",
PrevPage: ",",
NextPage: ".",
ScrollLeft: "H",
ScrollRight: "L",
GotoTop: "<",
GotoBottom: ">",
PrevBlock: "<left>",
@ -479,6 +495,7 @@ func GetDefaultConfig() *UserConfig {
CheckoutCommit: "<space>",
ResetCherryPick: "<c-R>",
CopyCommitMessageToClipboard: "<c-y>",
OpenLogMenu: "<c-l>",
},
Stash: KeybindingStashConfig{
PopStash: "g",

View file

@ -10,6 +10,9 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
// after selecting the 200th commit, we'll load in all the rest
const COMMIT_THRESHOLD = 200
// list panel functions
func (gui *Gui) getSelectedLocalCommit() *models.Commit {
@ -23,7 +26,7 @@ func (gui *Gui) getSelectedLocalCommit() *models.Commit {
func (gui *Gui) handleCommitSelect() error {
state := gui.State.Panels.Commits
if state.SelectedLineIdx > 290 && state.LimitCommits {
if state.SelectedLineIdx > COMMIT_THRESHOLD && state.LimitCommits {
state.LimitCommits = false
go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil {
@ -134,6 +137,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
FilterPath: gui.State.Modes.Filtering.GetPath(),
IncludeRebaseCommits: true,
RefName: "HEAD",
All: gui.State.ShowWholeGitGraph,
},
)
if err != nil {
@ -679,3 +683,87 @@ func (gui *Gui) handleCopySelectedCommitMessageToClipboard() error {
return nil
}
func (gui *Gui) handleOpenLogMenu() error {
return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{
{
displayString: gui.Tr.ToggleShowGitGraphAll,
onPress: func() error {
gui.State.ShowWholeGitGraph = !gui.State.ShowWholeGitGraph
if gui.State.ShowWholeGitGraph {
gui.State.Panels.Commits.LimitCommits = false
}
return gui.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{COMMITS}})
})
},
},
{
displayString: gui.Tr.ShowGitGraph,
opensMenu: true,
onPress: func() error {
onSelect := func(value string) {
gui.Config.GetUserConfig().Git.Log.ShowGraph = value
gui.render()
}
return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{
{
displayString: "always",
onPress: func() error {
onSelect("always")
return nil
},
},
{
displayString: "never",
onPress: func() error {
onSelect("never")
return nil
},
},
{
displayString: "when maximised",
onPress: func() error {
onSelect("when-maximised")
return nil
},
},
}, createMenuOptions{showCancel: true})
},
},
{
displayString: gui.Tr.SortCommits,
opensMenu: true,
onPress: func() error {
onSelect := func(value string) error {
gui.Config.GetUserConfig().Git.Log.Order = value
return gui.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{COMMITS}})
})
}
return gui.createMenu(gui.Tr.LogMenuTitle, []*menuItem{
{
displayString: "topological (topo-order)",
onPress: func() error {
return onSelect("topo-order")
},
},
{
displayString: "date-order",
onPress: func() error {
return onSelect("date-order")
},
},
{
displayString: "author-date-order",
onPress: func() error {
return onSelect("author-date-order")
},
},
}, createMenuOptions{showCancel: true})
},
},
}, createMenuOptions{showCancel: true})
}

View file

@ -399,6 +399,8 @@ func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error
return nil
}
_ = 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)
if err := gui.deactivateContext(gui.State.Contexts.CommitFiles); err != nil {

View file

@ -29,15 +29,6 @@ func (gui *Gui) getRemoteNames() []string {
return result
}
// func (gui *Gui) getRemoteUrls() []string {
// result := make([]string, len(gui.State.Remotes))
// for i, remote := range gui.State.Remotes {
// result[i] = remote.Urls[0]
// gui.GitCommand.GetRemotesToOwnersMap()
// }
// return result
// }
func matchesToSuggestions(matches []string) []*types.Suggestion {
suggestions := make([]*types.Suggestion, len(matches))
for i, match := range matches {

View file

@ -10,6 +10,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
const HORIZONTAL_SCROLL_FACTOR = 3
// these views need to be re-rendered when the screen mode changes. The commits view,
// for example, will show authorship information in half and full screen mode.
func (gui *Gui) rerenderViewsWithScreenModeDependentContent() error {
@ -114,7 +116,7 @@ func (gui *Gui) linesToScrollDown(view *gocui.View) int {
func (gui *Gui) scrollUpMain() error {
if gui.canScrollMergePanel() {
gui.State.Panels.Merging.UserScrolling = true
gui.State.Panels.Merging.UserVerticalScrolling = true
}
return gui.scrollUpView(gui.Views.Main)
@ -122,12 +124,33 @@ func (gui *Gui) scrollUpMain() error {
func (gui *Gui) scrollDownMain() error {
if gui.canScrollMergePanel() {
gui.State.Panels.Merging.UserScrolling = true
gui.State.Panels.Merging.UserVerticalScrolling = true
}
return gui.scrollDownView(gui.Views.Main)
}
func (gui *Gui) scrollLeftMain() error {
gui.scrollLeft(gui.Views.Main)
return nil
}
func (gui *Gui) scrollRightMain() error {
gui.scrollRight(gui.Views.Main)
return nil
}
func (gui *Gui) scrollLeft(view *gocui.View) {
newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
_ = view.SetOriginX(newOriginX)
}
func (gui *Gui) scrollRight(view *gocui.View) {
_ = view.SetOriginX(view.OriginX() + view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR)
}
func (gui *Gui) scrollUpSecondary() error {
return gui.scrollUpView(gui.Views.Secondary)
}

View file

@ -23,6 +23,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/modes/filtering"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
@ -145,9 +146,9 @@ type LblPanelState struct {
type MergingPanelState struct {
*mergeconflicts.State
// UserScrolling tells us if the user has started scrolling through the file themselves
// UserVerticalScrolling tells us if the user has started scrolling through the file themselves
// in which case we won't auto-scroll to a conflict.
UserScrolling bool
UserVerticalScrolling bool
}
type filePanelState struct {
@ -315,6 +316,8 @@ type guiState struct {
RetainOriginalDir bool
IsRefreshingFiles bool
Searching searchingState
// if this is true, we'll load our commits using `git log --all`
ShowWholeGitGraph bool
ScreenMode WindowMaximisation
Ptmx *os.File
PrevMainWidth int
@ -400,7 +403,7 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
Remotes: &remotePanelState{listPanelState{SelectedLineIdx: 0}},
RemoteBranches: &remoteBranchesState{listPanelState{SelectedLineIdx: -1}},
Tags: &tagsPanelState{listPanelState{SelectedLineIdx: -1}},
Commits: &commitPanelState{listPanelState: listPanelState{SelectedLineIdx: -1}, LimitCommits: true},
Commits: &commitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, LimitCommits: true},
ReflogCommits: &reflogCommitPanelState{listPanelState{SelectedLineIdx: 0}},
SubCommits: &subCommitPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, refName: ""},
CommitFiles: &commitFilesPanelState{listPanelState: listPanelState{SelectedLineIdx: -1}, refName: ""},
@ -408,8 +411,8 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
Menu: &menuPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}, OnPress: nil},
Suggestions: &suggestionsPanelState{listPanelState: listPanelState{SelectedLineIdx: 0}},
Merging: &MergingPanelState{
State: mergeconflicts.NewState(),
UserScrolling: false,
State: mergeconflicts.NewState(),
UserVerticalScrolling: false,
},
},
GithubState: &GithubState{},
@ -466,8 +469,8 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *oscom
var RuneReplacements = map[rune]string{
// for the commit graph
'⏣': "M",
'⎔': "o",
graph.MergeSymbol: "M",
graph.CommitSymbol: "o",
}
// Run setup the gui with keybindings and start the mainloop

View file

@ -724,6 +724,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleFetchRemote,
Description: gui.Tr.LcFetchRemote,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.OpenLogMenu),
Handler: gui.handleOpenLogMenu,
Description: gui.Tr.LcOpenLogMenu,
OpensMenu: true,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
@ -1426,6 +1434,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY), string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.ScrollLeft),
Handler: gui.scrollLeftMain,
Description: gui.Tr.LcScrollLeft,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY), string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.ScrollRight),
Handler: gui.scrollRightMain,
Description: gui.Tr.LcScrollRight,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},

View file

@ -174,7 +174,7 @@ func (gui *Gui) focusSelection(state *LblPanelState) error {
newOrigin := state.CalculateOrigin(origin, bufferHeight)
gui.g.Update(func(*gocui.Gui) error {
if err := stagingView.SetOrigin(0, newOrigin); err != nil {
if err := stagingView.SetOriginY(newOrigin); err != nil {
return err
}

View file

@ -2,6 +2,8 @@ package gui
import (
"fmt"
"github.com/jesseduffield/gocui"
)
type ListContext struct {
@ -14,6 +16,10 @@ type ListContext struct {
// the boolean here tells us whether the item is nil. This is needed because you can't work it out on the calling end once the pointer is wrapped in an interface (unless you want to use reflection)
SelectedItem func() (ListItem, bool)
OnGetPanelState func() IListPanelState
// if this is true, we'll call GetDisplayStrings for just the visible part of the
// view and re-render that. This is useful when you need to render different
// content based on the selection (e.g. for showing the selected commit)
RenderSelection bool
Gui *Gui
@ -26,6 +32,8 @@ type IListContext interface {
OnRender() error
handlePrevLine() error
handleNextLine() error
handleScrollLeft() error
handleScrollRight() error
handleLineChange(change int) error
handleNextPage() error
handleGotoTop() error
@ -60,10 +68,17 @@ type ListItem interface {
func (self *ListContext) FocusLine() {
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
// ignoring error for now
return
}
view.FocusPoint(0, self.GetPanelState().GetSelectedLineIdx())
// we need a way of knowing whether we've rendered to the view yet.
view.FocusPoint(view.OriginX(), self.GetPanelState().GetSelectedLineIdx())
if self.RenderSelection {
_, originY := view.Origin()
displayStrings := self.GetDisplayStrings(originY, view.InnerHeight())
self.Gui.renderDisplayStringsAtPos(view, originY, displayStrings)
}
view.Footer = formatListFooter(self.GetPanelState().GetSelectedLineIdx(), self.GetItemsLength())
}
@ -106,6 +121,13 @@ func (self *ListContext) HandleFocusLost() error {
return self.OnFocusLost()
}
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
return nil
}
_ = view.SetOriginX(0)
return nil
}
@ -139,8 +161,36 @@ func (self *ListContext) handleNextLine() error {
return self.handleLineChange(1)
}
func (self *ListContext) handleScrollLeft() error {
return self.scroll(self.Gui.scrollLeft)
}
func (self *ListContext) handleScrollRight() error {
return self.scroll(self.Gui.scrollRight)
}
func (self *ListContext) scroll(scrollFunc func(*gocui.View)) error {
if self.ignoreKeybinding() {
return nil
}
// get the view, move the origin
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
return nil
}
scrollFunc(view)
return self.HandleFocus()
}
func (self *ListContext) ignoreKeybinding() bool {
return !self.Gui.isPopupPanel(self.ViewName) && self.Gui.popupPanelFocused()
}
func (self *ListContext) handleLineChange(change int) error {
if !self.Gui.isPopupPanel(self.ViewName) && self.Gui.popupPanelFocused() {
if self.ignoreKeybinding() {
return nil
}
@ -184,7 +234,7 @@ func (self *ListContext) handlePrevPage() error {
}
func (self *ListContext) handleClick() error {
if !self.Gui.isPopupPanel(self.ViewName) && self.Gui.popupPanelFocused() {
if self.ignoreKeybinding() {
return nil
}

View file

@ -1,6 +1,8 @@
package gui
import (
"log"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
@ -158,21 +160,48 @@ func (gui *Gui) branchCommitsListContext() IListContext {
OnClickSelectedItem: gui.handleViewCommitFiles,
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if gui.currentContext().GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
selectedCommit := gui.getSelectedLocalCommit()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
return presentation.GetCommitListDisplayStrings(
gui.State.Commits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
selectedCommitSha,
startIdx,
length,
gui.shouldShowGraph(),
)
},
SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedLocalCommit()
return item, item != nil
},
RenderSelection: true,
}
}
func (gui *Gui) shouldShowGraph() bool {
value := gui.Config.GetUserConfig().Git.Log.ShowGraph
switch value {
case "always":
return true
case "never":
return false
case "when-maximised":
return gui.State.ScreenMode != SCREEN_NORMAL
}
log.Fatalf("Unknown value for git.log.showGraph: %s. Expected one of: 'always', 'never', 'when-maximised'", value)
return false
}
func (gui *Gui) reflogCommitsListContext() IListContext {
parseEmoji := gui.Config.GetUserConfig().Git.ParseEmoji
return &ListContext{
@ -216,18 +245,30 @@ func (gui *Gui) subCommitsListContext() IListContext {
OnFocus: gui.handleSubCommitSelect,
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if gui.currentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := gui.getSelectedSubCommit()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
return presentation.GetCommitListDisplayStrings(
gui.State.SubCommits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
selectedCommitSha,
0,
len(gui.State.SubCommits),
gui.shouldShowGraph(),
)
},
SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedSubCommit()
return item, item != nil
},
RenderSelection: true,
}
}
@ -362,6 +403,8 @@ func (gui *Gui) getListContextKeyBindings() []*Binding {
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.GotoTop), Modifier: gocui.ModNone, Handler: listContext.handleGotoTop, Description: gui.Tr.LcGotoTop},
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
{ViewName: listContext.GetViewName(), Contexts: []string{string(listContext.GetKey())}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listContext.handleClick},
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: listContext.handleScrollLeft},
{ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: listContext.handleScrollRight},
}...)
// the commits panel needs to lazyload things so it has a couple of its own handlers

View file

@ -1,6 +1,7 @@
package gui
import (
"errors"
"fmt"
"strings"
@ -13,6 +14,8 @@ type menuItem struct {
displayString string
displayStrings []string
onPress func() error
// only applies when displayString is used
opensMenu bool
}
// every item in a list context needs an ID
@ -65,8 +68,16 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr
stringArrays := make([][]string, len(items))
for i, item := range items {
if item.opensMenu && item.displayStrings != nil {
return errors.New("Message for the developer of this app: you've set opensMenu with displaystrings on the menu panel. Bad developer!. Apologies, user")
}
if item.displayStrings == nil {
stringArrays[i] = []string{item.displayString}
styledStr := item.displayString
if item.opensMenu {
styledStr = opensMenuStyle(styledStr)
}
stringArrays[i] = []string{styledStr}
} else {
stringArrays[i] = item.displayStrings
}

View file

@ -203,7 +203,7 @@ func (gui *Gui) catSelectedFile() (string, error) {
}
func (gui *Gui) scrollToConflict() error {
if gui.State.Panels.Merging.UserScrolling {
if gui.State.Panels.Merging.UserVerticalScrolling {
return nil
}
@ -315,5 +315,5 @@ func (gui *Gui) withMergeConflictLock(f func() error) error {
}
func (gui *Gui) takeOverMergeConflictScrolling() {
gui.State.Panels.Merging.UserScrolling = false
gui.State.Panels.Merging.UserVerticalScrolling = false
}

View file

@ -36,12 +36,16 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
func (gui *Gui) displayDescription(binding *Binding) string {
if binding.OpensMenu {
return style.FgMagenta.Sprintf("%s...", binding.Description)
return opensMenuStyle(binding.Description)
}
return style.FgCyan.Sprint(binding.Description)
}
func opensMenuStyle(str string) string {
return style.FgMagenta.Sprintf("%s...", str)
}
func (gui *Gui) handleCreateOptionsMenu() error {
view := gui.g.CurrentView()
if view == nil {

View file

@ -2,120 +2,163 @@ package presentation
import (
"strings"
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/kyokomi/emoji/v2"
)
func GetCommitListDisplayStrings(commits []*models.Commit, fullDescription bool, cherryPickedCommitShaMap map[string]bool, diffName string, parseEmoji bool) [][]string {
lines := make([][]string, len(commits))
type pipeSetCacheKey struct {
commitSha string
commitCount int
}
var displayFunc func(*models.Commit, map[string]bool, bool, bool) []string
if fullDescription {
displayFunc = getFullDescriptionDisplayStringsForCommit
var pipeSetCache = make(map[pipeSetCacheKey][][]*graph.Pipe)
var mutex sync.Mutex
func GetCommitListDisplayStrings(
commits []*models.Commit,
fullDescription bool,
cherryPickedCommitShaMap map[string]bool,
diffName string,
parseEmoji bool,
selectedCommitSha string,
startIdx int,
length int,
showGraph bool,
) [][]string {
mutex.Lock()
defer mutex.Unlock()
if len(commits) == 0 {
return nil
}
// given that our cache key is a commit sha and a commit count, it's very important that we don't actually try to render pipes
// when dealing with things like filtered commits.
cacheKey := pipeSetCacheKey{
commitSha: commits[0].Sha,
commitCount: len(commits),
}
pipeSets, ok := pipeSetCache[cacheKey]
if !ok {
// pipe sets are unique to a commit head. and a commit count. Sometimes we haven't loaded everything for that.
// so let's just cache it based on that.
getStyle := func(commit *models.Commit) style.TextStyle {
return authors.AuthorStyle(commit.Author)
}
pipeSets = graph.GetPipeSets(commits, getStyle)
pipeSetCache[cacheKey] = pipeSets
}
if startIdx > len(commits) {
return nil
}
end := startIdx + length
if end > len(commits)-1 {
end = len(commits) - 1
}
filteredCommits := commits[startIdx : end+1]
var getGraphLine func(int) string
if showGraph {
filteredPipeSets := pipeSets[startIdx : end+1]
graphLines := graph.RenderAux(filteredPipeSets, filteredCommits, selectedCommitSha)
getGraphLine = func(idx int) string { return graphLines[idx] }
} else {
displayFunc = getDisplayStringsForCommit
getGraphLine = func(idx int) string { return "" }
}
for i := range commits {
diffed := commits[i].Sha == diffName
lines[i] = displayFunc(commits[i], cherryPickedCommitShaMap, diffed, parseEmoji)
lines := make([][]string, 0, len(filteredCommits))
for i, commit := range filteredCommits {
lines = append(lines, displayCommit(commit, cherryPickedCommitShaMap, diffName, parseEmoji, getGraphLine(i), fullDescription))
}
return lines
}
func getFullDescriptionDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[string]bool, diffed, parseEmoji bool) []string {
shaColor := theme.DefaultTextColor
switch c.Status {
case "unpushed":
shaColor = style.FgRed
case "pushed":
shaColor = style.FgYellow
case "merged":
shaColor = style.FgGreen
case "rebasing":
shaColor = style.FgBlue
case "reflog":
shaColor = style.FgBlue
}
func displayCommit(
commit *models.Commit,
cherryPickedCommitShaMap map[string]bool,
diffName string,
parseEmoji bool,
graphLine string,
fullDescription bool,
) []string {
if diffed {
shaColor = theme.DiffTerminalColor
} else if cherryPickedCommitShaMap[c.Sha] {
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
shaColor = theme.CherryPickedCommitTextStyle
}
tagString := ""
secondColumnString := style.FgBlue.Sprint(utils.UnixToDate(c.UnixTimestamp))
if c.Action != "" {
secondColumnString = actionColorMap(c.Action).Sprint(c.Action)
} else if c.ExtraInfo != "" {
tagString = style.FgMagenta.SetBold().Sprint(c.ExtraInfo) + " "
}
name := c.Name
if parseEmoji {
name = emoji.Sprint(name)
}
return []string{
shaColor.Sprint(c.ShortSha()),
secondColumnString,
authors.LongAuthor(c.Author),
tagString + theme.DefaultTextColor.Sprint(name),
}
}
func getDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[string]bool, diffed, parseEmoji bool) []string {
shaColor := theme.DefaultTextColor
switch c.Status {
case "unpushed":
shaColor = style.FgRed
case "pushed":
shaColor = style.FgYellow
case "merged":
shaColor = style.FgGreen
case "rebasing":
shaColor = style.FgBlue
case "reflog":
shaColor = style.FgBlue
}
if diffed {
shaColor = theme.DiffTerminalColor
} else if cherryPickedCommitShaMap[c.Sha] {
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
shaColor = theme.CherryPickedCommitTextStyle
}
shaColor := getShaColor(commit, diffName, cherryPickedCommitShaMap)
actionString := ""
tagString := ""
if c.Action != "" {
actionString = actionColorMap(c.Action).Sprint(utils.WithPadding(c.Action, 7)) + " "
} else if len(c.Tags) > 0 {
tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(c.Tags, " ")) + " "
if commit.Action != "" {
actionString = actionColorMap(commit.Action).Sprint(commit.Action) + " "
}
name := c.Name
tagString := ""
if fullDescription {
if commit.ExtraInfo != "" {
tagString = style.FgMagenta.SetBold().Sprint(commit.ExtraInfo) + " "
}
} else {
if len(commit.Tags) > 0 {
tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(commit.Tags, " ")) + " "
}
}
name := commit.Name
if parseEmoji {
name = emoji.Sprint(name)
}
return []string{
shaColor.Sprint(c.ShortSha()),
authors.ShortAuthor(c.Author),
actionString + tagString + theme.DefaultTextColor.Sprint(name),
authorFunc := authors.ShortAuthor
if fullDescription {
authorFunc = authors.LongAuthor
}
cols := make([]string, 0, 5)
cols = append(cols, shaColor.Sprint(commit.ShortSha()))
if fullDescription {
cols = append(cols, style.FgBlue.Sprint(utils.UnixToDate(commit.UnixTimestamp)))
}
cols = append(
cols,
actionString,
authorFunc(commit.Author),
graphLine+tagString+theme.DefaultTextColor.Sprint(name),
)
return cols
}
func getShaColor(commit *models.Commit, diffName string, cherryPickedCommitShaMap map[string]bool) style.TextStyle {
diffed := commit.Sha == diffName
shaColor := theme.DefaultTextColor
switch commit.Status {
case "unpushed":
shaColor = style.FgRed
case "pushed":
shaColor = style.FgYellow
case "merged":
shaColor = style.FgGreen
case "rebasing":
shaColor = style.FgBlue
case "reflog":
shaColor = style.FgBlue
}
if diffed {
shaColor = theme.DiffTerminalColor
} else if cherryPickedCommitShaMap[commit.Sha] {
shaColor = theme.CherryPickedCommitTextStyle
}
return shaColor
}
func actionColorMap(str string) style.TextStyle {

View file

@ -1,11 +1,15 @@
package graph
import (
"io"
"sync"
"github.com/gookit/color"
"github.com/jesseduffield/lazygit/pkg/gui/style"
)
const mergeSymbol = '⏣'
const commitSymbol = '⎔'
const MergeSymbol = '⏣'
const CommitSymbol = '◯'
type cellType int
@ -22,18 +26,18 @@ type Cell struct {
style style.TextStyle
}
func (cell *Cell) render() string {
func (cell *Cell) render(writer io.StringWriter) {
up, down, left, right := cell.up, cell.down, cell.left, cell.right
first, second := getBoxDrawingChars(up, down, left, right)
var adjustedFirst rune
var adjustedFirst string
switch cell.cellType {
case CONNECTION:
adjustedFirst = first
case COMMIT:
adjustedFirst = commitSymbol
adjustedFirst = string(CommitSymbol)
case MERGE:
adjustedFirst = mergeSymbol
adjustedFirst = string(MergeSymbol)
}
var rightStyle *style.TextStyle
@ -47,13 +51,46 @@ func (cell *Cell) render() string {
// assert on the style of a space given a space has no styling (assuming we
// stick to only using foreground styles)
var styledSecondChar string
if second == ' ' {
if second == " " {
styledSecondChar = " "
} else {
styledSecondChar = rightStyle.Sprint(string(second))
styledSecondChar = cachedSprint(*rightStyle, second)
}
return cell.style.Sprint(string(adjustedFirst)) + styledSecondChar
_, _ = writer.WriteString(cachedSprint(cell.style, adjustedFirst))
_, _ = writer.WriteString(styledSecondChar)
}
type rgbCacheKey struct {
*color.RGBStyle
str string
}
var rgbCache = make(map[rgbCacheKey]string)
var rgbCacheMutex sync.RWMutex
func cachedSprint(style style.TextStyle, str string) string {
switch v := style.Style.(type) {
case *color.RGBStyle:
rgbCacheMutex.RLock()
key := rgbCacheKey{v, str}
value, ok := rgbCache[key]
rgbCacheMutex.RUnlock()
if ok {
return value
}
value = style.Sprint(str)
rgbCacheMutex.Lock()
rgbCache[key] = value
rgbCacheMutex.Unlock()
return value
case color.Basic:
return style.Sprint(str)
case color.Style:
value := style.Sprint(str)
return value
}
return style.Sprint(str)
}
func (cell *Cell) reset() {
@ -102,39 +139,39 @@ func (cell *Cell) setType(cellType cellType) *Cell {
return cell
}
func getBoxDrawingChars(up, down, left, right bool) (rune, rune) {
func getBoxDrawingChars(up, down, left, right bool) (string, string) {
if up && down && left && right {
return '│', '─'
return "│", "─"
} else if up && down && left && !right {
return '│', ' '
return "│", " "
} else if up && down && !left && right {
return '│', '─'
return "│", "─"
} else if up && down && !left && !right {
return '│', ' '
return "│", " "
} else if up && !down && left && right {
return '┴', '─'
return "┴", "─"
} else if up && !down && left && !right {
return '╯', ' '
return "╯", " "
} else if up && !down && !left && right {
return '╰', '─'
return "╰", "─"
} else if up && !down && !left && !right {
return '╵', ' '
return "╵", " "
} else if !up && down && left && right {
return '┬', '─'
return "┬", "─"
} else if !up && down && left && !right {
return '╮', ' '
return "╮", " "
} else if !up && down && !left && right {
return '╭', '─'
return "╭", "─"
} else if !up && down && !left && !right {
return '╷', ' '
return "╷", " "
} else if !up && !down && left && right {
return '─', '─'
return "─", "─"
} else if !up && !down && left && !right {
return '─', ' '
return "─", " "
} else if !up && !down && !left && right {
return '╶', '─'
return "╶", "─"
} else if !up && !down && !left && !right {
return ' ', ' '
return " ", " "
} else {
panic("should not be possible")
}

View file

@ -1,6 +1,7 @@
package graph
import (
"runtime"
"sort"
"strings"
"sync"
@ -29,7 +30,7 @@ type Pipe struct {
var highlightStyle = style.FgLightWhite.SetBold()
func ContainsCommitSha(pipes []Pipe, sha string) bool {
func ContainsCommitSha(pipes []*Pipe, sha string) bool {
for _, pipe := range pipes {
if equalHashes(pipe.fromSha, sha) {
return true
@ -57,14 +58,14 @@ func RenderCommitGraph(commits []*models.Commit, selectedCommitSha string, getSt
return lines
}
func GetPipeSets(commits []*models.Commit, getStyle func(c *models.Commit) style.TextStyle) [][]Pipe {
func GetPipeSets(commits []*models.Commit, getStyle func(c *models.Commit) style.TextStyle) [][]*Pipe {
if len(commits) == 0 {
return nil
}
pipes := []Pipe{{fromPos: 0, toPos: 0, fromSha: "START", toSha: commits[0].Sha, kind: STARTS, style: style.FgDefault}}
pipes := []*Pipe{{fromPos: 0, toPos: 0, fromSha: "START", toSha: commits[0].Sha, kind: STARTS, style: style.FgDefault}}
pipeSets := [][]Pipe{}
pipeSets := [][]*Pipe{}
for _, commit := range commits {
pipes = getNextPipes(pipes, commit, getStyle)
pipeSets = append(pipeSets, pipes)
@ -73,29 +74,51 @@ func GetPipeSets(commits []*models.Commit, getStyle func(c *models.Commit) style
return pipeSets
}
func RenderAux(pipeSets [][]Pipe, commits []*models.Commit, selectedCommitSha string) []string {
lines := make([]string, len(pipeSets))
func RenderAux(pipeSets [][]*Pipe, commits []*models.Commit, selectedCommitSha string) []string {
maxProcs := runtime.GOMAXPROCS(0)
lines := make([]string, 0, len(pipeSets))
// splitting up the rendering of the graph into multiple goroutines allows us to render the graph in parallel
chunks := make([][]string, maxProcs)
perProc := len(pipeSets) / maxProcs
wg := sync.WaitGroup{}
wg.Add(len(pipeSets))
for i, pipeSet := range pipeSets {
wg.Add(maxProcs)
for i := 0; i < maxProcs; i++ {
i := i
pipeSet := pipeSet
go func() {
defer wg.Done()
var prevCommit *models.Commit
if i > 0 {
prevCommit = commits[i-1]
from := i * perProc
to := (i + 1) * perProc
if i == maxProcs-1 {
to = len(pipeSets)
}
line := renderPipeSet(pipeSet, selectedCommitSha, prevCommit)
lines[i] = line
innerLines := make([]string, 0, to-from)
for j, pipeSet := range pipeSets[from:to] {
k := from + j
var prevCommit *models.Commit
if k > 0 {
prevCommit = commits[k-1]
}
line := renderPipeSet(pipeSet, selectedCommitSha, prevCommit)
innerLines = append(innerLines, line)
}
chunks[i] = innerLines
wg.Done()
}()
}
wg.Wait()
for _, chunk := range chunks {
lines = append(lines, chunk...)
}
return lines
}
func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *models.Commit) style.TextStyle) []Pipe {
currentPipes := make([]Pipe, 0, len(prevPipes))
func getNextPipes(prevPipes []*Pipe, commit *models.Commit, getStyle func(c *models.Commit) style.TextStyle) []*Pipe {
currentPipes := make([]*Pipe, 0, len(prevPipes))
maxPos := 0
for _, pipe := range prevPipes {
// a pipe that terminated in the previous line has no bearing on the current line
@ -106,7 +129,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
maxPos = utils.Max(maxPos, pipe.toPos)
}
newPipes := make([]Pipe, 0, len(currentPipes)+len(commit.Parents))
newPipes := make([]*Pipe, 0, len(currentPipes)+len(commit.Parents))
// start by assuming that we've got a brand new commit not related to any preceding commit.
// (this only happens when we're doing `git log --all`). These will be tacked onto the far end.
pos := maxPos + 1
@ -124,7 +147,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
traversedSpots := make(map[int]bool)
if len(commit.Parents) > 0 {
newPipes = append(newPipes, Pipe{
newPipes = append(newPipes, &Pipe{
fromPos: pos,
toPos: pos,
fromSha: commit.Sha,
@ -177,7 +200,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
for _, pipe := range currentPipes {
if equalHashes(pipe.toSha, commit.Sha) {
// terminating here
newPipes = append(newPipes, Pipe{
newPipes = append(newPipes, &Pipe{
fromPos: pipe.toPos,
toPos: pos,
fromSha: pipe.fromSha,
@ -189,7 +212,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
} else if pipe.toPos < pos {
// continuing here
availablePos := getNextAvailablePosForContinuingPipe()
newPipes = append(newPipes, Pipe{
newPipes = append(newPipes, &Pipe{
fromPos: pipe.toPos,
toPos: availablePos,
fromSha: pipe.fromSha,
@ -205,7 +228,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
for _, parent := range commit.Parents[1:] {
availablePos := getNextAvailablePosForNewPipe()
// need to act as if continuing pipes are going to continue on the same line.
newPipes = append(newPipes, Pipe{
newPipes = append(newPipes, &Pipe{
fromPos: pos,
toPos: availablePos,
fromSha: commit.Sha,
@ -229,7 +252,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
last = i
}
}
newPipes = append(newPipes, Pipe{
newPipes = append(newPipes, &Pipe{
fromPos: pipe.toPos,
toPos: last,
fromSha: pipe.fromSha,
@ -253,7 +276,7 @@ func getNextPipes(prevPipes []Pipe, commit *models.Commit, getStyle func(c *mode
}
func renderPipeSet(
pipes []Pipe,
pipes []*Pipe,
selectedCommitSha string,
prevCommit *models.Commit,
) string {
@ -279,7 +302,7 @@ func renderPipeSet(
cells[i] = &Cell{cellType: CONNECTION, style: style.FgDefault}
}
renderPipe := func(pipe Pipe, style style.TextStyle, overrideRightStyle bool) {
renderPipe := func(pipe *Pipe, style style.TextStyle, overrideRightStyle bool) {
left := pipe.left()
right := pipe.right()
@ -313,9 +336,9 @@ func renderPipeSet(
// so we have our commit pos again, now it's time to build the cells.
// we'll handle the one that's sourced from our selected commit last so that it can override the other cells.
selectedPipes := []Pipe{}
selectedPipes := []*Pipe{}
// pre-allocating this one because most of the time we'll only have non-selected pipes
nonSelectedPipes := make([]Pipe, 0, len(pipes))
nonSelectedPipes := make([]*Pipe, 0, len(pipes))
for _, pipe := range pipes {
if highlight && equalHashes(pipe.fromSha, selectedCommitSha) {
@ -356,11 +379,13 @@ func renderPipeSet(
cells[commitPos].setType(cType)
renderedCells := make([]string, len(cells))
for i, cell := range cells {
renderedCells[i] = cell.render()
// using a string builder here for the sake of performance
writer := &strings.Builder{}
writer.Grow(len(cells) * 2)
for _, cell := range cells {
cell.render(writer)
}
return strings.Join(renderedCells, "")
return writer.String()
}
func equalHashes(a, b string) bool {

View file

@ -1,11 +1,14 @@
package graph
import (
"fmt"
"math/rand"
"strings"
"testing"
"github.com/gookit/color"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
@ -42,20 +45,20 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "D", Parents: []string{"G"}},
},
expectedOutput: `
1
2
3
1
2
3
4
7
5
8
7
5
8
9
B
D
A
E
F
D `,
B
D
A
E
F
D `,
},
{
name: "with a path that has room to move to the left",
@ -68,12 +71,12 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "6", Parents: []string{"7"}},
},
expectedOutput: `
1
1
2
4
3
5
6 `,
3
5
6 `,
},
{
name: "with a new commit",
@ -87,13 +90,13 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "6", Parents: []string{"7"}},
},
expectedOutput: `
1
1
2
4
Z
3
5
6 `,
Z
3
5
6 `,
},
{
name: "with a path that has room to move to the left and continues",
@ -106,12 +109,12 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "7", Parents: []string{"11"}},
},
expectedOutput: `
1
1
2
3
5
4
7 `,
4
7 `,
},
{
name: "with a path that has room to move to the left and continues",
@ -125,13 +128,13 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "B", Parents: []string{"C"}},
},
expectedOutput: `
1
1
2
3
5
7
4
B `,
4
B `,
},
{
name: "with a path that has room to move to the left and continues",
@ -144,10 +147,10 @@ func TestRenderCommitGraph(t *testing.T) {
},
expectedOutput: `
1
3
3
2
4
6 `,
6 `,
},
{
name: "new merge path fills gap before continuing path on right",
@ -160,10 +163,10 @@ func TestRenderCommitGraph(t *testing.T) {
},
expectedOutput: `
1
4
2
4
2
A
B `,
B `,
},
{
name: "with a path that has room to move to the left and continues",
@ -178,14 +181,14 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "C", Parents: []string{"D"}},
},
expectedOutput: `
1
1
2
3
5
7
4
B
C `,
4
B
C `,
},
{
name: "with a path that has room to move to the left and continues",
@ -202,16 +205,16 @@ func TestRenderCommitGraph(t *testing.T) {
{Sha: "D", Parents: []string{"F"}},
},
expectedOutput: `
1
1
2
3
5
7
8
4
B
C
D `,
4
B
C
D `,
},
}
@ -253,7 +256,7 @@ func TestRenderPipeSet(t *testing.T) {
tests := []struct {
name string
pipes []Pipe
pipes []*Pipe
commit *models.Commit
prevCommit *models.Commit
expectedStr string
@ -261,27 +264,27 @@ func TestRenderPipeSet(t *testing.T) {
}{
{
name: "single cell",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "b", kind: TERMINATES, style: cyan},
{fromPos: 0, toPos: 0, fromSha: "b", toSha: "c", kind: STARTS, style: green},
},
prevCommit: &models.Commit{Sha: "a"},
expectedStr: "",
expectedStr: "",
expectedStyles: []style.TextStyle{green},
},
{
name: "single cell, selected",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "selected", kind: TERMINATES, style: cyan},
{fromPos: 0, toPos: 0, fromSha: "selected", toSha: "c", kind: STARTS, style: green},
},
prevCommit: &models.Commit{Sha: "a"},
expectedStr: "",
expectedStr: "",
expectedStyles: []style.TextStyle{highlightStyle},
},
{
name: "terminating hook and starting hook, selected",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "selected", kind: TERMINATES, style: cyan},
{fromPos: 1, toPos: 0, fromSha: "c", toSha: "selected", kind: TERMINATES, style: yellow},
{fromPos: 0, toPos: 0, fromSha: "selected", toSha: "d", kind: STARTS, style: green},
@ -294,8 +297,8 @@ func TestRenderPipeSet(t *testing.T) {
},
},
{
name: "terminating hook and starting hook, prioritise the starting one",
pipes: []Pipe{
name: "terminating hook and starting hook, prioritise the terminating one",
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "b", kind: TERMINATES, style: red},
{fromPos: 1, toPos: 0, fromSha: "c", toSha: "b", kind: TERMINATES, style: magenta},
{fromPos: 0, toPos: 0, fromSha: "b", toSha: "d", kind: STARTS, style: green},
@ -309,7 +312,7 @@ func TestRenderPipeSet(t *testing.T) {
},
{
name: "starting and terminating pipe sharing some space",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: yellow},
{fromPos: 1, toPos: 1, fromSha: "b1", toSha: "b2", kind: CONTINUES, style: magenta},
@ -324,7 +327,7 @@ func TestRenderPipeSet(t *testing.T) {
},
{
name: "starting and terminating pipe sharing some space, with selection",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "selected", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "selected", toSha: "a3", kind: STARTS, style: yellow},
{fromPos: 1, toPos: 1, fromSha: "b1", toSha: "b2", kind: CONTINUES, style: magenta},
@ -339,21 +342,21 @@ func TestRenderPipeSet(t *testing.T) {
},
{
name: "many terminating pipes",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: yellow},
{fromPos: 1, toPos: 0, fromSha: "b1", toSha: "a2", kind: TERMINATES, style: magenta},
{fromPos: 2, toPos: 0, fromSha: "c1", toSha: "a2", kind: TERMINATES, style: green},
},
prevCommit: &models.Commit{Sha: "a1"},
expectedStr: "─┴─╯",
expectedStr: "─┴─╯",
expectedStyles: []style.TextStyle{
yellow, magenta, magenta, green, green,
},
},
{
name: "starting pipe passing through",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: yellow},
{fromPos: 0, toPos: 3, fromSha: "a2", toSha: "d3", kind: STARTS, style: yellow},
@ -368,7 +371,7 @@ func TestRenderPipeSet(t *testing.T) {
},
{
name: "starting and terminating path crossing continuing path",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: yellow},
{fromPos: 0, toPos: 1, fromSha: "a2", toSha: "b3", kind: STARTS, style: yellow},
@ -383,7 +386,7 @@ func TestRenderPipeSet(t *testing.T) {
},
{
name: "another clash of starting and terminating paths",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: yellow},
{fromPos: 0, toPos: 1, fromSha: "a2", toSha: "b3", kind: STARTS, style: yellow},
@ -398,44 +401,44 @@ func TestRenderPipeSet(t *testing.T) {
},
{
name: "commit whose previous commit is selected",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "selected", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: yellow},
},
prevCommit: &models.Commit{Sha: "selected"},
expectedStr: "",
expectedStr: "",
expectedStyles: []style.TextStyle{
yellow,
},
},
{
name: "commit whose previous commit is selected and is a merge commit",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "selected", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 1, toPos: 1, fromSha: "selected", toSha: "b3", kind: CONTINUES, style: red},
},
prevCommit: &models.Commit{Sha: "selected"},
expectedStr: " │",
expectedStr: " │",
expectedStyles: []style.TextStyle{
highlightStyle, nothing, highlightStyle,
},
},
{
name: "commit whose previous commit is selected and is a merge commit, with continuing pipe inbetween",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "selected", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 1, toPos: 1, fromSha: "z1", toSha: "z3", kind: CONTINUES, style: green},
{fromPos: 2, toPos: 2, fromSha: "selected", toSha: "b3", kind: CONTINUES, style: red},
},
prevCommit: &models.Commit{Sha: "selected"},
expectedStr: " │ │",
expectedStr: " │ │",
expectedStyles: []style.TextStyle{
highlightStyle, nothing, green, nothing, highlightStyle,
},
},
{
name: "when previous commit is selected, not a merge commit, and spawns a continuing pipe",
pipes: []Pipe{
pipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a1", toSha: "a2", kind: TERMINATES, style: red},
{fromPos: 0, toPos: 0, fromSha: "a2", toSha: "a3", kind: STARTS, style: green},
{fromPos: 0, toPos: 1, fromSha: "a2", toSha: "b3", kind: STARTS, style: green},
@ -471,57 +474,27 @@ func TestRenderPipeSet(t *testing.T) {
}
}
func TestCellRender(t *testing.T) {
tests := []struct {
cell *Cell
expectedString string
}{
{
cell: &Cell{
up: true,
down: true,
cellType: CONNECTION,
style: style.FgCyan,
},
expectedString: "\x1b[36m│\x1b[0m ",
},
{
cell: &Cell{
up: true,
down: true,
cellType: COMMIT,
style: style.FgCyan,
},
expectedString: "\x1b[36m⎔\x1b[0m ",
},
}
for _, test := range tests {
assert.EqualValues(t, test.expectedString, test.cell.render())
}
}
func TestGetNextPipes(t *testing.T) {
tests := []struct {
prevPipes []Pipe
prevPipes []*Pipe
commit *models.Commit
expected []Pipe
expected []*Pipe
}{
{
prevPipes: []Pipe{
prevPipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "b", kind: STARTS, style: style.FgDefault},
},
commit: &models.Commit{
Sha: "b",
Parents: []string{"c"},
},
expected: []Pipe{
expected: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "b", kind: TERMINATES, style: style.FgDefault},
{fromPos: 0, toPos: 0, fromSha: "b", toSha: "c", kind: STARTS, style: style.FgDefault},
},
},
{
prevPipes: []Pipe{
prevPipes: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "a", toSha: "b", kind: TERMINATES, style: style.FgDefault},
{fromPos: 0, toPos: 0, fromSha: "b", toSha: "c", kind: STARTS, style: style.FgDefault},
{fromPos: 0, toPos: 1, fromSha: "b", toSha: "d", kind: STARTS, style: style.FgDefault},
@ -530,7 +503,7 @@ func TestGetNextPipes(t *testing.T) {
Sha: "d",
Parents: []string{"e"},
},
expected: []Pipe{
expected: []*Pipe{
{fromPos: 0, toPos: 0, fromSha: "b", toSha: "c", kind: CONTINUES, style: style.FgDefault},
{fromPos: 1, toPos: 1, fromSha: "b", toSha: "d", kind: TERMINATES, style: style.FgDefault},
{fromPos: 1, toPos: 1, fromSha: "d", toSha: "e", kind: STARTS, style: style.FgDefault},
@ -551,3 +524,47 @@ func TestGetNextPipes(t *testing.T) {
assert.EqualValues(t, test.expected, pipes)
}
}
func BenchmarkRenderCommitGraph(b *testing.B) {
commits := generateCommits(50)
getStyle := func(commit *models.Commit) style.TextStyle {
return authors.AuthorStyle(commit.Author)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
RenderCommitGraph(commits, "selected", getStyle)
}
}
func generateCommits(count int) []*models.Commit {
rand.Seed(1234)
pool := []*models.Commit{{Sha: "a", Author: "A"}}
commits := make([]*models.Commit, 0, count)
authorPool := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
for len(commits) < count {
currentCommitIdx := rand.Intn(len(pool))
currentCommit := pool[currentCommitIdx]
pool = append(pool[0:currentCommitIdx], pool[currentCommitIdx+1:]...)
// I need to pick a random number of parents to add
parentCount := rand.Intn(2) + 1
for j := 0; j < parentCount; j++ {
reuseParent := rand.Intn(6) != 1 && j <= len(pool)-1 && j != 0
var newParent *models.Commit
if reuseParent {
newParent = pool[j]
} else {
newParent = &models.Commit{
Sha: fmt.Sprintf("%s%d", currentCommit.Sha, j),
Author: authorPool[rand.Intn(len(authorPool))],
}
pool = append(pool, newParent)
}
currentCommit.Parents = append(currentCommit.Parents, newParent.Sha)
}
commits = append(commits, currentCommit)
}
return commits
}

View file

@ -4,6 +4,7 @@
package gui
import (
"io"
"os/exec"
"strings"
@ -50,14 +51,19 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
manager := gui.getManager(view)
ptmx, err := pty.Start(cmd)
if err != nil {
return err
start := func() (*exec.Cmd, io.Reader) {
ptmx, err := pty.Start(cmd)
if err != nil {
gui.Log.Error(err)
}
gui.State.Ptmx = ptmx
return cmd, ptmx
}
gui.State.Ptmx = ptmx
onClose := func() {
ptmx.Close()
gui.State.Ptmx.Close()
gui.State.Ptmx = nil
}
@ -65,7 +71,7 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
return err
}
if err := manager.NewTask(manager.NewCmdTask(ptmx, cmd, prefix, height+oy+10, onClose), cmdStr); err != nil {
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, onClose), cmdStr); err != nil {
return err
}

View file

@ -39,19 +39,19 @@ func TestMerge(t *testing.T) {
{
"no color",
nil,
TextStyle{style: color.Style{}},
TextStyle{Style: color.Style{}},
"foo",
},
{
"only fg color",
[]TextStyle{FgRed},
TextStyle{fg: &Color{basic: &fgRed}, style: color.Style{fgRed}},
TextStyle{fg: &Color{basic: &fgRed}, Style: color.Style{fgRed}},
"\x1b[31mfoo\x1b[0m",
},
{
"only bg color",
[]TextStyle{BgRed},
TextStyle{bg: &Color{basic: &bgRed}, style: color.Style{bgRed}},
TextStyle{bg: &Color{basic: &bgRed}, Style: color.Style{bgRed}},
"\x1b[41mfoo\x1b[0m",
},
{
@ -60,7 +60,7 @@ func TestMerge(t *testing.T) {
TextStyle{
fg: &Color{basic: &fgBlue},
bg: &Color{basic: &bgRed},
style: color.Style{fgBlue, bgRed},
Style: color.Style{fgBlue, bgRed},
},
"\x1b[34;41mfoo\x1b[0m",
},
@ -69,7 +69,7 @@ func TestMerge(t *testing.T) {
[]TextStyle{AttrBold},
TextStyle{
decoration: Decoration{bold: true},
style: color.Style{color.OpBold},
Style: color.Style{color.OpBold},
},
"\x1b[1mfoo\x1b[0m",
},
@ -81,7 +81,7 @@ func TestMerge(t *testing.T) {
bold: true,
underline: true,
},
style: color.Style{color.OpBold, color.OpUnderscore},
Style: color.Style{color.OpBold, color.OpUnderscore},
},
"\x1b[1;4mfoo\x1b[0m",
},
@ -95,7 +95,7 @@ func TestMerge(t *testing.T) {
bold: true,
underline: true,
},
style: color.Style{fgBlue, bgRed, color.OpBold, color.OpUnderscore},
Style: color.Style{fgBlue, bgRed, color.OpBold, color.OpUnderscore},
},
"\x1b[34;41;1;4mfoo\x1b[0m",
},
@ -104,7 +104,7 @@ func TestMerge(t *testing.T) {
[]TextStyle{New().SetFg(rgbPink)},
TextStyle{
fg: &rgbPink,
style: color.NewRGBStyle(rgbPinkLib).SetOpts(color.Opts{}),
Style: color.NewRGBStyle(rgbPinkLib).SetOpts(color.Opts{}),
},
// '38;2' qualifies an RGB foreground color
"\x1b[38;2;255;0;255mfoo\x1b[0m",
@ -115,7 +115,7 @@ func TestMerge(t *testing.T) {
TextStyle{
fg: &rgbPink,
bg: &rgbYellow,
style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{}),
Style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{}),
},
// '48;2' qualifies an RGB background color
"\x1b[38;2;255;0;255;48;2;255;255;0mfoo\x1b[0m",
@ -130,7 +130,7 @@ func TestMerge(t *testing.T) {
bold: true,
underline: true,
},
style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{color.OpBold, color.OpUnderscore}),
Style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{color.OpBold, color.OpUnderscore}),
},
"\x1b[38;2;255;0;255;48;2;255;255;0;1;4mfoo\x1b[0m",
},
@ -140,7 +140,7 @@ func TestMerge(t *testing.T) {
TextStyle{
fg: &rgbYellow,
bg: &Color{basic: &bgRed},
style: color.NewRGBStyle(
Style: color.NewRGBStyle(
rgbYellowLib,
fgRed.RGB(), // We need to use FG here, https://github.com/gookit/color/issues/39
).SetOpts(color.Opts{}),

View file

@ -30,7 +30,9 @@ type TextStyle struct {
bg *Color
decoration Decoration
style Sprinter
// making this public so that we can use a type switch to get to the underlying
// value so we can cache styles. This is very much a hack.
Style Sprinter
}
type Sprinter interface {
@ -40,16 +42,16 @@ type Sprinter interface {
func New() TextStyle {
s := TextStyle{}
s.style = s.deriveStyle()
s.Style = s.deriveStyle()
return s
}
func (b TextStyle) Sprint(a ...interface{}) string {
return b.style.Sprint(a...)
return b.Style.Sprint(a...)
}
func (b TextStyle) Sprintf(format string, a ...interface{}) string {
return b.style.Sprintf(format, a...)
return b.Style.Sprintf(format, a...)
}
// note that our receiver here is not a pointer which means we're receiving a
@ -57,31 +59,31 @@ func (b TextStyle) Sprintf(format string, a ...interface{}) string {
// TextStyle receiver without actually modifying the original.
func (b TextStyle) SetBold() TextStyle {
b.decoration.SetBold()
b.style = b.deriveStyle()
b.Style = b.deriveStyle()
return b
}
func (b TextStyle) SetUnderline() TextStyle {
b.decoration.SetUnderline()
b.style = b.deriveStyle()
b.Style = b.deriveStyle()
return b
}
func (b TextStyle) SetReverse() TextStyle {
b.decoration.SetReverse()
b.style = b.deriveStyle()
b.Style = b.deriveStyle()
return b
}
func (b TextStyle) SetBg(color Color) TextStyle {
b.bg = &color
b.style = b.deriveStyle()
b.Style = b.deriveStyle()
return b
}
func (b TextStyle) SetFg(color Color) TextStyle {
b.fg = &color
b.style = b.deriveStyle()
b.Style = b.deriveStyle()
return b
}
@ -96,7 +98,7 @@ func (b TextStyle) MergeStyle(other TextStyle) TextStyle {
b.bg = other.bg
}
b.style = b.deriveStyle()
b.Style = b.deriveStyle()
return b
}

View file

@ -1,6 +1,7 @@
package gui
import (
"io"
"os/exec"
"strings"
@ -20,18 +21,22 @@ func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
manager := gui.getManager(view)
r, err := cmd.StdoutPipe()
if err != nil {
return err
}
cmd.Stderr = cmd.Stdout
start := func() (*exec.Cmd, io.Reader) {
r, err := cmd.StdoutPipe()
if err != nil {
gui.Log.Warn(err)
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
return err
if err := cmd.Start(); err != nil {
gui.Log.Warn(err)
}
return cmd, r
}
if err := manager.NewTask(manager.NewCmdTask(r, cmd, prefix, height+oy+10, nil), cmdStr); err != nil {
return err
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil {
gui.Log.Warn(err)
}
return nil
@ -90,7 +95,6 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
view.Reset()
},
func() {
// gui.g.Draw(view) // doing this causes an issue when there's a popup panel in front of the main view.
gui.render()
},
func() {

View file

@ -308,6 +308,11 @@ func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) {
v.SetContent(list)
}
func (gui *Gui) renderDisplayStringsAtPos(v *gocui.View, y int, displayStrings [][]string) {
list := utils.RenderDisplayStrings(displayStrings)
v.OverwriteLines(y, list)
}
func (gui *Gui) globalOptionsMap() map[string]string {
keybindingConfig := gui.Config.GetUserConfig().Keybinding
@ -318,6 +323,7 @@ func (gui *Gui) globalOptionsMap() map[string]string {
gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.Tr.LcQuit,
gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.Tr.LcMenu,
fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.Tr.LcJump,
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.Tr.LcScrollLeftRight,
}
}

View file

@ -271,6 +271,9 @@ type TranslationSet struct {
LcStashOptions string
NotARepository string
LcJump string
LcScrollLeftRight string
LcScrollLeft string
LcScrollRight string
DiscardPatch string
DiscardPatchConfirm string
CantPatchWhileRebasingError string
@ -434,6 +437,7 @@ type TranslationSet struct {
SelectConfigFile string
NoConfigFileFoundErr string
LcLoadingFileSuggestions string
LcLoadingCommits string
MustSpecifyOriginError string
GitOutput string
GitCommandFailed string
@ -441,6 +445,11 @@ type TranslationSet struct {
AbortTitle string
AbortPrompt string
SelectRemoteRepository string
LcOpenLogMenu string
LogMenuTitle string
ToggleShowGitGraphAll string
ShowGitGraph string
SortCommits string
Spans Spans
}
@ -805,6 +814,9 @@ func englishTranslationSet() TranslationSet {
LcStashOptions: "Stash options",
NotARepository: "Error: must be run inside a git repository",
LcJump: "jump to panel",
LcScrollLeftRight: "scroll left/right",
LcScrollLeft: "scroll left",
LcScrollRight: "scroll right",
DiscardPatch: "Discard Patch",
DiscardPatchConfirm: "You can only build a patch from one commit/stash-entry at a time. Discard current patch?",
CantPatchWhileRebasingError: "You cannot build a patch or run patch commands while in a merging or rebasing state",
@ -969,12 +981,18 @@ func englishTranslationSet() TranslationSet {
SelectConfigFile: "Select config file",
NoConfigFileFoundErr: "No config file found",
LcLoadingFileSuggestions: "loading file suggestions",
LcLoadingCommits: "loading commits",
MustSpecifyOriginError: "Must specify a remote if specifying a branch",
GitOutput: "Git output:",
GitCommandFailed: "Git command failed. Check command log for details (open with %s)",
OpenPr: "Open PR #",
AbortTitle: "Abort %s",
AbortPrompt: "Are you sure you want to abort the current %s?",
LcOpenLogMenu: "open log menu",
LogMenuTitle: "Commit Log Options",
ToggleShowGitGraphAll: "toggle show whole git graph (pass the `--all` flag to `git log`)",
ShowGitGraph: "show git graph",
SortCommits: "commit sort order",
Spans: Spans{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit",

View file

@ -14,6 +14,11 @@ import (
"github.com/sirupsen/logrus"
)
const THROTTLE_TIME = time.Millisecond * 30
// we use this to check if the system is under stress right now. Hopefully this makes sense on other machines
const COMMAND_START_THRESHOLD = time.Millisecond * 10
type Task struct {
stop chan struct{}
stopped bool
@ -38,6 +43,12 @@ type ViewBufferManager struct {
beforeStart func()
refreshView func()
onEndOfInput func()
// if the user flicks through a heap of items, with each one
// spawning a process to render something to the main view,
// it can slow things down quite a bit. In these situations we
// want to throttle the spawning of processes.
throttle bool
}
func (m *ViewBufferManager) GetTaskKey() string {
@ -69,18 +80,35 @@ func (m *ViewBufferManager) ReadLines(n int) {
})
}
func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string, linesToRead int, onDone func()) func(chan struct{}) error {
func (m *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead int, onDone func()) func(chan struct{}) error {
return func(stop chan struct{}) error {
if m.throttle {
m.Log.Info("throttling task")
time.Sleep(THROTTLE_TIME)
}
select {
case <-stop:
return nil
default:
}
startTime := time.Now()
cmd, r := start()
timeToStart := time.Since(startTime)
go utils.Safe(func() {
<-stop
// we use the time it took to start the program as a way of checking if things
// are running slow at the moment. This is admittedly a crude estimate, but
// the point is that we only want to throttle when things are running slow
// and the user is flicking through a bunch of items.
m.throttle = time.Since(startTime) < THROTTLE_TIME && timeToStart > COMMAND_START_THRESHOLD
if err := oscommands.Kill(cmd); err != nil {
if !strings.Contains(err.Error(), "process already finished") {
m.Log.Errorf("error when running cmd task: %v", err)
}
}
if onDone != nil {
onDone()
}
})
loadingMutex := sync.Mutex{}

View file

@ -6,21 +6,24 @@ import (
)
var decoloriseCache = make(map[string]string)
var decoloriseMutex sync.Mutex
var decoloriseMutex sync.RWMutex
// Decolorise strips a string of color
func Decolorise(str string) string {
decoloriseMutex.Lock()
defer decoloriseMutex.Unlock()
decoloriseMutex.RLock()
val := decoloriseCache[str]
decoloriseMutex.RUnlock()
if decoloriseCache[str] != "" {
return decoloriseCache[str]
if val != "" {
return val
}
re := regexp.MustCompile(`\x1B\[([0-9]{1,3}(;[0-9]{1,3})*)?[mGK]`)
ret := re.ReplaceAllString(str, "")
decoloriseMutex.Lock()
decoloriseCache[str] = ret
decoloriseMutex.Unlock()
return ret
}

View file

@ -75,8 +75,6 @@ type GuiMutexes struct {
tickingMutex sync.Mutex
ViewsMutex sync.Mutex
drawMutex sync.Mutex
}
type PlayMode int
@ -936,8 +934,6 @@ func (g *Gui) drawListFooter(v *View, fgColor, bgColor Attribute) error {
// flush updates the gui, re-drawing frames and buffers.
func (g *Gui) flush() error {
g.Mutexes.drawMutex.Lock()
defer g.Mutexes.drawMutex.Unlock()
// pretty sure we don't need this, but keeping it here in case we get weird visual artifacts
// g.clear(g.FgColor, g.BgColor)
@ -966,18 +962,6 @@ func (g *Gui) flush() error {
return nil
}
func (g *Gui) Draw(v *View) error {
g.Mutexes.drawMutex.Lock()
defer g.Mutexes.drawMutex.Unlock()
if err := g.draw(v); err != nil {
return err
}
Screen.Show()
return nil
}
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if g.suspended {

View file

@ -218,7 +218,7 @@ func (v *View) SelectSearchResult(index int) error {
}
y := v.searcher.searchPositions[index].y
v.FocusPoint(0, y)
v.FocusPoint(v.ox, y)
if v.searcher.onSelectItem != nil {
return v.searcher.onSelectItem(y, index, itemCount)
}
@ -227,10 +227,9 @@ func (v *View) SelectSearchResult(index int) error {
func (v *View) Search(str string) error {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.searcher.search(str)
v.updateSearchPositions()
if len(v.searcher.searchPositions) > 0 {
// get the first result past the current cursor
currentIndex := 0
@ -243,8 +242,10 @@ func (v *View) Search(str string) error {
}
}
v.searcher.currentSearchIndex = currentIndex
v.writeMutex.Unlock()
return v.SelectSearchResult(currentIndex)
} else {
v.writeMutex.Unlock()
return v.searcher.onSelectItem(-1, -1, 0)
}
}
@ -475,9 +476,33 @@ func (v *View) SetOrigin(x, y int) error {
return nil
}
func (v *View) SetOriginX(x int) error {
if x < 0 {
return ErrInvalidPoint
}
v.ox = x
return nil
}
func (v *View) SetOriginY(y int) error {
if y < 0 {
return ErrInvalidPoint
}
v.oy = y
return nil
}
// Origin returns the origin position of the view.
func (v *View) Origin() (x, y int) {
return v.ox, v.oy
return v.OriginX(), v.OriginY()
}
func (v *View) OriginX() int {
return v.ox
}
func (v *View) OriginY() int {
return v.oy
}
// SetWritePos sets the write position of the view's internal buffer.
@ -879,11 +904,18 @@ func (v *View) draw() error {
if v.Autoscroll && visibleViewLinesHeight > maxY {
v.oy = visibleViewLinesHeight - maxY
}
if len(v.viewLines) == 0 {
return nil
}
start := v.oy
if start > len(v.viewLines)-1 {
start = len(v.viewLines) - 1
}
y := 0
for i, vline := range v.viewLines {
if i < v.oy {
continue
}
for _, vline := range v.viewLines[start:] {
if y >= maxY {
break
}
@ -1112,14 +1144,6 @@ func (v *View) SetHighlight(y int, on bool) error {
return nil
}
func lineWidth(line []cell) (n int) {
for i := range line {
n += runewidth.RuneWidth(line[i].chr)
}
return
}
func lineWrap(line []cell, columns int) [][]cell {
if columns == 0 {
return [][]cell{line}
@ -1227,3 +1251,16 @@ func (v *View) ClearTextArea() {
_ = v.SetOrigin(0, 0)
_ = v.SetCursor(0, 0)
}
// only call this function if you don't care where v.wx and v.wy end up
func (v *View) OverwriteLines(y int, content string) {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
// break by newline, then for each line, write it, then add that erase command
v.wx = 0
v.wy = y
lines := strings.Replace(content, "\n", "\x1b[K\n", -1)
v.writeString(lines)
}

View file

@ -430,8 +430,25 @@ func GetsockoptXucred(fd, level, opt int) (*Xucred, error) {
return x, err
}
func SysctlKinfoProcSlice(name string) ([]KinfoProc, error) {
mib, err := sysctlmib(name)
func SysctlKinfoProc(name string, args ...int) (*KinfoProc, error) {
mib, err := sysctlmib(name, args...)
if err != nil {
return nil, err
}
var kinfo KinfoProc
n := uintptr(SizeofKinfoProc)
if err := sysctl(mib, (*byte)(unsafe.Pointer(&kinfo)), &n, nil, 0); err != nil {
return nil, err
}
if n != SizeofKinfoProc {
return nil, EIO
}
return &kinfo, nil
}
func SysctlKinfoProcSlice(name string, args ...int) ([]KinfoProc, error) {
mib, err := sysctlmib(name, args...)
if err != nil {
return nil, err
}

View file

@ -116,6 +116,7 @@ const (
ARPHRD_LAPB = 0x204
ARPHRD_LOCALTLK = 0x305
ARPHRD_LOOPBACK = 0x304
ARPHRD_MCTP = 0x122
ARPHRD_METRICOM = 0x17
ARPHRD_NETLINK = 0x338
ARPHRD_NETROM = 0x0
@ -472,6 +473,7 @@ const (
DM_DEV_WAIT = 0xc138fd08
DM_DIR = "mapper"
DM_GET_TARGET_VERSION = 0xc138fd11
DM_IMA_MEASUREMENT_FLAG = 0x80000
DM_INACTIVE_PRESENT_FLAG = 0x40
DM_INTERNAL_SUSPEND_FLAG = 0x40000
DM_IOCTL = 0xfd
@ -716,6 +718,7 @@ const (
ETH_P_LOOPBACK = 0x9000
ETH_P_MACSEC = 0x88e5
ETH_P_MAP = 0xf9
ETH_P_MCTP = 0xfa
ETH_P_MOBITEX = 0x15
ETH_P_MPLS_MC = 0x8848
ETH_P_MPLS_UC = 0x8847
@ -751,6 +754,21 @@ const (
ETH_P_WCCP = 0x883e
ETH_P_X25 = 0x805
ETH_P_XDSA = 0xf8
EV_ABS = 0x3
EV_CNT = 0x20
EV_FF = 0x15
EV_FF_STATUS = 0x17
EV_KEY = 0x1
EV_LED = 0x11
EV_MAX = 0x1f
EV_MSC = 0x4
EV_PWR = 0x16
EV_REL = 0x2
EV_REP = 0x14
EV_SND = 0x12
EV_SW = 0x5
EV_SYN = 0x0
EV_VERSION = 0x10001
EXABYTE_ENABLE_NEST = 0xf0
EXT2_SUPER_MAGIC = 0xef53
EXT3_SUPER_MAGIC = 0xef53
@ -789,9 +807,11 @@ const (
FAN_DELETE_SELF = 0x400
FAN_DENY = 0x2
FAN_ENABLE_AUDIT = 0x40
FAN_EPIDFD = -0x2
FAN_EVENT_INFO_TYPE_DFID = 0x3
FAN_EVENT_INFO_TYPE_DFID_NAME = 0x2
FAN_EVENT_INFO_TYPE_FID = 0x1
FAN_EVENT_INFO_TYPE_PIDFD = 0x4
FAN_EVENT_METADATA_LEN = 0x18
FAN_EVENT_ON_CHILD = 0x8000000
FAN_MARK_ADD = 0x1
@ -811,6 +831,7 @@ const (
FAN_MOVE_SELF = 0x800
FAN_NOFD = -0x1
FAN_NONBLOCK = 0x2
FAN_NOPIDFD = -0x1
FAN_ONDIR = 0x40000000
FAN_OPEN = 0x20
FAN_OPEN_EXEC = 0x1000
@ -821,6 +842,7 @@ const (
FAN_REPORT_DIR_FID = 0x400
FAN_REPORT_FID = 0x200
FAN_REPORT_NAME = 0x800
FAN_REPORT_PIDFD = 0x80
FAN_REPORT_TID = 0x100
FAN_UNLIMITED_MARKS = 0x20
FAN_UNLIMITED_QUEUE = 0x10
@ -1997,6 +2019,7 @@ const (
PR_SPEC_ENABLE = 0x2
PR_SPEC_FORCE_DISABLE = 0x8
PR_SPEC_INDIRECT_BRANCH = 0x1
PR_SPEC_L1D_FLUSH = 0x2
PR_SPEC_NOT_AFFECTED = 0x0
PR_SPEC_PRCTL = 0x1
PR_SPEC_STORE_BYPASS = 0x0
@ -2432,12 +2455,15 @@ const (
SMART_WRITE_THRESHOLDS = 0xd7
SMB_SUPER_MAGIC = 0x517b
SOCKFS_MAGIC = 0x534f434b
SOCK_BUF_LOCK_MASK = 0x3
SOCK_DCCP = 0x6
SOCK_IOC_TYPE = 0x89
SOCK_PACKET = 0xa
SOCK_RAW = 0x3
SOCK_RCVBUF_LOCK = 0x2
SOCK_RDM = 0x4
SOCK_SEQPACKET = 0x5
SOCK_SNDBUF_LOCK = 0x1
SOL_AAL = 0x109
SOL_ALG = 0x117
SOL_ATM = 0x108

View file

@ -293,6 +293,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -294,6 +294,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -300,6 +300,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -290,6 +290,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -293,6 +293,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x20
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -293,6 +293,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x20
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -293,6 +293,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x20
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -293,6 +293,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x20
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -348,6 +348,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -352,6 +352,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -352,6 +352,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -281,6 +281,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -356,6 +356,7 @@ const (
SO_BPF_EXTENSIONS = 0x30
SO_BROADCAST = 0x6
SO_BSDCOMPAT = 0xe
SO_BUF_LOCK = 0x48
SO_BUSY_POLL = 0x2e
SO_BUSY_POLL_BUDGET = 0x46
SO_CNX_ADVICE = 0x35

View file

@ -347,6 +347,7 @@ const (
SO_BPF_EXTENSIONS = 0x32
SO_BROADCAST = 0x20
SO_BSDCOMPAT = 0x400
SO_BUF_LOCK = 0x51
SO_BUSY_POLL = 0x30
SO_BUSY_POLL_BUDGET = 0x49
SO_CNX_ADVICE = 0x37

View file

@ -444,4 +444,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
)

View file

@ -366,4 +366,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
)

View file

@ -7,6 +7,7 @@
package unix
const (
SYS_SYSCALL_MASK = 0
SYS_RESTART_SYSCALL = 0
SYS_EXIT = 1
SYS_FORK = 2
@ -407,4 +408,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -311,4 +311,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
)

View file

@ -428,4 +428,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 4444
SYS_LANDLOCK_ADD_RULE = 4445
SYS_LANDLOCK_RESTRICT_SELF = 4446
SYS_PROCESS_MRELEASE = 4448
)

View file

@ -358,4 +358,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 5444
SYS_LANDLOCK_ADD_RULE = 5445
SYS_LANDLOCK_RESTRICT_SELF = 5446
SYS_PROCESS_MRELEASE = 5448
)

View file

@ -358,4 +358,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 5444
SYS_LANDLOCK_ADD_RULE = 5445
SYS_LANDLOCK_RESTRICT_SELF = 5446
SYS_PROCESS_MRELEASE = 5448
)

View file

@ -428,4 +428,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 4444
SYS_LANDLOCK_ADD_RULE = 4445
SYS_LANDLOCK_RESTRICT_SELF = 4446
SYS_PROCESS_MRELEASE = 4448
)

View file

@ -435,4 +435,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -407,4 +407,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -407,4 +407,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -309,4 +309,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -372,4 +372,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -386,4 +386,5 @@ const (
SYS_LANDLOCK_CREATE_RULESET = 444
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
)

View file

@ -3264,7 +3264,8 @@ const (
LWTUNNEL_ENCAP_BPF = 0x6
LWTUNNEL_ENCAP_SEG6_LOCAL = 0x7
LWTUNNEL_ENCAP_RPL = 0x8
LWTUNNEL_ENCAP_MAX = 0x8
LWTUNNEL_ENCAP_IOAM6 = 0x9
LWTUNNEL_ENCAP_MAX = 0x9
MPLS_IPTUNNEL_UNSPEC = 0x0
MPLS_IPTUNNEL_DST = 0x1
@ -3617,7 +3618,9 @@ const (
ETHTOOL_A_COALESCE_TX_USECS_HIGH = 0x15
ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH = 0x16
ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL = 0x17
ETHTOOL_A_COALESCE_MAX = 0x17
ETHTOOL_A_COALESCE_USE_CQE_MODE_TX = 0x18
ETHTOOL_A_COALESCE_USE_CQE_MODE_RX = 0x19
ETHTOOL_A_COALESCE_MAX = 0x19
ETHTOOL_A_PAUSE_UNSPEC = 0x0
ETHTOOL_A_PAUSE_HEADER = 0x1
ETHTOOL_A_PAUSE_AUTONEG = 0x2

4
vendor/modules.txt vendored
View file

@ -161,7 +161,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
# github.com/jesseduffield/gocui v0.3.1-0.20211031223253-24baf341da75
# github.com/jesseduffield/gocui v0.3.1-0.20211102104458-40df0be5a474
## explicit
github.com/jesseduffield/gocui
# github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@ -253,7 +253,7 @@ golang.org/x/crypto/ssh/knownhosts
golang.org/x/net/context
golang.org/x/net/internal/socks
golang.org/x/net/proxy
# golang.org/x/sys v0.0.0-20211031064116-611d5d643895
# golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c
## explicit
golang.org/x/sys/cpu
golang.org/x/sys/internal/unsafeheader