From 8730d58ba6197ac44ca117a8968e399fbacc6047 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 857a4e359..a7d4c2713 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 9738186d9..d4e09fc08 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 801fe14d3..06fe9847f 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -533,6 +533,8 @@ func (gui *Gui) onUserConfigLoaded() error { presentation.SetCustomBranches(userConfig.Gui.BranchColors, false) } + gui.updateTerminalTitle() + return nil } @@ -1141,13 +1143,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 f5fea99e4..4a61742ba 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 236c23278..f96132aba 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 45a2b9efe..4a100c5cb 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,