Use the detailed commit graph automatically in terminals that draw it

The detailed commit graph is only drawn for users who set
gui.commitGraphStyle to 'detailed'. Many users of terminals that can
draw it will never find out about it. A terminal can't be asked whether
it draws a given character, but it does tell us its name and version
when tcell asks for them with XTVERSION at startup.

Add an 'auto' value and make it the default. It uses the detailed graph
in kitty from 0.36.2 and in Ghostty from 1.0.0 on. These are the first
versions that draw all of the symbols. Everywhere else it stays with
the classic graph. This includes tmux, because tmux answers XTVERSION
itself. So far, WezTerm draws the symbols only in its nightly builds, so
it isn't detected until a release has them. VS Code isn't detected
either, because it only draws the symbols with GPU acceleration.

The expected output of the integration tests has the classic graph, so
pin it in their config. Otherwise they would fail when run with a
visible UI in one of these terminals.

Log the terminal's name and version at startup, to make it possible to
find out why 'auto' picked what it did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-10-04 18:47:01 +02:00
co-authored by Claude Opus 5.5
parent da5b9b54eb
commit 4780c8bb72
10 changed files with 97 additions and 9 deletions
+4 -2
View File
@@ -302,14 +302,16 @@ gui:
showFileIcons: true
# How the commit graph is drawn.
# One of: 'classic' (default) | 'detailed'
# One of: 'auto' (default) | 'classic' | 'detailed'
# 'detailed' connects the lines to the commit circles, and shows exactly where
# branches fork off and merge. It draws the graph with the git branch drawing
# symbols (U+F5D0 to U+F60D), so it needs a terminal that draws these itself:
# kitty, Ghostty, WezTerm (nightly builds), Contour, or VS Code's terminal with
# GPU acceleration. Other terminals need a font that contains them, such as
# https://github.com/rbong/flog-symbols.
commitGraphStyle: classic
# 'auto' uses 'detailed' if lazygit recognizes the terminal as one that draws
# these symbols (kitty and Ghostty), and 'classic' otherwise.
commitGraphStyle: auto
# Length of author name in (non-expanded) commits view. 2 means show initials
# only.
+4 -3
View File
@@ -180,9 +180,10 @@ type GuiConfig struct {
// If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.
ShowFileIcons bool `yaml:"showFileIcons"`
// How the commit graph is drawn.
// One of: 'classic' (default) | 'detailed'
// One of: 'auto' (default) | 'classic' | 'detailed'
// 'detailed' connects the lines to the commit circles, and shows exactly where branches fork off and merge. It draws the graph with the git branch drawing symbols (U+F5D0 to U+F60D), so it needs a terminal that draws these itself: kitty, Ghostty, WezTerm (nightly builds), Contour, or VS Code's terminal with GPU acceleration. Other terminals need a font that contains them, such as https://github.com/rbong/flog-symbols.
CommitGraphStyle string `yaml:"commitGraphStyle" jsonschema:"enum=classic,enum=detailed"`
// 'auto' uses 'detailed' if lazygit recognizes the terminal as one that draws these symbols (kitty and Ghostty), and 'classic' otherwise.
CommitGraphStyle string `yaml:"commitGraphStyle" jsonschema:"enum=auto,enum=classic,enum=detailed"`
// Length of author name in (non-expanded) commits view. 2 means show initials only.
CommitAuthorShortLength int `yaml:"commitAuthorShortLength"`
// Length of author name in expanded commits view. 2 means show initials only.
@@ -926,7 +927,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
ShowIcons: false,
NerdFontsVersion: "",
ShowFileIcons: true,
CommitGraphStyle: "classic",
CommitGraphStyle: "auto",
CommitAuthorShortLength: 2,
CommitAuthorLongLength: 17,
CommitHashLength: 8,
+1 -1
View File
@@ -31,7 +31,7 @@ func (config *UserConfig) Validate() error {
return err
}
if err := validateEnum("gui.commitGraphStyle", config.Gui.CommitGraphStyle,
[]string{"classic", "detailed"}); err != nil {
[]string{"auto", "classic", "detailed"}); err != nil {
return err
}
if err := validateEnum("git.autoForwardBranches", config.Git.AutoForwardBranches,
+6
View File
@@ -733,6 +733,12 @@ func (g *Gui) DetectedColorScheme() DetectedColorScheme {
return g.colorScheme
}
// Terminal returns the name and version that the terminal reported at
// startup. Both are empty if it didn't report them.
func (g *Gui) Terminal() (string, string) {
return g.screen.Terminal()
}
// SetColorSchemeChangeHandler sets a function to call on the UI thread whenever
// the terminal's colors change after startup.
func (g *Gui) SetColorSchemeChangeHandler(handler func(DetectedColorScheme) error) {
+6 -1
View File
@@ -352,8 +352,13 @@ func shouldShowGraph(c *ContextCommon) bool {
}
func commitGraphSymbolSet(c *ContextCommon) graph.SymbolSet {
if c.UserConfig().Gui.CommitGraphStyle == "detailed" {
switch c.UserConfig().Gui.CommitGraphStyle {
case "detailed":
return graph.BranchDrawingSymbols
case "auto":
if graph.TerminalDrawsBranchDrawingSymbols(c.GocuiGui().Terminal()) {
return graph.BranchDrawingSymbols
}
}
return graph.BoxDrawingSymbols
}
+2
View File
@@ -934,6 +934,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
g.ErrorHandler = gui.PopupHandler.ErrorHandler
terminalName, terminalVersion := g.Terminal()
gui.c.Log.Infof("Terminal: %s %s", terminalName, terminalVersion)
gui.c.Log.Infof("Terminal color scheme: %s", g.DetectedColorScheme())
g.SetColorSchemeChangeHandler(func(colorScheme gocui.DetectedColorScheme) error {
gui.c.Log.Infof("Terminal color scheme changed: %s", colorScheme)
@@ -1,5 +1,11 @@
package graph
import (
"slices"
"strconv"
"strings"
)
// The branch drawing symbols are characters in the Unicode Private Use Area
// (U+F5D0 to U+F60D) for drawing git graphs, introduced by kitty in
// https://github.com/kovidgoyal/kitty/pull/7681. Unlike the box drawing
@@ -95,3 +101,32 @@ func (cell *Cell) branchDrawingChars() (string, string) {
return first, second
}
}
// The terminals that draw the branch drawing symbols themselves, with the
// first version that draws all of them
var terminalsWithBranchDrawingSymbols = map[string][]int{
"kitty": {0, 36, 2},
"ghostty": {1, 0, 0},
}
// TerminalDrawsBranchDrawingSymbols says whether a terminal is known to draw
// the branch drawing symbols itself, given the name and version it reports
func TerminalDrawsBranchDrawingSymbols(name, version string) bool {
minVersion, ok := terminalsWithBranchDrawingSymbols[strings.ToLower(name)]
return ok && slices.Compare(versionNumbers(version), minVersion) >= 0
}
// The numbers at the start of the dot-separated parts of a version, e.g.
// [1 3 0] for "1.3.0-dev+abc"
func versionNumbers(version string) []int {
var numbers []int
for part := range strings.SplitSeq(version, ".") {
digits := part[:len(part)-len(strings.TrimLeft(part, "0123456789"))]
number, err := strconv.Atoi(digits)
if err != nil {
break
}
numbers = append(numbers, number)
}
return numbers
}
@@ -0,0 +1,33 @@
package graph
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTerminalDrawsBranchDrawingSymbols(t *testing.T) {
tests := []struct {
name string
version string
expected bool
}{
{name: "kitty", version: "0.36.1", expected: false},
{name: "kitty", version: "0.36.2", expected: true},
{name: "kitty", version: "0.44.0", expected: true},
{name: "ghostty", version: "0.9.0", expected: false},
{name: "ghostty", version: "1.0.0", expected: true},
{name: "ghostty", version: "1.3.0-main+0123abcd", expected: true},
{name: "WezTerm", version: "20250601-102030-89abcdef", expected: false},
{name: "tmux", version: "3.5a", expected: false},
{name: "iTerm2", version: "3.6.4", expected: false},
{name: "kitty", version: "", expected: false},
{name: "", version: "", expected: false},
}
for _, test := range tests {
t.Run(test.name+" "+test.version, func(t *testing.T) {
assert.Equal(t, test.expected, TerminalDrawsBranchDrawingSymbols(test.name, test.version))
})
}
}
+3 -2
View File
@@ -1224,11 +1224,12 @@
"commitGraphStyle": {
"type": "string",
"enum": [
"auto",
"classic",
"detailed"
],
"description": "How the commit graph is drawn.\nOne of: 'classic' (default) | 'detailed'\n'detailed' connects the lines to the commit circles, and shows exactly where branches fork off and merge. It draws the graph with the git branch drawing symbols (U+F5D0 to U+F60D), so it needs a terminal that draws these itself: kitty, Ghostty, WezTerm (nightly builds), Contour, or VS Code's terminal with GPU acceleration. Other terminals need a font that contains them, such as https://github.com/rbong/flog-symbols.",
"default": "classic"
"description": "How the commit graph is drawn.\nOne of: 'auto' (default) | 'classic' | 'detailed'\n'detailed' connects the lines to the commit circles, and shows exactly where branches fork off and merge. It draws the graph with the git branch drawing symbols (U+F5D0 to U+F60D), so it needs a terminal that draws these itself: kitty, Ghostty, WezTerm (nightly builds), Contour, or VS Code's terminal with GPU acceleration. Other terminals need a font that contains them, such as https://github.com/rbong/flog-symbols.\n'auto' uses 'detailed' if lazygit recognizes the terminal as one that draws these symbols (kitty and Ghostty), and 'classic' otherwise.",
"default": "auto"
},
"commitAuthorShortLength": {
"type": "integer",
+3
View File
@@ -12,6 +12,9 @@ gui:
- black
# Not important in tests but it creates clutter in demos
showRandomTip: false
# The expected output of the tests draws the commit graph with box drawing
# characters, whichever terminal a test runs in
commitGraphStyle: classic
animateExplosion: false # takes too long
git:
# We don't want to run any periodic background git commands because it'll introduce race conditions and flakiness.