Skip to content
Merged
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
12 changes: 12 additions & 0 deletions docs-master/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ gui:
# NerdFontsVersion is not empty.
showFileIcons: true

# How the commit graph is drawn.
# 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.
# '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.
commitAuthorShortLength: 2
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ type GuiConfig struct {
NerdFontsVersion string `yaml:"nerdFontsVersion" jsonschema:"enum=2,enum=3,enum="`
// 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: '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.
// '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.
Expand Down Expand Up @@ -922,6 +927,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
ShowIcons: false,
NerdFontsVersion: "",
ShowFileIcons: true,
CommitGraphStyle: "auto",
CommitAuthorShortLength: 2,
CommitAuthorLongLength: 17,
CommitHashLength: 8,
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/user_config_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func (config *UserConfig) Validate() error {
[]string{"mixed", "filesFirst", "foldersFirst"}); err != nil {
return err
}
if err := validateEnum("gui.commitGraphStyle", config.Gui.CommitGraphStyle,
[]string{"auto", "classic", "detailed"}); err != nil {
return err
}
if err := validateEnum("git.autoForwardBranches", config.Git.AutoForwardBranches,
[]string{"none", "onlyMainBranches", "allBranches"}); err != nil {
return err
Expand Down
6 changes: 6 additions & 0 deletions pkg/gocui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions pkg/gui/context/local_commits_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
Expand Down Expand Up @@ -73,6 +74,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
startIdx,
endIdx,
shouldShowGraph(c),
commitGraphSymbolSet(c),
c.Model().BisectInfo,
)
}
Expand Down Expand Up @@ -349,6 +351,18 @@ func shouldShowGraph(c *ContextCommon) bool {
return false
}

func commitGraphSymbolSet(c *ContextCommon) graph.SymbolSet {
switch c.UserConfig().Gui.CommitGraphStyle {
case "detailed":
return graph.BranchDrawingSymbols
case "auto":
if graph.TerminalDrawsBranchDrawingSymbols(c.GocuiGui().Terminal()) {
return graph.BranchDrawingSymbols
}
}
return graph.BoxDrawingSymbols
}

func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPositions []int,
modelToViewIndex func(int) int, searchStr string,
) []gocui.SearchPosition {
Expand Down
1 change: 1 addition & 0 deletions pkg/gui/context/sub_commits_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func NewSubCommitsContext(
startIdx,
endIdx,
shouldShowGraph(c),
commitGraphSymbolSet(c),
git_commands.NewNullBisectInfo(),
)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions pkg/gui/presentation/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func GetCommitListDisplayStrings(
startIdx int,
endIdx int,
showGraph bool,
graphSymbolSet graph.SymbolSet,
bisectInfo *git_commands.BisectInfo,
) [][]string {
mutex.Lock()
Expand Down Expand Up @@ -107,6 +108,7 @@ func GetCommitListDisplayStrings(
graphPipeSets,
graphCommits,
selectedCommitHashPtr,
graphSymbolSet,
)
allGraphLines = append(allGraphLines, graphLines...)
}
Expand All @@ -124,6 +126,7 @@ func GetCommitListDisplayStrings(
graphPipeSets,
graphCommits,
selectedCommitHashPtr,
graphSymbolSet,
)
allGraphLines = append(allGraphLines, graphLines...)
}
Expand All @@ -145,6 +148,7 @@ func GetCommitListDisplayStrings(
graphPipeSets,
graphCommits,
selectedCommitHashPtr,
graphSymbolSet,
)
getGraphLine = func(idx int) string {
if idx >= graphOffset {
Expand Down
3 changes: 2 additions & 1 deletion pkg/gui/presentation/commits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ func TestGetCommitListDisplayStrings(t *testing.T) {
s.startIdx,
s.endIdx,
s.showGraph,
graph.BoxDrawingSymbols,
s.bisectInfo,
)

Expand All @@ -635,7 +636,7 @@ func TestGraphColorsFollowTheAuthorColors(t *testing.T) {
models.NewCommit(hashPool, models.NewCommitOpts{Hash: "authorcolors2", AuthorName: "Jane Doe"}),
}
renderGraph := func() string {
return strings.Join(graph.RenderAux(loadPipesets(commits), commits, nil), "\n")
return strings.Join(graph.RenderAux(loadPipesets(commits), commits, nil, graph.BoxDrawingSymbols), "\n")
}

authors.SetCustomAuthors(map[string]string{"Jane Doe": "red"})
Expand Down
132 changes: 132 additions & 0 deletions pkg/gui/presentation/graph/branch_drawing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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
// characters, they can show how the lines in a cell connect. For example, there
// is a symbol for a cell in which a line from above bends to the left and a
// line from the left bends down.

const branchDrawingHorizontal = "\uf5d0"

// The lines in a cell that isn't a commit
type lineCourses struct {
up, down verticalLine
horizontal bool
}

var branchDrawingConnections = map[lineCourses]string{
{}: " ",
{horizontal: true}: branchDrawingHorizontal,
// A horizontal line passing behind a vertical one only shows in the
// neighbouring cells
{up: straightLine, down: straightLine}: "\uf5d1", // │
{up: straightLine, down: straightLine, horizontal: true}: "\uf5d1", // │
{up: lineToLeft}: "\uf5d9", // ╯
{up: lineToRight}: "\uf5d8", // ╰
{down: lineToLeft}: "\uf5d7", // ╮
{down: lineToRight}: "\uf5d6", // ╭
{up: lineToLeft, horizontal: true}: "\uf5e3", // ╯ on ─
{up: lineToRight, horizontal: true}: "\uf5e4", // ╰ on ─
{down: lineToLeft, horizontal: true}: "\uf5e0", // ╮ on ─
{down: lineToRight, horizontal: true}: "\uf5e1", // ╭ on ─
{up: lineToLeft, down: lineToLeft}: "\uf5df", // ╯ and ╮
{up: lineToRight, down: lineToRight}: "\uf5dc", // ╰ and ╭
{up: lineToLeft, down: lineToLeft, horizontal: true}: "\uf5e8", // ╯ and ╮ on ─
{up: lineToRight, down: lineToRight, horizontal: true}: "\uf5e9", // ╰ and ╭ on ─
{up: lineToLeft, down: lineToRight, horizontal: true}: "\uf5ec", // ╯ and ╭ on ─
{up: lineToRight, down: lineToLeft, horizontal: true}: "\uf5ed", // ╰ and ╮ on ─
}

// The edges of a commit's cell that its lines touch
type commitEdges struct {
up, down, left, right bool
}

// Commits are drawn as hollow circles, merge commits as filled ones. Both
// connect to the lines at the given edges.
var branchDrawingCommitSymbols = map[commitEdges]struct{ commit, merge string }{
{}: {"\uf5ef", "\uf5ee"},
{right: true}: {"\uf5f1", "\uf5f0"},
{left: true}: {"\uf5f3", "\uf5f2"},
{left: true, right: true}: {"\uf5f5", "\uf5f4"},
{down: true}: {"\uf5f7", "\uf5f6"},
{up: true}: {"\uf5f9", "\uf5f8"},
{up: true, down: true}: {"\uf5fb", "\uf5fa"},
{down: true, right: true}: {"\uf5fd", "\uf5fc"},
{down: true, left: true}: {"\uf5ff", "\uf5fe"},
{up: true, right: true}: {"\uf601", "\uf600"},
{up: true, left: true}: {"\uf603", "\uf602"},
{up: true, down: true, right: true}: {"\uf605", "\uf604"},
{up: true, down: true, left: true}: {"\uf607", "\uf606"},
{down: true, left: true, right: true}: {"\uf609", "\uf608"},
{up: true, left: true, right: true}: {"\uf60b", "\uf60a"},
{up: true, down: true, left: true, right: true}: {"\uf60d", "\uf60c"},
}

func (cell *Cell) branchDrawingChars() (string, string) {
second := " "
if cell.right {
second = branchDrawingHorizontal
}

switch cell.cellType {
case COMMIT, MERGE:
symbols := branchDrawingCommitSymbols[commitEdges{
up: cell.up != noLine,
down: cell.down != noLine,
left: cell.left,
right: cell.right,
}]
if cell.cellType == MERGE {
return symbols.merge, second
}
return symbols.commit, second
default:
if cell.horizontalOnTop {
return branchDrawingHorizontal, second
}
if first, ok := branchDrawingConnections[lineCourses{cell.up, cell.down, cell.horizontal}]; ok {
return first, second
}
// There is no symbol for lines that the layout of the graph never
// produces, such as a lone half of a line
first, _ := getBoxDrawingChars(cell.up != noLine, cell.down != noLine, cell.left, cell.right)
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
}
33 changes: 33 additions & 0 deletions pkg/gui/presentation/graph/branch_drawing_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
}
}
Loading
Loading