From 1eb00d8d1484324a94e52d671d2fca5f829947e2 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 25 Mar 2025 09:32:00 +0100 Subject: [PATCH 1/3] Add newlines for correct markdown See https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md#md031---fenced-code-blocks-should-be-surrounded-by-blank-lines --- docs/Config.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/Config.md b/docs/Config.md index 421fbab53..53c6c8176 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -649,32 +649,39 @@ os: ``` ## Custom Command for Opening a Link + ```yaml os: openLink: 'bash -C /path/to/your/shell-script.sh {{link}}' ``` + 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. ## Custom Command for Copying to and Pasting from Clipboard + ```yaml os: copyToClipboardCmd: '' ``` + 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. If you are working on a terminal that supports OSC52, the following command will let you take advantage of it: + ```yaml os: copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty ``` 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`: + ```yaml os: copyToClipboardCmd: printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty ``` 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: + ```yaml os: copyToClipboardCmd: > @@ -686,10 +693,12 @@ os: ``` A custom command for reading from the clipboard can be set using + ```yaml os: readFromClipboardCmd: '' ``` + 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. ## Configuring File Editing @@ -960,6 +969,7 @@ In situations where certain naming pattern is used for branches, this can be use Example: Some branches: + - jsmith/AB-123 - cwilson/AB-125 From 8ec37f80b7d0f8cf385423a64ad8e1dddf0dbbfd Mon Sep 17 00:00:00 2001 From: hasecilu Date: Fri, 14 Mar 2025 13:58:16 -0600 Subject: [PATCH 2/3] Let users to define custom icons and color for files on the config file Co-authored-by: Stefan Haller --- docs/Config.md | 30 +++++++++++++++++++ pkg/config/user_config.go | 15 ++++++++++ pkg/gui/context/commit_files_context.go | 2 +- pkg/gui/context/working_tree_context.go | 2 +- pkg/gui/presentation/files.go | 13 ++++++--- pkg/gui/presentation/files_test.go | 5 ++-- pkg/gui/presentation/icons/file_icons.go | 10 ++++++- schema/config.json | 37 ++++++++++++++++++++++++ 8 files changed, 105 insertions(+), 9 deletions(-) diff --git a/docs/Config.md b/docs/Config.md index 53c6c8176..0a8c9d1e7 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -43,6 +43,15 @@ gui: # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color branchColorPatterns: {} + # Custom icons for filenames and file extensions + # See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color + customIcons: + # Map of filenames to icon properties (icon and color) + filenames: {} + + # Map of file extensions (including the dot) to icon properties (icon and color) + extensions: {} + # The number of lines you scroll by when scrolling the main window scrollHeight: 2 @@ -841,6 +850,27 @@ 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. +## Custom Files Icon & Color + +You can customize the icon and color of files based on filenames or extensions: + +```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" +``` + +Note that there is no support for regular expressions. + ## Example Coloring ![border example](../../assets/colored-border-example.png) diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index c991b3213..3cb945a8d 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -57,6 +57,9 @@ type GuiConfig struct { BranchColors map[string]string `yaml:"branchColors" jsonschema:"deprecated"` // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color BranchColorPatterns map[string]string `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"` // The number of lines you scroll by when scrolling the main window ScrollHeight int `yaml:"scrollHeight" jsonschema:"minimum=1"` // If true, allow scrolling past the bottom of the content in the main window @@ -707,6 +710,18 @@ type CustomCommandMenuOption struct { Value string `yaml:"value" jsonschema:"example=feature,minLength=1"` } +type CustomIconsConfig struct { + // Map of filenames to icon properties (icon and color) + Filenames map[string]IconProperties `yaml:"filenames"` + // Map of file extensions (including the dot) to icon properties (icon and color) + Extensions map[string]IconProperties `yaml:"extensions"` +} + +type IconProperties struct { + Icon string `yaml:"icon"` + Color string `yaml:"color"` +} + func GetDefaultConfig() *UserConfig { return &UserConfig{ Gui: GuiConfig{ diff --git a/pkg/gui/context/commit_files_context.go b/pkg/gui/context/commit_files_context.go index dc92139bd..40bc730ef 100644 --- a/pkg/gui/context/commit_files_context.go +++ b/pkg/gui/context/commit_files_context.go @@ -39,7 +39,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext { } showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons - lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons) + lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons, &c.UserConfig().Gui.CustomIcons) return lo.Map(lines, func(line string, _ int) []string { return []string{line} }) diff --git a/pkg/gui/context/working_tree_context.go b/pkg/gui/context/working_tree_context.go index cef1eb5c2..4fd2443ab 100644 --- a/pkg/gui/context/working_tree_context.go +++ b/pkg/gui/context/working_tree_context.go @@ -31,7 +31,7 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext { getDisplayStrings := func(_ int, _ int) [][]string { showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView - lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat) + lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat, &c.UserConfig().Gui.CustomIcons) return lo.Map(lines, func(line string, _ int) []string { return []string{line} }) diff --git a/pkg/gui/presentation/files.go b/pkg/gui/presentation/files.go index 2b1f3fafa..f4e232938 100644 --- a/pkg/gui/presentation/files.go +++ b/pkg/gui/presentation/files.go @@ -6,6 +6,7 @@ import ( "github.com/gookit/color" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/patch" + "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" "github.com/jesseduffield/lazygit/pkg/gui/style" @@ -23,12 +24,13 @@ func RenderFileTree( submoduleConfigs []*models.SubmoduleConfig, showFileIcons bool, showNumstat bool, + customIconsConfig *config.CustomIconsConfig, ) []string { collapsedPaths := tree.CollapsedPaths() return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.File], treeDepth int, visualDepth int, isCollapsed bool) string { fileNode := filetree.NewFileNode(node) - return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node) + return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node, customIconsConfig) }) } @@ -36,12 +38,13 @@ func RenderCommitFileTree( tree *filetree.CommitFileTreeViewModel, patchBuilder *patch.PatchBuilder, showFileIcons bool, + customIconsConfig *config.CustomIconsConfig, ) []string { collapsedPaths := tree.CollapsedPaths() return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.CommitFile], treeDepth int, visualDepth int, isCollapsed bool) string { status := commitFilePatchStatus(node, tree, patchBuilder) - return getCommitFileLine(isCollapsed, treeDepth, visualDepth, node, status, showFileIcons) + return getCommitFileLine(isCollapsed, treeDepth, visualDepth, node, status, showFileIcons, customIconsConfig) }) } @@ -116,6 +119,7 @@ func getFileLine( showFileIcons bool, submoduleConfigs []*models.SubmoduleConfig, node *filetree.Node[models.File], + customIconsConfig *config.CustomIconsConfig, ) string { name := fileNameAtDepth(node, treeDepth) output := "" @@ -156,7 +160,7 @@ func getFileLine( isDirectory := file == nil if showFileIcons { - icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory) + icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory, customIconsConfig) paint := color.HEX(icon.Color, false) output += paint.Sprint(icon.Icon) + nameColor.Sprint(" ") } @@ -218,6 +222,7 @@ func getCommitFileLine( node *filetree.Node[models.CommitFile], status patch.PatchStatus, showFileIcons bool, + customIconsConfig *config.CustomIconsConfig, ) string { indentation := strings.Repeat(" ", visualDepth) name := commitFileNameAtDepth(node, treeDepth) @@ -266,7 +271,7 @@ func getCommitFileLine( isLinkedWorktree := false if showFileIcons { - icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory) + icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory, customIconsConfig) paint := color.HEX(icon.Color, false) output += paint.Sprint(icon.Icon) + " " } diff --git a/pkg/gui/presentation/files_test.go b/pkg/gui/presentation/files_test.go index 858b836d8..ac97aa2bc 100644 --- a/pkg/gui/presentation/files_test.go +++ b/pkg/gui/presentation/files_test.go @@ -7,6 +7,7 @@ import ( "github.com/gookit/color" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/patch" + "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/stretchr/testify/assert" @@ -91,7 +92,7 @@ func TestRenderFileTree(t *testing.T) { for _, path := range s.collapsedPaths { viewModel.ToggleCollapsed(path) } - result := RenderFileTree(viewModel, nil, false, s.showLineChanges) + result := RenderFileTree(viewModel, nil, false, s.showLineChanges, &config.CustomIconsConfig{}) assert.EqualValues(t, s.expected, result) }) } @@ -161,7 +162,7 @@ func TestRenderCommitFileTree(t *testing.T) { }, ) patchBuilder.Start("from", "to", false, false) - result := RenderCommitFileTree(viewModel, patchBuilder, false) + result := RenderCommitFileTree(viewModel, patchBuilder, false, &config.CustomIconsConfig{}) assert.EqualValues(t, s.expected, result) }) } diff --git a/pkg/gui/presentation/icons/file_icons.go b/pkg/gui/presentation/icons/file_icons.go index 85033e919..e0ad17404 100644 --- a/pkg/gui/presentation/icons/file_icons.go +++ b/pkg/gui/presentation/icons/file_icons.go @@ -3,6 +3,8 @@ package icons import ( "path/filepath" "strings" + + "github.com/jesseduffield/lazygit/pkg/config" ) // NOTE: Visit next links for inspiration: @@ -763,13 +765,19 @@ func patchFileIconsForNerdFontsV2() { extIconMap[".vue"] = IconProperties{Icon: "\ufd42", Color: "#89e051"} // ﵂ } -func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) IconProperties { +func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool, customIconsConfig *config.CustomIconsConfig) IconProperties { base := filepath.Base(name) + if icon, ok := customIconsConfig.Filenames[base]; ok { + return IconProperties{Color: icon.Color, Icon: icon.Icon} + } if icon, ok := nameIconMap[base]; ok { return icon } ext := strings.ToLower(filepath.Ext(name)) + if icon, ok := customIconsConfig.Extensions[ext]; ok { + return IconProperties{Color: icon.Color, Icon: icon.Icon} + } if icon, ok := extIconMap[ext]; ok { return icon } diff --git a/schema/config.json b/schema/config.json index 475447357..a4b4d0f43 100644 --- a/schema/config.json +++ b/schema/config.json @@ -263,6 +263,27 @@ "additionalProperties": false, "type": "object" }, + "CustomIconsConfig": { + "properties": { + "filenames": { + "additionalProperties": { + "$ref": "#/$defs/IconProperties" + }, + "type": "object", + "description": "Map of filenames to icon properties (icon and color)" + }, + "extensions": { + "additionalProperties": { + "$ref": "#/$defs/IconProperties" + }, + "type": "object", + "description": "Map of file extensions (including the dot) to icon properties (icon and color)" + } + }, + "additionalProperties": false, + "type": "object", + "description": "Custom icons for filenames and file extensions\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color" + }, "GitConfig": { "properties": { "paging": { @@ -404,6 +425,10 @@ "type": "object", "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color" }, + "customIcons": { + "$ref": "#/$defs/CustomIconsConfig", + "description": "Custom icons for filenames and file extensions\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-files-icon--color" + }, "scrollHeight": { "type": "integer", "minimum": 1, @@ -700,6 +725,18 @@ "type": "object", "description": "Config relating to the Lazygit UI" }, + "IconProperties": { + "properties": { + "icon": { + "type": "string" + }, + "color": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, "KeybindingAmendAttributeConfig": { "properties": { "resetAuthor": { From 0f37f62770f1572919ab2a1ca548486fed7926be Mon Sep 17 00:00:00 2001 From: hasecilu Date: Fri, 14 Mar 2025 16:12:32 -0600 Subject: [PATCH 3/3] Fix double '#' on hexadecimal colors --- pkg/gui/presentation/icons/file_icons.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/gui/presentation/icons/file_icons.go b/pkg/gui/presentation/icons/file_icons.go index e0ad17404..a6ff458e8 100644 --- a/pkg/gui/presentation/icons/file_icons.go +++ b/pkg/gui/presentation/icons/file_icons.go @@ -9,7 +9,7 @@ import ( // NOTE: Visit next links for inspiration: // https://github.com/eza-community/eza/blob/main/src/output/icons.rs -// https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua +// https://github.com/nvim-tree/nvim-web-devicons/tree/master/lua/nvim-web-devicons/default var ( DEFAULT_FILE_ICON = IconProperties{Icon: "\uf15b", Color: "#878787"} //  @@ -17,6 +17,7 @@ var ( DEFAULT_DIRECTORY_ICON = IconProperties{Icon: "\uf07b", Color: "#878787"} //  ) +// NOTE: The filename map is case sensitive. var nameIconMap = map[string]IconProperties{ ".atom": {Icon: "\ue764", Color: "#EED9B7"}, //  ".babelrc": {Icon: "\ue639", Color: "#FED836"}, //  @@ -92,7 +93,7 @@ var nameIconMap = map[string]IconProperties{ "Cargo.lock": {Icon: "\ue7a8", Color: "#DEA584"}, //  "Cargo.toml": {Icon: "\ue7a8", Color: "#DEA584"}, //  "checkhealth": {Icon: "\U000f04d9", Color: "#75B4FB"}, // 󰓙 - "cmakelists.txt": {Icon: "\ue794", Color: "##DCE3EB"}, //  + "CMakeLists.txt": {Icon: "\ue794", Color: "#DCE3EB"}, //  "CODE_OF_CONDUCT": {Icon: "\uf4ae", Color: "#E41662"}, //  "CODE_OF_CONDUCT.md": {Icon: "\uf4ae", Color: "#E41662"}, //  "CODE-OF-CONDUCT.md": {Icon: "\uf4ae", Color: "#E41662"}, //  @@ -282,7 +283,7 @@ var extIconMap = map[string]IconProperties{ ".cljd": {Icon: "\ue76a", Color: "#519ABA"}, //  ".cljs": {Icon: "\ue642", Color: "#2AB6F6"}, //  ".cls": {Icon: "\ue69b", Color: "#4B5163"}, //  - ".cmake": {Icon: "\ue794", Color: "##DCE3EB"}, //  + ".cmake": {Icon: "\ue794", Color: "#DCE3EB"}, //  ".cmd": {Icon: "\uebc4", Color: "#FF7043"}, //  ".cob": {Icon: "\u2699", Color: "#005CA5"}, // ⚙ ".cobol": {Icon: "\u2699", Color: "#005CA5"}, // ⚙ @@ -404,14 +405,14 @@ var extIconMap = map[string]IconProperties{ ".guardfile": {Icon: "\ue21e", Color: "#626262"}, //  ".gv": {Icon: "\U000f1049", Color: "#005F87"}, // 󱁉 ".gz": {Icon: "\uf410", Color: "#ECA517"}, //  - ".h": {Icon: "\uf0fd", Color: "##A074C4"}, //  + ".h": {Icon: "\uf0fd", Color: "#A074C4"}, //  ".haml": {Icon: "\ue664", Color: "#F4521E"}, //  ".hbs": {Icon: "\U000f15de", Color: "#FF7043"}, // 󱗞 ".hc": {Icon: "\U000f00a2", Color: "#FAF743"}, // 󰂢 ".heex": {Icon: "\ue62d", Color: "#9575CE"}, //  ".hex": {Icon: "\U000f12a7", Color: "#25A79A"}, // 󱊧 - ".hh": {Icon: "\uf0fd", Color: "##A074C4"}, //  - ".hpp": {Icon: "\uf0fd", Color: "##A074C4"}, //  + ".hh": {Icon: "\uf0fd", Color: "#A074C4"}, //  + ".hpp": {Icon: "\uf0fd", Color: "#A074C4"}, //  ".hrl": {Icon: "\ue7b1", Color: "#B83998"}, //  ".hs": {Icon: "\ue61f", Color: "#FFA726"}, //  ".htm": {Icon: "\uf13b", Color: "#E44E27"}, //  @@ -419,7 +420,7 @@ var extIconMap = map[string]IconProperties{ ".huff": {Icon: "\U000f0858", Color: "#CFD8DD"}, // 󰡘 ".hurl": {Icon: "\uf0ec", Color: "#FF0288"}, //  ".hx": {Icon: "\ue666", Color: "#F68713"}, //  - ".hxx": {Icon: "\uf0fd", Color: "##A074C4"}, //  + ".hxx": {Icon: "\uf0fd", Color: "#A074C4"}, //  ".ical": {Icon: "\uf073", Color: "#2B9EF3"}, //  ".icalendar": {Icon: "\uf073", Color: "#2B9EF3"}, //  ".ico": {Icon: "\U000f021f", Color: "#25A6A0"}, // 󰈟 @@ -690,7 +691,7 @@ var extIconMap = map[string]IconProperties{ ".tlz": {Icon: "\uf410", Color: "#ECA517"}, //  ".tmux": {Icon: "\uebc8", Color: "#14BA19"}, //  ".toml": {Icon: "\ue6b2", Color: "#9C4221"}, //  - ".torrent": {Icon: "\ue275", Color: "##4C90E8"}, //  + ".torrent": {Icon: "\ue275", Color: "#4C90E8"}, //  ".tres": {Icon: "\ue65f", Color: "#42A5F5"}, //  ".ts": {Icon: "\U000f06e6", Color: "#0188D1"}, // 󰛦 ".tscn": {Icon: "\ue65f", Color: "#42A5F5"}, // 