mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 13:37:09 -04:00
Try branch color patterns in the order they are written
If several patterns in gui.branchColorPatterns match a branch, the color it gets is picked at random, and it can change from one render to the next. The patterns are kept in a Go map, and Go randomizes the order in which a map is iterated. Keep the patterns in a list instead, in the order in which they are written, and let the first match win. If a repo's config file has patterns too, put them in front of those of the global config file, because they are more specific. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ae1b43dc96
commit
8e396228e9
@@ -1069,6 +1069,8 @@ gui:
|
||||
|
||||
Note that the regular expressions are not implicitly anchored to the beginning/end of the branch name. If you want to do that, add leading `^` and/or trailing `$` as needed.
|
||||
|
||||
If several patterns match a branch, the first one wins.
|
||||
|
||||
## Custom Files Icon & Color
|
||||
|
||||
You can customize the icon and color of files based on filenames or extensions:
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/karimkhaleel/jsonschema"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ColorPatterns assigns colors to the names that match regular expressions.
|
||||
// It's written in YAML as a mapping from pattern to color, and keeps the
|
||||
// patterns in the order in which they are written, so that the first pattern
|
||||
// that matches a name can decide its color.
|
||||
type ColorPatterns []ColorPattern
|
||||
|
||||
type ColorPattern struct {
|
||||
Pattern string
|
||||
Color string
|
||||
}
|
||||
|
||||
// UnmarshalYAML puts the patterns it reads in front of the ones that are there
|
||||
// already, which come from config files that were loaded earlier.
|
||||
func (p *ColorPatterns) UnmarshalYAML(node *yaml.Node) error {
|
||||
// Decoding into a map reports malformed input the same way as for the
|
||||
// other maps in the config.
|
||||
var colors map[string]string
|
||||
if err := node.Decode(&colors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patterns := make(ColorPatterns, 0, len(colors))
|
||||
for i := 0; i < len(node.Content)-1; i += 2 {
|
||||
var pattern string
|
||||
if err := node.Content[i].Decode(&pattern); err != nil {
|
||||
return err
|
||||
}
|
||||
patterns = append(patterns, ColorPattern{Pattern: pattern, Color: colors[pattern]})
|
||||
}
|
||||
|
||||
*p = patterns.over(*p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p ColorPatterns) MarshalYAML() (any, error) {
|
||||
node := &yaml.Node{Kind: yaml.MappingNode}
|
||||
for _, pattern := range p {
|
||||
node.Content = append(node.Content,
|
||||
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: pattern.Pattern},
|
||||
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: pattern.Color},
|
||||
)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// JSONSchema describes the patterns as the mapping they are written as.
|
||||
func (ColorPatterns) JSONSchema() *jsonschema.Schema {
|
||||
return &jsonschema.Schema{
|
||||
Type: "object",
|
||||
AdditionalProperties: &jsonschema.Schema{Type: "string"},
|
||||
}
|
||||
}
|
||||
|
||||
// over returns p followed by the patterns of lower that p doesn't have.
|
||||
func (p ColorPatterns) over(lower ColorPatterns) ColorPatterns {
|
||||
return slices.Concat(p, lo.Reject(lower, func(l ColorPattern, _ int) bool {
|
||||
return slices.ContainsFunc(p, func(c ColorPattern) bool { return c.Pattern == l.Pattern })
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type colorPatternsConfig struct {
|
||||
Patterns ColorPatterns `yaml:"patterns"`
|
||||
}
|
||||
|
||||
func TestColorPatternsKeepTheirOrder(t *testing.T) {
|
||||
var config colorPatternsConfig
|
||||
err := yaml.Unmarshal([]byte("patterns:\n"+
|
||||
" '^b': red\n"+
|
||||
" '^a': '#00ff00'\n"+
|
||||
" '^c': blue\n"), &config)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ColorPatterns{
|
||||
{Pattern: "^b", Color: "red"},
|
||||
{Pattern: "^a", Color: "#00ff00"},
|
||||
{Pattern: "^c", Color: "blue"},
|
||||
}, config.Patterns)
|
||||
}
|
||||
|
||||
func TestColorPatternsOfALaterFileComeFirst(t *testing.T) {
|
||||
var config colorPatternsConfig
|
||||
err := yaml.Unmarshal([]byte("patterns:\n"+
|
||||
" '^a': red\n"+
|
||||
" '^b': green\n"), &config)
|
||||
assert.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("patterns:\n"+
|
||||
" '^c': blue\n"+
|
||||
" '^b': yellow\n"), &config)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ColorPatterns{
|
||||
{Pattern: "^c", Color: "blue"},
|
||||
{Pattern: "^b", Color: "yellow"},
|
||||
{Pattern: "^a", Color: "red"},
|
||||
}, config.Patterns)
|
||||
}
|
||||
|
||||
func TestColorPatternsMustBeAMapping(t *testing.T) {
|
||||
var config colorPatternsConfig
|
||||
err := yaml.Unmarshal([]byte("patterns: 5\n"), &config)
|
||||
assert.ErrorContains(t, err, "cannot unmarshal !!int `5` into map[string]string")
|
||||
}
|
||||
|
||||
func TestColorPatternsSurviveMarshalling(t *testing.T) {
|
||||
config := colorPatternsConfig{Patterns: ColorPatterns{
|
||||
{Pattern: "^b", Color: "red"},
|
||||
{Pattern: "^a", Color: "#00ff00"},
|
||||
{Pattern: "true", Color: "blue"},
|
||||
}}
|
||||
content, err := yaml.Marshal(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var roundTripped colorPatternsConfig
|
||||
err = yaml.Unmarshal(content, &roundTripped)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config, roundTripped)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ type GuiConfig struct {
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
|
||||
AuthorColors map[string]string `yaml:"authorColors"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
|
||||
BranchColorPatterns map[string]string `yaml:"branchColorPatterns"`
|
||||
BranchColorPatterns ColorPatterns `yaml:"branchColorPatterns"`
|
||||
// Custom icons for filenames and file extensions
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color
|
||||
CustomIcons CustomIconsConfig `yaml:"customIcons"`
|
||||
|
||||
@@ -19,11 +19,12 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type colorMatcher struct {
|
||||
patterns map[string]*style.TextStyle
|
||||
type branchColorPattern struct {
|
||||
pattern string
|
||||
style style.TextStyle
|
||||
}
|
||||
|
||||
var colorPatterns *colorMatcher
|
||||
var branchColorPatterns []branchColorPattern
|
||||
|
||||
func GetBranchListDisplayStrings(
|
||||
branches []*models.Branch,
|
||||
@@ -192,21 +193,13 @@ func getBranchDisplayStrings(
|
||||
|
||||
// GetBranchTextStyle branch color
|
||||
func GetBranchTextStyle(name string) style.TextStyle {
|
||||
if style, ok := colorPatterns.match(name); ok {
|
||||
return *style
|
||||
}
|
||||
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
|
||||
func (m *colorMatcher) match(name string) (*style.TextStyle, bool) {
|
||||
for pattern, style := range m.patterns {
|
||||
if matched, _ := regexp.MatchString(pattern, name); matched {
|
||||
return style, true
|
||||
for _, p := range branchColorPatterns {
|
||||
if matched, _ := regexp.MatchString(p.pattern, name); matched {
|
||||
return p.style
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
|
||||
func BranchStatus(
|
||||
@@ -262,10 +255,10 @@ func divergenceStr(
|
||||
return result
|
||||
}
|
||||
|
||||
func SetCustomBranches(customBranchColors map[string]string) {
|
||||
colorPatterns = &colorMatcher{
|
||||
patterns: utils.SetCustomColors(customBranchColors),
|
||||
}
|
||||
func SetCustomBranches(patterns config.ColorPatterns) {
|
||||
branchColorPatterns = lo.Map(patterns, func(p config.ColorPattern, _ int) branchColorPattern {
|
||||
return branchColorPattern{pattern: p.Pattern, style: utils.CustomColorStyle(p.Color)}
|
||||
})
|
||||
}
|
||||
|
||||
func WithPrColor(state string, text string, isBg bool) string {
|
||||
|
||||
@@ -2,6 +2,7 @@ package presentation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -9,10 +10,12 @@ import (
|
||||
"github.com/gookit/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xo/terminfo"
|
||||
@@ -436,3 +439,21 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBranchTextStyle(t *testing.T) {
|
||||
defer SetCustomBranches(nil)
|
||||
|
||||
patterns := config.ColorPatterns{
|
||||
{Pattern: "^feature/", Color: "green"},
|
||||
{Pattern: `ISSUE-\d+`, Color: "red"},
|
||||
}
|
||||
|
||||
SetCustomBranches(patterns)
|
||||
assert.Equal(t, style.FgGreen, GetBranchTextStyle("feature/ISSUE-1"))
|
||||
assert.Equal(t, style.FgRed, GetBranchTextStyle("fix/ISSUE-1"))
|
||||
assert.Equal(t, theme.DefaultTextColor, GetBranchTextStyle("main"))
|
||||
|
||||
slices.Reverse(patterns)
|
||||
SetCustomBranches(patterns)
|
||||
assert.Equal(t, style.FgRed, GetBranchTextStyle("feature/ISSUE-1"))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
"$id": "https://github.com/jesseduffield/lazygit/pkg/config/user-config",
|
||||
"$ref": "#/$defs/UserConfig",
|
||||
"$defs": {
|
||||
"ColorPatterns": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object",
|
||||
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color"
|
||||
},
|
||||
"CommitConfig": {
|
||||
"properties": {
|
||||
"signOff": {
|
||||
@@ -538,10 +545,7 @@
|
||||
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color"
|
||||
},
|
||||
"branchColorPatterns": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object",
|
||||
"$ref": "#/$defs/ColorPatterns",
|
||||
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color"
|
||||
},
|
||||
"customIcons": {
|
||||
|
||||
Reference in New Issue
Block a user