Skip to content

Commit 5038fa2

Browse files
authored
Let users define custom icons and color for files on the config file (#4395)
- **PR Description** _Yet another icon-related PR_ - Implementation on config file for user defined file icons and colors. - Fix for some file icon mappings. I use [eza](https://github.com/eza-community/eza), [nvim-wewb-devicons](https://github.com/nvim-tree/nvim-web-devicons) and lazygit; the first ones already let user to override icons and colors via a config file, so with this change I can customize based on filenames and file extensions. | Before | After | |--------|--------| | ![image](https://github.com/user-attachments/assets/bc747d99-25c1-4b74-868e-a31178d7d22e) | ![image](https://github.com/user-attachments/assets/9a43eda9-d167-4602-a891-8c7c3e530289) | ```yaml gui: customIcons: filenames: "CONTRIBUTING.md": { icon: "\uede2", color: "#FEDDEF" } "HACKING.md": { icon: "\uede2", color: "#FEDDEF" } extensions: ".cat": icon: "\U000f011b" color: "#BC4009" ".dog": icon: "\U000f0a43" color: "#B6977E" ```
2 parents 7d26a75 + 0f37f62 commit 5038fa2

File tree

8 files changed

+124
-17
lines changed

8 files changed

+124
-17
lines changed

docs/Config.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ gui:
4343
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
4444
branchColorPatterns: {}
4545

46+
# Custom icons for filenames and file extensions
47+
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color
48+
customIcons:
49+
# Map of filenames to icon properties (icon and color)
50+
filenames: {}
51+
52+
# Map of file extensions (including the dot) to icon properties (icon and color)
53+
extensions: {}
54+
4655
# The number of lines you scroll by when scrolling the main window
4756
scrollHeight: 2
4857

@@ -649,32 +658,39 @@ os:
649658
```
650659
651660
## Custom Command for Opening a Link
661+
652662
```yaml
653663
os:
654664
openLink: 'bash -C /path/to/your/shell-script.sh {{link}}'
655665
```
666+
656667
Specify the external command to invoke when opening URL links (i.e. creating MR/PR in GitLab, BitBucket or GitHub). `{{link}}` will be replaced by the URL to be opened. A simple shell script can be used to further mangle the passed URL.
657668

658669
## Custom Command for Copying to and Pasting from Clipboard
670+
659671
```yaml
660672
os:
661673
copyToClipboardCmd: ''
662674
```
675+
663676
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
664677

665678
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
679+
666680
```yaml
667681
os:
668682
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty
669683
```
670684

671685
For tmux you need to wrap it with the [tmux escape sequence](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it), and enable passthrough in tmux config with `set -g allow-passthrough on`:
686+
672687
```yaml
673688
os:
674689
copyToClipboardCmd: printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty
675690
```
676691

677692
For the best of both worlds, we can let the command determine if we are running in a tmux session and send the correct sequence:
693+
678694
```yaml
679695
os:
680696
copyToClipboardCmd: >
@@ -686,10 +702,12 @@ os:
686702
```
687703

688704
A custom command for reading from the clipboard can be set using
705+
689706
```yaml
690707
os:
691708
readFromClipboardCmd: ''
692709
```
710+
693711
It is used, for example, when pasting a commit message into the commit message panel. The command is supposed to output the clipboard content to stdout.
694712

695713
## Configuring File Editing
@@ -832,6 +850,27 @@ gui:
832850

833851
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.
834852

853+
## Custom Files Icon & Color
854+
855+
You can customize the icon and color of files based on filenames or extensions:
856+
857+
```yaml
858+
gui:
859+
customIcons:
860+
filenames:
861+
"CONTRIBUTING.md": { icon: "\uede2", color: "#FEDDEF" }
862+
"HACKING.md": { icon: "\uede2", color: "#FEDDEF" }
863+
extensions:
864+
".cat":
865+
icon: "\U000f011b"
866+
color: "#BC4009"
867+
".dog":
868+
icon: "\U000f0a43"
869+
color: "#B6977E"
870+
```
871+
872+
Note that there is no support for regular expressions.
873+
835874
## Example Coloring
836875

837876
![border example](../../assets/colored-border-example.png)
@@ -960,6 +999,7 @@ In situations where certain naming pattern is used for branches, this can be use
960999
Example:
9611000

9621001
Some branches:
1002+
9631003
- jsmith/AB-123
9641004
- cwilson/AB-125
9651005

pkg/config/user_config.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ type GuiConfig struct {
5757
BranchColors map[string]string `yaml:"branchColors" jsonschema:"deprecated"`
5858
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
5959
BranchColorPatterns map[string]string `yaml:"branchColorPatterns"`
60+
// Custom icons for filenames and file extensions
61+
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color
62+
CustomIcons CustomIconsConfig `yaml:"customIcons"`
6063
// The number of lines you scroll by when scrolling the main window
6164
ScrollHeight int `yaml:"scrollHeight" jsonschema:"minimum=1"`
6265
// If true, allow scrolling past the bottom of the content in the main window
@@ -707,6 +710,18 @@ type CustomCommandMenuOption struct {
707710
Value string `yaml:"value" jsonschema:"example=feature,minLength=1"`
708711
}
709712

713+
type CustomIconsConfig struct {
714+
// Map of filenames to icon properties (icon and color)
715+
Filenames map[string]IconProperties `yaml:"filenames"`
716+
// Map of file extensions (including the dot) to icon properties (icon and color)
717+
Extensions map[string]IconProperties `yaml:"extensions"`
718+
}
719+
720+
type IconProperties struct {
721+
Icon string `yaml:"icon"`
722+
Color string `yaml:"color"`
723+
}
724+
710725
func GetDefaultConfig() *UserConfig {
711726
return &UserConfig{
712727
Gui: GuiConfig{

pkg/gui/context/commit_files_context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
3939
}
4040

4141
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
42-
lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons)
42+
lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons, &c.UserConfig().Gui.CustomIcons)
4343
return lo.Map(lines, func(line string, _ int) []string {
4444
return []string{line}
4545
})

pkg/gui/context/working_tree_context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
3131
getDisplayStrings := func(_ int, _ int) [][]string {
3232
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
3333
showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView
34-
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat)
34+
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat, &c.UserConfig().Gui.CustomIcons)
3535
return lo.Map(lines, func(line string, _ int) []string {
3636
return []string{line}
3737
})

pkg/gui/presentation/files.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/gookit/color"
77
"github.com/jesseduffield/lazygit/pkg/commands/models"
88
"github.com/jesseduffield/lazygit/pkg/commands/patch"
9+
"github.com/jesseduffield/lazygit/pkg/config"
910
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
1011
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
1112
"github.com/jesseduffield/lazygit/pkg/gui/style"
@@ -23,25 +24,27 @@ func RenderFileTree(
2324
submoduleConfigs []*models.SubmoduleConfig,
2425
showFileIcons bool,
2526
showNumstat bool,
27+
customIconsConfig *config.CustomIconsConfig,
2628
) []string {
2729
collapsedPaths := tree.CollapsedPaths()
2830
return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.File], treeDepth int, visualDepth int, isCollapsed bool) string {
2931
fileNode := filetree.NewFileNode(node)
3032

31-
return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node)
33+
return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node, customIconsConfig)
3234
})
3335
}
3436

3537
func RenderCommitFileTree(
3638
tree *filetree.CommitFileTreeViewModel,
3739
patchBuilder *patch.PatchBuilder,
3840
showFileIcons bool,
41+
customIconsConfig *config.CustomIconsConfig,
3942
) []string {
4043
collapsedPaths := tree.CollapsedPaths()
4144
return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.CommitFile], treeDepth int, visualDepth int, isCollapsed bool) string {
4245
status := commitFilePatchStatus(node, tree, patchBuilder)
4346

44-
return getCommitFileLine(isCollapsed, treeDepth, visualDepth, node, status, showFileIcons)
47+
return getCommitFileLine(isCollapsed, treeDepth, visualDepth, node, status, showFileIcons, customIconsConfig)
4548
})
4649
}
4750

@@ -116,6 +119,7 @@ func getFileLine(
116119
showFileIcons bool,
117120
submoduleConfigs []*models.SubmoduleConfig,
118121
node *filetree.Node[models.File],
122+
customIconsConfig *config.CustomIconsConfig,
119123
) string {
120124
name := fileNameAtDepth(node, treeDepth)
121125
output := ""
@@ -156,7 +160,7 @@ func getFileLine(
156160
isDirectory := file == nil
157161

158162
if showFileIcons {
159-
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
163+
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory, customIconsConfig)
160164
paint := color.HEX(icon.Color, false)
161165
output += paint.Sprint(icon.Icon) + nameColor.Sprint(" ")
162166
}
@@ -218,6 +222,7 @@ func getCommitFileLine(
218222
node *filetree.Node[models.CommitFile],
219223
status patch.PatchStatus,
220224
showFileIcons bool,
225+
customIconsConfig *config.CustomIconsConfig,
221226
) string {
222227
indentation := strings.Repeat(" ", visualDepth)
223228
name := commitFileNameAtDepth(node, treeDepth)
@@ -266,7 +271,7 @@ func getCommitFileLine(
266271
isLinkedWorktree := false
267272

268273
if showFileIcons {
269-
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
274+
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory, customIconsConfig)
270275
paint := color.HEX(icon.Color, false)
271276
output += paint.Sprint(icon.Icon) + " "
272277
}

pkg/gui/presentation/files_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/gookit/color"
88
"github.com/jesseduffield/lazygit/pkg/commands/models"
99
"github.com/jesseduffield/lazygit/pkg/commands/patch"
10+
"github.com/jesseduffield/lazygit/pkg/config"
1011
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
1112
"github.com/jesseduffield/lazygit/pkg/utils"
1213
"github.com/stretchr/testify/assert"
@@ -91,7 +92,7 @@ func TestRenderFileTree(t *testing.T) {
9192
for _, path := range s.collapsedPaths {
9293
viewModel.ToggleCollapsed(path)
9394
}
94-
result := RenderFileTree(viewModel, nil, false, s.showLineChanges)
95+
result := RenderFileTree(viewModel, nil, false, s.showLineChanges, &config.CustomIconsConfig{})
9596
assert.EqualValues(t, s.expected, result)
9697
})
9798
}
@@ -161,7 +162,7 @@ func TestRenderCommitFileTree(t *testing.T) {
161162
},
162163
)
163164
patchBuilder.Start("from", "to", false, false)
164-
result := RenderCommitFileTree(viewModel, patchBuilder, false)
165+
result := RenderCommitFileTree(viewModel, patchBuilder, false, &config.CustomIconsConfig{})
165166
assert.EqualValues(t, s.expected, result)
166167
})
167168
}

pkg/gui/presentation/icons/file_icons.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ package icons
33
import (
44
"path/filepath"
55
"strings"
6+
7+
"github.com/jesseduffield/lazygit/pkg/config"
68
)
79

810
// NOTE: Visit next links for inspiration:
911
// https://github.com/eza-community/eza/blob/main/src/output/icons.rs
10-
// https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua
12+
// https://github.com/nvim-tree/nvim-web-devicons/tree/master/lua/nvim-web-devicons/default
1113

1214
var (
1315
DEFAULT_FILE_ICON = IconProperties{Icon: "\uf15b", Color: "#878787"} // 
1416
DEFAULT_SUBMODULE_ICON = IconProperties{Icon: "\U000f02a2", Color: "#FF4F00"} // 󰊢
1517
DEFAULT_DIRECTORY_ICON = IconProperties{Icon: "\uf07b", Color: "#878787"} // 
1618
)
1719

20+
// NOTE: The filename map is case sensitive.
1821
var nameIconMap = map[string]IconProperties{
1922
".atom": {Icon: "\ue764", Color: "#EED9B7"}, // 
2023
".babelrc": {Icon: "\ue639", Color: "#FED836"}, // 
@@ -90,7 +93,7 @@ var nameIconMap = map[string]IconProperties{
9093
"Cargo.lock": {Icon: "\ue7a8", Color: "#DEA584"}, // 
9194
"Cargo.toml": {Icon: "\ue7a8", Color: "#DEA584"}, // 
9295
"checkhealth": {Icon: "\U000f04d9", Color: "#75B4FB"}, // 󰓙
93-
"cmakelists.txt": {Icon: "\ue794", Color: "##DCE3EB"}, // 
96+
"CMakeLists.txt": {Icon: "\ue794", Color: "#DCE3EB"}, // 
9497
"CODE_OF_CONDUCT": {Icon: "\uf4ae", Color: "#E41662"}, // 
9598
"CODE_OF_CONDUCT.md": {Icon: "\uf4ae", Color: "#E41662"}, // 
9699
"CODE-OF-CONDUCT.md": {Icon: "\uf4ae", Color: "#E41662"}, // 
@@ -280,7 +283,7 @@ var extIconMap = map[string]IconProperties{
280283
".cljd": {Icon: "\ue76a", Color: "#519ABA"}, // 
281284
".cljs": {Icon: "\ue642", Color: "#2AB6F6"}, // 
282285
".cls": {Icon: "\ue69b", Color: "#4B5163"}, // 
283-
".cmake": {Icon: "\ue794", Color: "##DCE3EB"}, // 
286+
".cmake": {Icon: "\ue794", Color: "#DCE3EB"}, // 
284287
".cmd": {Icon: "\uebc4", Color: "#FF7043"}, // 
285288
".cob": {Icon: "\u2699", Color: "#005CA5"}, // ⚙
286289
".cobol": {Icon: "\u2699", Color: "#005CA5"}, // ⚙
@@ -402,22 +405,22 @@ var extIconMap = map[string]IconProperties{
402405
".guardfile": {Icon: "\ue21e", Color: "#626262"}, // 
403406
".gv": {Icon: "\U000f1049", Color: "#005F87"}, // 󱁉
404407
".gz": {Icon: "\uf410", Color: "#ECA517"}, // 
405-
".h": {Icon: "\uf0fd", Color: "##A074C4"}, // 
408+
".h": {Icon: "\uf0fd", Color: "#A074C4"}, // 
406409
".haml": {Icon: "\ue664", Color: "#F4521E"}, // 
407410
".hbs": {Icon: "\U000f15de", Color: "#FF7043"}, // 󱗞
408411
".hc": {Icon: "\U000f00a2", Color: "#FAF743"}, // 󰂢
409412
".heex": {Icon: "\ue62d", Color: "#9575CE"}, // 
410413
".hex": {Icon: "\U000f12a7", Color: "#25A79A"}, // 󱊧
411-
".hh": {Icon: "\uf0fd", Color: "##A074C4"}, // 
412-
".hpp": {Icon: "\uf0fd", Color: "##A074C4"}, // 
414+
".hh": {Icon: "\uf0fd", Color: "#A074C4"}, // 
415+
".hpp": {Icon: "\uf0fd", Color: "#A074C4"}, // 
413416
".hrl": {Icon: "\ue7b1", Color: "#B83998"}, // 
414417
".hs": {Icon: "\ue61f", Color: "#FFA726"}, // 
415418
".htm": {Icon: "\uf13b", Color: "#E44E27"}, // 
416419
".html": {Icon: "\uf13b", Color: "#E44E27"}, // 
417420
".huff": {Icon: "\U000f0858", Color: "#CFD8DD"}, // 󰡘
418421
".hurl": {Icon: "\uf0ec", Color: "#FF0288"}, // 
419422
".hx": {Icon: "\ue666", Color: "#F68713"}, // 
420-
".hxx": {Icon: "\uf0fd", Color: "##A074C4"}, // 
423+
".hxx": {Icon: "\uf0fd", Color: "#A074C4"}, // 
421424
".ical": {Icon: "\uf073", Color: "#2B9EF3"}, // 
422425
".icalendar": {Icon: "\uf073", Color: "#2B9EF3"}, // 
423426
".ico": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟
@@ -688,7 +691,7 @@ var extIconMap = map[string]IconProperties{
688691
".tlz": {Icon: "\uf410", Color: "#ECA517"}, // 
689692
".tmux": {Icon: "\uebc8", Color: "#14BA19"}, // 
690693
".toml": {Icon: "\ue6b2", Color: "#9C4221"}, // 
691-
".torrent": {Icon: "\ue275", Color: "##4C90E8"}, // 
694+
".torrent": {Icon: "\ue275", Color: "#4C90E8"}, // 
692695
".tres": {Icon: "\ue65f", Color: "#42A5F5"}, // 
693696
".ts": {Icon: "\U000f06e6", Color: "#0188D1"}, // 󰛦
694697
".tscn": {Icon: "\ue65f", Color: "#42A5F5"}, // 
@@ -763,13 +766,19 @@ func patchFileIconsForNerdFontsV2() {
763766
extIconMap[".vue"] = IconProperties{Icon: "\ufd42", Color: "#89e051"} // ﵂
764767
}
765768

766-
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) IconProperties {
769+
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool, customIconsConfig *config.CustomIconsConfig) IconProperties {
767770
base := filepath.Base(name)
771+
if icon, ok := customIconsConfig.Filenames[base]; ok {
772+
return IconProperties{Color: icon.Color, Icon: icon.Icon}
773+
}
768774
if icon, ok := nameIconMap[base]; ok {
769775
return icon
770776
}
771777

772778
ext := strings.ToLower(filepath.Ext(name))
779+
if icon, ok := customIconsConfig.Extensions[ext]; ok {
780+
return IconProperties{Color: icon.Color, Icon: icon.Icon}
781+
}
773782
if icon, ok := extIconMap[ext]; ok {
774783
return icon
775784
}

0 commit comments

Comments
 (0)