1package core
  2
  3import (
  4	"image/color"
  5	"strings"
  6
  7	"github.com/alecthomas/chroma/v2"
  8	"github.com/charmbracelet/crush/internal/exp/diffview"
  9	"github.com/charmbracelet/crush/internal/tui/styles"
 10	"github.com/charmbracelet/lipgloss/v2"
 11	"github.com/charmbracelet/x/ansi"
 12)
 13
 14func Section(text string, width int) string {
 15	t := styles.CurrentTheme()
 16	char := "─"
 17	length := lipgloss.Width(text) + 1
 18	remainingWidth := width - length
 19	lineStyle := t.S().Base.Foreground(t.Border)
 20	if remainingWidth > 0 {
 21		text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth))
 22	}
 23	return text
 24}
 25
 26func Title(title string, width int) string {
 27	t := styles.CurrentTheme()
 28	char := "╱"
 29	length := lipgloss.Width(title) + 1
 30	remainingWidth := width - length
 31	titleStyle := t.S().Base.Foreground(t.Primary)
 32	if remainingWidth > 0 {
 33		lines := strings.Repeat(char, remainingWidth)
 34		lines = styles.ApplyForegroundGrad(lines, t.Primary, t.Secondary)
 35		title = titleStyle.Render(title) + " " + lines
 36	}
 37	return title
 38}
 39
 40type StatusOpts struct {
 41	Icon             string
 42	IconColor        color.Color
 43	NoIcon           bool // If true, no icon will be displayed
 44	Title            string
 45	TitleColor       color.Color
 46	Description      string
 47	DescriptionColor color.Color
 48	ExtraContent     string // Additional content to append after the description
 49}
 50
 51func Status(ops StatusOpts, width int) string {
 52	t := styles.CurrentTheme()
 53	icon := "●"
 54	iconColor := t.Success
 55	if ops.Icon != "" {
 56		icon = ops.Icon
 57	} else if ops.NoIcon {
 58		icon = ""
 59	}
 60	if ops.IconColor != nil {
 61		iconColor = ops.IconColor
 62	}
 63	title := ops.Title
 64	titleColor := t.FgMuted
 65	if ops.TitleColor != nil {
 66		titleColor = ops.TitleColor
 67	}
 68	description := ops.Description
 69	descriptionColor := t.FgSubtle
 70	if ops.DescriptionColor != nil {
 71		descriptionColor = ops.DescriptionColor
 72	}
 73	title = t.S().Base.Foreground(titleColor).Render(title)
 74	if description != "" {
 75		extraContentWidth := lipgloss.Width(ops.ExtraContent)
 76		if extraContentWidth > 0 {
 77			extraContentWidth += 1
 78		}
 79		description = ansi.Truncate(description, width-lipgloss.Width(icon)-lipgloss.Width(title)-2-extraContentWidth, "…")
 80	}
 81	description = t.S().Base.Foreground(descriptionColor).Render(description)
 82
 83	content := []string{}
 84	if icon != "" {
 85		content = append(content, t.S().Base.Foreground(iconColor).Render(icon))
 86	}
 87	content = append(content, title, description)
 88	if ops.ExtraContent != "" {
 89		content = append(content, ops.ExtraContent)
 90	}
 91
 92	return strings.Join(content, " ")
 93}
 94
 95type ButtonOpts struct {
 96	Text           string
 97	UnderlineIndex int  // Index of character to underline (0-based)
 98	Selected       bool // Whether this button is selected
 99}
100
101// SelectableButton creates a button with an underlined character and selection state
102func SelectableButton(opts ButtonOpts) string {
103	t := styles.CurrentTheme()
104
105	// Base style for the button
106	buttonStyle := t.S().Text
107
108	// Apply selection styling
109	if opts.Selected {
110		buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
111	} else {
112		buttonStyle = buttonStyle.Background(t.BgSubtle)
113	}
114
115	// Create the button text with underlined character
116	text := opts.Text
117	if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
118		before := text[:opts.UnderlineIndex]
119		underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
120		after := text[opts.UnderlineIndex+1:]
121
122		message := buttonStyle.Render(before) +
123			buttonStyle.Underline(true).Render(underlined) +
124			buttonStyle.Render(after)
125
126		return buttonStyle.Padding(0, 2).Render(message)
127	}
128
129	// Fallback if no underline index specified
130	return buttonStyle.Padding(0, 2).Render(text)
131}
132
133// SelectableButtons creates a horizontal row of selectable buttons
134func SelectableButtons(buttons []ButtonOpts, spacing string) string {
135	if spacing == "" {
136		spacing = "  "
137	}
138
139	var parts []string
140	for i, button := range buttons {
141		parts = append(parts, SelectableButton(button))
142		if i < len(buttons)-1 {
143			parts = append(parts, spacing)
144		}
145	}
146
147	return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
148}
149
150func DiffFormatter() *diffview.DiffView {
151	t := styles.CurrentTheme()
152	formatDiff := diffview.New()
153	style := chroma.MustNewStyle("crush", styles.GetChromaTheme())
154	diff := formatDiff.ChromaStyle(style).Style(t.S().Diff).TabWidth(4)
155	return diff
156}