1// Package lazygit provides a dialog component for embedding lazygit in the TUI.
2package lazygit
3
4import (
5 "context"
6 "fmt"
7 "image/color"
8 "os"
9 "path/filepath"
10
11 "github.com/charmbracelet/crush/internal/terminal"
12 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
13 "github.com/charmbracelet/crush/internal/tui/components/dialogs/termdialog"
14 "github.com/charmbracelet/crush/internal/tui/styles"
15)
16
17// LazygitDialogID is the unique identifier for the lazygit dialog.
18const LazygitDialogID dialogs.DialogID = "lazygit"
19
20// NewDialog creates a new lazygit dialog. The context controls the lifetime
21// of the lazygit process - when cancelled, the process will be killed.
22func NewDialog(ctx context.Context, workingDir string) *termdialog.Dialog {
23 themeConfig := createThemedConfig()
24 configEnv := buildConfigEnv(themeConfig)
25
26 cmd := terminal.PrepareCmd(
27 ctx,
28 "lazygit",
29 nil,
30 workingDir,
31 []string{configEnv},
32 )
33
34 return termdialog.New(termdialog.Config{
35 ID: LazygitDialogID,
36 Title: "Lazygit",
37 LoadingMsg: "Starting lazygit...",
38 Term: terminal.New(terminal.Config{Context: ctx, Cmd: cmd}),
39 OnClose: func() {
40 if themeConfig != "" {
41 _ = os.Remove(themeConfig)
42 }
43 },
44 })
45}
46
47// buildConfigEnv builds the LG_CONFIG_FILE env var, merging user's default
48// config (if it exists) with our theme override. User config comes first so
49// our theme settings take precedence.
50func buildConfigEnv(themeConfig string) string {
51 userConfig := defaultConfigPath()
52 if userConfig != "" {
53 if _, err := os.Stat(userConfig); err == nil {
54 return "LG_CONFIG_FILE=" + userConfig + "," + themeConfig
55 }
56 }
57 return "LG_CONFIG_FILE=" + themeConfig
58}
59
60// defaultConfigPath returns the default lazygit config path for the current OS.
61func defaultConfigPath() string {
62 configDir, err := os.UserConfigDir()
63 if err != nil {
64 return ""
65 }
66 return filepath.Join(configDir, "lazygit", "config.yml")
67}
68
69// colorToHex converts a color.Color to a hex string.
70func colorToHex(c color.Color) string {
71 r, g, b, _ := c.RGBA()
72 return fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8)
73}
74
75// createThemedConfig creates a temporary lazygit config file with Crush theme.
76// Theme mappings align with Crush's UX patterns:
77// - Borders: BorderFocus (purple) for active, Border (gray) for inactive
78// - Selection: Primary (purple) background matches app's TextSelected style
79// - Status: Success (green), Error (red), Info (blue), Warning (orange)
80func createThemedConfig() string {
81 t := styles.CurrentTheme()
82
83 config := fmt.Sprintf(`git:
84 autoFetch: true
85gui:
86 theme:
87 activeBorderColor:
88 - "%s"
89 - bold
90 inactiveBorderColor:
91 - "%s"
92 searchingActiveBorderColor:
93 - "%s"
94 - bold
95 optionsTextColor:
96 - "%s"
97 selectedLineBgColor:
98 - "%s"
99 inactiveViewSelectedLineBgColor:
100 - "%s"
101 cherryPickedCommitFgColor:
102 - "%s"
103 cherryPickedCommitBgColor:
104 - "%s"
105 markedBaseCommitFgColor:
106 - "%s"
107 markedBaseCommitBgColor:
108 - "%s"
109 unstagedChangesColor:
110 - "%s"
111 defaultFgColor:
112 - default
113`,
114 colorToHex(t.BorderFocus),
115 colorToHex(t.FgMuted),
116 colorToHex(t.Info),
117 colorToHex(t.FgMuted),
118 colorToHex(t.Primary),
119 colorToHex(t.BgSubtle),
120 colorToHex(t.Success),
121 colorToHex(t.BgSubtle),
122 colorToHex(t.Info),
123 colorToHex(t.BgSubtle),
124 colorToHex(t.Error),
125 )
126
127 f, err := os.CreateTemp("", "crush-lazygit-*.yml")
128 if err != nil {
129 return ""
130 }
131 defer f.Close()
132
133 _, _ = f.WriteString(config)
134 return f.Name()
135}