diff --git a/internal/tui/components/dialogs/commands/commands.go b/internal/tui/components/dialogs/commands/commands.go index 0b7bd6cd342600bf6611079a7d3cbc091a3cfb19..25a1048b41c43aab3279eda97bcaa5587f4dc644 100644 --- a/internal/tui/components/dialogs/commands/commands.go +++ b/internal/tui/components/dialogs/commands/commands.go @@ -87,6 +87,7 @@ type ( OpenExternalEditorMsg struct{} ToggleYoloModeMsg struct{} OpenLazygitMsg struct{} + OpenGhDashMsg struct{} CompactMsg struct { SessionID string } @@ -452,6 +453,20 @@ func (c *commandDialogCmp) defaultCommands() []Command { }) } + // Add gh-dash command if gh CLI with dash extension is installed. + if out, _, err := sh.Exec(c.ctx, "gh extension list"); err == nil { + if strings.Contains(out, "dash") { + commands = append(commands, Command{ + ID: "ghdash", + Title: "Open GitHub Dashboard", + Description: "Open gh-dash for GitHub pull requests and issues", + Handler: func(cmd Command) tea.Cmd { + return util.CmdHandler(OpenGhDashMsg{}) + }, + }) + } + } + return append(commands, []Command{ { ID: "toggle_yolo", diff --git a/internal/tui/components/dialogs/ghdash/ghdash.go b/internal/tui/components/dialogs/ghdash/ghdash.go new file mode 100644 index 0000000000000000000000000000000000000000..7bc87a338dd458d07efe07cc1f3a7eb026f3736f --- /dev/null +++ b/internal/tui/components/dialogs/ghdash/ghdash.go @@ -0,0 +1,91 @@ +// Package ghdash provides a dialog component for embedding gh-dash in the TUI. +package ghdash + +import ( + "context" + "fmt" + "image/color" + "os" + + "github.com/charmbracelet/crush/internal/terminal" + "github.com/charmbracelet/crush/internal/tui/components/dialogs" + "github.com/charmbracelet/crush/internal/tui/components/dialogs/termdialog" + "github.com/charmbracelet/crush/internal/tui/styles" +) + +// DialogID is the unique identifier for the gh-dash dialog. +const DialogID dialogs.DialogID = "ghdash" + +// NewDialog creates a new gh-dash dialog. The context controls the lifetime +// of the gh-dash process - when cancelled, the process will be killed. +func NewDialog(ctx context.Context, workingDir string) *termdialog.Dialog { + configFile := createThemedConfig() + + cmd := terminal.PrepareCmd( + ctx, + "gh", + []string{"dash", "--config", configFile}, + workingDir, + nil, + ) + + return termdialog.New(termdialog.Config{ + ID: DialogID, + Title: "GitHub Dashboard", + LoadingMsg: "Starting gh-dash...", + Term: terminal.New(terminal.Config{Context: ctx, Cmd: cmd}), + OnClose: func() { + if configFile != "" { + _ = os.Remove(configFile) + } + }, + }) +} + +// colorToHex converts a color.Color to a hex string. +func colorToHex(c color.Color) string { + r, g, b, _ := c.RGBA() + return fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8) +} + +// createThemedConfig creates a temporary gh-dash config file with Crush theme. +func createThemedConfig() string { + t := styles.CurrentTheme() + + config := fmt.Sprintf(`theme: + colors: + text: + primary: "%s" + secondary: "%s" + inverted: "%s" + faint: "%s" + warning: "%s" + success: "%s" + background: + selected: "%s" + border: + primary: "%s" + secondary: "%s" + faint: "%s" +`, + colorToHex(t.FgBase), + colorToHex(t.FgMuted), + colorToHex(t.BgBase), + colorToHex(t.FgHalfMuted), + colorToHex(t.Warning), + colorToHex(t.Success), + colorToHex(t.Primary), + colorToHex(t.BorderFocus), + colorToHex(t.Border), + colorToHex(t.BgSubtle), + ) + + f, err := os.CreateTemp("", "crush-ghdash-*.yml") + if err != nil { + return "" + } + defer f.Close() + + _, _ = f.WriteString(config) + return f.Name() +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 7d2e447b4510cf5e9de4bd0c7eea47f710b3d3e2..ff84cda848f9fc99a37134c68c78b0435da97c08 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -28,6 +28,7 @@ import ( "github.com/charmbracelet/crush/internal/tui/components/dialogs" "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands" "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker" + "github.com/charmbracelet/crush/internal/tui/components/dialogs/ghdash" "github.com/charmbracelet/crush/internal/tui/components/dialogs/lazygit" "github.com/charmbracelet/crush/internal/tui/components/dialogs/models" "github.com/charmbracelet/crush/internal/tui/components/dialogs/permissions" @@ -312,6 +313,14 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return a, util.CmdHandler(dialogs.OpenDialogMsg{ Model: lazygit.NewDialog(a.app.Context(), a.app.Config().WorkingDir()), }) + // GhDash + case commands.OpenGhDashMsg: + if a.dialog.ActiveDialogID() == ghdash.DialogID { + return a, util.CmdHandler(dialogs.CloseDialogMsg{}) + } + return a, util.CmdHandler(dialogs.OpenDialogMsg{ + Model: ghdash.NewDialog(a.app.Context(), a.app.Config().WorkingDir()), + }) // Permissions case pubsub.Event[permission.PermissionNotification]: item, ok := a.pages[a.currentPage]