From 945a675ce4b25287ab19bcfbe18d7e4994274518 Mon Sep 17 00:00:00 2001 From: Rabin Yasharzadehe Date: Tue, 7 Apr 2026 16:04:25 +0300 Subject: [PATCH] feat: add configurable terminal window title Add a `gui.terminalTitle` config option (default: `lazygit::{{repoName}}`) that sets the terminal window title via ANSI escape sequences through gocui.Screen.SetTitle(). The title updates on startup and when switching repos. Control characters are sanitized to prevent escape sequence injection. Set to empty string to disable. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs-master/Config.md | 5 +++ pkg/config/user_config.go | 5 +++ pkg/gui/gui.go | 30 ++++++++++++++-- pkg/utils/template.go | 12 +++++++ pkg/utils/template_test.go | 70 ++++++++++++++++++++++++++++++++++++++ schema-master/config.json | 5 +++ 6 files changed, 124 insertions(+), 3 deletions(-) diff --git a/docs-master/Config.md b/docs-master/Config.md index 857a4e35980..a7d4c27138f 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -358,6 +358,11 @@ gui: # is already active, go to next tab instead switchTabsWithPanelJumpKeys: false + # Format string for the terminal window title. Supports placeholders: + # - {{repoName}}: Name of the current repository + # Set to empty string to disable terminal title updates. + terminalTitle: lazygit::{{repoName}} + # Config relating to git git: # Array of diff renderers. Each entry has the following format: diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 9738186d93a..d4e09fc08fd 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -222,6 +222,10 @@ type GuiConfig struct { SwitchToFilesAfterStashApply bool `yaml:"switchToFilesAfterStashApply"` // If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead SwitchTabsWithPanelJumpKeys bool `yaml:"switchTabsWithPanelJumpKeys"` + // Format string for the terminal window title. Supports placeholders: + // - {{repoName}}: Name of the current repository + // Set to empty string to disable terminal title updates. + TerminalTitle string `yaml:"terminalTitle"` } func (c *GuiConfig) UseFuzzySearch() bool { @@ -934,6 +938,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig { SwitchToFilesAfterStashPop: true, SwitchToFilesAfterStashApply: true, SwitchTabsWithPanelJumpKeys: false, + TerminalTitle: "lazygit::{{repoName}}", }, Git: GitConfig{ Commit: CommitConfig{ diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bde383cafdd..d825544df6b 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -538,6 +538,8 @@ func (gui *Gui) onUserConfigLoaded() error { presentation.SetCustomBranches(userConfig.Gui.BranchColors, false) } + gui.updateTerminalTitle() + return nil } @@ -1142,13 +1144,35 @@ func (gui *Gui) loadNewRepo() error { } refresh(options) - if err := gui.os.UpdateWindowTitle(); err != nil { - return err - } + gui.updateTerminalTitle() return nil } +func (gui *Gui) updateTerminalTitle() { + titleFormat := gui.c.UserConfig().Gui.TerminalTitle + if titleFormat == "" { + return + } + + // gui.git may not be set yet during initial startup + if gui.git == nil || gui.git.RepoPaths == nil { + return + } + + repoName := gui.git.RepoPaths.RepoName() + title := utils.ResolvePlaceholderString(titleFormat, map[string]string{ + "repoName": repoName, + }) + + // Sanitize title by removing control characters that could break terminal behavior + title = utils.SanitizeTerminalTitle(title) + + if gocui.Screen != nil { + gocui.Screen.SetTitle(title) + } +} + func (gui *Gui) showIntroPopupMessage() { gui.waitForIntro.Add(1) diff --git a/pkg/utils/template.go b/pkg/utils/template.go index f5fea99e443..4a61742ba7a 100644 --- a/pkg/utils/template.go +++ b/pkg/utils/template.go @@ -31,3 +31,15 @@ func ResolvePlaceholderString(str string, arguments map[string]string) string { } return strings.NewReplacer(oldnews...).Replace(str) } + +// SanitizeTerminalTitle removes control characters from a string intended +// for use as a terminal title. Control characters (ASCII 0-31 and 127) could +// break terminal behavior or be used for escape sequence injection. +func SanitizeTerminalTitle(title string) string { + return strings.Map(func(r rune) rune { + if r < 32 || r == 127 { + return -1 // Remove control characters + } + return r + }, title) +} diff --git a/pkg/utils/template_test.go b/pkg/utils/template_test.go index 236c2327804..f96132aba83 100644 --- a/pkg/utils/template_test.go +++ b/pkg/utils/template_test.go @@ -66,3 +66,73 @@ func TestResolvePlaceholderString(t *testing.T) { assert.EqualValues(t, s.expected, ResolvePlaceholderString(s.templateString, s.arguments)) } } + +func TestSanitizeTerminalTitle(t *testing.T) { + scenarios := []struct { + name string + input string + expected string + }{ + { + name: "normal string", + input: "lazygit::myproject", + expected: "lazygit::myproject", + }, + { + name: "empty string", + input: "", + expected: "", + }, + { + name: "string with spaces", + input: "lazygit :: my project", + expected: "lazygit :: my project", + }, + { + name: "string with unicode", + input: "lazygit::项目", + expected: "lazygit::项目", + }, + { + name: "removes null byte", + input: "lazy\x00git", + expected: "lazygit", + }, + { + name: "removes escape sequence", + input: "lazy\x1b[31mgit", + expected: "lazy[31mgit", + }, + { + name: "removes newline and tab", + input: "lazy\ngit\ttitle", + expected: "lazygittitle", + }, + { + name: "removes carriage return", + input: "lazy\rgit", + expected: "lazygit", + }, + { + name: "removes DEL character", + input: "lazy\x7fgit", + expected: "lazygit", + }, + { + name: "removes bell character", + input: "lazy\x07git", + expected: "lazygit", + }, + { + name: "preserves printable special chars", + input: "lazy&git|test<>", + expected: "lazy&git|test<>", + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + assert.EqualValues(t, s.expected, SanitizeTerminalTitle(s.input)) + }) + } +} diff --git a/schema-master/config.json b/schema-master/config.json index 45a2b9efe4b..4a100c5cbc1 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -925,6 +925,11 @@ "type": "boolean", "description": "If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead", "default": false + }, + "terminalTitle": { + "type": "string", + "description": "Format string for the terminal window title. Supports placeholders:\n- {{repoName}}: Name of the current repository\nSet to empty string to disable terminal title updates.", + "default": "lazygit::{{repoName}}" } }, "additionalProperties": false,