Compare commits

..
Author SHA1 Message Date
Jesse Duffield 017367f940 Show commit merged/push statuses against branches
When viewing branches in an enlargened window, we now colour the commit shas based on
whether the branch has been merged to a main branch and whether it's been pushed to
the upstream.

This isn't using the exact same code as we use for commits, so it's possible we'll get
a colour mismatch when the user checks out a branch and views the commits in the commits
view, but if we come across that situation we'll just need to fix it.
2023-10-11 15:51:58 +11:00
Jesse Duffield 16369dc3c2 Introduce ConcurrentMap util 2023-10-11 15:50:17 +11:00
Jesse Duffield 86bb73cff1 Better Explain the red/yellow/green commit colouring 2023-10-11 15:50:17 +11:00
Jesse Duffield bac12231c3 Don't bother checking for origin/main when loading commits
The real reason I'm doing this is to that it's easier to explain the logic behind setting
commit merged statuses. Now it's just 'try the upstream, fallback to the local branch'.

Other justifications:
* we should avoid assuming that 'origin' is the main upstream remote
* it's one less git call
* surely the main branch _will_ have an upsream. I doubt many people have benefitted
  from the extra fallback to origin
2023-10-11 15:50:17 +11:00
33 changed files with 624 additions and 798 deletions
-6
View File
@@ -21,8 +21,6 @@ welcome your pull requests:
4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
5. Issue that pull request!
Please do not raise pull request from your fork's master branch: make a feature branch instead. Lazygit maintainers will sometimes push changes to your branch when reviewing a PR and we often can't do this if you use your master branch.
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place.
## Running in a VSCode dev container
@@ -99,10 +97,6 @@ To run gofumpt from your terminal go:
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
```
## Programming Font
Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these.
## Internationalisation
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
+8 -4
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -319,7 +319,7 @@ os:
editPreset: 'vscode'
```
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `nvim`, `nvim-remote`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
+3
View File
@@ -47,6 +47,7 @@ type GitCommand struct {
type Loaders struct {
BranchLoader *git_commands.BranchLoader
MergedBranchLoader *git_commands.MergedBranchLoader
CommitFileLoader *git_commands.CommitFileLoader
CommitLoader *git_commands.CommitLoader
FileLoader *git_commands.FileLoader
@@ -162,6 +163,7 @@ func NewGitCommandAux(
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
mergedBranchLoader := git_commands.NewMergedBranchLoader(gitCommon)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
@@ -200,6 +202,7 @@ func NewGitCommandAux(
Worktrees: worktreeLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
MergedBranchLoader: mergedBranchLoader,
},
RepoPaths: repoPaths,
}
+16 -40
View File
@@ -568,50 +568,26 @@ func (self *CommitLoader) getMergeBase(refName string) string {
}
func (self *CommitLoader) getExistingMainBranches() []string {
var existingBranches []string
var wg sync.WaitGroup
mainBranches := self.UserConfig.Git.MainBranches
existingBranches = make([]string, len(mainBranches))
for i, branchName := range mainBranches {
wg.Add(1)
i := i
branchName := branchName
go utils.Safe(func() {
defer wg.Done()
existingBranches := utils.ConcurrentMap(mainBranches, func(mainBranch string) string {
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", mainBranch+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
return strings.TrimSpace(ref)
}
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
existingBranches[i] = strings.TrimSpace(ref)
return
}
// If this failed, fallback to the local branch
ref := "refs/heads/" + mainBranch
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
return ref
}
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
return
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
}
})
}
wg.Wait()
return ""
})
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
return branch != ""
@@ -74,12 +74,10 @@ func TestGetCommits(t *testing.T) {
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches have an upstream
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/develop"}, "", errors.New("error")). // doesn't exist there, either, so it checks for a local branch
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "refs/remotes/origin/main", nil). // this one does too
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so check for a local branch
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/remotes/origin/master", "refs/remotes/origin/main"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
@@ -212,10 +210,8 @@ func TestGetCommits(t *testing.T) {
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")),
expectedCommits: []*models.Commit{
@@ -250,7 +246,6 @@ func TestGetCommits(t *testing.T) {
// here it's testing which of the configured main branches exist
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "refs/remotes/origin/develop", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "1.0-hotfixes@{u}"}, "refs/remotes/origin/1.0-hotfixes", nil).
@@ -0,0 +1,59 @@
package git_commands
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// Returns a set of branch names which are merged into a main branch.
// This
type MergedBranchLoader struct {
c *GitCommon
}
func NewMergedBranchLoader(c *GitCommon) *MergedBranchLoader {
return &MergedBranchLoader{c: c}
}
// TODO: check against upstreams, and share code with commit loader that determines main branches
func (self *MergedBranchLoader) Load() *set.Set[string] {
set := set.New[string]()
mainBranches := self.c.UserConfig.Git.MainBranches
results := utils.ConcurrentMap(mainBranches, func(mainBranch string) []string {
mergedBranches, err := self.GetMergedBranches(mainBranch)
if err != nil {
self.c.Log.Warnf("Failed to get merged branches for %s: %s", mainBranch, err)
return nil
}
return mergedBranches
})
for i := 0; i < len(mainBranches); i++ {
for _, mergedBranch := range results[i] {
set.Add(mergedBranch)
}
}
return set
}
func (self *MergedBranchLoader) GetMergedBranches(mainBranch string) ([]string, error) {
// git for-each-ref --merged master --format '%(refname)' refs/heads/
cmdArgs := NewGitCmd("for-each-ref").Arg(
"--merged",
mainBranch,
"--format",
"%(refname)",
"refs/heads/",
).ToArgv()
output, err := self.c.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
branches := utils.SplitLines(output)
return branches, nil
}
-1
View File
@@ -59,7 +59,6 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
openDirInEditorTemplate: `nvim --server "$NVIM" --remote-tab {{dir}}`,
suspend: false,
},
"lvim": standardTerminalEditorPreset("lvim"),
"emacs": standardTerminalEditorPreset("emacs"),
"micro": standardTerminalEditorPreset("micro"),
"nano": standardTerminalEditorPreset("nano"),
+23 -30
View File
@@ -20,11 +20,10 @@ type BaseContext struct {
onFocusFn onFocusFn
onFocusLostFn onFocusLostFn
focusable bool
transient bool
hasControlledBounds bool
needsRerenderOnWidthChange bool
highlightOnFocus bool
focusable bool
transient bool
hasControlledBounds bool
highlightOnFocus bool
*ParentContextMgr
}
@@ -37,15 +36,14 @@ type (
var _ types.IBaseContext = &BaseContext{}
type NewBaseContextOpts struct {
Kind types.ContextKind
Key types.ContextKey
View *gocui.View
WindowName string
Focusable bool
Transient bool
HasUncontrolledBounds bool // negating for the sake of making false the default
HighlightOnFocus bool
NeedsRerenderOnWidthChange bool
Kind types.ContextKind
Key types.ContextKey
View *gocui.View
WindowName string
Focusable bool
Transient bool
HasUncontrolledBounds bool // negating for the sake of making false the default
HighlightOnFocus bool
OnGetOptionsMap func() map[string]string
}
@@ -56,18 +54,17 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
hasControlledBounds := !opts.HasUncontrolledBounds
return &BaseContext{
kind: opts.Kind,
key: opts.Key,
view: opts.View,
windowName: opts.WindowName,
onGetOptionsMap: opts.OnGetOptionsMap,
focusable: opts.Focusable,
transient: opts.Transient,
hasControlledBounds: hasControlledBounds,
highlightOnFocus: opts.HighlightOnFocus,
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
ParentContextMgr: &ParentContextMgr{},
viewTrait: viewTrait,
kind: opts.Kind,
key: opts.Key,
view: opts.View,
windowName: opts.WindowName,
onGetOptionsMap: opts.OnGetOptionsMap,
focusable: opts.Focusable,
transient: opts.Transient,
hasControlledBounds: hasControlledBounds,
highlightOnFocus: opts.HighlightOnFocus,
ParentContextMgr: &ParentContextMgr{},
viewTrait: viewTrait,
}
}
@@ -193,10 +190,6 @@ func (self *BaseContext) HasControlledBounds() bool {
return self.hasControlledBounds
}
func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
return self.needsRerenderOnWidthChange
}
func (self *BaseContext) Title() string {
return ""
}
+6 -7
View File
@@ -30,10 +30,10 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
c.State().GetItemOperation,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Views().Branches.Width(),
c.Tr,
c.UserConfig,
c.Model().Worktrees,
c.Model().MergedBranches,
)
}
@@ -41,12 +41,11 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
FilteredListViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Branches,
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: true,
View: c.Views().Branches,
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+5 -6
View File
@@ -68,12 +68,11 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
SearchTrait: NewSearchTrait(c),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Commits,
WindowName: "commits",
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: true,
View: c.Views().Commits,
WindowName: "commits",
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+5 -6
View File
@@ -43,12 +43,11 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
FilteredListViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().ReflogCommits,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
NeedsRerenderOnWidthChange: true,
View: c.Views().ReflogCommits,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+6 -7
View File
@@ -36,13 +36,12 @@ func NewRemoteBranchesContext(
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().RemoteBranches,
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
NeedsRerenderOnWidthChange: true,
View: c.Views().RemoteBranches,
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+6 -7
View File
@@ -115,13 +115,12 @@ func NewSubCommitsContext(
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().SubCommits,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
NeedsRerenderOnWidthChange: true,
View: c.Views().SubCommits,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+34 -16
View File
@@ -429,25 +429,43 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
reflogCommits := self.c.Model().FilteredReflogCommits
if self.c.Modes().Filtering.Active() {
// in filter mode we filter our reflog commits to just those containing the path
// however we need all the reflog entries to populate the recencies of our branches
// which allows us to order them correctly. So if we're filtering we'll just
// manually load all the reflog commits here
var err error
reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
if err != nil {
self.c.Log.Error(err)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
reflogCommits := self.c.Model().FilteredReflogCommits
if self.c.Modes().Filtering.Active() {
// in filter mode we filter our reflog commits to just those containing the path
// however we need all the reflog entries to populate the recencies of our branches
// which allows us to order them correctly. So if we're filtering we'll just
// manually load all the reflog commits here
var err error
reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
if err != nil {
self.c.Log.Error(err)
}
}
}
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
if err != nil {
_ = self.c.Error(err)
}
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
if err != nil {
_ = self.c.Error(err)
}
self.c.Model().Branches = branches
self.c.Model().Branches = branches
}()
go func() {
defer wg.Done()
mergedBranches := self.c.Git().Loaders.MergedBranchLoader.Load()
self.c.Model().MergedBranches = mergedBranches
}()
wg.Wait()
if refreshWorktrees {
self.loadWorktrees()
+25 -1
View File
@@ -1,6 +1,7 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -16,7 +17,7 @@ func (self *ScreenModeActions) Next() error {
),
)
return nil
return self.rerenderViewsWithScreenModeDependentContent()
}
func (self *ScreenModeActions) Prev() error {
@@ -27,9 +28,32 @@ func (self *ScreenModeActions) Prev() error {
),
)
return self.rerenderViewsWithScreenModeDependentContent()
}
// 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 (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() error {
// for now we re-render all list views.
for _, context := range self.c.Context().AllList() {
if err := self.rerenderView(context.GetView()); err != nil {
return err
}
}
return nil
}
func (self *ScreenModeActions) rerenderView(view *gocui.View) error {
context, ok := self.c.Helpers().View.ContextForView(view.Name())
if !ok {
self.c.Log.Errorf("no context found for view %s", view.Name())
return nil
}
return context.HandleRender()
}
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
for i, val := range sl {
if val == current {
+1 -2
View File
@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/constants"
@@ -107,7 +106,7 @@ func (self *StatusController) onClick() error {
}
cx, _ := self.c.Views().Status.Cursor()
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now())
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr)
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {
+2
View File
@@ -8,6 +8,7 @@ import (
"strings"
"sync"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazycore/pkg/boxlayout"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
@@ -384,6 +385,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{},
MergedBranches: set.New[string](),
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath),
+2 -23
View File
@@ -44,13 +44,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
contextsToRerender := []types.Context{}
// we assume that the view has already been created.
setViewFromDimensions := func(context types.Context) (*gocui.View, error) {
viewName := context.GetViewName()
windowName := context.GetWindowName()
setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) {
dimensionsObj, ok := viewDimensions[windowName]
view, err := g.View(viewName)
@@ -72,16 +67,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
if view.Frame {
frameOffset = 0
}
if context.NeedsRerenderOnWidthChange() {
// view.Width() returns the width -1 for some reason
oldWidth := view.Width() + 1
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
if oldWidth != newWidth {
contextsToRerender = append(contextsToRerender, context)
}
}
_, err = g.SetView(
viewName,
dimensionsObj.X0-frameOffset,
@@ -100,7 +85,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
continue
}
_, err := setViewFromDimensions(context)
_, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName())
if err != nil && !gocui.IsUnknownView(err) {
return err
}
@@ -161,12 +146,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
for _, context := range contextsToRerender {
if err := context.HandleRender(); err != nil {
return err
}
}
// here is a good place log some stuff
// if you run `lazygit --logs`
// this will let you see these branches as prettified json
+22 -46
View File
@@ -3,8 +3,8 @@ package presentation
import (
"fmt"
"strings"
"time"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -14,7 +14,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mattn/go-runewidth"
"github.com/samber/lo"
)
@@ -25,14 +24,14 @@ func GetBranchListDisplayStrings(
getItemOperation func(item types.HasUrn) types.ItemOperation,
fullDescription bool,
diffName string,
viewWidth int,
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
worktrees []*models.Worktree,
mergedBranches *set.Set[string],
) [][]string {
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
diffed := branch.Name == diffName
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees, mergedBranches)
})
}
@@ -42,32 +41,11 @@ func getBranchDisplayStrings(
itemOperation types.ItemOperation,
fullDescription bool,
diffed bool,
viewWidth int,
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
worktrees []*models.Worktree,
now time.Time,
mergedBranches *set.Set[string],
) []string {
checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees)
showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash
branchStatus := BranchStatus(b, itemOperation, tr, now)
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
// Recency is always three characters, plus one for the space
availableWidth := viewWidth - 4
if len(branchStatus) > 0 {
availableWidth -= runewidth.StringWidth(branchStatus) + 1
}
if icons.IsIconEnabled() {
availableWidth -= 2 // one for the icon, one for the space
}
if showCommitHash {
availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1
}
if checkedOutByWorkTree {
availableWidth -= runewidth.StringWidth(worktreeIcon) + 1
}
displayName := b.Name
if b.DisplayName != "" {
displayName = b.DisplayName
@@ -78,19 +56,13 @@ func getBranchDisplayStrings(
nameTextStyle = theme.DiffTerminalColor
}
if len(displayName) > availableWidth {
// Never shorten the branch name to less then 3 characters
len := utils.Max(availableWidth, 4)
displayName = displayName[:len-1] + "…"
}
coloredName := nameTextStyle.Sprint(displayName)
if checkedOutByWorkTree {
branchStatus := utils.WithPadding(ColoredBranchStatus(b, itemOperation, tr), 2, utils.AlignLeft)
if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
}
if len(branchStatus) > 0 {
coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus)
coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus)
}
coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
recencyColor := style.FgCyan
if b.Recency == " *" {
@@ -104,8 +76,16 @@ func getBranchDisplayStrings(
res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b)))
}
if showCommitHash {
res = append(res, utils.ShortSha(b.CommitHash))
if fullDescription || userConfig.Gui.ShowBranchCommitHash {
var commitStyle style.TextStyle
if b.HasCommitsToPush() {
commitStyle = getShaStyleFromStatus(models.StatusUnpushed)
} else if mergedBranches.Includes(b.FullRefName()) {
commitStyle = getShaStyleFromStatus(models.StatusMerged)
} else {
commitStyle = getShaStyleFromStatus(models.StatusPushed)
}
res = append(res, commitStyle.Sprint(utils.ShortSha(b.CommitHash)))
}
res = append(res, coloredName)
@@ -143,7 +123,7 @@ func GetBranchTextStyle(name string) style.TextStyle {
}
}
func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle {
func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
colour := style.FgYellow
if itemOperation != types.ItemOperationNone {
colour = style.FgCyan
@@ -155,17 +135,13 @@ func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation)
colour = style.FgMagenta
}
return colour
return colour.Sprint(BranchStatus(branch, itemOperation, tr))
}
func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
return branchStatusColor(branch, itemOperation).Sprint(BranchStatus(branch, itemOperation, tr, time.Now()))
}
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet, now time.Time) string {
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
itemOperationStr := itemOperationToString(itemOperation, tr)
if itemOperationStr != "" {
return itemOperationStr + " " + utils.Loader(now)
return itemOperationStr + " " + utils.Loader()
}
if !branch.IsTrackingRemote() {
-214
View File
@@ -1,214 +0,0 @@
package presentation
import (
"fmt"
"testing"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
func Test_getBranchDisplayStrings(t *testing.T) {
scenarios := []struct {
branch *models.Branch
itemOperation types.ItemOperation
fullDescription bool
viewWidth int
useIcons bool
checkedOutByWorktree bool
expected []string
}{
// First some tests for when the view is wide enough so that everything fits:
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "branch_name"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: true,
expected: []string{"1m", "branch_name (worktree)"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 100,
useIcons: true,
checkedOutByWorktree: true,
expected: []string{"1m", "󰘬", "branch_name 󰌹"},
},
{
branch: &models.Branch{
Name: "branch_name",
Recency: "1m",
UpstreamRemote: "origin",
Pushables: "0",
Pullables: "0",
},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "branch_name ✓"},
},
{
branch: &models.Branch{
Name: "branch_name",
Recency: "1m",
UpstreamRemote: "origin",
Pushables: "3",
Pullables: "5",
},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: true,
expected: []string{"1m", "branch_name (worktree) ↑3↓5"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationPushing,
fullDescription: false,
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "branch_name Pushing |"},
},
{
branch: &models.Branch{
Name: "branch_name",
Recency: "1m",
CommitHash: "1234567890",
UpstreamRemote: "origin",
UpstreamBranch: "branch_name",
Pushables: "0",
Pullables: "0",
Subject: "commit title",
},
itemOperation: types.ItemOperationNone,
fullDescription: true,
viewWidth: 100,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
},
// Now tests for how we truncate the branch name when there's not enough room:
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 14,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "branch_na…"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 14,
useIcons: false,
checkedOutByWorktree: true,
expected: []string{"1m", "bra… (worktree)"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 14,
useIcons: true,
checkedOutByWorktree: true,
expected: []string{"1m", "󰘬", "branc… 󰌹"},
},
{
branch: &models.Branch{
Name: "branch_name",
Recency: "1m",
UpstreamRemote: "origin",
Pushables: "0",
Pullables: "0",
},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 14,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "branch_… ✓"},
},
{
branch: &models.Branch{
Name: "branch_name",
Recency: "1m",
UpstreamRemote: "origin",
Pushables: "3",
Pullables: "5",
},
itemOperation: types.ItemOperationNone,
fullDescription: false,
viewWidth: 30,
useIcons: false,
checkedOutByWorktree: true,
expected: []string{"1m", "branch_na… (worktree) ↑3↓5"},
},
{
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
itemOperation: types.ItemOperationPushing,
fullDescription: false,
viewWidth: 20,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "branc… Pushing |"},
},
{
branch: &models.Branch{
Name: "branch_name",
Recency: "1m",
CommitHash: "1234567890",
UpstreamRemote: "origin",
UpstreamBranch: "branch_name",
Pushables: "0",
Pullables: "0",
Subject: "commit title",
},
itemOperation: types.ItemOperationNone,
fullDescription: true,
viewWidth: 20,
useIcons: false,
checkedOutByWorktree: false,
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
},
}
c := utils.NewDummyCommon()
for i, s := range scenarios {
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
worktrees := []*models.Worktree{}
if s.checkedOutByWorktree {
worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name})
}
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) {
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig, worktrees, time.Time{})
assert.Equal(t, s.expected, strings)
})
}
}
+19 -14
View File
@@ -413,20 +413,8 @@ func getShaColor(
}
diffed := commit.Sha != "" && commit.Sha == diffName
shaColor := theme.DefaultTextColor
switch commit.Status {
case models.StatusUnpushed:
shaColor = style.FgRed
case models.StatusPushed:
shaColor = style.FgYellow
case models.StatusMerged:
shaColor = style.FgGreen
case models.StatusRebasing:
shaColor = style.FgBlue
case models.StatusReflog:
shaColor = style.FgBlue
default:
}
shaColor := getShaStyleFromStatus(commit.Status)
if diffed {
shaColor = theme.DiffTerminalColor
@@ -439,6 +427,23 @@ func getShaColor(
return shaColor
}
func getShaStyleFromStatus(status models.CommitStatus) style.TextStyle {
switch status {
case models.StatusUnpushed:
return style.FgRed
case models.StatusPushed:
return style.FgYellow
case models.StatusMerged:
return style.FgGreen
case models.StatusRebasing:
return style.FgBlue
case models.StatusReflog:
return style.FgBlue
default:
return theme.DefaultTextColor
}
}
func actionColorMap(action todo.TodoCommand) style.TextStyle {
switch action {
case todo.Pick:
+2 -7
View File
@@ -3,7 +3,6 @@ package presentation
import (
"strings"
"github.com/gookit/color"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
@@ -160,9 +159,7 @@ func getFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, di
isDirectory := file == nil
if icons.IsIconEnabled() {
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
paint := color.C256(icon.Color, false)
output += paint.Sprint(icon.Icon) + " "
output += restColor.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory))
}
output += restColor.Sprint(utils.EscapeSpecialChars(name))
@@ -201,9 +198,7 @@ func getCommitFileLine(name string, diffName string, commitFile *models.CommitFi
isDirectory := commitFile == nil
if icons.IsIconEnabled() {
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
paint := color.C256(icon.Color, false)
output += paint.Sprint(icon.Icon) + " "
output += colour.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory))
}
output += colour.Sprint(name)
+313 -314
View File
@@ -5,328 +5,327 @@ import (
)
// https://github.com/ogham/exa/blob/master/src/output/icons.rs
var (
DEFAULT_FILE_ICON = IconProperties{Icon: "\uf15b", Color: 241} // 
DEFAULT_SUBMODULE_ICON = IconProperties{Icon: "\uf1d3", Color: 202} // 
DEFAULT_DIRECTORY_ICON = IconProperties{Icon: "\uf07b", Color: 241} //
const (
DEFAULT_FILE_ICON = "\uf15b" // 
DEFAULT_SUBMODULE_ICON = "\uf1d3" // 
DEFAULT_DIRECTORY_ICON = "\uf114" //
)
// See https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua
var nameIconMap = map[string]IconProperties{
".Trash": {Icon: "\uf1f8", Color: 241}, //
".atom": {Icon: "\ue764", Color: 241}, //
".bashprofile": {Icon: "\ue615", Color: 113}, //
".bashrc": {Icon: "\ue795", Color: 113}, //
".idea": {Icon: "\ue7b5", Color: 241}, //
".git": {Icon: "\uf1d3", Color: 202}, // 
".gitattributes": {Icon: "\uf1d3", Color: 202}, // 
".gitconfig": {Icon: "\uf1d3", Color: 202}, //
".github": {Icon: "\uf408", Color: 241}, //
".gitignore": {Icon: "\uf1d3", Color: 202}, // 
".gitmodules": {Icon: "\uf1d3", Color: 202}, //
".rvm": {Icon: "\ue21e", Color: 160}, //
".vimrc": {Icon: "\ue62b", Color: 28}, //
".vscode": {Icon: "\ue70c", Color: 39}, //
".zshrc": {Icon: "\ue795", Color: 113}, //
"Cargo.lock": {Icon: "\ue7a8", Color: 216}, // 
"Cargo.toml": {Icon: "\ue7a8", Color: 216}, //
"bin": {Icon: "\ue5fc", Color: 241}, // 
"config": {Icon: "\ue5fc", Color: 241}, //
"docker-compose.yml": {Icon: "\uf308", Color: 68}, // 
"Dockerfile": {Icon: "\uf308", Color: 68}, //
"ds_store": {Icon: "\uf179", Color: 15}, //
"gitignore_global": {Icon: "\uf1d3", Color: 202}, //
"go.mod": {Icon: "\ue627", Color: 74}, //
"go.sum": {Icon: "\ue627", Color: 74}, //
"gradle": {Icon: "\ue256", Color: 168}, //
"gruntfile.coffee": {Icon: "\ue611", Color: 166}, // 
"gruntfile.js": {Icon: "\ue611", Color: 166}, // 
"gruntfile.ls": {Icon: "\ue611", Color: 166}, //
"gulpfile.coffee": {Icon: "\ue610", Color: 167}, // 
"gulpfile.js": {Icon: "\ue610", Color: 167}, // 
"gulpfile.ls": {Icon: "\ue610", Color: 168}, //
"hidden": {Icon: "\uf023", Color: 241}, //
"include": {Icon: "\ue5fc", Color: 241}, //
"lib": {Icon: "\uf121", Color: 241}, //
"localized": {Icon: "\uf179", Color: 15}, //
"Makefile": {Icon: "\ue975", Color: 241}, //
"node_modules": {Icon: "\ue718", Color: 197}, //
"npmignore": {Icon: "\ue71e", Color: 197}, //
"PKGBUILD": {Icon: "\uf303", Color: 38}, //
"rubydoc": {Icon: "\ue73b", Color: 160}, //
"yarn.lock": {Icon: "\ue6a7", Color: 74}, // 
var nameIconMap = map[string]string{
".Trash": "\uf1f8", // 
".atom": "\ue764", //
".bashprofile": "\ue615", //
".bashrc": "\uf489", //
".idea": "\ue7b5", //
".git": "\uf1d3", //
".gitattributes": "\uf1d3", // 
".gitconfig": "\uf1d3", // 
".github": "\uf408", //
".gitignore": "\uf1d3", //
".gitmodules": "\uf1d3", // 
".rvm": "\ue21e", //
".vimrc": "\ue62b", //
".vscode": "\ue70c", //
".zshrc": "\uf489", //
"Cargo.lock": "\ue7a8", //
"Cargo.toml": "\ue7a8", // 
"bin": "\ue5fc", //
"config": "\ue5fc", // 
"docker-compose.yml": "\uf308", //
"Dockerfile": "\uf308", // 
"ds_store": "\uf179", //
"gitignore_global": "\uf1d3", //
"go.mod": "\ue626", //
"go.sum": "\ue626", //
"gradle": "\ue256", //
"gruntfile.coffee": "\ue611", //
"gruntfile.js": "\ue611", // 
"gruntfile.ls": "\ue611", // 
"gulpfile.coffee": "\ue610", //
"gulpfile.js": "\ue610", // 
"gulpfile.ls": "\ue610", // 
"hidden": "\uf023", //
"include": "\ue5fc", //
"lib": "\uf121", //
"localized": "\uf179", //
"Makefile": "\uf489", //
"node_modules": "\ue718", //
"npmignore": "\ue71e", //
"PKGBUILD": "\uf303", //
"rubydoc": "\ue73b", //
"yarn.lock": "\ue718", //
}
var extIconMap = map[string]IconProperties{
".ai": {Icon: "\ue7b4", Color: 185}, // 
".android": {Icon: "\ue70e", Color: 70}, // 
".apk": {Icon: "\ue70e", Color: 70}, // 
".apple": {Icon: "\uf179", Color: 15}, // 
".avi": {Icon: "\uf03d", Color: 140}, // 
".avif": {Icon: "\uf1c5", Color: 140}, // 
".avro": {Icon: "\ue60b", Color: 130}, // 
".awk": {Icon: "\ue795", Color: 140}, //
".bash": {Icon: "\ue795", Color: 113}, //
".bash_history": {Icon: "\ue795", Color: 113}, //
".bash_profile": {Icon: "\ue795", Color: 113}, //
".bashrc": {Icon: "\ue795", Color: 113}, //
".bat": {Icon: "\uf17a", Color: 81}, // 
".bats": {Icon: "\ue795", Color: 241}, //
".bmp": {Icon: "\uf1c5", Color: 149}, // 
".bz": {Icon: "\uf410", Color: 239}, // 
".bz2": {Icon: "\uf410", Color: 239}, // 
".c": {Icon: "\ue61e", Color: 111}, // 
".c++": {Icon: "\ue61d", Color: 204}, // 
".cab": {Icon: "\ue70f", Color: 241}, // 
".cc": {Icon: "\ue61d", Color: 204}, // 
".cfg": {Icon: "\ue615", Color: 255}, // 
".class": {Icon: "\ue256", Color: 168}, // 
".clj": {Icon: "\ue768", Color: 113}, // 
".cljs": {Icon: "\ue76a", Color: 74}, // 
".cls": {Icon: "\uf034", Color: 239}, // 
".cmd": {Icon: "\ue70f", Color: 239}, // 
".coffee": {Icon: "\uf0f4", Color: 185}, // 
".conf": {Icon: "\ue615", Color: 66}, // 
".cp": {Icon: "\ue61d", Color: 74}, // 
".cpio": {Icon: "\uf410", Color: 239}, // 
".cpp": {Icon: "\ue61d", Color: 74}, // 
".cs": {Icon: "\U000f031b", Color: 58}, // 󰌛
".csh": {Icon: "\ue795", Color: 240}, //
".cshtml": {Icon: "\uf1fa", Color: 239}, // 
".csproj": {Icon: "\U000f031b", Color: 58}, // 󰌛
".css": {Icon: "\ue749", Color: 75}, // 
".csv": {Icon: "\uf1c3", Color: 113}, // 
".csx": {Icon: "\U000f031b", Color: 58}, // 󰌛
".cxx": {Icon: "\ue61d", Color: 74}, // 
".d": {Icon: "\ue7af", Color: 28}, // 
".dart": {Icon: "\ue798", Color: 25}, // 
".db": {Icon: "\uf1c0", Color: 188}, // 
".deb": {Icon: "\ue77d", Color: 88}, // 
".diff": {Icon: "\uf440", Color: 241}, // 
".djvu": {Icon: "\uf02d", Color: 241}, // 
".dll": {Icon: "\ue70f", Color: 241}, // 
".doc": {Icon: "\uf0219", Color: 26}, // 󰈙
".docx": {Icon: "\uf0219", Color: 26}, // 󰈙
".ds_store": {Icon: "\uf179", Color: 15}, // 
".DS_store": {Icon: "\uf179", Color: 15}, // 
".dump": {Icon: "\uf1c0", Color: 188}, // 
".ebook": {Icon: "\ue28b", Color: 241}, // 
".ebuild": {Icon: "\uf30d", Color: 56}, // 
".editorconfig": {Icon: "\ue615", Color: 241}, // 
".ejs": {Icon: "\ue618", Color: 185}, // 
".elm": {Icon: "\ue62c", Color: 74}, // 
".env": {Icon: "\uf462", Color: 227}, // 
".eot": {Icon: "\uf031", Color: 124}, // 
".epub": {Icon: "\ue28a", Color: 241}, // 
".erb": {Icon: "\ue73b", Color: 160}, // 
".erl": {Icon: "\ue7b1", Color: 163}, // 
".ex": {Icon: "\ue62d", Color: 140}, // 
".exe": {Icon: "\uf17a", Color: 81}, // 
".exs": {Icon: "\ue62d", Color: 140}, // 
".fish": {Icon: "\ue795", Color: 249}, //
".flac": {Icon: "\uf001", Color: 241}, // 
".flv": {Icon: "\uf03d", Color: 241}, // 
".font": {Icon: "\uf031", Color: 241}, // 
".fs": {Icon: "\ue7a7", Color: 74}, // 
".fsi": {Icon: "\ue7a7", Color: 74}, // 
".fsx": {Icon: "\ue7a7", Color: 74}, // 
".gdoc": {Icon: "\uf1c2", Color: 40}, // 
".gem": {Icon: "\ue21e", Color: 160}, // 
".gemfile": {Icon: "\ue21e", Color: 160}, // 
".gemspec": {Icon: "\ue21e", Color: 160}, // 
".gform": {Icon: "\uf298", Color: 40}, // 
".gif": {Icon: "\uf1c5", Color: 140}, // 
".git": {Icon: "\uf1d3", Color: 202}, // 
".gitattributes": {Icon: "\uf1d3", Color: 202}, // 
".gitignore": {Icon: "\uf1d3", Color: 202}, // 
".gitmodules": {Icon: "\uf1d3", Color: 202}, // 
".go": {Icon: "\ue627", Color: 74}, //
".gradle": {Icon: "\ue256", Color: 168}, // 
".groovy": {Icon: "\ue775", Color: 24}, // 
".gsheet": {Icon: "\uf1c3", Color: 10}, // 
".gslides": {Icon: "\uf1c4", Color: 226}, // 
".guardfile": {Icon: "\ue21e", Color: 241}, // 
".gz": {Icon: "\uf410", Color: 241}, // 
".h": {Icon: "\uf0fd", Color: 140}, // 
".hbs": {Icon: "\ue60f", Color: 202}, // 
".hpp": {Icon: "\uf0fd", Color: 140}, // 
".hs": {Icon: "\ue777", Color: 140}, // 
".htm": {Icon: "\uf13b", Color: 196}, // 
".html": {Icon: "\uf13b", Color: 196}, // 
".hxx": {Icon: "\uf0fd", Color: 140}, // 
".ico": {Icon: "\uf1c5", Color: 185}, // 
".image": {Icon: "\uf1c5", Color: 185}, // 
".iml": {Icon: "\ue7b5", Color: 239}, // 
".ini": {Icon: "\uf17a", Color: 81}, // 
".ipynb": {Icon: "\ue606", Color: 214}, // 
".iso": {Icon: "\ue271", Color: 239}, // 
".j2c": {Icon: "\uf1c5", Color: 239}, // 
".j2k": {Icon: "\uf1c5", Color: 239}, // 
".jad": {Icon: "\ue256", Color: 168}, // 
".jar": {Icon: "\ue256", Color: 168}, // 
".java": {Icon: "\ue256", Color: 168}, // 
".jfi": {Icon: "\uf1c5", Color: 241}, // 
".jfif": {Icon: "\uf1c5", Color: 241}, // 
".jif": {Icon: "\uf1c5", Color: 241}, // 
".jl": {Icon: "\ue624", Color: 241}, // 
".jmd": {Icon: "\uf48a", Color: 74}, // 
".jp2": {Icon: "\uf1c5", Color: 241}, // 
".jpe": {Icon: "\uf1c5", Color: 241}, // 
".jpeg": {Icon: "\uf1c5", Color: 241}, // 
".jpg": {Icon: "\uf1c5", Color: 241}, // 
".jpx": {Icon: "\uf1c5", Color: 241}, // 
".js": {Icon: "\ue74e", Color: 185}, // 
".json": {Icon: "\ue60b", Color: 185}, // 
".jsx": {Icon: "\ue7ba", Color: 45}, // 
".jxl": {Icon: "\uf1c5", Color: 241}, // 
".ksh": {Icon: "\ue795", Color: 241}, //
".kt": {Icon: "\ue634", Color: 99}, // 
".kts": {Icon: "\ue634", Color: 99}, // 
".latex": {Icon: "\uf034", Color: 241}, // 
".less": {Icon: "\ue758", Color: 54}, // 
".lhs": {Icon: "\ue777", Color: 140}, // 
".license": {Icon: "\U000f0219", Color: 185}, // 󰈙
".localized": {Icon: "\uf179", Color: 15}, // 
".lock": {Icon: "\uf023", Color: 241}, // 
".log": {Icon: "\uf18d", Color: 188}, // 
".lua": {Icon: "\ue620", Color: 74}, // 
".lz": {Icon: "\uf410", Color: 241}, // 
".lz4": {Icon: "\uf410", Color: 241}, // 
".lzh": {Icon: "\uf410", Color: 241}, // 
".lzma": {Icon: "\uf410", Color: 241}, // 
".lzo": {Icon: "\uf410", Color: 241}, // 
".m": {Icon: "\ue61e", Color: 111}, // 
".mm": {Icon: "\ue61d", Color: 111}, // 
".m4a": {Icon: "\uf001", Color: 239}, // 
".markdown": {Icon: "\uf48a", Color: 74}, // 
".md": {Icon: "\uf48a", Color: 74}, // 
".mdx": {Icon: "\uf48a", Color: 74}, // 
".mjs": {Icon: "\ue74e", Color: 185}, // 
".mk": {Icon: "\ue795", Color: 241}, //
".mkd": {Icon: "\uf48a", Color: 74}, // 
".mkv": {Icon: "\uf03d", Color: 241}, // 
".mobi": {Icon: "\ue28b", Color: 241}, // 
".mov": {Icon: "\uf03d", Color: 241}, // 
".mp3": {Icon: "\uf001", Color: 241}, // 
".mp4": {Icon: "\uf03d", Color: 241}, // 
".msi": {Icon: "\ue70f", Color: 241}, // 
".mustache": {Icon: "\ue60f", Color: 241}, // 
".nix": {Icon: "\uf313", Color: 111}, // 
".node": {Icon: "\U000f0399", Color: 197}, // 󰎙
".npmignore": {Icon: "\ue71e", Color: 197}, // 
".odp": {Icon: "\uf1c4", Color: 241}, // 
".ods": {Icon: "\uf1c3", Color: 241}, // 
".odt": {Icon: "\uf1c2", Color: 241}, // 
".ogg": {Icon: "\uf001", Color: 241}, // 
".ogv": {Icon: "\uf03d", Color: 241}, // 
".otf": {Icon: "\uf031", Color: 241}, // 
".part": {Icon: "\uf43a", Color: 241}, // 
".patch": {Icon: "\uf440", Color: 241}, // 
".pdf": {Icon: "\uf1c1", Color: 124}, // 
".php": {Icon: "\ue73d", Color: 61}, // 
".pl": {Icon: "\ue769", Color: 74}, // 
".png": {Icon: "\uf1c5", Color: 241}, // 
".ppt": {Icon: "\uf1c4", Color: 241}, // 
".pptx": {Icon: "\uf1c4", Color: 241}, // 
".procfile": {Icon: "\ue21e", Color: 241}, // 
".properties": {Icon: "\ue60b", Color: 185}, // 
".ps1": {Icon: "\ue795", Color: 241}, //
".psd": {Icon: "\ue7b8", Color: 241}, // 
".pxm": {Icon: "\uf1c5", Color: 241}, // 
".py": {Icon: "\ue606", Color: 214}, // 
".pyc": {Icon: "\ue606", Color: 214}, // 
".r": {Icon: "\uf25d", Color: 68}, // 
".rakefile": {Icon: "\ue21e", Color: 160}, // 
".rar": {Icon: "\uf410", Color: 241}, // 
".razor": {Icon: "\uf1fa", Color: 81}, // 
".rb": {Icon: "\ue21e", Color: 160}, // 
".rdata": {Icon: "\uf25d", Color: 68}, // 
".rdb": {Icon: "\ue76d", Color: 160}, // 
".rdoc": {Icon: "\uf48a", Color: 74}, // 
".rds": {Icon: "\uf25d", Color: 68}, // 
".readme": {Icon: "\uf48a", Color: 74}, // 
".rlib": {Icon: "\ue7a8", Color: 216}, // 
".rmd": {Icon: "\uf48a", Color: 74}, // 
".rpm": {Icon: "\ue7bb", Color: 52}, // 
".rs": {Icon: "\ue7a8", Color: 216}, // 
".rspec": {Icon: "\ue21e", Color: 160}, // 
".rspec_parallel": {Icon: "\ue21e", Color: 160}, // 
".rspec_status": {Icon: "\ue21e", Color: 160}, // 
".rss": {Icon: "\uf09e", Color: 130}, // 
".rtf": {Icon: "\U000f0219", Color: 241}, // 󰈙
".ru": {Icon: "\ue21e", Color: 160}, // 
".rubydoc": {Icon: "\ue73b", Color: 160}, // 
".sass": {Icon: "\ue603", Color: 169}, // 
".scala": {Icon: "\ue737", Color: 74}, // 
".scss": {Icon: "\ue749", Color: 204}, // 
".sh": {Icon: "\ue795", Color: 239}, //
".shell": {Icon: "\ue795", Color: 239}, //
".slim": {Icon: "\ue73b", Color: 160}, // 
".sln": {Icon: "\ue70c", Color: 39}, // 
".so": {Icon: "\uf17c", Color: 241}, // 
".sql": {Icon: "\uf1c0", Color: 188}, // 
".sqlite3": {Icon: "\ue7c4", Color: 25}, // 
".sty": {Icon: "\uf034", Color: 239}, // 
".styl": {Icon: "\ue600", Color: 148}, // 
".stylus": {Icon: "\ue600", Color: 148}, // 
".svelte": {Icon: "\ue697", Color: 208}, // 
".svg": {Icon: "\uf1c5", Color: 241}, // 
".swift": {Icon: "\ue755", Color: 208}, // 
".tar": {Icon: "\uf410", Color: 241}, // 
".taz": {Icon: "\uf410", Color: 241}, // 
".tbz": {Icon: "\uf410", Color: 241}, // 
".tbz2": {Icon: "\uf410", Color: 241}, // 
".tex": {Icon: "\uf034", Color: 79}, // 
".tgz": {Icon: "\uf410", Color: 241}, // 
".tiff": {Icon: "\uf1c5", Color: 241}, // 
".tlz": {Icon: "\uf410", Color: 241}, // 
".toml": {Icon: "\ue615", Color: 241}, // 
".torrent": {Icon: "\ue275", Color: 76}, // 
".ts": {Icon: "\ue628", Color: 74}, // 
".tsv": {Icon: "\uf1c3", Color: 241}, // 
".tsx": {Icon: "\ue7ba", Color: 74}, // 
".ttf": {Icon: "\uf031", Color: 241}, // 
".twig": {Icon: "\ue61c", Color: 241}, // 
".txt": {Icon: "\uf15c", Color: 241}, // 
".txz": {Icon: "\uf410", Color: 241}, // 
".tz": {Icon: "\uf410", Color: 241}, // 
".tzo": {Icon: "\uf410", Color: 241}, // 
".video": {Icon: "\uf03d", Color: 241}, // 
".vim": {Icon: "\ue62b", Color: 28}, // 
".vue": {Icon: "\U000f0844", Color: 113}, // 󰡄
".war": {Icon: "\ue256", Color: 168}, // 
".wav": {Icon: "\uf001", Color: 241}, // 
".webm": {Icon: "\uf03d", Color: 241}, // 
".webp": {Icon: "\uf1c5", Color: 241}, // 
".windows": {Icon: "\uf17a", Color: 81}, // 
".woff": {Icon: "\uf031", Color: 241}, // 
".woff2": {Icon: "\uf031", Color: 241}, // 
".xhtml": {Icon: "\uf13b", Color: 196}, // 
".xls": {Icon: "\uf1c3", Color: 34}, // 
".xlsx": {Icon: "\uf1c3", Color: 34}, // 
".xml": {Icon: "\uf121", Color: 160}, // 
".xul": {Icon: "\uf121", Color: 166}, // 
".xz": {Icon: "\uf410", Color: 241}, // 
".yaml": {Icon: "\uf481", Color: 160}, // 
".yml": {Icon: "\uf481", Color: 160}, // 
".zip": {Icon: "\uf410", Color: 241}, // 
".zsh": {Icon: "\ue795", Color: 241}, //
".zsh-theme": {Icon: "\ue795", Color: 241}, //
".zshrc": {Icon: "\ue795", Color: 241}, //
".zst": {Icon: "\uf410", Color: 241}, // 
var extIconMap = map[string]string{
".ai": "\ue7b4", // 
".android": "\ue70e", // 
".apk": "\ue70e", // 
".apple": "\uf179", // 
".avi": "\uf03d", // 
".avif": "\uf1c5", // 
".avro": "\ue60b", // 
".awk": "\uf489", //
".bash": "\uf489", //
".bash_history": "\uf489", //
".bash_profile": "\uf489", //
".bashrc": "\uf489", //
".bat": "\uf17a", // 
".bats": "\uf489", //
".bmp": "\uf1c5", // 
".bz": "\uf410", // 
".bz2": "\uf410", // 
".c": "\ue61e", // 
".c++": "\ue61d", // 
".cab": "\ue70f", // 
".cc": "\ue61d", // 
".cfg": "\ue615", // 
".class": "\ue256", // 
".clj": "\ue768", // 
".cljs": "\ue76a", // 
".cls": "\uf034", // 
".cmd": "\ue70f", // 
".coffee": "\uf0f4", // 
".conf": "\ue615", // 
".cp": "\ue61d", // 
".cpio": "\uf410", // 
".cpp": "\ue61d", // 
".cs": "\U000f031b", // 󰌛
".csh": "\uf489", //
".cshtml": "\uf1fa", // 
".csproj": "\U000f031b", // 󰌛
".css": "\ue749", // 
".csv": "\uf1c3", // 
".csx": "\U000f031b", // 󰌛
".cxx": "\ue61d", // 
".d": "\ue7af", // 
".dart": "\ue798", // 
".db": "\uf1c0", // 
".deb": "\ue77d", // 
".diff": "\uf440", // 
".djvu": "\uf02d", // 
".dll": "\ue70f", // 
".doc": "\uf1c2", //
".docx": "\uf1c2", //
".ds_store": "\uf179", // 
".DS_store": "\uf179", // 
".dump": "\uf1c0", // 
".ebook": "\ue28b", // 
".ebuild": "\uf30d", // 
".editorconfig": "\ue615", // 
".ejs": "\ue618", // 
".elm": "\ue62c", // 
".env": "\uf462", // 
".eot": "\uf031", // 
".epub": "\ue28a", // 
".erb": "\ue73b", // 
".erl": "\ue7b1", // 
".ex": "\ue62d", // 
".exe": "\uf17a", // 
".exs": "\ue62d", // 
".fish": "\uf489", //
".flac": "\uf001", // 
".flv": "\uf03d", // 
".font": "\uf031", // 
".fs": "\ue7a7", // 
".fsi": "\ue7a7", // 
".fsx": "\ue7a7", // 
".gdoc": "\uf1c2", // 
".gem": "\ue21e", // 
".gemfile": "\ue21e", // 
".gemspec": "\ue21e", // 
".gform": "\uf298", // 
".gif": "\uf1c5", // 
".git": "\uf1d3", // 
".gitattributes": "\uf1d3", // 
".gitignore": "\uf1d3", // 
".gitmodules": "\uf1d3", // 
".go": "\ue626", //
".gradle": "\ue256", // 
".groovy": "\ue775", // 
".gsheet": "\uf1c3", // 
".gslides": "\uf1c4", // 
".guardfile": "\ue21e", // 
".gz": "\uf410", // 
".h": "\uf0fd", // 
".hbs": "\ue60f", // 
".hpp": "\uf0fd", // 
".hs": "\ue777", // 
".htm": "\uf13b", // 
".html": "\uf13b", // 
".hxx": "\uf0fd", // 
".ico": "\uf1c5", // 
".image": "\uf1c5", // 
".iml": "\ue7b5", // 
".ini": "\uf17a", // 
".ipynb": "\ue606", // 
".iso": "\ue271", // 
".j2c": "\uf1c5", // 
".j2k": "\uf1c5", // 
".jad": "\ue256", // 
".jar": "\ue256", // 
".java": "\ue256", // 
".jfi": "\uf1c5", // 
".jfif": "\uf1c5", // 
".jif": "\uf1c5", // 
".jl": "\ue624", // 
".jmd": "\uf48a", // 
".jp2": "\uf1c5", // 
".jpe": "\uf1c5", // 
".jpeg": "\uf1c5", // 
".jpg": "\uf1c5", // 
".jpx": "\uf1c5", // 
".js": "\ue74e", // 
".json": "\ue60b", // 
".jsx": "\ue7ba", // 
".jxl": "\uf1c5", // 
".ksh": "\uf489", //
".kt": "\ue634", // 
".kts": "\ue634", // 
".latex": "\uf034", // 
".less": "\ue758", // 
".lhs": "\ue777", // 
".license": "\U000f0219", // 󰈙
".localized": "\uf179", // 
".lock": "\uf023", // 
".log": "\uf18d", // 
".lua": "\ue620", // 
".lz": "\uf410", // 
".lz4": "\uf410", // 
".lzh": "\uf410", // 
".lzma": "\uf410", // 
".lzo": "\uf410", // 
".m": "\ue61e", // 
".mm": "\ue61d", // 
".m4a": "\uf001", // 
".markdown": "\uf48a", // 
".md": "\uf48a", // 
".mdx": "\uf48a", // 
".mjs": "\ue74e", // 
".mk": "\uf489", //
".mkd": "\uf48a", // 
".mkv": "\uf03d", // 
".mobi": "\ue28b", // 
".mov": "\uf03d", // 
".mp3": "\uf001", // 
".mp4": "\uf03d", // 
".msi": "\ue70f", // 
".mustache": "\ue60f", // 
".nix": "\uf313", // 
".node": "\U000f0399", // 󰎙
".npmignore": "\ue71e", // 
".odp": "\uf1c4", // 
".ods": "\uf1c3", // 
".odt": "\uf1c2", // 
".ogg": "\uf001", // 
".ogv": "\uf03d", // 
".otf": "\uf031", // 
".part": "\uf43a", // 
".patch": "\uf440", // 
".pdf": "\uf1c1", // 
".php": "\ue73d", // 
".pl": "\ue769", // 
".png": "\uf1c5", // 
".ppt": "\uf1c4", // 
".pptx": "\uf1c4", // 
".procfile": "\ue21e", // 
".properties": "\ue60b", // 
".ps1": "\uf489", //
".psd": "\ue7b8", // 
".pxm": "\uf1c5", // 
".py": "\ue606", // 
".pyc": "\ue606", // 
".r": "\uf25d", // 
".rakefile": "\ue21e", // 
".rar": "\uf410", // 
".razor": "\uf1fa", // 
".rb": "\ue21e", // 
".rdata": "\uf25d", // 
".rdb": "\ue76d", // 
".rdoc": "\uf48a", // 
".rds": "\uf25d", // 
".readme": "\uf48a", // 
".rlib": "\ue7a8", // 
".rmd": "\uf48a", // 
".rpm": "\ue7bb", // 
".rs": "\ue7a8", // 
".rspec": "\ue21e", // 
".rspec_parallel": "\ue21e", // 
".rspec_status": "\ue21e", // 
".rss": "\uf09e", // 
".rtf": "\U000f0219", // 󰈙
".ru": "\ue21e", // 
".rubydoc": "\ue73b", // 
".sass": "\ue603", // 
".scala": "\ue737", // 
".scss": "\ue749", // 
".sh": "\uf489", //
".shell": "\uf489", //
".slim": "\ue73b", // 
".sln": "\ue70c", // 
".so": "\uf17c", // 
".sql": "\uf1c0", // 
".sqlite3": "\ue7c4", // 
".sty": "\uf034", // 
".styl": "\ue600", // 
".stylus": "\ue600", // 
".svelte": "\ue697", // 
".svg": "\uf1c5", // 
".swift": "\ue755", // 
".tar": "\uf410", // 
".taz": "\uf410", // 
".tbz": "\uf410", // 
".tbz2": "\uf410", // 
".tex": "\uf034", // 
".tgz": "\uf410", // 
".tiff": "\uf1c5", // 
".tlz": "\uf410", // 
".toml": "\ue615", // 
".torrent": "\ue275", // 
".ts": "\ue628", // 
".tsv": "\uf1c3", // 
".tsx": "\ue7ba", // 
".ttf": "\uf031", // 
".twig": "\ue61c", // 
".txt": "\uf15c", // 
".txz": "\uf410", // 
".tz": "\uf410", // 
".tzo": "\uf410", // 
".video": "\uf03d", // 
".vim": "\ue62b", // 
".vue": "\U000f0844", // 󰡄
".war": "\ue256", // 
".wav": "\uf001", // 
".webm": "\uf03d", // 
".webp": "\uf1c5", // 
".windows": "\uf17a", // 
".woff": "\uf031", // 
".woff2": "\uf031", // 
".xhtml": "\uf13b", // 
".xls": "\uf1c3", // 
".xlsx": "\uf1c3", // 
".xml": "\uf121", // 
".xul": "\uf121", // 
".xz": "\uf410", // 
".yaml": "\uf481", // 
".yml": "\uf481", // 
".zip": "\uf410", // 
".zsh": "\uf489", //
".zsh-theme": "\uf489", //
".zshrc": "\uf489", //
".zst": "\uf410", // 
}
func patchFileIconsForNerdFontsV2() {
extIconMap[".cs"] = IconProperties{Icon: "\uf81a", Color: 58} // 
extIconMap[".csproj"] = IconProperties{Icon: "\uf81a", Color: 58} // 
extIconMap[".csx"] = IconProperties{Icon: "\uf81a", Color: 58} // 
extIconMap[".license"] = IconProperties{Icon: "\uf718", Color: 241} // 
extIconMap[".node"] = IconProperties{Icon: "\uf898", Color: 197} // 
extIconMap[".rtf"] = IconProperties{Icon: "\uf718", Color: 241} // 
extIconMap[".vue"] = IconProperties{Icon: "\ufd42", Color: 113} // ﵂
extIconMap[".cs"] = "\uf81a" // 
extIconMap[".csproj"] = "\uf81a" // 
extIconMap[".csx"] = "\uf81a" // 
extIconMap[".license"] = "\uf718" // 
extIconMap[".node"] = "\uf898" // 
extIconMap[".rtf"] = "\uf718" // 
extIconMap[".vue"] = "\ufd42" // ﵂
}
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) IconProperties {
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) string {
base := filepath.Base(name)
if icon, ok := nameIconMap[base]; ok {
return icon
@@ -340,7 +339,7 @@ func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirecto
if isSubmodule {
return DEFAULT_SUBMODULE_ICON
} else if isLinkedWorktree {
return IconProperties{LINKED_WORKTREE_ICON, 239}
return LINKED_WORKTREE_ICON
} else if isDirectory {
return DEFAULT_DIRECTORY_ICON
}
+9 -18
View File
@@ -6,11 +6,6 @@ import (
"github.com/samber/lo"
)
type IconProperties struct {
Icon string
Color uint8
}
var isIconEnabled = false
func IsIconEnabled() bool {
@@ -18,18 +13,14 @@ func IsIconEnabled() bool {
}
func SetNerdFontsVersion(version string) {
if version == "" {
isIconEnabled = false
} else {
if !lo.Contains([]string{"2", "3"}, version) {
log.Fatalf("Unsupported nerdFontVersion %s", version)
}
if version == "2" {
patchGitIconsForNerdFontsV2()
patchFileIconsForNerdFontsV2()
}
isIconEnabled = true
if !lo.Contains([]string{"2", "3"}, version) {
log.Fatalf("Unsupported nerdFontVersion %s", version)
}
if version == "2" {
patchGitIconsForNerdFontsV2()
patchFileIconsForNerdFontsV2()
}
isIconEnabled = true
}
+1 -3
View File
@@ -1,8 +1,6 @@
package presentation
import (
"time"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
@@ -39,7 +37,7 @@ func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diff
descriptionStr := descriptionColor.Sprint(t.Description())
itemOperationStr := itemOperationToString(itemOperation, tr)
if itemOperationStr != "" {
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader(time.Now())) + " " + descriptionStr
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader()) + " " + descriptionStr
}
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
return res
+1 -1
View File
@@ -71,7 +71,7 @@ func (self *StatusManager) GetStatusString() string {
}
topStatus := self.statuses[0]
if topStatus.statusType == "waiting" {
return topStatus.message + " " + utils.Loader(time.Now())
return topStatus.message + " " + utils.Loader()
}
return topStatus.message
}
+12 -9
View File
@@ -1,6 +1,7 @@
package types
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -223,15 +224,17 @@ type MenuItem struct {
}
type Model struct {
CommitFiles []*models.CommitFile
Files []*models.File
Submodules []*models.SubmoduleConfig
Branches []*models.Branch
Commits []*models.Commit
StashEntries []*models.StashEntry
SubCommits []*models.Commit
Remotes []*models.Remote
Worktrees []*models.Worktree
CommitFiles []*models.CommitFile
Files []*models.File
Submodules []*models.SubmoduleConfig
Branches []*models.Branch
// set of branch names of the form 'refs/heads/mybranch'
MergedBranches *set.Set[string]
Commits []*models.Commit
StashEntries []*models.StashEntry
SubCommits []*models.Commit
Remotes []*models.Remote
Worktrees []*models.Worktree
// FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path
-3
View File
@@ -60,9 +60,6 @@ type IBaseContext interface {
// determined independently.
HasControlledBounds() bool
// true if the view needs to be rerendered when its width changes
NeedsRerenderOnWidthChange() bool
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
// no title will be set
Title() string
+1 -1
View File
@@ -19,7 +19,7 @@ import (
const unitTestDescription = "test test"
const (
defaultWidth = 150
defaultWidth = 100
defaultHeight = 100
)
+26 -1
View File
@@ -1,6 +1,10 @@
package utils
import "golang.org/x/exp/slices"
import (
"sync"
"golang.org/x/exp/slices"
)
// NextIndex returns the index of the element that comes after the given number
func NextIndex(numbers []int, currentNumber int) int {
@@ -179,3 +183,24 @@ func Shift[T any](slice []T) (T, []T) {
slice = slice[1:]
return value, slice
}
// Map function which handles each element in its own goroutine
func ConcurrentMap[T any, V any](items []T, fn func(T) V) []V {
results := make([]V, len(items))
var wg sync.WaitGroup
for i, item := range items {
i := i
item := item
wg.Add(1)
go func() {
defer wg.Done()
results[i] = fn(item)
}()
}
wg.Wait()
return results
}
+10
View File
@@ -315,3 +315,13 @@ func TestMoveElement(t *testing.T) {
})
})
}
func TestConcurrentMap(t *testing.T) {
in := []int{1, 2, 3}
out := ConcurrentMap(in, func(i int) int {
return i * 2
})
assert.EqualValues(t, []int{2, 4, 6}, out)
}
+2 -1
View File
@@ -28,8 +28,9 @@ func GetProjectRoot() string {
const LoaderAnimationInterval = 50
// Loader dumps a string to be displayed as a loader
func Loader(now time.Time) string {
func Loader() string {
characters := "|/-\\"
now := time.Now()
milliseconds := now.UnixMilli()
index := milliseconds / LoaderAnimationInterval % int64(len(characters))
return characters[index : index+1]