Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs-master/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,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 pagers. Each entry has the following format:
Expand Down
4 changes: 0 additions & 4 deletions pkg/commands/oscommands/os_default_platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ func getUserShell() string {
return "bash"
}

func (c *OSCommand) UpdateWindowTitle() error {
return nil
}

func TerminateProcessGracefully(cmd *exec.Cmd) error {
if cmd.Process == nil {
return nil
Expand Down
12 changes: 0 additions & 12 deletions pkg/commands/oscommands/os_windows.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package oscommands

import (
"fmt"
"os"
"os/exec"
"path/filepath"
)

func GetPlatform() *Platform {
Expand All @@ -15,15 +12,6 @@ func GetPlatform() *Platform {
}
}

func (c *OSCommand) UpdateWindowTitle() error {
path, getWdErr := os.Getwd()
if getWdErr != nil {
return getWdErr
}
argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit")
return c.Cmd.NewShell(argString, c.UserConfig().OS.ShellFunctionsFile).Run()
}

func TerminateProcessGracefully(cmd *exec.Cmd) error {
// Signals other than SIGKILL are not supported on Windows
return nil
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,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 {
Expand Down Expand Up @@ -889,6 +893,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
SwitchToFilesAfterStashPop: true,
SwitchToFilesAfterStashApply: true,
SwitchTabsWithPanelJumpKeys: false,
TerminalTitle: "lazygit::{{repoName}}",
},
Git: GitConfig{
Commit: CommitConfig{
Expand Down
30 changes: 27 additions & 3 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ func (gui *Gui) onUserConfigLoaded() error {
presentation.SetCustomBranches(userConfig.Gui.BranchColors, false)
}

gui.updateTerminalTitle()

return nil
}

Expand Down Expand Up @@ -1068,13 +1070,35 @@ func (gui *Gui) loadNewRepo() error {

gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})

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)

Expand Down
12 changes: 12 additions & 0 deletions pkg/utils/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
70 changes: 70 additions & 0 deletions pkg/utils/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
}
}
5 changes: 5 additions & 0 deletions schema-master/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,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,
Expand Down